UX: Split hide_profile_and_presence user option (#29632)

It splits the hide_profile_and_presence user option and the default_hide_profile_and_presence site setting for more granular control. It keeps the option to hide the profile under /u/username/preferences/interface and adds the presence toggle in the quick user menu.

Co-authored-by: Régis Hanol <regis@hanol.fr>
This commit is contained in:
Jan Cernik 2024-11-12 22:22:58 -03:00 committed by GitHub
parent d637bd6519
commit 234133bd3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 179 additions and 63 deletions

View File

@ -51,7 +51,8 @@ const DEFAULT_USER_PREFERENCES = [
"default_email_mailing_list_mode_frequency",
"default_email_previous_replies",
"default_email_in_reply_to",
"default_hide_profile_and_presence",
"default_hide_profile",
"default_hide_presence",
"default_other_new_topic_duration_minutes",
"default_other_auto_track_topics_after_msecs",
"default_other_notification_level_when_replying",

View File

@ -23,6 +23,36 @@
</li>
{{/if}}
<li class="online" title={{i18n "presence_toggle.title"}}>
<DButton @action={{this.togglePresence}} class="btn-flat profile-tab-btn">
{{d-icon (if this.isPresenceHidden "toggle-on" "toggle-off")}}
<span class="item-label">
{{i18n "presence_toggle.label"}}
</span>
</DButton>
</li>
<li class="do-not-disturb">
<DButton
@action={{this.doNotDisturbClick}}
class="btn-flat profile-tab-btn"
>
{{d-icon (if this.isInDoNotDisturb "toggle-on" "toggle-off")}}
<span class="item-label">
{{#if this.isInDoNotDisturb}}
<span>{{i18n "pause_notifications.label"}}</span>
{{#if this.showDoNotDisturbEndDate}}
{{format-age this.doNotDisturbDateTime}}
{{/if}}
{{else}}
{{i18n "pause_notifications.label"}}
{{/if}}
</span>
</DButton>
</li>
<hr />
<li class="summary">
<LinkTo @route="user.summary" @model={{this.currentUser}}>
{{d-icon "user"}}
@ -74,25 +104,6 @@
</LinkTo>
</li>
<li class="do-not-disturb">
<DButton
@action={{this.doNotDisturbClick}}
class="btn-flat profile-tab-btn"
>
{{d-icon (if this.isInDoNotDisturb "toggle-on" "toggle-off")}}
<span class="item-label">
{{#if this.isInDoNotDisturb}}
<span>{{i18n "pause_notifications.label"}}</span>
{{#if this.showDoNotDisturbEndDate}}
{{format-age this.doNotDisturbDateTime}}
{{/if}}
{{else}}
{{i18n "pause_notifications.label"}}
{{/if}}
</span>
</DButton>
</li>
{{#if this.showToggleAnonymousButton}}
<li
class={{if

View File

@ -60,6 +60,10 @@ export default class UserMenuProfileTabContent extends Component {
return date;
}
get isPresenceHidden() {
return this.currentUser.get("user_option.hide_presence");
}
@action
doNotDisturbClick() {
if (this.saving) {
@ -77,6 +81,12 @@ export default class UserMenuProfileTabContent extends Component {
}
}
@action
togglePresence() {
this.currentUser.set("user_option.hide_presence", !this.isPresenceHidden);
this.currentUser.save(["hide_presence"]);
}
@action
setUserStatusClick() {
this.args.closeUserMenu();

View File

@ -74,7 +74,8 @@ export default class InterfaceController extends Controller {
"allow_private_messages",
"enable_allowed_pm_users",
"homepage_id",
"hide_profile_and_presence",
"hide_profile",
"hide_presence",
"text_size",
"title_count_mode",
"skip_new_user_tips",

View File

@ -121,7 +121,8 @@ let userOptionFields = [
"allow_private_messages",
"enable_allowed_pm_users",
"homepage_id",
"hide_profile_and_presence",
"hide_profile",
"hide_presence",
"text_size",
"title_count_mode",
"timezone",
@ -187,7 +188,8 @@ export default class User extends RestModel.extend(Evented) {
@userOption("dynamic_favicon") dynamic_favicon;
@userOption("automatically_unpin_topics") automatically_unpin_topics;
@userOption("likes_notifications_disabled") likes_notifications_disabled;
@userOption("hide_profile_and_presence") hide_profile_and_presence;
@userOption("hide_profile") hide_profile;
@userOption("hide_presence") hide_presence;
@userOption("title_count_mode") title_count_mode;
@userOption("enable_defer") enable_defer;
@userOption("timezone") timezone;

View File

@ -199,8 +199,8 @@
{{/if}}
{{#if this.siteSettings.allow_users_to_hide_profile}}
<PreferenceCheckbox
@labelKey="user.hide_profile_and_presence"
@checked={{this.model.user_option.hide_profile_and_presence}}
@labelKey="user.hide_profile"
@checked={{this.model.user_option.hide_profile}}
data-setting-name="user-hide-profile"
class="pref-hide-profile"
/>

View File

@ -2722,6 +2722,8 @@ export default {
allow_private_messages: true,
homepage_id: null,
hide_profile_and_presence: false,
hide_profile: false,
hide_presence: false,
text_size: "normal",
text_size_seq: 0,
timezone: "America/Los_Angeles",
@ -3126,6 +3128,8 @@ export default {
allow_private_messages: true,
homepage_id: null,
hide_profile_and_presence: false,
hide_profile: false,
hide_presence: false,
text_size: "normal",
text_size_seq: 0,
title_count_mode: "notifications",

View File

@ -236,6 +236,11 @@
color: var(--tertiary);
}
}
hr {
border-top: 1px solid var(--primary-low);
width: 100%;
}
}
}

View File

@ -21,6 +21,7 @@ class UserOption < ActiveRecord::Base
belongs_to :user
before_create :set_defaults
before_save :update_hide_profile_and_presence
after_save :update_tracked_topics
scope :human_users, -> { where("user_id > 0") }
@ -94,7 +95,8 @@ class UserOption < ActiveRecord::Base
self.title_count_mode = SiteSetting.default_title_count_mode
self.hide_profile_and_presence = SiteSetting.default_hide_profile_and_presence
self.hide_profile = SiteSetting.default_hide_profile
self.hide_presence = SiteSetting.default_hide_presence
self.sidebar_link_to_filtered_list = SiteSetting.default_sidebar_link_to_filtered_list
self.sidebar_show_count_of_new_items = SiteSetting.default_sidebar_show_count_of_new_items
@ -223,6 +225,15 @@ class UserOption < ActiveRecord::Base
private
def update_hide_profile_and_presence
if hide_profile_changed? || hide_presence_changed?
self.hide_profile_and_presence = hide_profile || hide_presence
elsif hide_profile_and_presence_changed?
self.hide_profile = hide_profile_and_presence
self.hide_presence = hide_profile_and_presence
end
end
def update_tracked_topics
return unless saved_change_to_auto_track_topics_after_msecs?
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
@ -255,6 +266,8 @@ end
# homepage_id :integer
# theme_ids :integer default([]), not null, is an Array
# hide_profile_and_presence :boolean default(FALSE), not null
# hide_profile :boolean default(FALSE), not null
# hide_presence :boolean default(FALSE), not null
# text_size_key :integer default(0), not null
# text_size_seq :integer default(0), not null
# email_level :integer default(1), not null

View File

@ -7,7 +7,7 @@ module UserStatusMixin
def include_status?
@options[:include_status] && SiteSetting.enable_user_status &&
!object.user_option&.hide_profile_and_presence && object.has_status?
!object.user_option&.hide_profile && object.has_status?
end
def status

View File

@ -9,6 +9,8 @@ class CurrentUserOptionSerializer < ApplicationSerializer
:automatically_unpin_topics,
:likes_notifications_disabled,
:hide_profile_and_presence,
:hide_profile,
:hide_presence,
:title_count_mode,
:enable_defer,
:timezone,

View File

@ -29,6 +29,8 @@ class UserOptionSerializer < ApplicationSerializer
:enable_allowed_pm_users,
:homepage_id,
:hide_profile_and_presence,
:hide_profile,
:hide_presence,
:text_size,
:text_size_seq,
:title_count_mode,

View File

@ -126,7 +126,8 @@ class SiteSettingUpdateExistingUsers
default_include_tl0_in_digests: "include_tl0_in_digests",
default_text_size: "text_size_key",
default_title_count_mode: "title_count_mode_key",
default_hide_profile_and_presence: "hide_profile_and_presence",
default_hide_profile: "hide_profile",
default_hide_presence: "hide_presence",
default_sidebar_link_to_filtered_list: "sidebar_link_to_filtered_list",
default_sidebar_show_count_of_new_items: "sidebar_show_count_of_new_items",
}

View File

@ -42,7 +42,8 @@ class UserUpdater
allow_private_messages
enable_allowed_pm_users
homepage_id
hide_profile_and_presence
hide_profile
hide_presence
text_size
title_count_mode
timezone

View File

@ -1839,7 +1839,7 @@ en:
website: "Web Site"
email_settings: "Email"
hide_profile_and_presence: "Hide my public profile and presence features"
hide_profile: "Hide my public profile"
enable_physical_keyboard: "Enable physical keyboard support on iPad"
text_size:
@ -2146,6 +2146,10 @@ en:
pause_notifications: "Pause notifications"
remove_status: "Remove status"
presence_toggle:
label: "Online"
title: "Toggle presence features"
user_tips:
button: "Got it!"
skip: "Skip tips"

View File

@ -2596,7 +2596,8 @@ en:
default_email_in_reply_to: "Include excerpt of replied to post in emails by default."
default_hide_profile_and_presence: "Hide user public profile and presence features by default."
default_hide_profile: "Hide user public profile by default."
default_hide_presence: "Disable presence features by default."
default_other_new_topic_duration_minutes: "Global default condition for which a topic is considered new."
default_other_auto_track_topics_after_msecs: "Global default time before a topic is automatically tracked."

View File

@ -3007,7 +3007,9 @@ user_preferences:
default: 2
default_email_in_reply_to:
default: false
default_hide_profile_and_presence:
default_hide_profile:
default: false
default_hide_presence:
default: false
default_other_new_topic_duration_minutes:
enum: "NewTopicDurationSiteSetting"

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
class SplitHideProfileAndPresence < ActiveRecord::Migration[7.1]
def up
# Split default_hide_profile_and_presence if setting exists
result =
execute(
"SELECT value, data_type FROM site_settings WHERE name = 'default_hide_profile_and_presence' LIMIT 1",
).first
if result
value = result["value"]
data_type = result["data_type"]
execute "DELETE FROM site_settings WHERE name = 'default_hide_profile_and_presence'"
execute <<-SQL
INSERT INTO site_settings (name, value, data_type, created_at, updated_at)
VALUES
('default_hide_profile', '#{value}', '#{data_type}', NOW(), NOW()),
('default_hide_presence', '#{value}', '#{data_type}', NOW(), NOW());
SQL
end
# Add new columns to user_options
execute "ALTER TABLE user_options ADD COLUMN hide_profile BOOLEAN DEFAULT FALSE NOT NULL"
execute "ALTER TABLE user_options ADD COLUMN hide_presence BOOLEAN DEFAULT FALSE NOT NULL"
execute <<-SQL
UPDATE user_options
SET hide_profile = hide_profile_and_presence,
hide_presence = hide_profile_and_presence
WHERE hide_profile_and_presence;
SQL
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -131,7 +131,7 @@ module UserGuardian
return true if !SiteSetting.allow_users_to_hide_profile?
# If a user has hidden their profile, restrict it to them and staff
return is_me?(user) || is_staff? if user.user_option.try(:hide_profile_and_presence?)
return is_me?(user) || is_staff? if user.user_option.try(:hide_profile?)
true
end

View File

@ -173,6 +173,8 @@ def insert_user_options
like_notification_frequency,
skip_new_user_tips,
hide_profile_and_presence,
hide_profile,
hide_presence,
sidebar_link_to_filtered_list,
sidebar_show_count_of_new_items
)
@ -196,7 +198,9 @@ def insert_user_options
, #{SiteSetting.default_other_notification_level_when_replying}
, #{SiteSetting.default_other_like_notification_frequency}
, #{SiteSetting.default_other_skip_new_user_tips}
, #{SiteSetting.default_hide_profile_and_presence}
, #{SiteSetting.default_hide_profile || SiteSetting.default_hide_presence}
, #{SiteSetting.default_hide_profile}
, #{SiteSetting.default_hide_presence}
, #{SiteSetting.default_sidebar_link_to_filtered_list}
, #{SiteSetting.default_sidebar_show_count_of_new_items}
FROM users u

View File

@ -260,7 +260,7 @@ export default class Chat extends Service {
return;
}
if (this.currentUser.user_option?.hide_profile_and_presence) {
if (this.currentUser.user_option?.hide_presence) {
return;
}

View File

@ -68,7 +68,7 @@ describe UsersController do
before { sign_in(user) }
it "returns that the user can message themselves" do
user.user_option.update!(hide_profile_and_presence: false)
user.user_option.update!(hide_profile: false)
user.user_option.update!(chat_enabled: true)
get "/u/#{user.username}/card.json"
expect(response).to be_successful
@ -76,7 +76,7 @@ describe UsersController do
end
it "returns that the user can message themselves when the profile is hidden" do
user.user_option.update!(hide_profile_and_presence: true)
user.user_option.update!(hide_profile: true)
user.user_option.update!(chat_enabled: true)
get "/u/#{user.username}/card.json"
expect(response).to be_successful
@ -87,7 +87,7 @@ describe UsersController do
context "when hidden users" do
before do
sign_in(another_user)
user.user_option.update!(hide_profile_and_presence: true)
user.user_option.update!(hide_profile: true)
end
it "returns the correct partial response when the user has chat enabled" do

View File

@ -21,7 +21,7 @@ describe Chat::ChatablesSerializer do
end
context "with hidden profile" do
before { user_1.user_option.update!(hide_profile_and_presence: true) }
before { user_1.user_option.update!(hide_profile: true) }
it "doesnt include status" do
serializer =

View File

@ -11,7 +11,7 @@ export default class ComposerPresenceManager extends Service {
notifyState(intent, id) {
if (
this.siteSettings.allow_users_to_hide_profile &&
this.currentUser.user_option.hide_profile_and_presence
this.currentUser.user_option.hide_presence
) {
return;
}

View File

@ -586,6 +586,8 @@ class BulkImport::Base
like_notification_frequency
skip_new_user_tips
hide_profile_and_presence
hide_profile
hide_presence
sidebar_link_to_filtered_list
sidebar_show_count_of_new_items
timezone
@ -1321,7 +1323,8 @@ class BulkImport::Base
notification_level_when_replying: SiteSetting.default_other_notification_level_when_replying,
like_notification_frequency: SiteSetting.default_other_like_notification_frequency,
skip_new_user_tips: SiteSetting.default_other_skip_new_user_tips,
hide_profile_and_presence: SiteSetting.default_hide_profile_and_presence,
hide_profile: SiteSetting.default_hide_profile,
hide_presence: SiteSetting.default_hide_presence,
sidebar_link_to_filtered_list: SiteSetting.default_sidebar_link_to_filtered_list,
sidebar_show_count_of_new_items: SiteSetting.default_sidebar_show_count_of_new_items,
}

View File

@ -112,7 +112,7 @@ RSpec.describe UserGuardian do
let(:hidden_user) do
result = Fabricate(:user)
result.user_option.update_column(:hide_profile_and_presence, true)
result.user_option.update_column(:hide_profile, true)
result
end

View File

@ -28,7 +28,8 @@ RSpec.describe UserOption do
end
it "should not hide the profile and presence by default" do
expect(user.user_option.hide_profile_and_presence).to eq(false)
expect(user.user_option.hide_profile).to eq(false)
expect(user.user_option.hide_presence).to eq(false)
end
it "should correctly set digest frequency" do

View File

@ -2167,7 +2167,8 @@ RSpec.describe User do
SiteSetting.default_other_dynamic_favicon = true
SiteSetting.default_other_skip_new_user_tips = true
SiteSetting.default_hide_profile_and_presence = true
SiteSetting.default_hide_profile = true
SiteSetting.default_hide_presence = true
SiteSetting.default_topics_automatic_unpin = false
SiteSetting.default_categories_watching = category0.id.to_s
@ -2189,7 +2190,8 @@ RSpec.describe User do
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)
expect(options.hide_profile).to eq(true)
expect(options.hide_presence).to eq(true)
expect(options.automatically_unpin_topics).to eq(false)
expect(options.new_topic_duration_minutes).to eq(-1)
expect(options.auto_track_topics_after_msecs).to eq(0)

View File

@ -771,6 +771,12 @@
"hide_profile_and_presence": {
"type": "boolean"
},
"hide_profile": {
"type": "boolean"
},
"hide_presence": {
"type": "boolean"
},
"text_size": {
"type": "string"
},
@ -840,6 +846,8 @@
"enable_allowed_pm_users",
"homepage_id",
"hide_profile_and_presence",
"hide_profile",
"hide_presence",
"text_size",
"text_size_seq",
"title_count_mode",

View File

@ -942,8 +942,8 @@ RSpec.describe ListController do
end
end
context "when `hide_profile_and_presence` is true" do
before { user.user_option.update_columns(hide_profile_and_presence: true) }
context "when `hide_profile` is true" do
before { user.user_option.update_columns(hide_profile: true) }
it "returns 404" do
get "/topics/created-by/#{user.username}.json"
@ -1149,8 +1149,8 @@ RSpec.describe ListController do
end
describe "user_topics_feed" do
it "returns 404 if `hide_profile_and_presence` user option is checked" do
user.user_option.update_columns(hide_profile_and_presence: true)
it "returns 404 if `hide_profile` user option is checked" do
user.user_option.update_columns(hide_profile: true)
get "/u/#{user.username}/activity/topics.rss"
expect(response.status).to eq(404)
end

View File

@ -2630,8 +2630,8 @@ RSpec.describe PostsController do
expect(body).to include(public_post.topic.slug)
end
it "returns 404 if `hide_profile_and_presence` user option is checked" do
user.user_option.update_columns(hide_profile_and_presence: true)
it "returns 404 if `hide_profile` user option is checked" do
user.user_option.update_columns(hide_profile: true)
get "/u/#{user.username}/activity.rss"
expect(response.status).to eq(404)
@ -2641,7 +2641,7 @@ RSpec.describe PostsController do
end
it "succeeds when `allow_users_to_hide_profile` is false" do
user.user_option.update_columns(hide_profile_and_presence: true)
user.user_option.update_columns(hide_profile: true)
SiteSetting.allow_users_to_hide_profile = false
get "/u/#{user.username}/activity.rss"

View File

@ -55,7 +55,7 @@ RSpec.describe UserActionsController do
context "when user's profile is hidden" do
fab!(:post)
before { post.user.user_option.update_column(:hide_profile_and_presence, true) }
before { post.user.user_option.update_column(:hide_profile, true) }
context "when `allow_users_to_hide_profile` is disabled" do
before { SiteSetting.allow_users_to_hide_profile = false }

View File

@ -166,9 +166,9 @@ RSpec.describe UserBadgesController do
end
context "with hidden profiles" do
before { user.user_option.update_columns(hide_profile_and_presence: true) }
before { user.user_option.update_columns(hide_profile: true) }
it "returns 404 if `hide_profile_and_presence` user option is checked" do
it "returns 404 if `hide_profile` user option is checked" do
get "/user-badges/#{user.username}.json"
expect(response.status).to eq(404)
end

View File

@ -4282,8 +4282,8 @@ RSpec.describe UsersController do
end
end
context "when `hide_profile_and_presence` user option is checked" do
before_all { user1.user_option.update_columns(hide_profile_and_presence: true) }
context "when `hide_profile` user option is checked" do
before_all { user1.user_option.update_columns(hide_profile: true) }
it "returns 404" do
get "/u/#{user1.username_lower}/summary.json"
@ -4642,7 +4642,7 @@ RSpec.describe UsersController do
end
it "returns a hidden profile" do
user.user_option.update_column(:hide_profile_and_presence, true)
user.user_option.update_column(:hide_profile, true)
get "/u/#{user.username}.json"
expect(response.status).to eq(200)
@ -4840,7 +4840,7 @@ RSpec.describe UsersController do
it "should not be able to view a private user profile" do
user1.user_profile.update!(bio_raw: "Hello world!")
user1.user_option.update!(hide_profile_and_presence: true)
user1.user_option.update!(hide_profile: true)
get "/u/#{user1.username}"
@ -4913,7 +4913,7 @@ RSpec.describe UsersController do
end
context "when hidden users" do
before { user.user_option.update!(hide_profile_and_presence: true) }
before { user.user_option.update!(hide_profile: true) }
it "returns the correct partial response when the user has messages enabled" do
user.user_option.update!(allow_private_messages: true)
@ -4952,8 +4952,8 @@ RSpec.describe UsersController do
expect(response).to have_http_status(:forbidden)
end
context "when `hide_profile_and_presence` user option is checked" do
before { user2.user_option.update_columns(hide_profile_and_presence: true) }
context "when `hide_profile` user option is checked" do
before { user2.user_option.update_columns(hide_profile: true) }
it "does not include hidden profiles" do
get "/user-cards.json?user_ids=#{user.id},#{user2.id}"

View File

@ -34,7 +34,7 @@ RSpec.describe UserStatusMixin do
end
it "doesn't include status if user hid profile and presence" do
user.user_option.hide_profile_and_presence = true
user.user_option.hide_profile = true
serializer = DummySerializer.new(user, root: false, include_status: true)
json = serializer.as_json
expect(json[:status]).to be_nil