From 27bdfb64379016d7e36abbb1ebd9ddf59eb2cfdb Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Mon, 28 Oct 2024 14:18:15 +1000 Subject: [PATCH] FEATURE: Add user preference to disable smart lists (#29434) Followup 30fdd7738ec942126bb055c9a6a8d69ddffba2da Adds a new site setting and corresponding user preference to disable smart lists. By default they are enabled, because this is a better experience for most users. A small number of users would prefer to not have this enabled. Smart lists automatically append new items to each list started in the composer when enter is pressed. If enter is pressed on an empty list item, it is cleared. This setting will be removed when the new composer is complete. --- .../admin/addon/mixins/setting-component.js | 1 + .../discourse/app/components/d-editor.js | 24 ++++++---- .../app/controllers/preferences/interface.js | 1 + .../javascripts/discourse/app/models/user.js | 2 + .../app/templates/preferences/interface.hbs | 6 +++ .../tests/fixtures/session-fixtures.js | 7 ++- .../discourse/tests/fixtures/user-fixtures.js | 5 ++ .../integration/components/d-editor-test.js | 48 +++++++++++++++---- app/models/user_option.rb | 2 + .../current_user_option_serializer.rb | 1 + app/serializers/user_option_serializer.rb | 1 + .../site_setting_update_existing_users.rb | 1 + app/services/user_updater.rb | 1 + config/locales/client.en.yml | 3 +- config/locales/server.en.yml | 1 + config/site_settings.yml | 1 + ...28021339_add_smart_list_user_preference.rb | 7 +++ lib/tasks/import.rake | 2 + script/bulk_import/base.rb | 2 + spec/models/user_option_spec.rb | 2 + spec/models/user_spec.rb | 2 + .../api/schemas/json/user_get_response.json | 4 ++ 22 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 db/migrate/20241028021339_add_smart_list_user_preference.rb 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",