diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js index 8fa9c076c00..fb77bcf3b32 100644 --- a/app/assets/javascripts/admin/addon/mixins/setting-component.js +++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js @@ -57,6 +57,7 @@ const DEFAULT_USER_PREFERENCES = [ "default_other_notification_level_when_replying", "default_other_external_links_in_new_tab", "default_other_enable_quoting", + "default_other_enable_smart_lists", "default_other_enable_defer", "default_other_dynamic_favicon", "default_other_like_notification_frequency", diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index fdbaf179d59..a1c863b30fa 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -374,11 +374,13 @@ export default class DEditor extends Component { // // c.f. https://developer.mozilla.org/en-US/docs/Web/API/Element/beforeinput_event if (this._textarea) { - this._textarea.addEventListener( - "beforeinput", - this.onBeforeInputSmartList - ); - this._textarea.addEventListener("input", this.onInputSmartList); + if (this.currentUser.user_option.enable_smart_lists) { + this._textarea.addEventListener( + "beforeinput", + this.onBeforeInputSmartList + ); + this._textarea.addEventListener("input", this.onInputSmartList); + } this.element.addEventListener("paste", this.textManipulation.paste); } @@ -483,11 +485,13 @@ export default class DEditor extends Component { } if (this._textarea) { - this._textarea.removeEventListener( - "beforeinput", - this.onBeforeInputSmartList - ); - this._textarea.removeEventListener("input", this.onInputSmartList); + if (this.currentUser.user_option.enable_smart_lists) { + this._textarea.removeEventListener( + "beforeinput", + this.onBeforeInputSmartList + ); + this._textarea.removeEventListener("input", this.onInputSmartList); + } } this._itsatrap?.destroy(); diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js index 1829cae9f76..18fb89220b9 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js @@ -68,6 +68,7 @@ export default class InterfaceController extends Controller { "external_links_in_new_tab", "dynamic_favicon", "enable_quoting", + "enable_smart_lists", "enable_defer", "automatically_unpin_topics", "allow_private_messages", diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js index a32d63ca0ba..5d0d6a31675 100644 --- a/app/assets/javascripts/discourse/app/models/user.js +++ b/app/assets/javascripts/discourse/app/models/user.js @@ -108,6 +108,7 @@ let userOptionFields = [ "dark_scheme_id", "dynamic_favicon", "enable_quoting", + "enable_smart_lists", "enable_defer", "automatically_unpin_topics", "digest_after_minutes", @@ -182,6 +183,7 @@ export default class User extends RestModel.extend(Evented) { @userOption("mailing_list_mode") mailing_list_mode; @userOption("external_links_in_new_tab") external_links_in_new_tab; @userOption("enable_quoting") enable_quoting; + @userOption("enable_smart_lists") enable_smart_lists; @userOption("dynamic_favicon") dynamic_favicon; @userOption("automatically_unpin_topics") automatically_unpin_topics; @userOption("likes_notifications_disabled") likes_notifications_disabled; diff --git a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs index fcc86a0a8e8..f4a04c2ee8a 100644 --- a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs +++ b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs @@ -177,6 +177,12 @@ data-setting-name="user-enable-quoting" class="pref-enable-quoting" /> + `); @@ -1017,6 +1022,23 @@ third line` }); } + testCase( + "smart lists - when enable_smart_lists is false pressing enter on a line with a list item starting with *", + async function (assert, textarea) { + const initialValue = "* first item in list\n"; + this.set("value", initialValue); + setCaretPosition(textarea, initialValue.length); + await triggerEnter(textarea); + + assert.strictEqual( + this.value, + initialValue, + "it does not create an empty list item on the next line" + ); + }, + { enable_smart_lists: false } + ); + testCase( "smart lists - pressing enter on a line with a list item starting with *", async function (assert, textarea) { @@ -1030,7 +1052,8 @@ third line` initialValue + "* ", "it creates a list item on the next line" ); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1046,7 +1069,8 @@ third line` initialValue + "", "it doesn’t continue the list" ); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1062,7 +1086,8 @@ third line` initialValue + "* ", "it continues the list" ); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1073,7 +1098,8 @@ third line` setCaretPosition(textarea, initialValue.length); await triggerEnter(textarea); assert.strictEqual(this.value, initialValue + "- "); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1088,7 +1114,8 @@ third line` initialValue + "2. ", "it creates a list item on the next line with an auto-incremented number" ); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1103,7 +1130,8 @@ third line` "* first item in list\n* \n* second item in list", "it inserts a new list item on the next line" ); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1118,7 +1146,8 @@ third line` "1. first item in list\n2. \n3. second item in list", "it inserts a new list item on the next line and renumbers the rest of the list" ); - } + }, + { enable_smart_lists: true } ); testCase( @@ -1133,7 +1162,8 @@ third line` "* first item in list with empty line\n", "it removes the list item" ); - } + }, + { enable_smart_lists: true } ); (() => { diff --git a/app/models/user_option.rb b/app/models/user_option.rb index d8c45caeb00..a61091c5af2 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -73,6 +73,7 @@ class UserOption < ActiveRecord::Base self.email_in_reply_to = SiteSetting.default_email_in_reply_to self.enable_quoting = SiteSetting.default_other_enable_quoting + self.enable_smart_lists = SiteSetting.default_other_enable_smart_lists self.enable_defer = SiteSetting.default_other_enable_defer self.external_links_in_new_tab = SiteSetting.default_other_external_links_in_new_tab self.dynamic_favicon = SiteSetting.default_other_dynamic_favicon @@ -284,6 +285,7 @@ end # chat_separate_sidebar_mode :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 # # Indexes # diff --git a/app/serializers/current_user_option_serializer.rb b/app/serializers/current_user_option_serializer.rb index 09810ff33f7..6911a8c026a 100644 --- a/app/serializers/current_user_option_serializer.rb +++ b/app/serializers/current_user_option_serializer.rb @@ -4,6 +4,7 @@ class CurrentUserOptionSerializer < ApplicationSerializer attributes :mailing_list_mode, :external_links_in_new_tab, :enable_quoting, + :enable_smart_lists, :dynamic_favicon, :automatically_unpin_topics, :likes_notifications_disabled, diff --git a/app/serializers/user_option_serializer.rb b/app/serializers/user_option_serializer.rb index 97804335402..466d6046555 100644 --- a/app/serializers/user_option_serializer.rb +++ b/app/serializers/user_option_serializer.rb @@ -12,6 +12,7 @@ class UserOptionSerializer < ApplicationSerializer :dark_scheme_id, :dynamic_favicon, :enable_quoting, + :enable_smart_lists, :enable_defer, :digest_after_minutes, :automatically_unpin_topics, diff --git a/app/services/site_setting_update_existing_users.rb b/app/services/site_setting_update_existing_users.rb index 32a6fecb8f1..32313282862 100644 --- a/app/services/site_setting_update_existing_users.rb +++ b/app/services/site_setting_update_existing_users.rb @@ -113,6 +113,7 @@ class SiteSettingUpdateExistingUsers default_email_previous_replies: "email_previous_replies", default_email_in_reply_to: "email_in_reply_to", default_other_enable_quoting: "enable_quoting", + default_other_enable_smart_lists: "enable_smart_lists", default_other_enable_defer: "enable_defer", default_other_external_links_in_new_tab: "external_links_in_new_tab", default_other_dynamic_favicon: "dynamic_favicon", diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index a2e28d2d612..3e67a55f761 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -24,6 +24,7 @@ class UserUpdater email_messages_level external_links_in_new_tab enable_quoting + enable_smart_lists enable_defer color_scheme_id dark_scheme_id diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4f494b1c655..a8126c5978b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1358,6 +1358,7 @@ en: allow_private_messages: "Allow other users to send me personal messages" external_links_in_new_tab: "Open all external links in a new tab" enable_quoting: "Enable quote reply for highlighted text" + enable_smart_lists: "Enable smart lists when writing in the composer" enable_defer: "Enable mark topics as unread" experimental_sidebar: enable: "Enable sidebar" @@ -1427,7 +1428,7 @@ en: ignored_users: "Ignored" ignored_users_instructions: "Suppress all posts, notifications, and PMs from these users." tracked_topics_link: "Show" - automatically_unpin_topics: "Automatically unpin topics when I reach the bottom." + automatically_unpin_topics: "Automatically unpin topics when I reach the bottom" apps: "Apps" revoke_access: "Revoke Access" undo_revoke_access: "Undo Revoke Access" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2ed1a64bd31..e03cda120ed 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2602,6 +2602,7 @@ en: default_other_notification_level_when_replying: "Global default notification level when the user replies to a topic." default_other_external_links_in_new_tab: "Open external links in a new tab by default." default_other_enable_quoting: "Enable quote reply for highlighted text by default." + default_other_enable_smart_lists: "Enable smart lists when typing in the composer by default." default_other_enable_defer: "Enable defer topic functionality by default." default_other_dynamic_favicon: "Show new/updated topic count on browser icon by default." default_other_skip_new_user_tips: "Skip new user onboarding tips and badges." diff --git a/config/site_settings.yml b/config/site_settings.yml index 012df4fe9ab..44f4b2f3d2c 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -3059,6 +3059,7 @@ user_preferences: default_other_external_links_in_new_tab: false default_other_enable_quoting: true default_other_enable_defer: false + default_other_enable_smart_lists: true default_other_dynamic_favicon: false default_other_skip_new_user_tips: false default_other_like_notification_frequency: diff --git a/db/migrate/20241028021339_add_smart_list_user_preference.rb b/db/migrate/20241028021339_add_smart_list_user_preference.rb new file mode 100644 index 00000000000..f93c409b58d --- /dev/null +++ b/db/migrate/20241028021339_add_smart_list_user_preference.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddSmartListUserPreference < ActiveRecord::Migration[7.1] + def change + add_column :user_options, :enable_smart_lists, :boolean, default: true, null: false + end +end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index a7d1e844a50..8cdebd78302 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -164,6 +164,7 @@ def insert_user_options include_tl0_in_digests, automatically_unpin_topics, enable_quoting, + enable_smart_lists, external_links_in_new_tab, dynamic_favicon, new_topic_duration_minutes, @@ -187,6 +188,7 @@ def insert_user_options , #{SiteSetting.default_include_tl0_in_digests} , #{SiteSetting.default_topics_automatic_unpin} , #{SiteSetting.default_other_enable_quoting} + , #{SiteSetting.default_other_enable_smart_lists} , #{SiteSetting.default_other_external_links_in_new_tab} , #{SiteSetting.default_other_dynamic_favicon} , #{SiteSetting.default_other_new_topic_duration_minutes} diff --git a/script/bulk_import/base.rb b/script/bulk_import/base.rb index f054eb3fd98..93ddecf3a1c 100644 --- a/script/bulk_import/base.rb +++ b/script/bulk_import/base.rb @@ -577,6 +577,7 @@ class BulkImport::Base include_tl0_in_digests automatically_unpin_topics enable_quoting + enable_smart_lists external_links_in_new_tab dynamic_favicon new_topic_duration_minutes @@ -1306,6 +1307,7 @@ class BulkImport::Base include_tl0_in_digests: SiteSetting.default_include_tl0_in_digests, automatically_unpin_topics: SiteSetting.default_topics_automatic_unpin, enable_quoting: SiteSetting.default_other_enable_quoting, + enable_smart_lists: SiteSetting.default_other_enable_smart_lists, external_links_in_new_tab: SiteSetting.default_other_external_links_in_new_tab, dynamic_favicon: SiteSetting.default_other_dynamic_favicon, new_topic_duration_minutes: SiteSetting.default_other_new_topic_duration_minutes, diff --git a/spec/models/user_option_spec.rb b/spec/models/user_option_spec.rb index 175747f0f9a..107b5463cc0 100644 --- a/spec/models/user_option_spec.rb +++ b/spec/models/user_option_spec.rb @@ -73,6 +73,7 @@ RSpec.describe UserOption do describe "site settings" do it "should apply defaults from site settings" do SiteSetting.default_other_enable_quoting = false + SiteSetting.default_other_enable_smart_lists = false SiteSetting.default_other_enable_defer = true SiteSetting.default_other_external_links_in_new_tab = true SiteSetting.default_other_dynamic_favicon = true @@ -81,6 +82,7 @@ RSpec.describe UserOption do user = Fabricate(:user) expect(user.user_option.enable_quoting).to eq(false) + expect(user.user_option.enable_smart_lists).to eq(false) expect(user.user_option.enable_defer).to eq(true) expect(user.user_option.external_links_in_new_tab).to eq(true) expect(user.user_option.dynamic_favicon).to eq(true) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0d62b131305..9c58f4f4ab8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2163,6 +2163,7 @@ RSpec.describe User do SiteSetting.default_other_notification_level_when_replying = 3 # immediately SiteSetting.default_other_external_links_in_new_tab = true SiteSetting.default_other_enable_quoting = false + SiteSetting.default_other_enable_smart_lists = false SiteSetting.default_other_dynamic_favicon = true SiteSetting.default_other_skip_new_user_tips = true @@ -2185,6 +2186,7 @@ RSpec.describe User do expect(options.email_messages_level).to eq(UserOption.email_level_types[:never]) expect(options.external_links_in_new_tab).to eq(true) expect(options.enable_quoting).to eq(false) + expect(options.enable_smart_lists).to eq(false) expect(options.dynamic_favicon).to eq(true) expect(options.skip_new_user_tips).to eq(true) expect(options.hide_profile_and_presence).to eq(true) diff --git a/spec/requests/api/schemas/json/user_get_response.json b/spec/requests/api/schemas/json/user_get_response.json index 6783e532217..263a0f0096f 100644 --- a/spec/requests/api/schemas/json/user_get_response.json +++ b/spec/requests/api/schemas/json/user_get_response.json @@ -714,6 +714,9 @@ "enable_quoting": { "type": "boolean" }, + "enable_smart_lists": { + "type": "boolean" + }, "enable_defer": { "type": "boolean" }, @@ -820,6 +823,7 @@ "dark_scheme_id", "dynamic_favicon", "enable_quoting", + "enable_smart_lists", "enable_defer", "digest_after_minutes", "automatically_unpin_topics",