diff --git a/frontend_tests/node_tests/activity.js b/frontend_tests/node_tests/activity.js
index d7be071c16..440ef050e9 100644
--- a/frontend_tests/node_tests/activity.js
+++ b/frontend_tests/node_tests/activity.js
@@ -376,6 +376,7 @@ test("handlers", ({override, mock_template}) => {
test("first/prev/next", ({override, mock_template}) => {
let rendered_alice;
let rendered_fred;
+ user_settings.user_list_style = 2;
mock_template("presence_row.hbs", false, (data) => {
switch (data.user_id) {
@@ -392,6 +393,12 @@ test("first/prev/next", ({override, mock_template}) => {
user_circle_status: "translated: Active",
user_id: alice.user_id,
status_emoji_info: undefined,
+ status_text: undefined,
+ user_list_style: {
+ COMPACT: false,
+ WITH_STATUS: true,
+ WITH_AVATAR: false,
+ },
});
break;
case fred.user_id:
@@ -407,6 +414,12 @@ test("first/prev/next", ({override, mock_template}) => {
user_circle_status: "translated: Active",
faded: false,
status_emoji_info: undefined,
+ status_text: undefined,
+ user_list_style: {
+ COMPACT: false,
+ WITH_STATUS: true,
+ WITH_AVATAR: false,
+ },
});
break;
/* istanbul ignore next */
@@ -438,6 +451,7 @@ test("first/prev/next", ({override, mock_template}) => {
});
test("insert_one_user_into_empty_list", ({override, mock_template}) => {
+ user_settings.user_list_style = 2;
mock_template("presence_row.hbs", true, (data, html) => {
assert.deepEqual(data, {
href: "#narrow/pm-with/1-alice",
@@ -450,6 +464,12 @@ test("insert_one_user_into_empty_list", ({override, mock_template}) => {
user_circle_status: "translated: Active",
faded: true,
status_emoji_info: undefined,
+ status_text: undefined,
+ user_list_style: {
+ COMPACT: false,
+ WITH_STATUS: true,
+ WITH_AVATAR: false,
+ },
});
assert.ok(html.startsWith("
{
people.add_active_user(fred);
user_status.set_away(alice.user_id);
user_settings.emojiset = "google";
+ user_settings.user_list_style = 2;
const status_emoji_info = {
emoji_alt_code: false,
emoji_name: "car",
@@ -521,6 +522,12 @@ test("get_items_for_users", () => {
user_status.set_status_emoji({user_id, ...status_emoji_info});
}
+ const user_list_style = {
+ COMPACT: false,
+ WITH_STATUS: true,
+ WITH_AVATAR: false,
+ };
+
assert.deepEqual(buddy_data.get_items_for_users(user_ids), [
{
faded: false,
@@ -530,9 +537,11 @@ test("get_items_for_users", () => {
name: "Human Myself",
num_unread: 0,
status_emoji_info,
+ status_text: undefined,
user_circle_class: "user_circle_green",
user_circle_status: "translated: Active",
user_id: 1001,
+ user_list_style,
},
{
faded: false,
@@ -542,9 +551,11 @@ test("get_items_for_users", () => {
name: "Alice Smith",
num_unread: 0,
status_emoji_info,
+ status_text: undefined,
user_circle_class: "user_circle_empty_line",
user_circle_status: "translated: Unavailable",
user_id: 1002,
+ user_list_style,
},
{
faded: false,
@@ -554,9 +565,11 @@ test("get_items_for_users", () => {
name: "Fred Flintstone",
num_unread: 0,
status_emoji_info,
+ status_text: undefined,
user_circle_class: "user_circle_empty",
user_circle_status: "translated: Offline",
user_id: 1003,
+ user_list_style,
},
]);
});
diff --git a/frontend_tests/node_tests/dispatch.js b/frontend_tests/node_tests/dispatch.js
index 0b6a68b87c..53542e4135 100644
--- a/frontend_tests/node_tests/dispatch.js
+++ b/frontend_tests/node_tests/dispatch.js
@@ -885,6 +885,17 @@ run_test("user_settings", ({override}) => {
assert_same(user_settings.demote_inactive_streams, 2);
}
+ {
+ const stub = make_stub();
+ event = event_fixtures.user_settings__user_list_style;
+ override(settings_display, "report_user_list_style_change", stub.f);
+ user_settings.user_list_style = 1;
+ override(activity, "build_user_sidebar", stub.f);
+ dispatch(event);
+ assert.equal(stub.num_calls, 2);
+ assert_same(user_settings.user_list_style, 2);
+ }
+
event = event_fixtures.user_settings__enter_sends;
user_settings.enter_sends = false;
dispatch(event);
diff --git a/frontend_tests/node_tests/lib/events.js b/frontend_tests/node_tests/lib/events.js
index 025b5509d6..17708a4e44 100644
--- a/frontend_tests/node_tests/lib/events.js
+++ b/frontend_tests/node_tests/lib/events.js
@@ -915,6 +915,13 @@ exports.fixtures = {
value: true,
},
+ user_settings__user_list_style: {
+ type: "user_settings",
+ op: "update",
+ property: "user_list_style",
+ value: 2,
+ },
+
user_status__revoke_away: {
type: "user_status",
user_id: 63,
diff --git a/static/js/admin.js b/static/js/admin.js
index 357fb4c943..b1e3bcf580 100644
--- a/static/js/admin.js
+++ b/static/js/admin.js
@@ -89,6 +89,7 @@ function get_realm_level_notification_settings(options) {
export function build_page() {
const options = {
custom_profile_field_types: page_params.custom_profile_field_types,
+ full_name: page_params.full_name,
realm_name: page_params.realm_name,
realm_org_type: page_params.realm_org_type,
realm_available_video_chat_providers: page_params.realm_available_video_chat_providers,
@@ -158,6 +159,7 @@ export function build_page() {
settings_config.common_message_policy_values.by_admins_only.code,
...settings_org.get_organization_settings_options(),
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
+ user_list_style_values: settings_config.user_list_style_values,
color_scheme_values: settings_config.color_scheme_values,
default_view_values: settings_config.default_view_values,
settings_object: realm_user_settings_defaults,
diff --git a/static/js/buddy_data.js b/static/js/buddy_data.js
index 682275c2fd..f440deb01a 100644
--- a/static/js/buddy_data.js
+++ b/static/js/buddy_data.js
@@ -8,6 +8,7 @@ import * as people from "./people";
import * as presence from "./presence";
import * as timerender from "./timerender";
import * as unread from "./unread";
+import {user_settings} from "./user_settings";
import * as user_status from "./user_status";
import * as util from "./util";
@@ -179,6 +180,13 @@ export function info_for(user_id) {
const status_emoji_info = user_status.get_status_emoji(user_id);
const user_circle_status = status_description(user_id);
+ const status_text = user_status.get_status_text(user_id);
+ const user_list_style_value = user_settings.user_list_style;
+ const user_list_style = {
+ COMPACT: user_list_style_value === 1,
+ WITH_STATUS: user_list_style_value === 2,
+ WITH_AVATAR: user_list_style_value === 3,
+ };
return {
href: hash_util.pm_with_url(person.email),
@@ -190,6 +198,8 @@ export function info_for(user_id) {
num_unread: get_num_unread(user_id),
user_circle_class,
user_circle_status,
+ status_text,
+ user_list_style,
};
}
diff --git a/static/js/realm_user_settings_defaults.ts b/static/js/realm_user_settings_defaults.ts
index e1a16a5fcd..52181eb16c 100644
--- a/static/js/realm_user_settings_defaults.ts
+++ b/static/js/realm_user_settings_defaults.ts
@@ -34,6 +34,7 @@ export type RealmDefaultSettings = {
starred_message_counts: boolean;
translate_emoticons: boolean;
twenty_four_hour_time: boolean;
+ user_list_style: boolean;
wildcard_mentions_notify: boolean;
};
diff --git a/static/js/server_events_dispatch.js b/static/js/server_events_dispatch.js
index 5eaa95ea13..f9289ff3f0 100644
--- a/static/js/server_events_dispatch.js
+++ b/static/js/server_events_dispatch.js
@@ -621,6 +621,7 @@ export function dispatch_normal_event(event) {
"twenty_four_hour_time",
"translate_emoticons",
"display_emoji_reaction_users",
+ "user_list_style",
"starred_message_counts",
"send_stream_typing_notifications",
"send_private_typing_notifications",
@@ -652,6 +653,12 @@ export function dispatch_normal_event(event) {
stream_list.update_streams_sidebar();
stream_data.set_filter_out_inactives();
}
+ if (event.property === "user_list_style") {
+ settings_display.report_user_list_style_change(
+ settings_display.user_settings_panel,
+ );
+ activity.build_user_sidebar();
+ }
if (event.property === "dense_mode") {
$("body").toggleClass("less_dense_mode");
$("body").toggleClass("more_dense_mode");
diff --git a/static/js/settings.js b/static/js/settings.js
index 7d0b20cf7a..78eb1f60d2 100644
--- a/static/js/settings.js
+++ b/static/js/settings.js
@@ -83,6 +83,7 @@ export function build_page() {
can_create_new_bots: settings_bots.can_create_new_bots(),
settings_label,
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
+ user_list_style_values: settings_config.user_list_style_values,
color_scheme_values: settings_config.color_scheme_values,
default_view_values: settings_config.default_view_values,
twenty_four_hour_time_values: settings_config.twenty_four_hour_time_values,
diff --git a/static/js/settings_config.ts b/static/js/settings_config.ts
index 6f03f3f5db..b32b52c75f 100644
--- a/static/js/settings_config.ts
+++ b/static/js/settings_config.ts
@@ -39,6 +39,22 @@ export const demote_inactive_streams_values = {
},
};
+export const user_list_style_values = {
+ compact: {
+ code: 1,
+ description: $t({defaultMessage: "Compact"}),
+ },
+ with_status: {
+ code: 2,
+ description: $t({defaultMessage: "Show status text"}),
+ },
+ // The `with_avatar` design in still in discussion.
+ // with_avatar: {
+ // code: 3,
+ // description: $t({defaultMessage: "Show status text and avatar"}),
+ // },
+};
+
export const default_view_values = {
recent_topics: {
code: "recent_topics",
diff --git a/static/js/settings_display.js b/static/js/settings_display.js
index 50ee0dc6a5..37131c7d5f 100644
--- a/static/js/settings_display.js
+++ b/static/js/settings_display.js
@@ -166,6 +166,9 @@ export function set_up(settings_panel) {
$container
.find(`.setting_emojiset_choice[value="${CSS.escape(settings_object.emojiset)}"]`)
.prop("checked", true);
+ $container
+ .find(`.setting_user_list_style_choice[value=${settings_object.user_list_style}]`)
+ .prop("checked", true);
if (for_realm_settings) {
// For the realm-level defaults page, we use the common
@@ -224,6 +227,29 @@ export function set_up(settings_panel) {
},
});
});
+
+ $container.find(".setting_user_list_style_choice").on("click", function () {
+ const data = {user_list_style: $(this).val()};
+ const current_user_list_style = settings_object.user_list_style;
+ if (current_user_list_style === data.user_list_style) {
+ return;
+ }
+ const $spinner = $container.find(".theme-settings-status").expectOne();
+ loading.make_indicator($spinner, {text: settings_ui.strings.saving});
+
+ channel.patch({
+ url: "/json/settings",
+ data,
+ success() {},
+ error(xhr) {
+ ui_report.error(
+ settings_ui.strings.failure_html,
+ xhr,
+ $container.find(".theme-settings-status").expectOne(),
+ );
+ },
+ });
+ });
}
export async function report_emojiset_change(settings_panel) {
@@ -247,6 +273,25 @@ export async function report_emojiset_change(settings_panel) {
}
}
+export async function report_user_list_style_change(settings_panel) {
+ // TODO: Clean up how this works so we can use
+ // change_display_setting. The challenge is that we don't want to
+ // report success before the server_events request returns that
+ // causes the actual sprite sheet to change. The current
+ // implementation is wrong, though, in that it displays the UI
+ // update in all active browser windows.
+ const $spinner = $(settings_panel.container).find(".theme-settings-status");
+ if ($spinner.length) {
+ loading.destroy_indicator($spinner);
+ ui_report.success(
+ $t_html({defaultMessage: "User list style changed successfully!"}),
+ $spinner.expectOne(),
+ );
+ $spinner.expectOne();
+ settings_ui.display_checkmark($spinner);
+ }
+}
+
export function update_page(property) {
if (!overlays.settings_open()) {
return;
@@ -262,8 +307,8 @@ export function update_page(property) {
}
// settings_org.set_input_element_value doesn't support radio
- // button widgets like this one.
- if (property === "emojiset") {
+ // button widgets like these.
+ if (property === "emojiset" || property === "user_list_style") {
$container.find(`input[value=${CSS.escape(value)}]`).prop("checked", true);
return;
}
diff --git a/static/js/settings_org.js b/static/js/settings_org.js
index f85d3e10d1..e6bc110846 100644
--- a/static/js/settings_org.js
+++ b/static/js/settings_org.js
@@ -244,8 +244,14 @@ function get_subsection_property_elements(element) {
// structure, it needs custom code.
const $color_scheme_elem = $subsection.find(".setting_color_scheme");
const $emojiset_elem = $subsection.find("input[name='emojiset']:checked");
+ const $user_list_style_elem = $subsection.find("input[name='user_list_style']:checked");
const $translate_emoticons_elem = $subsection.find(".translate_emoticons");
- return [$color_scheme_elem, $emojiset_elem, $translate_emoticons_elem];
+ return [
+ $color_scheme_elem,
+ $emojiset_elem,
+ $user_list_style_elem,
+ $translate_emoticons_elem,
+ ];
}
return Array.from($subsection.find(".prop-element"));
}
@@ -533,13 +539,21 @@ function discard_property_element_changes(elem, for_realm_default_settings) {
);
break;
case "emojiset":
- // Because the emojiset widget has a unique radio button
- // structure, it needs custom reset code.
+ // Because this widget has a radio button structure, it
+ // needs custom reset code.
$elem
.closest(".org-subsection-parent")
.find(`.setting_emojiset_choice[value='${CSS.escape(property_value)}'`)
.prop("checked", true);
break;
+ case "user_list_style":
+ // Because this widget has a radio button structure, it
+ // needs custom reset code.
+ $elem
+ .closest(".org-subsection-parent")
+ .find(`.setting_user_list_style_choice[value='${CSS.escape(property_value)}'`)
+ .prop("checked", true);
+ break;
case "email_notifications_batching_period_seconds":
case "email_notification_batching_period_edit_minutes":
settings_notifications.set_notification_batching_ui(
diff --git a/static/js/settings_realm_user_settings_defaults.js b/static/js/settings_realm_user_settings_defaults.js
index 8ab6471558..a24efb9265 100644
--- a/static/js/settings_realm_user_settings_defaults.js
+++ b/static/js/settings_realm_user_settings_defaults.js
@@ -29,8 +29,8 @@ export function update_page(property) {
let value = realm_user_settings_defaults[property];
// settings_org.set_input_element_value doesn't support radio
- // button widgets like this one.
- if (property === "emojiset") {
+ // button widgets like these.
+ if (property === "emojiset" || property === "user_list_style") {
$container.find(`input[value=${CSS.escape(value)}]`).prop("checked", true);
return;
}
diff --git a/static/js/user_settings.ts b/static/js/user_settings.ts
index 7a3d5f7e2c..c7fc42d3ae 100644
--- a/static/js/user_settings.ts
+++ b/static/js/user_settings.ts
@@ -37,6 +37,7 @@ export type UserSettings = (StreamNotificationSettings & PmNotificationSettings)
pm_content_in_desktop_notifications: boolean;
presence_enabled: boolean;
realm_name_in_notifications: boolean;
+ user_list_style: number;
starred_message_counts: boolean;
translate_emoticons: boolean;
display_emoji_reaction_users: boolean;
diff --git a/static/styles/right_sidebar.css b/static/styles/right_sidebar.css
index 46d658095f..8c9cd96a3c 100644
--- a/static/styles/right_sidebar.css
+++ b/static/styles/right_sidebar.css
@@ -130,6 +130,24 @@
align-items: flex-start;
justify-content: space-between;
+ .user-name-and-status-emoji {
+ display: flex;
+ }
+
+ .status-text {
+ display: block;
+ width: 170px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ opacity: 0.75;
+ font-size: 90%;
+ }
+
+ span.status-text:not(:empty) {
+ margin-top: -3px;
+ }
+
.unread_count {
display: none;
margin-top: 2.5px;
diff --git a/static/styles/settings.css b/static/styles/settings.css
index 5454df1b06..0aaf7b5c7c 100644
--- a/static/styles/settings.css
+++ b/static/styles/settings.css
@@ -918,15 +918,10 @@ input[type="checkbox"] {
}
}
-.emojiset_choices {
- width: 250px;
+.emojiset_choices,
+.user_list_style_values {
padding: 0 10px;
- .emoji {
- height: 22px;
- width: 22px;
- }
-
label {
border-bottom: 1px solid hsla(0, 0%, 0%, 0.2);
padding: 8px 0 10px;
@@ -944,10 +939,56 @@ input[type="checkbox"] {
font-weight: 600;
}
}
- }
- .right {
- float: right;
+ .right {
+ float: right;
+ }
+ }
+}
+
+.emojiset_choices {
+ width: 250px;
+
+ .emoji {
+ height: 22px;
+ width: 22px;
+ }
+}
+
+$right_sidebar_width: 170px;
+$option_title_width: 180px;
+
+.user_list_style_values {
+ max-width: calc($right_sidebar_width + $option_title_width);
+
+ .preview {
+ background-color: inherit !important;
+ /* Match the 170px width of the right sidebar region for the name/status,
+ doing something reasonable if the window shrinks. */
+ width: calc(100% - $option_title_width);
+ text-align: left;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ overflow-y: visible;
+ position: relative;
+ height: 36px;
+
+ .user-name-and-status-text {
+ margin-top: -4px;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .status-text {
+ display: inline-block;
+ opacity: 0.75;
+ font-size: 90%;
+
+ &:not(:empty) {
+ margin-top: -3px;
+ }
+ }
}
}
diff --git a/static/templates/presence_row.hbs b/static/templates/presence_row.hbs
index fc875bf72b..f76fa65b3f 100644
--- a/static/templates/presence_row.hbs
+++ b/static/templates/presence_row.hbs
@@ -5,8 +5,18 @@
href="{{href}}"
data-user-id="{{user_id}}"
data-name="{{name}}">
- {{name}}
- {{> status_emoji status_emoji_info}}
+ {{#if user_list_style.WITH_STATUS}}
+
+
+ {{name}}
+ {{> status_emoji status_emoji_info}}
+
+
{{status_text}}
+
+ {{else}}
+ {{name}}
+ {{> status_emoji status_emoji_info}}
+ {{/if}}
{{#if num_unread}}{{num_unread}}{{/if}}
diff --git a/static/templates/settings/display_settings.hbs b/static/templates/settings/display_settings.hbs
index 0429534312..e08f0ba70e 100644
--- a/static/templates/settings/display_settings.hbs
+++ b/static/templates/settings/display_settings.hbs
@@ -77,6 +77,33 @@
label=settings_label.display_emoji_reaction_users
prefix=prefix}}
{{/if}}
+
+
diff --git a/static/templates/settings/organization_user_settings_defaults.hbs b/static/templates/settings/organization_user_settings_defaults.hbs
index 0a240bf9be..ceb70a09a1 100644
--- a/static/templates/settings/organization_user_settings_defaults.hbs
+++ b/static/templates/settings/organization_user_settings_defaults.hbs
@@ -6,7 +6,7 @@
{{#*inline "z-link"}}
{{> @partial-block }}{{/inline}}
{{/tr}}
- {{> display_settings prefix="realm_" for_realm_settings=true}}
+ {{> display_settings prefix="realm_" for_realm_settings=true full_name=full_name}}
{{> notification_settings prefix="realm_" for_realm_settings=true}}
diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md
index 887fcd1884..23cadede50 100644
--- a/templates/zerver/api/changelog.md
+++ b/templates/zerver/api/changelog.md
@@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 6.0
+**Feature level 141**
+
+* [`POST /register`](/api/register-queue), [`PATCH
+ /settings`](/api/update-settings), [`PATCH
+ /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults):
+ Added new `user_list_style` display setting, which controls the
+ layout of the right sidebar.
+
**Feature level 140**
* [`POST /register`](/api/register-queue): Added string field `server_emoji_data_url`
diff --git a/version.py b/version.py
index 6cd9902549..d12d074ded 100644
--- a/version.py
+++ b/version.py
@@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
# Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md, as well as
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
-API_FEATURE_LEVEL = 140
+API_FEATURE_LEVEL = 141
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump
diff --git a/zerver/migrations/0407_userprofile_user_list_style.py b/zerver/migrations/0407_userprofile_user_list_style.py
new file mode 100644
index 0000000000..03629f6b48
--- /dev/null
+++ b/zerver/migrations/0407_userprofile_user_list_style.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.0.6 on 2022-08-14 18:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("zerver", "0406_alter_realm_message_content_edit_limit_seconds"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="realmuserdefault",
+ name="user_list_style",
+ field=models.PositiveSmallIntegerField(default=2),
+ ),
+ migrations.AddField(
+ model_name="userprofile",
+ name="user_list_style",
+ field=models.PositiveSmallIntegerField(default=2),
+ ),
+ ]
diff --git a/zerver/models.py b/zerver/models.py
index 0abd389782..0f9b3aeed4 100644
--- a/zerver/models.py
+++ b/zerver/models.py
@@ -1523,6 +1523,17 @@ class UserBaseSettings(models.Model):
default=GOOGLE_EMOJISET, choices=EMOJISET_CHOICES, max_length=20
)
+ # User list style
+ USER_LIST_STYLE_COMPACT = 1
+ USER_LIST_STYLE_WITH_STATUS = 2
+ USER_LIST_STYLE_WITH_AVATAR = 3
+ USER_LIST_STYLE_CHOICES = [
+ USER_LIST_STYLE_COMPACT,
+ USER_LIST_STYLE_WITH_STATUS,
+ USER_LIST_STYLE_WITH_AVATAR,
+ ]
+ user_list_style: int = models.PositiveSmallIntegerField(default=USER_LIST_STYLE_WITH_STATUS)
+
### Notifications settings. ###
email_notifications_batching_period_seconds: int = models.IntegerField(default=120)
@@ -1621,6 +1632,7 @@ class UserBaseSettings(models.Model):
send_private_typing_notifications=bool,
send_read_receipts=bool,
send_stream_typing_notifications=bool,
+ user_list_style=int,
)
modern_notification_settings: Dict[str, Any] = dict(
diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml
index 0334cf6463..3c9be156f9 100644
--- a/zerver/openapi/zulip.yaml
+++ b/zerver/openapi/zulip.yaml
@@ -8516,6 +8516,23 @@ paths:
- 2
- 3
example: 1
+ - name: user_list_style
+ in: query
+ description: |
+ The style selected by the user for the right sidebar user list.
+
+ - 1 - Compact
+ - 2 - With status
+ - 3 - With avatar and status
+
+ **Changes**: New in Zulip 6.0 (feature level 141).
+ schema:
+ type: integer
+ enum:
+ - 1
+ - 2
+ - 3
+ example: 1
- name: enable_stream_desktop_notifications
in: query
description: |
@@ -10431,6 +10448,16 @@ paths:
- 1 - Automatic
- 2 - Always
- 3 - Never
+ user_list_style:
+ type: integer
+ description: |
+ The style selected by the user for the right sidebar user list.
+
+ - 1 - Compact
+ - 2 - With status
+ - 3 - With avatar and status
+
+ **Changes**: New in Zulip 6.0 (feature level 141).
timezone:
type: string
description: |
@@ -12344,6 +12371,16 @@ paths:
- 1 - Automatic
- 2 - Always
- 3 - Never
+ user_list_style:
+ type: integer
+ description: |
+ The style selected by the user for the right sidebar user list.
+
+ - 1 - Compact
+ - 2 - With status
+ - 3 - With avatar and status
+
+ **Changes**: New in Zulip 6.0 (feature level 141).
enable_stream_desktop_notifications:
type: boolean
description: |
@@ -13350,6 +13387,23 @@ paths:
- 2
- 3
example: 1
+ - name: user_list_style
+ in: query
+ description: |
+ The style selected by the user for the right sidebar user list.
+
+ - 1 - Compact
+ - 2 - With status
+ - 3 - With avatar and status
+
+ **Changes**: New in Zulip 6.0 (feature level 141).
+ schema:
+ type: integer
+ enum:
+ - 1
+ - 2
+ - 3
+ example: 1
- name: timezone
in: query
description: |
diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py
index 604d805dc4..adb5c3def3 100644
--- a/zerver/tests/test_events.py
+++ b/zerver/tests/test_events.py
@@ -2583,6 +2583,7 @@ class RealmPropertyActionTest(BaseAction):
default_view=["recent_topics", "all_messages"],
emojiset=[emojiset["key"] for emojiset in RealmUserDefault.emojiset_choices()],
demote_inactive_streams=UserProfile.DEMOTE_STREAMS_CHOICES,
+ user_list_style=UserProfile.USER_LIST_STYLE_CHOICES,
desktop_icon_count_display=[1, 2, 3],
notification_sound=["zulip", "ding"],
email_notifications_batching_period_seconds=[120, 300],
@@ -2656,6 +2657,7 @@ class UserDisplayActionTest(BaseAction):
default_language=["es", "de", "en"],
default_view=["all_messages", "recent_topics"],
demote_inactive_streams=[2, 3, 1],
+ user_list_style=[1, 2, 3],
color_scheme=[2, 3, 1],
)
diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py
index 70585dd2a1..cd5468ebf7 100644
--- a/zerver/tests/test_realm.py
+++ b/zerver/tests/test_realm.py
@@ -1202,6 +1202,7 @@ class RealmAPITest(ZulipTestCase):
default_view=["recent_topics", "all_messages"],
emojiset=[emojiset["key"] for emojiset in RealmUserDefault.emojiset_choices()],
demote_inactive_streams=UserProfile.DEMOTE_STREAMS_CHOICES,
+ user_list_style=UserProfile.USER_LIST_STYLE_CHOICES,
desktop_icon_count_display=[1, 2, 3],
notification_sound=["zulip", "ding"],
email_notifications_batching_period_seconds=[120, 300],
diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py
index cca3aeb802..e51e55f7fc 100644
--- a/zerver/tests/test_settings.py
+++ b/zerver/tests/test_settings.py
@@ -357,6 +357,7 @@ class ChangeSettingsTest(ZulipTestCase):
emojiset="google",
timezone="America/Denver",
demote_inactive_streams=2,
+ user_list_style=2,
color_scheme=2,
email_notifications_batching_period_seconds=100,
notification_sound="ding",
@@ -369,7 +370,7 @@ class ChangeSettingsTest(ZulipTestCase):
if test_value is None:
raise AssertionError(f"No test created for {setting_name}")
- if setting_name not in ["demote_inactive_streams", "color_scheme"]:
+ if setting_name not in ["demote_inactive_streams", "user_list_style", "color_scheme"]:
data = {setting_name: test_value}
else:
data = {setting_name: orjson.dumps(test_value).decode()}
@@ -395,6 +396,7 @@ class ChangeSettingsTest(ZulipTestCase):
emojiset="apple",
timezone="invalid_US/Mountain",
demote_inactive_streams=10,
+ user_list_style=10,
color_scheme=10,
notification_sound="invalid_sound",
desktop_icon_count_display=10,
diff --git a/zerver/views/realm.py b/zerver/views/realm.py
index a743a5e872..cbc93b2ff3 100644
--- a/zerver/views/realm.py
+++ b/zerver/views/realm.py
@@ -440,6 +440,9 @@ def update_realm_user_settings_defaults(
json_validator=check_bool, default=None
),
send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None),
+ user_list_style: Optional[int] = REQ(
+ json_validator=check_int_in(UserProfile.USER_LIST_STYLE_CHOICES), default=None
+ ),
) -> HttpResponse:
if notification_sound is not None or email_notifications_batching_period_seconds is not None:
check_settings_values(notification_sound, email_notifications_batching_period_seconds)
diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py
index 8865f2d7f9..87ee69e12d 100644
--- a/zerver/views/user_settings.py
+++ b/zerver/views/user_settings.py
@@ -217,6 +217,9 @@ def json_change_settings(
),
send_stream_typing_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None),
+ user_list_style: Optional[int] = REQ(
+ json_validator=check_int_in(UserProfile.USER_LIST_STYLE_CHOICES), default=None
+ ),
) -> HttpResponse:
if (
default_language is not None