From 5283088269331c654d7b62be4a2a07af989386db Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Wed, 10 Aug 2022 21:08:25 +0800 Subject: [PATCH 001/506] DEV: `site-settings:main` -> `service:site-settings` (#17854) --- app/assets/javascripts/discourse/app/lib/plugin-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 6bbfe81572..437f8bb4f3 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -491,7 +491,7 @@ class PluginApi { name === "hamburger-menu:generalLinks" || name === "hamburger-menu:footerLinks" ) { - const siteSettings = this.container.lookup("site-settings:main"); + const siteSettings = this.container.lookup("service:site-settings"); if (siteSettings.enable_experimental_sidebar_hamburger) { try { From 55fa94f75901fc95ac28e2fea8cc3813ab2750ab Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Wed, 10 Aug 2022 21:22:55 +0800 Subject: [PATCH 002/506] DEV: Attempt to fix flaky sidebar test (#17852) Makes displaying and hiding the list more deterministic. ``` Error: QUnit Test Failure: Exam Partition 1 - Acceptance: Sidebar - Community Section: clicking on more... link not ok 491 Firefox 91.0 - [722 ms] - Exam Partition 1 - Acceptance: Sidebar - Community Section: clicking on more... link --- actual: > true expected: > false stack: > @http://localhost:7357/assets/core-tests.js:9826:14 message: > additional section links are hidden negative: > false browser log: | ``` --- .../discourse/app/components/sidebar/more-section-links.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js index d339ad6edf..3e54ee34d2 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js @@ -72,8 +72,8 @@ export default class SidebarMoreSectionLinks extends GlimmerComponent { } @action - toggleSectionLinks() { - this.shouldDisplaySectionLinks = !this.shouldDisplaySectionLinks; + toggleSectionLinks(element) { + this.shouldDisplaySectionLinks = element.target.hasAttribute("open"); } #removeClickEventListener() { From e87ca397be35dd1449b783e1ab7e7cb8cc85bb04 Mon Sep 17 00:00:00 2001 From: Andrei Prigorshnev Date: Wed, 10 Aug 2022 19:49:26 +0400 Subject: [PATCH 003/506] FEATURE: show status in search results when mentioning user in composers (#17811) --- .../discourse/app/helpers/emoji.js | 8 +++--- .../templates/user-selector-autocomplete.hbr | 5 ++-- .../composer-editor-mentions-test.js | 26 ++++++++++++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/app/helpers/emoji.js b/app/assets/javascripts/discourse/app/helpers/emoji.js index 54e5f3faa0..4e0f5ee594 100644 --- a/app/assets/javascripts/discourse/app/helpers/emoji.js +++ b/app/assets/javascripts/discourse/app/helpers/emoji.js @@ -1,11 +1,9 @@ import { emojiUnescape } from "discourse/lib/text"; import { escapeExpression } from "discourse/lib/utilities"; import { htmlSafe } from "@ember/template"; -import { helper } from "@ember/component/helper"; +import { registerUnbound } from "discourse-common/lib/helpers"; -function emoji(code, options) { +registerUnbound("emoji", function (code, options) { const escaped = escapeExpression(`:${code}:`); return htmlSafe(emojiUnescape(escaped, options)); -} - -export default helper(emoji); +}); diff --git a/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr b/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr index 797bd64e35..7a1e11338f 100644 --- a/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr +++ b/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr @@ -5,8 +5,9 @@ {{avatar user imageSize="tiny"}} {{format-username user.username}} - {{user.name}} - {{decorate-username-selector user.username}} + {{#if user.status}} + {{emoji user.status.emoji title=user.status.description}} + {{/if}} {{/each}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js index ea6f29bc0e..39bbbb6949 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js @@ -1,6 +1,10 @@ import { test } from "qunit"; import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers"; -import { acceptance, query } from "discourse/tests/helpers/qunit-helpers"; +import { + acceptance, + exists, + query, +} from "discourse/tests/helpers/qunit-helpers"; import { setCaretPosition } from "discourse/lib/utilities"; acceptance("Composer - editor mentions", function (needs) { @@ -16,6 +20,10 @@ acceptance("Composer - editor mentions", function (needs) { name: "Some User", avatar_template: "https://avatars.discourse.org/v3/letter/t/41988e/{size}.png", + status: { + emoji: "tooth", + description: "off to dentist", + }, }, { username: "user2", @@ -104,4 +112,20 @@ acceptance("Composer - editor mentions", function (needs) { "should replace mention correctly" ); }); + + test("shows status on search results when mentioning a user", async function (assert) { + await visit("/"); + await click("#create-topic"); + + // emulate typing in "abc @u" + const editor = query(".d-editor-input"); + await fillIn(".d-editor-input", "@"); + await setCaretPosition(editor, 5); + await triggerKeyEvent(".d-editor-input", "keyup", "@"); + await fillIn(".d-editor-input", "@u"); + await setCaretPosition(editor, 6); + await triggerKeyEvent(".d-editor-input", "keyup", "U"); + + assert.ok(exists(".autocomplete .emoji[title='off to dentist']")); + }); }); From 2db076f9c8c1dbcd9045ae5a3d650637e191b8ca Mon Sep 17 00:00:00 2001 From: Bianca Nenciu Date: Wed, 10 Aug 2022 18:55:29 +0300 Subject: [PATCH 004/506] FIX: Don't notify editor when category or tag change (#17833) When a user was editing a topic they were also receiving a notification if they were watching any of the new category or tags. --- app/services/post_alerter.rb | 4 ++-- spec/jobs/notify_category_change_spec.rb | 16 ++++++++++++++++ spec/jobs/notify_tag_change_spec.rb | 8 ++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 spec/jobs/notify_category_change_spec.rb diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index 53942b31d5..bd5ee865cf 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -203,8 +203,8 @@ class PostAlerter warn_if_not_sidekiq - # Don't notify the OP - user_ids -= [post.user_id] + # Don't notify the OP and last editor + user_ids -= [post.user_id, post.last_editor_id] users = User.where(id: user_ids).includes(:do_not_disturb_timings) DiscourseEvent.trigger(:before_create_notifications_for_users, users, post) diff --git a/spec/jobs/notify_category_change_spec.rb b/spec/jobs/notify_category_change_spec.rb new file mode 100644 index 0000000000..d218760aed --- /dev/null +++ b/spec/jobs/notify_category_change_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +RSpec.describe ::Jobs::NotifyCategoryChange do + fab!(:user) { Fabricate(:user) } + fab!(:regular_user) { Fabricate(:trust_level_4) } + fab!(:post) { Fabricate(:post, user: regular_user) } + fab!(:category) { Fabricate(:category, name: 'test') } + + it "doesn't create notification for the editor who watches new tag" do + CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:watching_first_post], category.id) + post.topic.update!(category: category) + post.update!(last_editor_id: user.id) + + expect { described_class.new.execute(post_id: post.id, notified_user_ids: []) }.not_to change { Notification.count } + end +end diff --git a/spec/jobs/notify_tag_change_spec.rb b/spec/jobs/notify_tag_change_spec.rb index f0fab3d519..581fd0e251 100644 --- a/spec/jobs/notify_tag_change_spec.rb +++ b/spec/jobs/notify_tag_change_spec.rb @@ -40,6 +40,14 @@ RSpec.describe ::Jobs::NotifyTagChange do expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) }.not_to change { Notification.count } end + it "doesn't create notification for the editor who watches new tag" do + TagUser.change(user.id, tag.id, TagUser.notification_levels[:watching_first_post]) + TopicTag.create!(topic: post.topic, tag: tag) + post.update!(last_editor_id: user.id) + + expect { described_class.new.execute(post_id: post.id, notified_user_ids: []) }.not_to change { Notification.count } + end + it 'doesnt create notification for user watching category' do CategoryUser.create!( user_id: user.id, From 3764ebf96341692f3c5b79268007a1d1e78c5ef8 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 10 Aug 2022 12:17:31 -0400 Subject: [PATCH 005/506] minor padding update for revamped user-menu (#17864) --- app/assets/stylesheets/common/base/menu-panel.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index cc2b01f7e0..3eb492cf56 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -104,6 +104,7 @@ flex-direction: column; justify-content: space-between; border-left: 1px solid var(--primary-low); + padding: 0.75em 0; } .tabs-list { From 5ee2741a4cc675f884f78f093e7f01247f0001a4 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 11 Aug 2022 05:56:52 +0100 Subject: [PATCH 006/506] DEV: Update user-menu components to use `@glimmer/component` (#17869) Now that all of our singletons have been converted to true Ember Services, we can remove our custom `discourse/component/glimmer` superclass and use explicit injection --- .../components/user-menu/bookmark-notification-item.js | 4 ++-- .../discourse/app/components/user-menu/items-list.js | 4 ++-- .../discourse/app/components/user-menu/menu-item.js | 4 ++-- .../discourse/app/components/user-menu/menu.js | 10 ++++++++-- .../components/user-menu/message-notification-item.js | 4 ++-- .../app/components/user-menu/notification-item.js | 5 +++++ .../user-menu/notifications-list-empty-state.js | 4 ++-- .../app/components/user-menu/notifications-list.js | 5 +++++ .../app/components/user-menu/reviewable-item.js | 5 +++++ 9 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js index 58dc4c6673..cc5679bfb3 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js @@ -1,7 +1,7 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import Notification from "discourse/models/notification"; -export default class UserMenuBookmarkNotificationItem extends GlimmerComponent { +export default class UserMenuBookmarkNotificationItem extends Component { get component() { if (this.args.item.constructor === Notification) { return "user-menu/notification-item"; diff --git a/app/assets/javascripts/discourse/app/components/user-menu/items-list.js b/app/assets/javascripts/discourse/app/components/user-menu/items-list.js index 47a46b7f72..b0fc903986 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/items-list.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/items-list.js @@ -1,9 +1,9 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import { action } from "@ember/object"; import Session from "discourse/models/session"; -export default class UserMenuItemsList extends GlimmerComponent { +export default class UserMenuItemsList extends Component { @tracked loading = false; @tracked items = []; diff --git a/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js b/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js index 47ef43be91..c16a35f8a7 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js @@ -1,7 +1,7 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { action } from "@ember/object"; -export default class UserMenuItem extends GlimmerComponent { +export default class UserMenuItem extends Component { get className() {} get linkHref() { diff --git a/app/assets/javascripts/discourse/app/components/user-menu/menu.js b/app/assets/javascripts/discourse/app/components/user-menu/menu.js index 55b184ce3f..3686da0a77 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/menu.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/menu.js @@ -1,8 +1,9 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import { action } from "@ember/object"; import { NO_REMINDER_ICON } from "discourse/models/bookmark"; import UserMenuTab, { CUSTOM_TABS_CLASSES } from "discourse/lib/user-menu/tab"; +import { inject as service } from "@ember/service"; const DEFAULT_TAB_ID = "all-notifications"; const DEFAULT_PANEL_COMPONENT = "user-menu/notifications-list"; @@ -135,7 +136,12 @@ const CORE_TOP_TABS = [ }, ]; -export default class UserMenu extends GlimmerComponent { +export default class UserMenu extends Component { + @service currentUser; + @service siteSettings; + @service site; + @service appEvents; + @tracked currentTabId = DEFAULT_TAB_ID; @tracked currentPanelComponent = DEFAULT_PANEL_COMPONENT; diff --git a/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js index c5eb793b15..1726851d27 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js @@ -1,7 +1,7 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import Notification from "discourse/models/notification"; -export default class UserMenuMessageNotificationItem extends GlimmerComponent { +export default class UserMenuMessageNotificationItem extends Component { get component() { if (this.args.item.constructor === Notification) { return "user-menu/notification-item"; diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js index d0febb1102..ffb41c123d 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js @@ -4,8 +4,13 @@ import { action } from "@ember/object"; import { getRenderDirector } from "discourse/lib/notification-item"; import getURL from "discourse-common/lib/get-url"; import cookie from "discourse/lib/cookie"; +import { inject as service } from "@ember/service"; export default class UserMenuNotificationItem extends UserMenuItem { + @service currentUser; + @service siteSettings; + @service site; + constructor() { super(...arguments); this.renderDirector = getRenderDirector( diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notifications-list-empty-state.js b/app/assets/javascripts/discourse/app/components/user-menu/notifications-list-empty-state.js index ae818afeed..742bb3c18e 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/notifications-list-empty-state.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/notifications-list-empty-state.js @@ -1,3 +1,3 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import templateOnly from "@ember/component/template-only"; -export default class UserMenuNotificationsListEmptyState extends GlimmerComponent {} +export default templateOnly(); diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js b/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js index 2303f6fbbc..7604fda2ff 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js @@ -4,8 +4,13 @@ import { action } from "@ember/object"; import { ajax } from "discourse/lib/ajax"; import { postRNWebviewMessage } from "discourse/lib/utilities"; import showModal from "discourse/lib/show-modal"; +import { inject as service } from "@ember/service"; export default class UserMenuNotificationsList extends UserMenuItemsList { + @service currentUser; + @service site; + @service store; + get filterByTypes() { return null; } diff --git a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js b/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js index eab3e3563a..e10d64a35c 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js @@ -1,8 +1,13 @@ import UserMenuItem from "discourse/components/user-menu/menu-item"; import getURL from "discourse-common/lib/get-url"; import { getRenderDirector } from "discourse/lib/reviewable-item"; +import { inject as service } from "@ember/service"; export default class UserMenuReviewableItem extends UserMenuItem { + @service currentUser; + @service siteSettings; + @service site; + constructor() { super(...arguments); this.reviewable = this.args.item; From c85921a5484f8c8084b7fc6e096deb6171b66d2e Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Thu, 11 Aug 2022 14:38:56 +0800 Subject: [PATCH 007/506] FEATURE: Adds full screen composer submit button and prompt (#17839) Context: https://meta.discourse.org/t/still-display-the-reply-create-topic-button-when-using-full-screen-composition/123597/6?u=johani Right now, we don't show the submit buttons when you enter the full-screen composer. The reasons for that are described in the context link above. This PR adds the improvements highlighted here: https://meta.discourse.org/t/still-display-the-reply-create-topic-button-when-using-full-screen-composition/123597/12?u=johani Here's a list of the changes this PR introduces: 1. When you enter full-screen mode, we will now add a prompt that matches the browser fullscreen F11 function. It looks like so The prompt fades away after a couple of seconds. 2. This PR adds the submit buttons to the full-screen composer mode. The submit buttons should work like normal if the post has no errors. If the post has errors (title too short, body too short, required categories/tags), then the button will make the composer exit the full-screen mode so that users will see the errors and fix them. The error logic is based on what we currently have; this PR doesn't add any new validation. Here's a video of what that looks like: https://meta.discourse.org/t/-/127948/14?u=johani --- .../components/composer-fullscreen-prompt.hbs | 3 ++ .../components/composer-fullscreen-prompt.js | 24 ++++++++++ .../discourse/app/controllers/composer.js | 47 ++++++++++++++----- .../discourse/app/models/composer.js | 1 + .../discourse/app/templates/composer.hbs | 30 ++++++------ .../tests/acceptance/composer-test.js | 34 ++++++++++++++ app/assets/stylesheets/desktop/compose.scss | 21 +++++++++ config/locales/client.en.yml | 3 +- 8 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs create mode 100644 app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js diff --git a/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs new file mode 100644 index 0000000000..c9fc1d04e6 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs @@ -0,0 +1,3 @@ +
+ {{html-safe this.exitPrompt}} +
diff --git a/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js new file mode 100644 index 0000000000..5504224eb4 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js @@ -0,0 +1,24 @@ +import { action } from "@ember/object"; +import GlimmerComponent from "@glimmer/component"; +import I18n from "I18n"; + +export default class ComposerFullscreenPrompt extends GlimmerComponent { + @action + registerAnimationListener(element) { + this.#addAnimationEventListener(element); + } + + #addAnimationEventListener(element) { + element.addEventListener( + "animationend", + () => { + this.args.removeFullScreenExitPrompt(); + }, + { once: true } + ); + } + + get exitPrompt() { + return I18n.t("composer.exit_fullscreen_prompt"); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js index f315a8218d..a3af7e6307 100644 --- a/app/assets/javascripts/discourse/app/controllers/composer.js +++ b/app/assets/javascripts/discourse/app/controllers/composer.js @@ -502,6 +502,11 @@ export default Controller.extend({ return false; }, + @action + removeFullScreenExitPrompt() { + this.set("model.showFullScreenExitPrompt", false); + }, + actions: { togglePreview() { this.toggleProperty("showPreview"); @@ -655,16 +660,12 @@ export default Controller.extend({ toggle() { this.closeAutocomplete(); - if ( - isEmpty(this.get("model.reply")) && - isEmpty(this.get("model.title")) - ) { + const composer = this.model; + + if (isEmpty(composer?.reply) && isEmpty(composer?.title)) { this.close(); } else { - if ( - this.get("model.composeState") === Composer.OPEN || - this.get("model.composeState") === Composer.FULLSCREEN - ) { + if (composer?.viewOpenOrFullscreen) { this.shrink(); } else { this.cancelComposer(); @@ -747,9 +748,16 @@ export default Controller.extend({ return; } - if (this.get("model.viewOpen") || this.get("model.viewFullscreen")) { + const composer = this.model; + + if (composer?.viewOpen) { this.shrink(); } + + if (composer?.viewFullscreen) { + this.toggleFullscreen(); + this.focusComposer(); + } }, groupsMentioned(groups) { @@ -839,7 +847,11 @@ export default Controller.extend({ const composer = this.model; - if (composer.cantSubmitPost) { + if (composer?.cantSubmitPost) { + if (composer?.viewFullscreen) { + this.toggleFullscreen(); + } + this.set("lastValidatedAt", Date.now()); return; } @@ -1481,13 +1493,22 @@ export default Controller.extend({ toggleFullscreen() { this._saveDraft(); - if (this.get("model.composeState") === Composer.FULLSCREEN) { - this.set("model.composeState", Composer.OPEN); + + const composer = this.model; + + if (composer?.viewFullscreen) { + composer?.set("composeState", Composer.OPEN); } else { - this.set("model.composeState", Composer.FULLSCREEN); + composer?.set("composeState", Composer.FULLSCREEN); + composer?.set("showFullScreenExitPrompt", true); } }, + @discourseComputed("model.viewFullscreen", "model.showFullScreenExitPrompt") + showFullScreenPrompt(isFullscreen, showExitPrompt) { + return isFullscreen && showExitPrompt && !this.capabilities.touch; + }, + close() { // the 'fullscreen-composer' class is added to remove scrollbars from the // document while in fullscreen mode. If the composer is closed for any reason diff --git a/app/assets/javascripts/discourse/app/models/composer.js b/app/assets/javascripts/discourse/app/models/composer.js index 7149ec2712..9b4854a638 100644 --- a/app/assets/javascripts/discourse/app/models/composer.js +++ b/app/assets/javascripts/discourse/app/models/composer.js @@ -124,6 +124,7 @@ const Composer = RestModel.extend({ noBump: false, draftSaving: false, draftForceSave: false, + showFullScreenExitPrompt: false, archetypes: reads("site.archetypes"), diff --git a/app/assets/javascripts/discourse/app/templates/composer.hbs b/app/assets/javascripts/discourse/app/templates/composer.hbs index e0c1f52ed1..abfa412e0a 100644 --- a/app/assets/javascripts/discourse/app/templates/composer.hbs +++ b/app/assets/javascripts/discourse/app/templates/composer.hbs @@ -4,6 +4,10 @@ {{#if this.visible}} + {{#if this.showFullScreenPrompt}} + + {{/if}} + {{#if this.model.viewOpenOrFullscreen}}
@@ -91,21 +95,19 @@
- {{#unless this.model.viewFullscreen}} - + - {{#if this.site.mobileView}} - - {{#if this.canEdit}} - {{d-icon "times"}} - {{else}} - {{d-icon "far-trash-alt"}} - {{/if}} - - {{else}} - {{i18n "close"}} - {{/if}} - {{/unless}} + {{#if this.site.mobileView}} + + {{#if this.canEdit}} + {{d-icon "times"}} + {{else}} + {{d-icon "far-trash-alt"}} + {{/if}} + + {{else}} + {{i18n "close"}} + {{/if}} {{#if this.site.mobileView}} {{#if this.whisperOrUnlistTopic}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 5c60c6086b..705caa4a8d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -509,6 +509,12 @@ acceptance("Composer", function (needs) { "it expands composer to full screen" ); + assert.strictEqual( + count(".composer-fullscreen-prompt"), + 1, + "the exit fullscreen prompt is visible" + ); + await click(".toggle-fullscreen"); assert.strictEqual( @@ -535,6 +541,34 @@ acceptance("Composer", function (needs) { ); }); + test("Composer fullscreen submit button", async function (assert) { + await visit("/t/this-is-a-test-topic/9"); + await click(".topic-post:nth-of-type(1) button.reply"); + + assert.strictEqual( + count("#reply-control.open"), + 1, + "it starts in open state by default" + ); + + await click(".toggle-fullscreen"); + + assert.strictEqual( + count("#reply-control button.create"), + 1, + "it shows composer submit button in fullscreen" + ); + + await fillIn(".d-editor-input", "too short"); + await click("#reply-control button.create"); + + assert.strictEqual( + count("#reply-control.open"), + 1, + "it goes back to open state if there's errors" + ); + }); + test("Composer can toggle between reply and createTopic", async function (assert) { await visit("/t/this-is-a-test-topic/9"); await click(".topic-post:nth-of-type(1) button.reply"); diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 31b430a09f..8b45b63e56 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -288,3 +288,24 @@ a.toggle-preview { } } } + +.composer-fullscreen-prompt { + animation: fadeIn 1s ease-in-out; + animation-delay: 1.5s; + animation-direction: reverse; + animation-fill-mode: forwards; + position: fixed; + left: 50%; + top: 10%; + transform: translate(-50%, 0); + .rtl & { + // R2 is not smart enough to support this swap + transform: translate(50%, 0); + } + z-index: z("header") + 1; + background: var(--primary-very-high); + color: var(--secondary); + padding: 0.5em 0.75em; + pointer-events: none; + border-radius: 2px; +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3b776812ef..a4b5a3d496 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2259,6 +2259,7 @@ en: abandon: "close composer and discard draft" enter_fullscreen: "enter fullscreen composer" exit_fullscreen: "exit fullscreen composer" + exit_fullscreen_prompt: "Press ESC to exit full screen" show_toolbar: "show composer toolbar" hide_toolbar: "hide composer toolbar" modal_ok: "OK" @@ -2307,7 +2308,7 @@ en: image_alt_text: aria_label: Alt text for image - delete_image_button: Delete Image + delete_image_button: Delete Image notifications: tooltip: From d6bba1ea9d3e42e31987087d42558019dc1da96d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 11 Aug 2022 11:03:57 +0100 Subject: [PATCH 008/506] DEV: Invalidate theme cache when S3 configuration changes (#17872) Compiled themes often include upload URLs which will need to be re-calculated --- app/models/theme.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/theme.rb b/app/models/theme.rb index 4727426a15..9bda971d54 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -159,6 +159,9 @@ class Theme < ActiveRecord::Base BASE_COMPILER_VERSION, Ember::VERSION, GlobalSetting.cdn_url, + GlobalSetting.s3_cdn_url, + GlobalSetting.s3_endpoint, + GlobalSetting.s3_bucket, Discourse.current_hostname ] Digest::SHA1.hexdigest(dependencies.join) From fe436523a5e6f19d361d77fe69d69ad8e0d9ff91 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Thu, 11 Aug 2022 19:47:49 +0530 Subject: [PATCH 009/506] FIX: don't preview color scheme if it's not current user's profile. (#17855) --- .../app/controllers/preferences/interface.js | 14 ++++++- .../user-preferences-interface-test.js | 41 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js index d91dde1b52..808f2c263d 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js @@ -13,6 +13,7 @@ import { computed } from "@ember/object"; import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { reload } from "discourse/helpers/page-reloader"; +import { propertyEqual } from "discourse/lib/computed"; const USER_HOMES = { 1: "latest", @@ -33,6 +34,7 @@ export default Controller.extend({ selectedDarkColorSchemeId: null, preferencesController: controller("preferences"), makeColorSchemeDefault: true, + canPreviewColorScheme: propertyEqual("model.id", "currentUser.id"), init() { this._super(...arguments); @@ -352,9 +354,13 @@ export default Controller.extend({ loadColorScheme(colorSchemeId) { this.setProperties({ selectedColorSchemeId: colorSchemeId, - previewingColorScheme: true, + previewingColorScheme: this.canPreviewColorScheme, }); + if (!this.canPreviewColorScheme) { + return; + } + if (colorSchemeId < 0) { const defaultTheme = this.userSelectableThemes.findBy( "id", @@ -375,9 +381,13 @@ export default Controller.extend({ loadDarkColorScheme(colorSchemeId) { this.setProperties({ selectedDarkColorSchemeId: colorSchemeId, - previewingColorScheme: true, + previewingColorScheme: this.canPreviewColorScheme, }); + if (!this.canPreviewColorScheme) { + return; + } + if (colorSchemeId === -1) { // load preview of regular scheme when dark scheme is disabled loadColorSchemeStylesheet( diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js index 50cf773d12..7c36de796d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js @@ -11,6 +11,7 @@ import Session from "discourse/models/session"; import Site from "discourse/models/site"; import selectKit from "discourse/tests/helpers/select-kit-helper"; import { test } from "qunit"; +import userFixtures from "discourse/tests/fixtures/user-fixtures"; acceptance("User Preferences - Interface", function (needs) { needs.user(); @@ -147,6 +148,14 @@ acceptance( success: "OK", }); }); + server.get("/color-scheme-stylesheet/3.json", () => { + return helper.response({ + new_href: "3.css", + }); + }); + server.get("/u/charlie.json", () => { + return helper.response(userFixtures["/u/charlie.json"]); + }); }); test("show option to disable dark mode", async function (assert) { @@ -310,5 +319,37 @@ acceptance( "resets dark scheme dropdown" ); }); + + test("preview the color scheme only in current user's profile", async function (assert) { + let site = Site.current(); + + site.set("default_dark_color_scheme", { id: 1, name: "Dark" }); + site.set("user_color_schemes", [ + { id: 2, name: "Cool Breeze" }, + { id: 3, name: "Dark Night", is_dark: true }, + ]); + + await visit("/u/eviltrout/preferences/interface"); + + await selectKit(".light-color-scheme .combobox").expand(); + await selectKit(".light-color-scheme .combobox").selectRowByValue(3); + + assert.ok( + document.querySelector("link#cs-preview-light").href.endsWith("/3.css"), + "correct stylesheet loaded" + ); + + document.querySelector("link#cs-preview-light").remove(); + + await visit("/u/charlie/preferences/interface"); + + await selectKit(".light-color-scheme .combobox").expand(); + await selectKit(".light-color-scheme .combobox").selectRowByValue(3); + + assert.notOk( + document.querySelector("link#cs-preview-light"), + "stylesheet not loaded" + ); + }); } ); From 58b135d6d30c7440f77b7d22e97a360bf8cd1cfd Mon Sep 17 00:00:00 2001 From: Roman Rizzi Date: Thu, 11 Aug 2022 11:33:41 -0300 Subject: [PATCH 010/506] DEV:Using symbols is deprecated, use strings instead (#17874) --- app/jobs/regular/publish_group_membership_updates.rb | 5 +++-- app/models/group.rb | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/jobs/regular/publish_group_membership_updates.rb b/app/jobs/regular/publish_group_membership_updates.rb index a9bc5dc93e..6b93e87a0b 100644 --- a/app/jobs/regular/publish_group_membership_updates.rb +++ b/app/jobs/regular/publish_group_membership_updates.rb @@ -3,12 +3,13 @@ module Jobs class PublishGroupMembershipUpdates < ::Jobs::Base def execute(args) - raise Discourse::InvalidParameters.new(:type) if !%w[add remove].include?(args[:type]) + available_types = [Group::AUTO_GROUPS_ADD, Group::AUTO_GROUPS_REMOVE] + raise Discourse::InvalidParameters.new(:type) if !available_types.include?(args[:type]) group = Group.find_by(id: args[:group_id]) return if !group - added_members = args[:type] == 'add' + added_members = args[:type] == Group::AUTO_GROUPS_ADD User.human_users.where(id: args[:user_ids]).each do |user| if added_members diff --git a/app/models/group.rb b/app/models/group.rb index 1228288ca5..aa6fdc0220 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -103,6 +103,9 @@ class Group < ActiveRecord::Base AUTO_GROUP_IDS = Hash[*AUTO_GROUPS.to_a.flatten.reverse] STAFF_GROUPS = [:admins, :moderators, :staff] + AUTO_GROUPS_ADD = "add" + AUTO_GROUPS_REMOVE = "remove" + IMAP_SETTING_ATTRIBUTES = [ "imap_server", "imap_port", @@ -493,7 +496,7 @@ class Group < ActiveRecord::Base if removed_user_ids.present? Jobs.enqueue( :publish_group_membership_updates, - user_ids: removed_user_ids, group_id: group.id, type: :remove + user_ids: removed_user_ids, group_id: group.id, type: AUTO_GROUPS_REMOVE ) end @@ -526,7 +529,7 @@ class Group < ActiveRecord::Base if added_user_ids.present? Jobs.enqueue( :publish_group_membership_updates, - user_ids: added_user_ids, group_id: group.id, type: :add + user_ids: added_user_ids, group_id: group.id, type: AUTO_GROUPS_ADD ) end From c789c689c2e2956661c491978564f3aa6fbc6173 Mon Sep 17 00:00:00 2001 From: Bianca Nenciu Date: Thu, 11 Aug 2022 19:09:48 +0300 Subject: [PATCH 011/506] FIX: Remove dead and large images from oneboxes (#17868) Dead and large images are replaced with a placeholder, either a broken chain icon or a short text. This commit no longer applies this transformation for images inside Oneboxes, but removes them instead. --- lib/cooked_post_processor.rb | 14 ++++- spec/lib/cooked_post_processor_spec.rb | 76 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 340559a839..2f27ecdf46 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -378,10 +378,20 @@ class CookedPostProcessor still_an_image = true if info&.too_large? - add_large_image_placeholder!(img) + if img.ancestors('.onebox, .onebox-body').blank? + add_large_image_placeholder!(img) + else + img.remove + end + still_an_image = false elsif info&.download_failed? - add_broken_image_placeholder!(img) + if img.ancestors('.onebox, .onebox-body').blank? + add_broken_image_placeholder!(img) + else + img.remove + end + still_an_image = false elsif info&.downloaded? && upload = info&.upload img["src"] = UrlHelper.cook_url(upload.url, secure: @with_secure_media) diff --git a/spec/lib/cooked_post_processor_spec.rb b/spec/lib/cooked_post_processor_spec.rb index 4c633b5a54..a4515487e0 100644 --- a/spec/lib/cooked_post_processor_spec.rb +++ b/spec/lib/cooked_post_processor_spec.rb @@ -1132,6 +1132,44 @@ RSpec.describe CookedPostProcessor do expect(cpp.doc.to_s).to include(I18n.t("upload.placeholders.too_large_humanized", max_size: "4 MB")) end + it "removes large images from onebox" do + url = 'https://example.com/article' + + Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML + + HTML + + post = Fabricate(:post, raw: url) + + PostHotlinkedMedia.create!(url: "//example.com/favicon.ico", post: post, status: 'too_large') + PostHotlinkedMedia.create!(url: "//example.com/article.jpeg", post: post, status: 'too_large') + + cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) + cpp.post_process + + expect(cpp.doc).to match_html <<~HTML + + HTML + end + it "replaces broken image placeholder" do url = 'https://image.com/my-avatar' image_url = 'https://image.com/avatar.png' @@ -1148,6 +1186,44 @@ RSpec.describe CookedPostProcessor do expect(cpp.doc.to_s).to have_tag("span.broken-image") expect(cpp.doc.to_s).to include(I18n.t("post.image_placeholder.broken")) end + + it "removes broken images from onebox" do + url = 'https://example.com/article' + + Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML + + HTML + + post = Fabricate(:post, raw: url) + + PostHotlinkedMedia.create!(url: "//example.com/favicon.ico", post: post, status: 'download_failed') + PostHotlinkedMedia.create!(url: "//example.com/article.jpeg", post: post, status: 'download_failed') + + cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) + cpp.post_process + + expect(cpp.doc).to match_html <<~HTML + + HTML + end end describe "#post_process_oneboxes removes nofollow if add_rel_nofollow_to_user_content is disabled" do From cef264ab1f46beb48a193683ca520ef8a64412b2 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 11 Aug 2022 18:47:15 +0100 Subject: [PATCH 012/506] DEV: Update welcome-topic-banner to use `@glimmer/component` (#17876) Now that all of our singletons have been converted to true Ember Services, we can remove our custom `discourse/component/glimmer` superclass and use explicit injection --- .../discourse/app/components/welcome-topic-banner.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/welcome-topic-banner.js b/app/assets/javascripts/discourse/app/components/welcome-topic-banner.js index e8778b4f84..cef596d797 100644 --- a/app/assets/javascripts/discourse/app/components/welcome-topic-banner.js +++ b/app/assets/javascripts/discourse/app/components/welcome-topic-banner.js @@ -1,10 +1,14 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { action } from "@ember/object"; import { getOwner } from "discourse-common/lib/get-owner"; import Topic from "discourse/models/topic"; import Composer from "discourse/models/composer"; +import { inject as service } from "@ember/service"; + +export default class WelcomeTopicBanner extends Component { + @service siteSettings; + @service store; -export default class WelcomeTopicBanner extends GlimmerComponent { @action editWelcomeTopic() { const topicController = getOwner(this).lookup("controller:topic"); From 06030743e826ad00eaec4fc5efa35dd7a80a867c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 Aug 2022 00:07:57 +0100 Subject: [PATCH 013/506] DEV: Update sidebar components to use `@glimmer/component` (#17875) Now that all of our singletons have been converted to true Ember Services, we can remove our custom `discourse/component/glimmer` superclass and use explicit injection This also updates `section-message` to be a templateOnly glimmer component rather than a classic component. --- .../javascripts/discourse/app/components/sidebar.js | 8 ++++++-- .../app/components/sidebar/categories-section.js | 6 ++++-- .../app/components/sidebar/community-section.js | 8 ++++++-- .../discourse/app/components/sidebar/messages-section.js | 9 +++++++-- .../app/components/sidebar/more-section-links.js | 4 ++-- .../discourse/app/components/sidebar/section-link.js | 4 ++-- .../discourse/app/components/sidebar/section-message.js | 4 ++-- .../discourse/app/components/sidebar/section.js | 7 +++++-- .../discourse/app/components/sidebar/sections.js | 8 ++++++-- .../discourse/app/components/sidebar/tags-section.js | 6 ++++-- 10 files changed, 44 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/sidebar.js b/app/assets/javascripts/discourse/app/components/sidebar.js index afb309f54f..44479e77fb 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar.js +++ b/app/assets/javascripts/discourse/app/components/sidebar.js @@ -1,7 +1,11 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { bind } from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; + +export default class Sidebar extends Component { + @service appEvents; + @service site; -export default class Sidebar extends GlimmerComponent { constructor() { super(...arguments); diff --git a/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js b/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js index fa96b5eb35..75c69bd255 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js @@ -4,11 +4,13 @@ import { cached } from "@glimmer/tracking"; import { inject as service } from "@ember/service"; import { action } from "@ember/object"; -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import CategorySectionLink from "discourse/lib/sidebar/categories-section/category-section-link"; -export default class SidebarCategoriesSection extends GlimmerComponent { +export default class SidebarCategoriesSection extends Component { @service router; + @service topicTrackingState; + @service currentUser; constructor() { super(...arguments); diff --git a/app/assets/javascripts/discourse/app/components/sidebar/community-section.js b/app/assets/javascripts/discourse/app/components/sidebar/community-section.js index e704cfe18d..d2cd5b7296 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/community-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/community-section.js @@ -1,4 +1,4 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import Composer from "discourse/models/composer"; import { getOwner } from "discourse-common/lib/get-owner"; import PermissionType from "discourse/models/permission-type"; @@ -30,8 +30,12 @@ const ADMIN_MAIN_SECTION_LINKS = [AdminSectionLink]; const MORE_SECTION_LINKS = [GroupsSectionLink, UsersSectionLink]; const MORE_SECONDARY_SECTION_LINKS = [AboutSectionLink, FAQSectionLink]; -export default class SidebarCommunitySection extends GlimmerComponent { +export default class SidebarCommunitySection extends Component { @service router; + @service topicTrackingState; + @service currentUser; + @service appEvents; + @service siteSettings; moreSectionLinks = [...MORE_SECTION_LINKS, ...customSectionLinks].map( (sectionLinkClass) => { diff --git a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js index 7c2ff92c3a..21b132b5a6 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js @@ -1,10 +1,11 @@ import { cached } from "@glimmer/tracking"; import { getOwner } from "discourse-common/lib/get-owner"; -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { bind } from "discourse-common/utils/decorators"; import GroupMessageSectionLink from "discourse/lib/sidebar/messages-section/group-message-section-link"; import PersonalMessageSectionLink from "discourse/lib/sidebar/messages-section/personal-message-section-link"; +import { inject as service } from "@ember/service"; export const INBOX = "inbox"; export const UNREAD = "unread"; @@ -22,7 +23,11 @@ export const PERSONAL_MESSAGES_INBOX_FILTERS = [ export const GROUP_MESSAGES_INBOX_FILTERS = [INBOX, NEW, UNREAD, ARCHIVE]; -export default class SidebarMessagesSection extends GlimmerComponent { +export default class SidebarMessagesSection extends Component { + @service appEvents; + @service pmTopicTrackingState; + @service currentUser; + constructor() { super(...arguments); diff --git a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js index 3e54ee34d2..f67009bd70 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js @@ -4,9 +4,9 @@ import { inject as service } from "@ember/service"; import { isEmpty } from "@ember/utils"; import { bind } from "discourse-common/utils/decorators"; -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; -export default class SidebarMoreSectionLinks extends GlimmerComponent { +export default class SidebarMoreSectionLinks extends Component { @tracked shouldDisplaySectionLinks = false; @tracked activeSectionLink; @service router; diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js index 32dc9eae3b..790a7b0f56 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js @@ -1,7 +1,7 @@ -import GlimmerComponent from "@glimmer/component"; +import Component from "@glimmer/component"; import { htmlSafe } from "@ember/template"; -export default class SectionLink extends GlimmerComponent { +export default class SectionLink extends Component { willDestroy() { if (this.args.willDestroy) { this.args.willDestroy(); diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-message.js b/app/assets/javascripts/discourse/app/components/sidebar/section-message.js index 87d5ddb040..742bb3c18e 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section-message.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-message.js @@ -1,3 +1,3 @@ -import Component from "@ember/component"; +import templateOnly from "@ember/component/template-only"; -export default Component.extend({}); +export default templateOnly(); diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section.js b/app/assets/javascripts/discourse/app/components/sidebar/section.js index f01b8433a6..c5da94691e 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/section.js @@ -1,9 +1,12 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; import { action } from "@ember/object"; import { tracked } from "@glimmer/tracking"; -export default class SidebarSection extends GlimmerComponent { +export default class SidebarSection extends Component { + @service keyValueStore; + @tracked displaySection; collapsedSidebarSectionKey = `sidebar-section-${this.args.sectionName}-collapsed`; diff --git a/app/assets/javascripts/discourse/app/components/sidebar/sections.js b/app/assets/javascripts/discourse/app/components/sidebar/sections.js index 748d408d8f..93cbe0cc60 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/sections.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/sections.js @@ -1,8 +1,12 @@ -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import { customSections as sidebarCustomSections } from "discourse/lib/sidebar/custom-sections"; import { getOwner, setOwner } from "@ember/application"; +import { inject as service } from "@ember/service"; + +export default class SidebarSections extends Component { + @service siteSettings; + @service currentUser; -export default class SidebarSections extends GlimmerComponent { customSections; constructor() { diff --git a/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js b/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js index 47a845ceb2..f2722244c4 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js @@ -4,11 +4,13 @@ import { cached } from "@glimmer/tracking"; import { inject as service } from "@ember/service"; import { action } from "@ember/object"; -import GlimmerComponent from "discourse/components/glimmer"; +import Component from "@glimmer/component"; import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link"; -export default class SidebarTagsSection extends GlimmerComponent { +export default class SidebarTagsSection extends Component { @service router; + @service topicTrackingState; + @service currentUser; constructor() { super(...arguments); From e4fbb3be21e23276208a2ee68536a201ce45f119 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 Aug 2022 03:43:38 +0100 Subject: [PATCH 014/506] DEV: Update composer-fullscreen-prompt to template-only component (#17871) 1. Replace `{{did-insert` with the builtin `{{on` modifier 2. Move the i18n call into the template With both of those changes, there is no logic left in the backing class, so we can switch to `templateOnly()` which is significantly faster. (granted, not a big deal for a component like this, but it makes for a good demonstration) --- .../components/composer-fullscreen-prompt.hbs | 4 +-- .../components/composer-fullscreen-prompt.js | 25 ++----------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs index c9fc1d04e6..e75db03048 100644 --- a/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs @@ -1,3 +1,3 @@ -
- {{html-safe this.exitPrompt}} +
+ {{html-safe (i18n "composer.exit_fullscreen_prompt")}}
diff --git a/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js index 5504224eb4..742bb3c18e 100644 --- a/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js +++ b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js @@ -1,24 +1,3 @@ -import { action } from "@ember/object"; -import GlimmerComponent from "@glimmer/component"; -import I18n from "I18n"; +import templateOnly from "@ember/component/template-only"; -export default class ComposerFullscreenPrompt extends GlimmerComponent { - @action - registerAnimationListener(element) { - this.#addAnimationEventListener(element); - } - - #addAnimationEventListener(element) { - element.addEventListener( - "animationend", - () => { - this.args.removeFullScreenExitPrompt(); - }, - { once: true } - ); - } - - get exitPrompt() { - return I18n.t("composer.exit_fullscreen_prompt"); - } -} +export default templateOnly(); From 3deabb00d4eac414954c67306541b2faff9ef4ba Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Fri, 12 Aug 2022 11:26:56 +0800 Subject: [PATCH 015/506] DEV: Route PM only tags to PM tags show route (#17870) Previously, PM only tags were being routed to the public topic list with the tag added as a filter. However, the public topic list does not fetch PMs and hence PM only tags did not provide any value when added to the Sidebar. This commit changes that by allowing the client to differentiate PM only tag and thus routes the link to the PM tags show route. Counts for PM only tags section links are not supported as of this commit and will be added in a follow up commit. --- .../app/components/sidebar/tags-section.js | 31 +++++++--- .../app/controllers/preferences/sidebar.js | 15 +++-- .../tags-section/pm-tag-section-link.js | 22 ++++++++ .../sidebar/tags-section/tag-section-link.js | 32 +++++------ .../javascripts/discourse/app/models/user.js | 19 +++++-- .../components/sidebar/tags-section.hbs | 2 +- .../sidebar-categories-section-test.js | 2 +- .../acceptance/sidebar-tags-section-test.js | 56 +++++++++++++++++-- .../user-preferences-sidebar-test.js | 13 ++++- .../concerns/user_sidebar_tags_mixin.rb | 11 ++++ app/serializers/current_user_serializer.rb | 11 +--- app/serializers/sidebar/tag_serializer.rb | 11 ++++ app/serializers/user_card_serializer.rb | 6 +- app/serializers/user_serializer.rb | 4 +- .../current_user_serializer_spec.rb | 42 ++++++-------- spec/serializers/user_serializer_spec.rb | 48 ++++++++++++++++ 16 files changed, 244 insertions(+), 81 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/lib/sidebar/tags-section/pm-tag-section-link.js create mode 100644 app/serializers/concerns/user_sidebar_tags_mixin.rb create mode 100644 app/serializers/sidebar/tag_serializer.rb diff --git a/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js b/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js index f2722244c4..55dc9a376e 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js @@ -1,15 +1,17 @@ import I18n from "I18n"; import { cached } from "@glimmer/tracking"; +import Component from "@glimmer/component"; import { inject as service } from "@ember/service"; import { action } from "@ember/object"; -import Component from "@glimmer/component"; import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link"; +import PMTagSectionLink from "discourse/lib/sidebar/tags-section/pm-tag-section-link"; export default class SidebarTagsSection extends Component { @service router; @service topicTrackingState; + @service pmTopicTrackingState; @service currentUser; constructor() { @@ -17,7 +19,9 @@ export default class SidebarTagsSection extends Component { this.callbackId = this.topicTrackingState.onStateChange(() => { this.sectionLinks.forEach((sectionLink) => { - sectionLink.refreshCounts(); + if (sectionLink.refreshCounts) { + sectionLink.refreshCounts(); + } }); }); } @@ -30,13 +34,22 @@ export default class SidebarTagsSection extends Component { get sectionLinks() { const links = []; - for (const tagName of this.currentUser.sidebarTagNames) { - links.push( - new TagSectionLink({ - tagName, - topicTrackingState: this.topicTrackingState, - }) - ); + for (const tag of this.currentUser.sidebarTags) { + if (tag.pm_only) { + links.push( + new PMTagSectionLink({ + tag, + currentUser: this.currentUser, + }) + ); + } else { + links.push( + new TagSectionLink({ + tag, + topicTrackingState: this.topicTrackingState, + }) + ); + } } return links; diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js b/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js index 73c840b008..2b712f0290 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js @@ -12,24 +12,29 @@ export default class extends Controller { @action save() { const initialSidebarCategoryIds = this.model.sidebarCategoryIds; - const initialSidebarTagNames = this.model.sidebarTagNames; - - this.model.set("sidebar_tag_names", this.selectedSidebarTagNames); this.model.set( "sidebarCategoryIds", this.selectedSidebarCategories.mapBy("id") ); + this.model.set("sidebar_tag_names", this.selectedSidebarTagNames); + this.model .save() - .then(() => { + .then((result) => { + if (result.user.sidebar_tags) { + this.model.set("sidebar_tags", result.user.sidebar_tags); + } + this.saved = true; }) .catch((error) => { this.model.set("sidebarCategoryIds", initialSidebarCategoryIds); - this.model.set("sidebar_tag_names", initialSidebarTagNames); popupAjaxError(error); + }) + .finally(() => { + this.model.set("sidebar_tag_names", []); }); } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/pm-tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/pm-tag-section-link.js new file mode 100644 index 0000000000..aec926b601 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/pm-tag-section-link.js @@ -0,0 +1,22 @@ +export default class PMTagSectionLink { + constructor({ tag, currentUser }) { + this.tag = tag; + this.currentUser = currentUser; + } + + get name() { + return this.tag.name; + } + + get models() { + return [this.currentUser, this.tag.name]; + } + + get route() { + return "userPrivateMessages.tagsShow"; + } + + get text() { + return this.tag.name; + } +} diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js index fda4b8fa29..dd24165a49 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js @@ -8,8 +8,8 @@ export default class TagSectionLink { @tracked totalUnread = 0; @tracked totalNew = 0; - constructor({ tagName, topicTrackingState }) { - this.tagName = tagName; + constructor({ tag, topicTrackingState }) { + this.tagName = tag.name; this.topicTrackingState = topicTrackingState; this.refreshCounts(); } @@ -31,18 +31,24 @@ export default class TagSectionLink { return this.tagName; } - get model() { - return this.tagName; + get models() { + return [this.tagName]; + } + + get route() { + if (this.totalUnread > 0) { + return "tag.showUnread"; + } else if (this.totalNew > 0) { + return "tag.showNew"; + } else { + return "tag.show"; + } } get currentWhen() { return "tag.show tag.showNew tag.showUnread tag.showTop"; } - get route() { - return "tag.show"; - } - get text() { return this.tagName; } @@ -58,14 +64,4 @@ export default class TagSectionLink { }); } } - - get route() { - if (this.totalUnread > 0) { - return "tag.showUnread"; - } else if (this.totalNew > 0) { - return "tag.showNew"; - } else { - return "tag.show"; - } - } } diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js index 99e9eb4d8c..76c4d82869 100644 --- a/app/assets/javascripts/discourse/app/models/user.js +++ b/app/assets/javascripts/discourse/app/models/user.js @@ -1,7 +1,7 @@ import EmberObject, { computed, get, getProperties } from "@ember/object"; import cookie, { removeCookie } from "discourse/lib/cookie"; import { defaultHomepage, escapeExpression } from "discourse/lib/utilities"; -import { alias, equal, filterBy, gt, or } from "@ember/object/computed"; +import { alias, equal, filterBy, gt, mapBy, or } from "@ember/object/computed"; import getURL, { getURLWithCDN } from "discourse-common/lib/get-url"; import { A } from "@ember/array"; import Badge from "discourse/models/badge"; @@ -314,15 +314,23 @@ const User = RestModel.extend({ sidebarCategoryIds: alias("sidebar_category_ids"), - @discourseComputed("sidebar_tag_names.[]") - sidebarTagNames(sidebarTagNames) { - if (!sidebarTagNames || sidebarTagNames.length === 0) { + @discourseComputed("sidebar_tags.[]") + sidebarTags(sidebarTags) { + if (!sidebarTags || sidebarTags.length === 0) { return []; } - return sidebarTagNames; + if (this.siteSettings.tags_sort_alphabetically) { + return sidebarTags.sort((a, b) => { + return a.name.localeCompare(b); + }); + } else { + return sidebarTags; + } }, + sidebarTagNames: mapBy("sidebarTags", "name"), + @discourseComputed("sidebar_category_ids.[]") sidebarCategories(sidebarCategoryIds) { if (!sidebarCategoryIds || sidebarCategoryIds.length === 0) { @@ -446,6 +454,7 @@ const User = RestModel.extend({ ); User.current().setProperties(userProps); this.setProperties(updatedState); + return result; }) .finally(() => { this.set("isSaving", false); diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/tags-section.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/tags-section.hbs index 1f94d16fc8..5e9e419389 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar/tags-section.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/tags-section.hbs @@ -16,7 +16,7 @@ @content={{sectionLink.text}} @currentWhen={{sectionLink.currentWhen}} @badgeText={{sectionLink.badgeText}} - @model={{sectionLink.model}}> + @models={{sectionLink.models}} > {{/each}} {{else}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js index 7abc849dbb..9c7c2390b1 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js @@ -57,7 +57,7 @@ acceptance( acceptance("Sidebar - Categories Section", function (needs) { needs.user({ sidebar_category_ids: [], - sidebar_tag_names: [], + sidebar_tags: [], }); needs.settings({ diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js index 4d370ea55a..c70e71b0f4 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js @@ -42,7 +42,18 @@ acceptance("Sidebar - Tags section", function (needs) { tracked_tags: ["tag1"], watched_tags: ["tag2", "tag3"], watching_first_post_tags: [], - sidebar_tag_names: ["tag1", "tag2", "tag3"], + sidebar_tags: [ + { name: "tag1", pm_only: false }, + { name: "tag2", pm_only: false }, + { + name: "tag3", + pm_only: false, + }, + { + name: "tag4", + pm_only: true, + }, + ], }); needs.pretender((server, helper) => { @@ -52,6 +63,20 @@ acceptance("Sidebar - Tags section", function (needs) { }); }); + server.get("/topics/private-messages-tags/:username/:tagId", () => { + const topics = [ + { id: 1, posters: [] }, + { id: 2, posters: [] }, + { id: 3, posters: [] }, + ]; + + return helper.response({ + topic_list: { + topics, + }, + }); + }); + ["latest", "top", "new", "unread"].forEach((type) => { server.get(`/tag/:tagId/l/${type}.json`, () => { return helper.response( @@ -85,7 +110,7 @@ acceptance("Sidebar - Tags section", function (needs) { test("section content when user has not added any tags", async function (assert) { updateCurrentUser({ - sidebar_tag_names: [], + sidebar_tags: [], }); await visit("/"); @@ -106,8 +131,8 @@ acceptance("Sidebar - Tags section", function (needs) { assert.strictEqual( count(".sidebar-section-tags .sidebar-section-link"), - 3, - "3 section links under the section" + 4, + "4 section links under the section" ); assert.strictEqual( @@ -167,6 +192,29 @@ acceptance("Sidebar - Tags section", function (needs) { ); }); + test("private message tag section links for user", async function (assert) { + await visit("/"); + + await click(".sidebar-section-link-tag4"); + + assert.strictEqual( + currentURL(), + "/u/eviltrout/messages/tags/tag4", + "it should transition to user's private message tag4 tag page" + ); + + assert.strictEqual( + count(".sidebar-section-tags .sidebar-section-link.active"), + 1, + "only one link is marked as active" + ); + + assert.ok( + exists(`.sidebar-section-link-tag4.active`), + "the tag4 section link is marked as active" + ); + }); + test("visiting tag discovery top route", async function (assert) { await visit(`/tag/tag1/l/top`); diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js index 6a2d3d429e..c5c2b3aeb4 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js @@ -12,7 +12,7 @@ import selectKit from "discourse/tests/helpers/select-kit-helper"; acceptance("User Preferences - Sidebar", function (needs) { needs.user({ sidebar_category_ids: [], - sidebar_tag_names: [], + sidebar_tags: [], }); needs.settings({ @@ -39,7 +39,14 @@ acceptance("User Preferences - Sidebar", function (needs) { // This request format will cause an error return helper.response(400, {}); } else { - return helper.response({ user: {} }); + return helper.response({ + user: { + sidebar_tags: [ + { name: "monkey", pm_only: false }, + { name: "gazelle", pm_only: false }, + ], + }, + }); } }); }); @@ -121,7 +128,7 @@ acceptance("User Preferences - Sidebar", function (needs) { }); test("user encountering error when adding tags to sidebar", async function (assert) { - updateCurrentUser({ sidebar_tag_names: ["monkey"] }); + updateCurrentUser({ sidebar_tags: [{ name: "monkey", pm_only: false }] }); await visit("/"); diff --git a/app/serializers/concerns/user_sidebar_tags_mixin.rb b/app/serializers/concerns/user_sidebar_tags_mixin.rb new file mode 100644 index 0000000000..f9ec79fbee --- /dev/null +++ b/app/serializers/concerns/user_sidebar_tags_mixin.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module UserSidebarTagsMixin + def self.included(base) + base.has_many :sidebar_tags, serializer: Sidebar::TagSerializer, embed: :objects + end + + def include_sidebar_tags? + SiteSetting.enable_experimental_sidebar_hamburger && SiteSetting.tagging_enabled + end +end diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index a922e4fcaa..cea7bda185 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -2,6 +2,7 @@ class CurrentUserSerializer < BasicUserSerializer include UserTagNotificationsMixin + include UserSidebarTagsMixin attributes :name, :unread_notifications, @@ -75,7 +76,7 @@ class CurrentUserSerializer < BasicUserSerializer :pending_posts_count, :status, :sidebar_category_ids, - :sidebar_tag_names, + :sidebar_tags, :likes_notifications_disabled, :grouped_unread_high_priority_notifications, :redesigned_user_menu_enabled @@ -315,14 +316,6 @@ class CurrentUserSerializer < BasicUserSerializer SiteSetting.enable_experimental_sidebar_hamburger end - def sidebar_tag_names - object.sidebar_tags.pluck(:name) - end - - def include_sidebar_tag_names? - include_sidebar_category_ids? && SiteSetting.tagging_enabled - end - def include_status? SiteSetting.enable_user_status && object.has_status? end diff --git a/app/serializers/sidebar/tag_serializer.rb b/app/serializers/sidebar/tag_serializer.rb new file mode 100644 index 0000000000..3e576cd247 --- /dev/null +++ b/app/serializers/sidebar/tag_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Sidebar + class TagSerializer < ::ApplicationSerializer + attributes :name, :pm_only + + def pm_only + object.topic_count == 0 && object.pm_topic_count > 0 + end + end +end diff --git a/app/serializers/user_card_serializer.rb b/app/serializers/user_card_serializer.rb index 3170936174..a109e9ebbf 100644 --- a/app/serializers/user_card_serializer.rb +++ b/app/serializers/user_card_serializer.rb @@ -16,7 +16,11 @@ class UserCardSerializer < BasicUserSerializer attributes(*attrs) attrs.each do |attr| define_method "include_#{attr}?" do - can_edit + if defined?(super) + super() && can_edit + else + can_edit + end end end end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 7c0a97378c..0e9f975032 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -2,6 +2,7 @@ class UserSerializer < UserCardSerializer include UserTagNotificationsMixin + include UserSidebarTagsMixin attributes :bio_raw, :bio_cooked, @@ -62,7 +63,8 @@ class UserSerializer < UserCardSerializer :user_api_keys, :user_auth_tokens, :user_notification_schedule, - :use_logo_small_as_avatar + :use_logo_small_as_avatar, + :sidebar_tags untrusted_attributes :bio_raw, :bio_cooked, diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb index 7063db3fdc..85ede7e134 100644 --- a/spec/serializers/current_user_serializer_spec.rb +++ b/spec/serializers/current_user_serializer_spec.rb @@ -220,45 +220,39 @@ RSpec.describe CurrentUserSerializer do end end - describe '#sidebar_tag_names' do + describe '#sidebar_tags' do fab!(:tag_sidebar_section_link) { Fabricate(:tag_sidebar_section_link, user: user) } fab!(:tag_sidebar_section_link_2) { Fabricate(:tag_sidebar_section_link, user: user) } - it "is not included when SiteSeting.enable_experimental_sidebar_hamburger is false" do - SiteSetting.enable_experimental_sidebar_hamburger = false - - json = serializer.as_json - - expect(json[:sidebar_tag_names]).to eq(nil) - end - - it "is not included when SiteSeting.tagging_enabled is false" do - SiteSetting.enable_experimental_sidebar_hamburger = true - SiteSetting.tagging_enabled = false - - json = serializer.as_json - - expect(json[:sidebar_tag_names]).to eq(nil) - end - it "is not included when experimental sidebar has not been enabled" do SiteSetting.enable_experimental_sidebar_hamburger = false SiteSetting.tagging_enabled = true json = serializer.as_json - expect(json[:sidebar_tag_names]).to eq(nil) + expect(json[:sidebar_tags]).to eq(nil) end - it "is present when experimental sidebar has been enabled" do + it "is not included when tagging has not been enabled" do SiteSetting.enable_experimental_sidebar_hamburger = true - SiteSetting.tagging_enabled = true + SiteSetting.tagging_enabled = false json = serializer.as_json - expect(json[:sidebar_tag_names]).to contain_exactly( - tag_sidebar_section_link.linkable.name, - tag_sidebar_section_link_2.linkable.name + expect(json[:sidebar_tags]).to eq(nil) + end + + it "is present when experimental sidebar and tagging has been enabled" do + SiteSetting.enable_experimental_sidebar_hamburger = true + SiteSetting.tagging_enabled = true + + tag_sidebar_section_link_2.linkable.update!(pm_topic_count: 5, topic_count: 0) + + json = serializer.as_json + + expect(json[:sidebar_tags]).to contain_exactly( + { name: tag_sidebar_section_link.linkable.name, pm_only: false }, + { name: tag_sidebar_section_link_2.linkable.name, pm_only: true } ) end end diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb index bfe37e15bf..966d5f225f 100644 --- a/spec/serializers/user_serializer_spec.rb +++ b/spec/serializers/user_serializer_spec.rb @@ -376,4 +376,52 @@ RSpec.describe UserSerializer do expect(json[:user_api_keys][2][:id]).to eq(user_api_key_2.id) end end + + describe '#sidebar_tags' do + fab!(:tag_sidebar_section_link) { Fabricate(:tag_sidebar_section_link, user: user) } + fab!(:tag_sidebar_section_link_2) { Fabricate(:tag_sidebar_section_link, user: user) } + + context 'when viewing self' do + subject(:json) { UserSerializer.new(user, scope: Guardian.new(user), root: false).as_json } + + it "is not included when SiteSeting.enable_experimental_sidebar_hamburger is false" do + SiteSetting.enable_experimental_sidebar_hamburger = false + SiteSetting.tagging_enabled = true + + expect(json[:sidebar_tags]).to eq(nil) + end + + it "is not included when SiteSeting.tagging_enabled is false" do + SiteSetting.enable_experimental_sidebar_hamburger = true + SiteSetting.tagging_enabled = false + + expect(json[:sidebar_tags]).to eq(nil) + end + + it "is present when experimental sidebar and tagging has been enabled" do + SiteSetting.enable_experimental_sidebar_hamburger = true + SiteSetting.tagging_enabled = true + + tag_sidebar_section_link_2.linkable.update!(pm_topic_count: 5, topic_count: 0) + + expect(json[:sidebar_tags]).to contain_exactly( + { name: tag_sidebar_section_link.linkable.name, pm_only: false }, + { name: tag_sidebar_section_link_2.linkable.name, pm_only: true } + ) + end + end + + context 'when viewing another user' do + fab!(:user2) { Fabricate(:user) } + + subject(:json) { UserSerializer.new(user, scope: Guardian.new(user2), root: false).as_json } + + it "is not present even when experimental sidebar and tagging has been enabled" do + SiteSetting.enable_experimental_sidebar_hamburger = true + SiteSetting.tagging_enabled = true + + expect(json[:sidebar_tags]).to eq(nil) + end + end + end end From 68cefc9f9db68e434587f0f3b56487736aefce3a Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Fri, 12 Aug 2022 12:12:19 +0800 Subject: [PATCH 016/506] DEV: Remove flaky sidebar acceptance test. (#17882) At a certain point, the cost of debugging a flaky acceptance test is just too high for what we're testing for here. I've decided to just accept the risk of a minor UX feature. Follow-up to 55fa94f75901fc95ac28e2fea8cc3813ab2750ab --- .../tests/acceptance/sidebar-community-section-test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js index 5dbcee7748..e3584ab589 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js @@ -148,15 +148,6 @@ acceptance("Sidebar - Community Section", function (needs) { await click( ".sidebar-section-community .sidebar-more-section-links-details-summary" ); - - await click("#main-outlet"); - - assert.notOk( - exists( - ".sidebar-section-community .sidebar-more-section-links-details-content" - ), - "additional section links are hidden when clicking outside" - ); }); test("clicking on everything link", async function (assert) { From b653b868060349e2f99756f5459ec4219d87635d Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Fri, 12 Aug 2022 12:12:39 +0800 Subject: [PATCH 017/506] DEV: Remove undefined in Sidebar::SectionLink class attribute (#17881) --- .../app/components/sidebar/section-link.js | 11 +++++- .../components/sidebar/section-link-test.js | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/discourse/tests/integration/components/sidebar/section-link-test.js diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js index 790a7b0f56..d8badbf436 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js @@ -9,7 +9,16 @@ export default class SectionLink extends Component { } get classNames() { - return `${this.args.class} sidebar-section-link sidebar-section-link-${this.args.linkName}`; + let classNames = [ + "sidebar-section-link", + `sidebar-section-link-${this.args.linkName}`, + ]; + + if (this.args.class) { + classNames.push(this.args.class); + } + + return classNames.join(" "); } get models() { diff --git a/app/assets/javascripts/discourse/tests/integration/components/sidebar/section-link-test.js b/app/assets/javascripts/discourse/tests/integration/components/sidebar/section-link-test.js new file mode 100644 index 0000000000..f413c02faa --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/sidebar/section-link-test.js @@ -0,0 +1,35 @@ +import { module, test } from "qunit"; + +import { hbs } from "ember-cli-htmlbars"; +import { render } from "@ember/test-helpers"; + +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { query } from "discourse/tests/helpers/qunit-helpers"; + +module("Integration | Component | sidebar | section-link", function (hooks) { + setupRenderingTest(hooks); + + test("default class attribute for link", async function (assert) { + const template = hbs``; + + await render(template); + + assert.strictEqual( + query("a").className, + "sidebar-section-link sidebar-section-link-test ember-view", + "has the right class attribute for the link" + ); + }); + + test("custom class attribute for link", async function (assert) { + const template = hbs``; + + await render(template); + + assert.strictEqual( + query("a").className, + "sidebar-section-link sidebar-section-link-test 123 abc ember-view", + "has the right class attribute for the link" + ); + }); +}); From b5a6015155605b2705383fd8c931d0205014004d Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Fri, 12 Aug 2022 14:40:44 +0300 Subject: [PATCH 018/506] DEV: Fallback to `bookmarkable_url` if bookmark reminder notification has no topic info (#17883) This fix is for the experimental user menu. Some `bookmark_reminder` notifications may not be associated with a topic/post (e.g. bookmark reminder for a chat message) in which case the default notification renderer cannot figure out the `href` for those `bookmark_reminder` notifications. This commit teaches the `bookmark_reminder` notification type renderer to fallback to `bookmarkable_url` that's present in the notification data if the default notification renderer doesn't return a `href` for the notification. --- .../notification-items/bookmark-reminder.js | 11 ++++++ .../bookmark-reminder-test.js | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js b/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js index d465f4a71b..6031896353 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js +++ b/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js @@ -1,5 +1,6 @@ import NotificationItemBase from "discourse/lib/notification-items/base"; import I18n from "I18n"; +import getUrl from "discourse-common/lib/get-url"; export default class extends NotificationItemBase { get linkTitle() { @@ -14,4 +15,14 @@ export default class extends NotificationItemBase { get description() { return super.description || this.notification.data.title; } + + get linkHref() { + let linkHref = super.linkHref; + if (linkHref) { + return linkHref; + } + if (this.notification.data.bookmarkable_url) { + return getUrl(this.notification.data.bookmarkable_url); + } + } } diff --git a/app/assets/javascripts/discourse/tests/unit/lib/notification-items/bookmark-reminder-test.js b/app/assets/javascripts/discourse/tests/unit/lib/notification-items/bookmark-reminder-test.js index 53fa153903..8bbe50351b 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/notification-items/bookmark-reminder-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/notification-items/bookmark-reminder-test.js @@ -82,4 +82,42 @@ discourseModule("Unit | Notification Items | bookmark-reminder", function () { "description falls back to the bookmark title if there's no fancy title" ); }); + + test("linkHref", function (assert) { + let notification = getNotification(); + let director = createRenderDirector( + notification, + "bookmark_reminder", + this.siteSettings + ); + assert.strictEqual( + director.linkHref, + "/t/this-is-fancy-title/449/113", + "is a link to the topic that the bookmark belongs to" + ); + + notification = getNotification({ + post_number: null, + topic_id: null, + fancy_title: null, + slug: null, + data: { + title: "bookmark from some plugin", + display_username: "osama", + bookmark_name: "", + bookmarkable_url: "/link/to/somewhere", + bookmarkable_id: 4324, + }, + }); + director = createRenderDirector( + notification, + "bookmark_reminder", + this.siteSettings + ); + assert.strictEqual( + director.linkHref, + "/link/to/somewhere", + "falls back to bookmarkable_url" + ); + }); }); From 69664d215372b2ee3715ba613776f38dabc4d6bd Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Fri, 12 Aug 2022 15:15:43 +0300 Subject: [PATCH 019/506] DEV: Make group message summary notification Link to the group inbox (#17884) This fix is for the experimental user menu. --- .../notification-items/group-message-summary.js | 7 +++++++ .../group-message-summary-test.js | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js b/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js index 2c02a414fc..01c91e0897 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js +++ b/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js @@ -1,4 +1,5 @@ import NotificationItemBase from "discourse/lib/notification-items/base"; +import { userPath } from "discourse/lib/url"; import I18n from "I18n"; export default class extends NotificationItemBase { @@ -12,4 +13,10 @@ export default class extends NotificationItemBase { get label() { return null; } + + get linkHref() { + return userPath( + `${this.notification.data.username}/messages/group/${this.notification.data.group_name}` + ); + } } diff --git a/app/assets/javascripts/discourse/tests/unit/lib/notification-items/group-message-summary-test.js b/app/assets/javascripts/discourse/tests/unit/lib/notification-items/group-message-summary-test.js index 64c1053d04..8e662fe0cd 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/notification-items/group-message-summary-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/notification-items/group-message-summary-test.js @@ -47,5 +47,19 @@ discourseModule( "displays the right content" ); }); + + test("linkHref", function (assert) { + const notification = getNotification(); + const director = createRenderDirector( + notification, + "group_message_summary", + this.siteSettings + ); + assert.strictEqual( + director.linkHref, + "/u/drummers.boss/messages/group/drummers", + "links to the group inbox in the user profile" + ); + }); } ); From 4b7059417307f2b40d90715e9fe68f743a729ba9 Mon Sep 17 00:00:00 2001 From: Bianca Nenciu Date: Fri, 12 Aug 2022 15:45:09 +0300 Subject: [PATCH 020/506] FIX: Reset flair group if user is removed from group (#17862) The flair used to stay set even if the user was removed from the group. --- app/models/group_user.rb | 12 ++++++++---- ...0_reset_flair_group_id_if_not_group_member.rb | 16 ++++++++++++++++ spec/models/group_user_spec.rb | 5 +++-- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20220811170600_reset_flair_group_id_if_not_group_member.rb diff --git a/app/models/group_user.rb b/app/models/group_user.rb index babfaade4e..5ee988eaa7 100644 --- a/app/models/group_user.rb +++ b/app/models/group_user.rb @@ -8,7 +8,7 @@ class GroupUser < ActiveRecord::Base after_destroy :grant_other_available_title after_save :set_primary_group - after_destroy :remove_primary_group, :recalculate_trust_level + after_destroy :remove_primary_and_flair_group, :recalculate_trust_level before_create :set_notification_level after_save :grant_trust_level @@ -84,10 +84,14 @@ class GroupUser < ActiveRecord::Base user.update!(primary_group: group) if group.primary_group end - def remove_primary_group - return if user.primary_group_id != group_id + def remove_primary_and_flair_group return if self.destroyed_by_association&.active_record == User # User is being destroyed, so don't try to update - user.update_attribute(:primary_group_id, nil) + + updates = {} + updates[:primary_group_id] = nil if user.primary_group_id == group_id + updates[:flair_group_id] = nil if user.flair_group_id == group_id + + user.update(updates) if updates.present? end def grant_other_available_title diff --git a/db/migrate/20220811170600_reset_flair_group_id_if_not_group_member.rb b/db/migrate/20220811170600_reset_flair_group_id_if_not_group_member.rb new file mode 100644 index 0000000000..a29db0a5a6 --- /dev/null +++ b/db/migrate/20220811170600_reset_flair_group_id_if_not_group_member.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class ResetFlairGroupIdIfNotGroupMember < ActiveRecord::Migration[7.0] + def change + execute <<~SQL + UPDATE users + SET flair_group_id = NULL + WHERE flair_group_id IS NOT NULL AND NOT EXISTS ( + SELECT 1 + FROM group_users + WHERE group_users.user_id = users.id + AND group_users.group_id = users.flair_group_id + ) + SQL + end +end diff --git a/spec/models/group_user_spec.rb b/spec/models/group_user_spec.rb index 1ceb91f596..16b9404b1e 100644 --- a/spec/models/group_user_spec.rb +++ b/spec/models/group_user_spec.rb @@ -226,8 +226,8 @@ RSpec.describe GroupUser do describe '#destroy!' do fab!(:group) { Fabricate(:group) } - it "removes `primary_group_id` and exec `match_primary_group_changes` method on user model" do - user = Fabricate(:user, primary_group: group) + it "removes `primary_group_id`, `flair_group_id` and exec `match_primary_group_changes` method on user model" do + user = Fabricate(:user, primary_group: group, flair_group: group) group_user = Fabricate(:group_user, group: group, user: user) user.expects(:match_primary_group_changes).once @@ -235,6 +235,7 @@ RSpec.describe GroupUser do user.reload expect(user.primary_group_id).to be_nil + expect(user.flair_group_id).to be_nil end end end From 2c5ab4720468251a84c91c177453440247d7f7e0 Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Fri, 12 Aug 2022 11:30:39 -0400 Subject: [PATCH 021/506] FIX: Regression with Categories nav item (#17885) --- .../discourse/app/controllers/navigation/default.js | 2 +- app/assets/javascripts/discourse/app/models/nav-item.js | 2 +- .../tests/acceptance/topic-discovery-tracked-test.js | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/navigation/default.js b/app/assets/javascripts/discourse/app/controllers/navigation/default.js index b9af541cdf..2e46e7abb1 100644 --- a/app/assets/javascripts/discourse/app/controllers/navigation/default.js +++ b/app/assets/javascripts/discourse/app/controllers/navigation/default.js @@ -10,6 +10,6 @@ export default Controller.extend(FilterModeMixin, { @discourseComputed("router.currentRoute.queryParams.f") skipCategoriesNavItem(filterParamValue) { - return !filterParamValue || filterParamValue !== TRACKED_QUERY_PARAM_VALUE; + return filterParamValue === TRACKED_QUERY_PARAM_VALUE; }, }); diff --git a/app/assets/javascripts/discourse/app/models/nav-item.js b/app/assets/javascripts/discourse/app/models/nav-item.js index dffb6fb5d5..c93a673ef4 100644 --- a/app/assets/javascripts/discourse/app/models/nav-item.js +++ b/app/assets/javascripts/discourse/app/models/nav-item.js @@ -262,7 +262,7 @@ NavItem.reopenClass({ } if ( - (category || !args.skipCategoriesNavItem) && + (category || args.skipCategoriesNavItem) && i.name.startsWith("categor") ) { return false; diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js index f60f66ac0b..5fbb9f3be8 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js @@ -69,6 +69,13 @@ acceptance("Topic Discovery Tracked", function (needs) { "the categories nav item is displayed when tracked filter is not present" ); + await visit("/categories"); + + assert.ok( + exists("#navigation-bar li.categories"), + "the categories nav item is displayed on categories route when tracked filter is not present" + ); + await visit("/?f=tracked"); assert.ok( From 636be8cac53e909e6c296dc3ba3c8af7851b8ad2 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 Aug 2022 17:05:51 +0100 Subject: [PATCH 022/506] DEV: Remove `discourse/components/glimmer` superclass (#17877) This was a temporary solution while we updated the resolver and migrated all our singletons to true ember services. Now that's done, we can switch to use `@glimmer/component` directly, and explicitly inject services as required. --- .../discourse/app/components/glimmer.js | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 app/assets/javascripts/discourse/app/components/glimmer.js diff --git a/app/assets/javascripts/discourse/app/components/glimmer.js b/app/assets/javascripts/discourse/app/components/glimmer.js deleted file mode 100644 index b3142a9060..0000000000 --- a/app/assets/javascripts/discourse/app/components/glimmer.js +++ /dev/null @@ -1,24 +0,0 @@ -import GlimmerComponent from "@glimmer/component"; -import { inject as service } from "@ember/service"; - -/* - Glimmer components are not EmberObjects, and therefore do not support automatic - injection of the things defined in `pre-initializers/inject-discourse-objects`. - - This base class provides an alternative. All these references are looked up lazily, - so the performance impact should be negligible -*/ - -export default class DiscourseGlimmerComponent extends GlimmerComponent { - @service appEvents; - @service store; - @service("search") searchService; - @service keyValueStore; - @service pmTopicTrackingState; - @service siteSettings; - @service messageBus; - @service currentUser; - @service session; - @service site; - @service topicTrackingState; -} From 2422ca0e67400fb1c2f68104f109d06384315ddf Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 Aug 2022 18:39:01 +0100 Subject: [PATCH 023/506] PERF: Add exponential backoff for DistributedMutex (#17886) Polling every 0.001s can cause extreme load on the redis instance, especially in scenarios where multiple app instances are waiting on the same lock. This commit introduces an exponential backoff starting from 0.001s and reaching a maximum interval of 1s. Previously `CHECK_READONLY_ATTEMPTS` was 10, and resulted in a block for 0.01s. Under the new logic, 10 attempts take more than 1s. Therefore CHECK_READONLY_ATTEMPTS is reduced to 5, bringing its total time to around 0.031s --- lib/distributed_mutex.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/distributed_mutex.rb b/lib/distributed_mutex.rb index b724948d23..bc29d14951 100644 --- a/lib/distributed_mutex.rb +++ b/lib/distributed_mutex.rb @@ -4,7 +4,7 @@ # Expiration happens when the current time is greater than the expire time class DistributedMutex DEFAULT_VALIDITY = 60 - CHECK_READONLY_ATTEMPTS = 10 + CHECK_READONLY_ATTEMPTS = 5 LOCK_SCRIPT = DiscourseRedis::EvalHelper.new <<~LUA local now = redis.call("time")[1] @@ -85,7 +85,9 @@ class DistributedMutex return expire_time if expire_time - sleep 0.001 + # Exponential backoff, max duration 1s + interval = attempts < 10 ? (0.001 * 2**attempts) : 1 + sleep interval # in readonly we will never be able to get a lock if @using_global_redis && Discourse.recently_readonly? From 5afdf24292dbc1daeabf346a3d01806b9a3d2ac8 Mon Sep 17 00:00:00 2001 From: Rafael dos Santos Silva Date: Fri, 12 Aug 2022 15:30:28 -0300 Subject: [PATCH 024/506] FEATURE: PWA users are prompted for push before first post (#17888) --- .../discourse/app/components/notification-consent-banner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/components/notification-consent-banner.js b/app/assets/javascripts/discourse/app/components/notification-consent-banner.js index d19abc998d..23c7b79472 100644 --- a/app/assets/javascripts/discourse/app/components/notification-consent-banner.js +++ b/app/assets/javascripts/discourse/app/components/notification-consent-banner.js @@ -32,7 +32,7 @@ export default DesktopNotificationConfig.extend({ this.siteSettings.push_notifications_prompt && !isNotSupported && this.currentUser && - anyPosts && + (this.capabilities.isPwa || anyPosts) && Notification.permission !== "denied" && Notification.permission !== "granted" && !isEnabled && From 494cc2c69d22a77fb339f7c6c7dc947549937cbe Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 12 Aug 2022 21:20:18 +0200 Subject: [PATCH 025/506] A11Y: moves anchor rendering out of conditional to prevent losing focus (#17887) --- .../app/templates/components/expand-post.hbs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs b/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs index 79b9709fb0..119ffede11 100644 --- a/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs @@ -1,11 +1,9 @@ {{#if this.item.truncated}} - {{#if this.expanded}} - + + {{#if this.expanded}} {{d-icon "chevron-up"}} - - {{else}} - + {{else}} {{d-icon "chevron-down"}} - - {{/if}} + {{/if}} + {{/if}} From bcb89beb99613514d4091220a18dbf85319eb8ab Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 12 Aug 2022 21:24:20 +0200 Subject: [PATCH 026/506] A11Y: makes toolbar tabindex independent from its context (#17889) Prior to this fix, if `` was used in a context where quote would not be the first button, then no button would be focusable. --- .../discourse/app/components/composer-editor.js | 1 - .../discourse/app/components/d-editor.js | 6 ++++++ .../tests/integration/components/d-editor-test.js | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index 83d14ad641..545039b79d 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -816,7 +816,6 @@ export default Component.extend(ComposerUploadUppy, { extraButtons(toolbar) { toolbar.addButton({ - tabindex: "0", id: "quote", group: "fontStyles", icon: "far-comment", diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index e6fdda2695..7cb33c3e13 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -363,6 +363,12 @@ export default Component.extend(TextareaTextManipulation, { if (this.extraButtons) { this.extraButtons(toolbar); } + + const firstButton = toolbar.groups.mapBy("buttons").flat().firstObject; + if (firstButton) { + firstButton.tabindex = 0; + } + return toolbar; }, diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js index 3720f70b7f..3a6fbdbf8f 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js @@ -6,6 +6,7 @@ import { exists, paste, query, + queryAll, } from "discourse/tests/helpers/qunit-helpers"; import { getTextareaSelection, @@ -705,6 +706,18 @@ third line` ); }); + test("toolbar buttons tabindex", async function (assert) { + await render(hbs``); + const buttons = queryAll(".d-editor-button-bar .btn"); + + assert.strictEqual( + buttons[0].getAttribute("tabindex"), + "0", + "it makes the first button focusable" + ); + assert.strictEqual(buttons[1].getAttribute("tabindex"), "-1"); + }); + testCase("replace-text event by default", async function (assert) { this.set("value", "red green blue"); From 5e521b6c0d54cdfdd34abba885a57f55538d8f42 Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Fri, 12 Aug 2022 15:37:41 -0500 Subject: [PATCH 027/506] DEV: Add before-composer-fields plugin outlet (#17891) Add ability to insert content before composer-fields --- app/assets/javascripts/discourse/app/templates/composer.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/app/templates/composer.hbs b/app/assets/javascripts/discourse/app/templates/composer.hbs index abfa412e0a..9b0bab4b90 100644 --- a/app/assets/javascripts/discourse/app/templates/composer.hbs +++ b/app/assets/javascripts/discourse/app/templates/composer.hbs @@ -45,6 +45,7 @@
+ {{#unless this.model.viewFullscreen}} {{#if this.model.canEditTitle}} {{#if this.model.creatingPrivateMessage}} From 3dac4fe075d3301c142fd4e5022f2a8cb4e56b50 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Sat, 13 Aug 2022 14:21:58 +0200 Subject: [PATCH 028/506] A11Y: ensures featured topic btn is focused when modal closes (#17898) --- .../app/controllers/preferences/profile.js | 5 +- .../tests/acceptance/preferences-test.js | 57 ---------------- .../user-preferences-profile-test.js | 68 +++++++++++++++++-- 3 files changed, 68 insertions(+), 62 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js index 2470c060f6..a3ea290db2 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js @@ -69,10 +69,13 @@ export default Controller.extend({ actions: { showFeaturedTopicModal() { - showModal("feature-topic-on-profile", { + const modal = showModal("feature-topic-on-profile", { model: this.model, title: "user.feature_topic_on_profile.title", }); + modal.set("onClose", () => { + document.querySelector(".feature-topic-on-profile-btn")?.focus(); + }); }, clearFeaturedTopicFromProfile() { diff --git a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js index e8bc847fec..1c0a2fbfee 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js @@ -474,63 +474,6 @@ acceptance("User Preferences when badges are disabled", function (needs) { }); }); -acceptance( - "User can select a topic to feature on profile if site setting in enabled", - function (needs) { - needs.user(); - needs.settings({ allow_featured_topic_on_user_profiles: true }); - needs.pretender((server, helper) => { - server.put("/u/eviltrout/feature-topic", () => { - return helper.response({ - success: true, - }); - }); - }); - - test("setting featured topic on profile", async function (assert) { - await visit("/u/eviltrout/preferences/profile"); - - assert.ok( - !exists(".featured-topic-link"), - "no featured topic link to present" - ); - assert.ok( - !exists(".clear-feature-topic-on-profile-btn"), - "clear button not present" - ); - - const selectTopicBtn = query( - ".feature-topic-on-profile-btn:nth-of-type(1)" - ); - assert.ok(exists(selectTopicBtn), "feature topic button is present"); - - await click(selectTopicBtn); - - assert.ok( - exists(".feature-topic-on-profile"), - "topic picker modal is open" - ); - - const topicRadioBtn = query( - 'input[name="choose_topic_id"]:nth-of-type(1)' - ); - assert.ok(exists(topicRadioBtn), "Topic options are prefilled"); - await click(topicRadioBtn); - - await click(".save-featured-topic-on-profile"); - - assert.ok( - exists(".featured-topic-link"), - "link to featured topic is present" - ); - assert.ok( - exists(".clear-feature-topic-on-profile-btn"), - "clear button is present" - ); - }); - } -); - acceptance("Custom User Fields", function (needs) { needs.user(); needs.site({ diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js index 3abd43494c..1bf8cfa1f4 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js @@ -1,9 +1,69 @@ -import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; -import { visit } from "@ember/test-helpers"; +import { + acceptance, + exists, + query, +} from "discourse/tests/helpers/qunit-helpers"; +import { click, visit } from "@ember/test-helpers"; import { test } from "qunit"; +acceptance("User - Preferences - Profile - Featured topic", function (needs) { + needs.user(); + + needs.settings({ allow_featured_topic_on_user_profiles: true }); + + needs.pretender((server, helper) => { + server.put("/u/eviltrout/feature-topic", () => + helper.response({ success: true }) + ); + }); + + test("setting featured topic on profile", async function (assert) { + await visit("/u/eviltrout/preferences/profile"); + + assert.ok( + !exists(".featured-topic-link"), + "no featured topic link to present" + ); + assert.ok( + !exists(".clear-feature-topic-on-profile-btn"), + "clear button not present" + ); + + await click(".feature-topic-on-profile-btn"); + + assert.ok( + exists(".feature-topic-on-profile"), + "topic picker modal is open" + ); + + await click(query('input[name="choose_topic_id"]')); + await click(".save-featured-topic-on-profile"); + + assert.ok( + exists(".featured-topic-link"), + "link to featured topic is present" + ); + assert.ok( + exists(".clear-feature-topic-on-profile-btn"), + "clear button is present" + ); + }); + + test("focused item after closing feature topic modal", async function (assert) { + await visit("/u/eviltrout/preferences/profile"); + await click(".feature-topic-on-profile-btn"); + await click(".modal-close"); + + assert.equal( + document.activeElement, + query(".feature-topic-on-profile-btn"), + "it keeps focus on the feature topic button" + ); + }); +}); + acceptance( - "User profile preferences without default calendar set", + "User - Preferences - Profile - No default calendar set", function (needs) { needs.user({ default_calendar: "none_selected" }); @@ -19,7 +79,7 @@ acceptance( ); acceptance( - "User profile preferences with default calendar set", + "User - Preferences - Profile - Default calendar set", function (needs) { needs.user({ default_calendar: "google" }); From 7476c22324a29bba3e025f05e16dce71a197fcd5 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Sat, 13 Aug 2022 14:25:32 +0200 Subject: [PATCH 029/506] DEV: implements parseAsync in discourse/lib/text (#17899) `parseAsync` allows to parse a block of markdown into tokens. Usage: ```javascript import { parseAsync } from "discourse/lib/text"; // ... await parseAsync("**test**").then((tokens) => { console.log(tokens); }) ``` --- app/assets/javascripts/discourse/app/lib/text.js | 6 ++++++ .../discourse/tests/unit/lib/text-test.js | 14 ++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 app/assets/javascripts/discourse/tests/unit/lib/text-test.js diff --git a/app/assets/javascripts/discourse/app/lib/text.js b/app/assets/javascripts/discourse/app/lib/text.js index 3ee8baea18..ee0feba09f 100644 --- a/app/assets/javascripts/discourse/app/lib/text.js +++ b/app/assets/javascripts/discourse/app/lib/text.js @@ -68,6 +68,12 @@ export function sanitizeAsync(text, options) { }); } +export function parseAsync(md, options) { + return loadMarkdownIt().then(() => { + return createPrettyText(options).opts.engine.parse(md); + }); +} + function loadMarkdownIt() { return new Promise((resolve) => { let markdownItURL = Session.currentProp("markdownItURL"); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/text-test.js b/app/assets/javascripts/discourse/tests/unit/lib/text-test.js new file mode 100644 index 0000000000..699effbe53 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/lib/text-test.js @@ -0,0 +1,14 @@ +import { module, test } from "qunit"; +import { parseAsync } from "discourse/lib/text"; + +module("Unit | Utility | text", function () { + test("parseAsync", async function (assert) { + await parseAsync("**test**").then((tokens) => { + assert.strictEqual( + tokens[1].children[1].type, + "strong_open", + "it parses the raw markdown" + ); + }); + }); +}); From ccd76ec48d45920f1b572c34dd83d3c93549d2e8 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Sat, 13 Aug 2022 15:56:41 +0200 Subject: [PATCH 030/506] FIX: markdown-it parse fn requires an env arg with {} as default (#17900) See https://markdown-it.github.io/markdown-it/#MarkdownIt.parse for more details --- app/assets/javascripts/discourse/app/lib/text.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/lib/text.js b/app/assets/javascripts/discourse/app/lib/text.js index ee0feba09f..623e165246 100644 --- a/app/assets/javascripts/discourse/app/lib/text.js +++ b/app/assets/javascripts/discourse/app/lib/text.js @@ -68,9 +68,9 @@ export function sanitizeAsync(text, options) { }); } -export function parseAsync(md, options) { +export function parseAsync(md, options = {}, env = {}) { return loadMarkdownIt().then(() => { - return createPrettyText(options).opts.engine.parse(md); + return createPrettyText(options).opts.engine.parse(md, env); }); } From 4f7c29845d14da0fb474ddae1c2eab67c78d59c5 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Sat, 13 Aug 2022 17:52:16 +0200 Subject: [PATCH 031/506] UX: Remove extraneous margins in profile pic modal (#17896) (notice horizontal margins around "Gravatar") --- app/assets/stylesheets/common/base/user.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index c4dfe45e47..e000a57ed2 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -388,6 +388,9 @@ button { margin-left: auto; } + label a { + margin: 0; + } } $label-max-width: 300px; label.radio { From 0cbdbe3be945e42efb8d95e00f18e6825bb213ae Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Sat, 13 Aug 2022 17:52:31 +0200 Subject: [PATCH 032/506] DEV: Enqueueing symbol args is deprecated (#17897) Fixes warning: ``` Deprecation notice: Jobs::SendSystemMessage was enqueued with argument values which do not cleanly serialize to/from JSON. This means that the job will be run with slightly different values than the ones supplied to `enqueue`. Argument values should be strings, booleans, numbers, or nil (or arrays/hashes of those value types). (deprecated since Discourse 2.9) (removal in Discourse 3.0) At /var/www/discourse/lib/post_destroyer.rb:335:in `notify_deletion` ``` --- lib/post_destroyer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb index cd0f037b16..d848ec75b7 100644 --- a/lib/post_destroyer.rb +++ b/lib/post_destroyer.rb @@ -335,7 +335,7 @@ class PostDestroyer Jobs.enqueue( :send_system_message, user_id: @post.user_id, - message_type: notify_responders ? :flags_agreed_and_post_deleted_for_responders : :flags_agreed_and_post_deleted, + message_type: notify_responders ? "flags_agreed_and_post_deleted_for_responders" : "flags_agreed_and_post_deleted", message_options: { flagged_post_raw_content: notify_responders ? options[:parent_post].raw : @post.raw, flagged_post_response_raw_content: @post.raw, From 23618338446c05a9465f34581881232e675ee8e4 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Sat, 13 Aug 2022 22:40:26 +0200 Subject: [PATCH 033/506] FIX: Don't raise on deleted topic in UpdateHotlinkedRaw (#17901) Fixes: ``` Job exception: undefined method `acting_user=' for nil:NilClass ``` in ``` /var/www/discourse/lib/post_revisor.rb:181:in `revise!' /var/www/discourse/app/models/post.rb:646:in `revise' /var/www/discourse/app/jobs/regular/update_hotlinked_raw.rb:24:in `execute' ``` --- app/jobs/regular/update_hotlinked_raw.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/jobs/regular/update_hotlinked_raw.rb b/app/jobs/regular/update_hotlinked_raw.rb index b938a1ffaa..1fb425839b 100644 --- a/app/jobs/regular/update_hotlinked_raw.rb +++ b/app/jobs/regular/update_hotlinked_raw.rb @@ -11,6 +11,7 @@ module Jobs post = Post.find_by(id: @post_id) return if post.nil? return if post.cook_method == Post.cook_methods[:raw_html] + return if post.topic.nil? hotlinked_map = post.post_hotlinked_media.preload(:upload).map { |r| [r.url, r] }.to_h From fdfac8c720a04600988c2c24d4dd5e37662eeced Mon Sep 17 00:00:00 2001 From: Andrei Prigorshnev Date: Sun, 14 Aug 2022 18:55:54 +0400 Subject: [PATCH 034/506] UX: improve styles of the user status message component (#17904) --- .../stylesheets/common/components/user-status-message.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/stylesheets/common/components/user-status-message.scss b/app/assets/stylesheets/common/components/user-status-message.scss index 8e45d8e6d2..b8f1aae0c4 100644 --- a/app/assets/stylesheets/common/components/user-status-message.scss +++ b/app/assets/stylesheets/common/components/user-status-message.scss @@ -1,3 +1,8 @@ +.user-status-message-description { + margin-left: 0.1em; + color: var(--primary-800); +} + .user-status-message-tooltip { .emoji { width: 1em; From 5d1cf006ab50a6db008ecb6f1163bddd9e5de121 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Sun, 14 Aug 2022 17:30:15 +0200 Subject: [PATCH 035/506] DEV: Tweak core_frontend_tests timeouts (#17902) Each test chunk takes about 10 minutes, so those timeouts can be decreased from 20 to 15. And there are three of those chunks so total can be a bit over 30 minutes, hence the bump to 35. --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a2c9f200b8..38e1e947c6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -179,7 +179,7 @@ jobs: name: core frontend (${{ matrix.browser }}) runs-on: ubuntu-latest container: discourse/discourse_test:slim-browsers - timeout-minutes: 30 + timeout-minutes: 35 strategy: fail-fast: false @@ -223,16 +223,16 @@ jobs: if: ${{ always() }} working-directory: ./app/assets/javascripts/discourse run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=1 --launch "${{ matrix.browser }}" --random - timeout-minutes: 20 + timeout-minutes: 15 - name: Core QUnit 2 if: ${{ always() }} working-directory: ./app/assets/javascripts/discourse run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=2 --launch "${{ matrix.browser }}" --random - timeout-minutes: 20 + timeout-minutes: 15 - name: Core QUnit 3 if: ${{ always() }} working-directory: ./app/assets/javascripts/discourse run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=3 --launch "${{ matrix.browser }}" --random - timeout-minutes: 20 + timeout-minutes: 15 From 92035cdc6284b596fd977b6394bc8b7168a7cd41 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Sun, 14 Aug 2022 17:31:37 +0200 Subject: [PATCH 036/506] DEV: Change test localStorage prefix (#17906) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to something more easily recognizable and less collision-prone --- app/assets/javascripts/discourse/app/lib/key-value-store.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/lib/key-value-store.js b/app/assets/javascripts/discourse/app/lib/key-value-store.js index b5436cf853..55a843ebfc 100644 --- a/app/assets/javascripts/discourse/app/lib/key-value-store.js +++ b/app/assets/javascripts/discourse/app/lib/key-value-store.js @@ -1,5 +1,7 @@ import { isTesting } from "discourse-common/config/environment"; +const TEST_KEY_PREFIX = "__test_"; + // A simple key value store that uses LocalStorage let safeLocalStorage; @@ -20,7 +22,7 @@ export default class KeyValueStore { context = null; constructor(ctx) { - this.context = isTesting() ? `test_${ctx}` : ctx; + this.context = isTesting() ? `${TEST_KEY_PREFIX}${ctx}` : ctx; } abandonLocal() { From f25e77922684fc780d7b050ffb808b3374206dc3 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Sun, 14 Aug 2022 17:31:49 +0200 Subject: [PATCH 037/506] DEV: Prefer kvs over raw localStorage (#17907) --- .../discourse/app/components/time-shortcut-picker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js b/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js index ad8da0313f..a7597ec88d 100644 --- a/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js +++ b/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js @@ -127,8 +127,8 @@ export default Component.extend({ }, _loadLastUsedCustomDatetime() { - let lastTime = localStorage.lastCustomTime; - let lastDate = localStorage.lastCustomDate; + const lastTime = this.keyValueStore.lastCustomTime; + const lastDate = this.keyValueStore.lastCustomDate; if (lastTime && lastDate) { let parsed = parseCustomDatetime(lastDate, lastTime, this.userTimezone); @@ -242,8 +242,8 @@ export default Component.extend({ if (customDatetime.isValid() && this.customDate) { dateTime = customDatetime; - localStorage.lastCustomTime = this.customTime; - localStorage.lastCustomDate = this.customDate; + this.keyValueStore.lastCustomTime = this.customTime; + this.keyValueStore.lastCustomDate = this.customDate; } } else { dateTime = this.options.findBy("id", type).time; From ecf67e084ffb12da1233b4b824412b01abf36c3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 00:54:53 +0200 Subject: [PATCH 038/506] Build(deps-dev): Bump test-prof from 1.0.9 to 1.0.10 (#17914) Bumps [test-prof](https://github.com/test-prof/test-prof) from 1.0.9 to 1.0.10. - [Release notes](https://github.com/test-prof/test-prof/releases) - [Changelog](https://github.com/test-prof/test-prof/blob/master/CHANGELOG.md) - [Commits](https://github.com/test-prof/test-prof/compare/v1.0.9...v1.0.10) --- updated-dependencies: - dependency-name: test-prof dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1478b8c75d..d0eaa1bae7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -476,7 +476,7 @@ GEM sshkey (2.0.0) stackprof (0.2.20) strscan (3.0.4) - test-prof (1.0.9) + test-prof (1.0.10) thor (1.2.1) tilt (2.0.11) timeout (0.3.0) From d15c5b87b02672ca4eb41ba7abeb55be3b140aa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 00:57:27 +0200 Subject: [PATCH 039/506] Build(deps-dev): Bump bullet from 7.0.2 to 7.0.3 (#17913) Bumps [bullet](https://github.com/flyerhzm/bullet) from 7.0.2 to 7.0.3. - [Release notes](https://github.com/flyerhzm/bullet/releases) - [Changelog](https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md) - [Commits](https://github.com/flyerhzm/bullet/compare/7.0.2...7.0.3) --- updated-dependencies: - dependency-name: bullet dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d0eaa1bae7..65cd2181c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,7 +93,7 @@ GEM bootsnap (1.13.0) msgpack (~> 1.2) builder (3.2.4) - bullet (7.0.2) + bullet (7.0.3) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) From 01d87e78b46cd7cf23b4cd8ee88ab4aea6fdb6d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 01:00:16 +0200 Subject: [PATCH 040/506] Build(deps): Bump rubocop from 1.34.1 to 1.35.0 (#17912) Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.34.1 to 1.35.0. - [Release notes](https://github.com/rubocop/rubocop/releases) - [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubocop/rubocop/compare/v1.34.1...v1.35.0) --- updated-dependencies: - dependency-name: rubocop dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 65cd2181c8..2c9cfa8288 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -418,14 +418,14 @@ GEM activesupport (>= 3.1, < 7.1) json-schema (~> 2.2) railties (>= 3.1, < 7.1) - rubocop (1.34.1) + rubocop (1.35.0) json (~> 2.3) parallel (~> 1.10) parser (>= 3.1.2.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.20.0, < 2.0) + rubocop-ast (>= 1.20.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.21.0) From 0be67cb513498f784bcb431dc11094d60bf6337d Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 15 Aug 2022 02:04:44 +0200 Subject: [PATCH 041/506] DEV: Fix random test ordering (#17905) And don't reorder failed tests when providing a seed - that was making failure debugging more difficult. --- .../javascripts/discourse/tests/test-boot-ember-cli.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/tests/test-boot-ember-cli.js b/app/assets/javascripts/discourse/tests/test-boot-ember-cli.js index 690702f1b1..3ada4484ab 100644 --- a/app/assets/javascripts/discourse/tests/test-boot-ember-cli.js +++ b/app/assets/javascripts/discourse/tests/test-boot-ember-cli.js @@ -41,7 +41,10 @@ document.addEventListener("discourse-booted", () => { if (QUnit.config.seed === undefined) { // If we're running in browser, default to random order. Otherwise, let Ember Exam // handle randomization. - QUnit.config.seed = true; + QUnit.config.seed = Math.random().toString(36).slice(2); + } else { + // Don't reorder when specifying a seed + QUnit.config.reorder = false; } loader.loadModules(); @@ -53,4 +56,5 @@ document.addEventListener("discourse-booted", () => { setupEmberOnerrorValidation: !skipCore, }); }); + window.EmberENV.TESTS_FILE_LOADED = true; From 3a21618e4e828583f8f9c6a629c57b136e4ef6ee Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 15 Aug 2022 05:52:07 +0530 Subject: [PATCH 042/506] FEATURE: allow wizard checkbox field to be disabled (#17916) * FEATURE: allow wizard checkbox field to be disabled * Changes per review feedback --- .../wizard/addon/components/wizard-field.js | 1 + .../templates/components/wizard-field-checkbox.hbs | 2 +- app/serializers/wizard_field_serializer.rb | 10 +++++++++- lib/wizard/field.rb | 3 ++- spec/lib/wizard/wizard_step_spec.rb | 5 ++++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/wizard/addon/components/wizard-field.js b/app/assets/javascripts/wizard/addon/components/wizard-field.js index 63b42c536d..d732f6bf47 100644 --- a/app/assets/javascripts/wizard/addon/components/wizard-field.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-field.js @@ -7,6 +7,7 @@ export default Component.extend({ ":wizard-container__field", "typeClasses", "field.invalid", + "field.disabled", ], @discourseComputed("field.type", "field.id") diff --git a/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs index a0a5086725..978ec3a70e 100644 --- a/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs +++ b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs @@ -1,5 +1,5 @@