From 2cff8c82e36e584346040200a2bcc3192bb3b9ad Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Wed, 22 Jan 2025 13:17:45 +0100 Subject: [PATCH] UX: adds chat send shortcut user preference (#30473) Users can now decide if they want to send a message on: - enter - meta + enter If you choose meta + enter, enter will add a linebreak. Screenshot 2025-01-21 at 12 57 48 --- .../stylesheets/common/base/discourse.scss | 6 ++ app/models/user_option.rb | 1 + .../discourse/components/chat-composer.js | 23 ++++--- .../discourse/controllers/preferences-chat.js | 22 ++++++ .../initializers/chat-user-options.js | 2 + .../discourse/templates/preferences/chat.hbs | 34 ++++++++++ plugins/chat/config/locales/client.en.yml | 7 ++ ...62229_add_chat_send_shortcut_preference.rb | 7 ++ .../chat/lib/chat/user_option_extension.rb | 8 +++ plugins/chat/plugin.rb | 5 ++ .../chat/spec/system/chat_composer_spec.rb | 68 +++++++++++++++++++ .../page_objects/chat/components/composer.rb | 13 ++++ .../spec/system/user_chat_preferences_spec.rb | 9 +++ 13 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 plugins/chat/db/migrate/20241226162229_add_chat_send_shortcut_preference.rb diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 0c366c2c245..553509952c5 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -611,6 +611,12 @@ textarea { font-weight: normal; } } + + .radio-group { + display: flex; + flex-direction: column; + gap: 0.5em; + } } // Special elements diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 9983feeae5d..84b0c2d463b 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -296,6 +296,7 @@ end # sidebar_show_count_of_new_items :boolean default(FALSE), not null # watched_precedence_over_muted :boolean # chat_separate_sidebar_mode :integer default(0), not null +# chat_send_shortcut :integer default(0), not null # topics_unread_when_closed :boolean default(TRUE), not null # show_thread_title_prompts :boolean default(TRUE), not null # enable_smart_lists :boolean default(TRUE), not null diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js index 9075351d672..7781d38f1d1 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js @@ -309,7 +309,6 @@ export default class ChatComposer extends Component { if ( this.site.mobileView || event.altKey || - event.metaKey || this.#isAutocompleteDisplayed() ) { return; @@ -320,18 +319,22 @@ export default class ChatComposer extends Component { } if (event.key === "Enter") { - if (event.shiftKey) { - // Shift+Enter: insert newline + // if we are inside a code block just insert newline + const { pre } = this.composer.textarea.getSelected({ lineVal: true }); + if (this.composer.textarea.isInside(pre, /(^|\n)```/g)) { return; } - // Ctrl+Enter, plain Enter: send - if (!event.ctrlKey) { - // if we are inside a code block just insert newline - const { pre } = this.composer.textarea.getSelected({ lineVal: true }); - if (this.composer.textarea.isInside(pre, /(^|\n)```/g)) { - return; - } + const shortcutPreference = + this.currentUser.user_option.chat_send_shortcut; + const send = + (shortcutPreference === "enter" && !event.shiftKey) || + event.ctrlKey || + event.metaKey; + + if (!send) { + // insert newline + return; } this.onSend(); diff --git a/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js b/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js index 4da09e5ba15..3af87360378 100644 --- a/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js +++ b/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js @@ -4,6 +4,8 @@ import { service } from "@ember/service"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseComputed from "discourse/lib/decorators"; import { isTesting } from "discourse/lib/environment"; +import { PLATFORM_KEY_MODIFIER } from "discourse/lib/keyboard-shortcuts"; +import { translateModKey } from "discourse/lib/utilities"; import { i18n } from "discourse-i18n"; import { CHAT_SOUNDS } from "discourse/plugins/chat/discourse/services/chat-audio-manager"; @@ -16,6 +18,7 @@ const CHAT_ATTRS = [ "chat_email_frequency", "chat_header_indicator_preference", "chat_separate_sidebar_mode", + "chat_send_shortcut", ]; export const HEADER_INDICATOR_PREFERENCE_NEVER = "never"; @@ -29,6 +32,21 @@ export default class PreferencesChatController extends Controller { subpageTitle = i18n("chat.admin.title"); + chatSendShortcutOptions = [ + { + label: i18n("chat.send_shortcut.enter.label"), + value: "enter", + description: i18n("chat.send_shortcut.enter.description"), + }, + { + label: i18n("chat.send_shortcut.meta_enter.label", { + meta_key: translateModKey(PLATFORM_KEY_MODIFIER), + }), + value: "meta_enter", + description: i18n("chat.send_shortcut.meta_enter.description"), + }, + ]; + emailFrequencyOptions = [ { name: i18n("chat.email_frequency.never"), value: "never" }, { name: i18n("chat.email_frequency.when_away"), value: "when_away" }, @@ -77,6 +95,10 @@ export default class PreferencesChatController extends Controller { } } + get chatSendShortcut() { + return this.model.get("user_option.chat_send_shortcut"); + } + @discourseComputed chatSounds() { return Object.keys(CHAT_SOUNDS).map((value) => { diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js index f360310f7fb..79cf1be0250 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js @@ -8,6 +8,7 @@ const CHAT_SOUND = "chat_sound"; const CHAT_EMAIL_FREQUENCY = "chat_email_frequency"; const CHAT_HEADER_INDICATOR_PREFERENCE = "chat_header_indicator_preference"; const CHAT_SEPARATE_SIDEBAR_MODE = "chat_separate_sidebar_mode"; +const CHAT_SEND_SHORTCUT = "chat_send_shortcut"; export default { name: "chat-user-options", @@ -24,6 +25,7 @@ export default { api.addSaveableUserOptionField(CHAT_EMAIL_FREQUENCY); api.addSaveableUserOptionField(CHAT_HEADER_INDICATOR_PREFERENCE); api.addSaveableUserOptionField(CHAT_SEPARATE_SIDEBAR_MODE); + api.addSaveableUserOptionField(CHAT_SEND_SHORTCUT); } }); }, diff --git a/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs b/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs index f5a81b228e0..c458caf86ed 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.hbs @@ -116,6 +116,40 @@ /> +
+
+ {{#each this.chatSendShortcutOptions as |option|}} +
+ + + {{option.description}} + +
+ {{/each}} +
+
+