Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api_docs/unmerged.d/ZF-1abcca.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
[`GET /streams`](/api/get-streams), [`GET /events`](/api/get-events),
[`POST /register`](/api/register-queue): Added `can_unsubscribe_group`
field to Stream and Subscription objects.
* [`POST /users/me/subscriptions`](/api/subscribe),
[`PATCH /streams/{stream_id}`](/api/update-stream): Added `can_unsubscribe_group`
parameter for setting the user group whose members can unsubscribe themselves from channel.
* [`DELETE /users/me/subscriptions`](/api/unsubscribe): Unsubscription is allowed for organization administrators,
users who can administer the channel or remove other subscribers, and members of the channel's `can_unsubscribe_group`.
1 change: 1 addition & 0 deletions web/src/group_permission_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const stream_group_setting_name_schema = z.enum([
"can_remove_subscribers_group",
"can_send_message_group",
"can_subscribe_group",
"can_unsubscribe_group",
"can_resolve_topics_group",
]);
export type StreamGroupSettingName = z.infer<typeof stream_group_setting_name_schema>;
Expand Down
4 changes: 4 additions & 0 deletions web/src/settings_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,9 @@ export const all_group_setting_labels = {
can_send_message_group: $t({defaultMessage: "Who can post to this channel"}),
can_administer_channel_group: $t({defaultMessage: "Who can administer this channel"}),
can_subscribe_group: $t({defaultMessage: "Who can subscribe to this channel"}),
can_unsubscribe_group: $t({
defaultMessage: "Who can unsubscribe from this channel",
}),
can_remove_subscribers_group: $t({
defaultMessage: "Who can unsubscribe anyone from this channel",
}),
Expand Down Expand Up @@ -888,6 +891,7 @@ export const stream_group_permission_settings: StreamGroupSettingName[] = [
"can_move_messages_out_of_channel_group",
"can_move_messages_within_channel_group",
"can_subscribe_group",
"can_unsubscribe_group",
"can_add_subscribers_group",
"can_remove_subscribers_group",
"can_resolve_topics_group",
Expand Down
20 changes: 18 additions & 2 deletions web/src/stream_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,14 +690,30 @@ export function user_can_set_topics_policy(sub?: StreamSubscription): boolean {
return user_can_set_topics_policy && can_administer_channel(sub);
}

export function can_unsubscribe(sub: StreamSubscription): boolean {
// Handles if the user is an organization admin, or in one of these groups:
// can_administer_channel_group or can_remove_subscribers_group.
if (can_unsubscribe_others(sub)) {
return true;
}

return settings_data.user_has_permission_for_group_setting(
sub.can_unsubscribe_group,
"can_unsubscribe_group",
"stream",
);
}

export function can_toggle_subscription(sub: StreamSubscription): boolean {
if (page_params.is_spectator) {
return false;
}

// Currently, you can always remove your subscription if you're subscribed.
// If the user is subscribed, they can unsubscribe themselves only if they are
// an organization admin, or in one of these groups: can_administer_channel_group,
// can_remove_subscribers_group, or can_unsubscribe_group.
if (sub.subscribed) {
return true;
return can_unsubscribe(sub);
}

if (has_content_access(sub)) {
Expand Down
2 changes: 1 addition & 1 deletion web/src/stream_edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function show_subscription_settings(sub: SettingsSubscription): void {
stream_ui_updates.update_add_subscriptions_elements(sub);

if (!stream_data.can_toggle_subscription(sub)) {
stream_ui_updates.initialize_cant_subscribe_popover();
stream_ui_updates.initialize_subscription_toggle_disabled_popover();
}

const $subscriber_container = $edit_container.find(".edit_subscribers_for_stream");
Expand Down
8 changes: 7 additions & 1 deletion web/src/stream_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@ export function update_property<P extends keyof UpdatableStreamProperties>(
sub,
group_setting_value_schema.parse(value),
);
if (property === "can_subscribe_group" || property === "can_add_subscribers_group") {
if (
property === "can_subscribe_group" ||
property === "can_add_subscribers_group" ||
property === "can_unsubscribe_group" ||
property === "can_remove_subscribers_group" ||
property === "can_administer_channel_group"
) {
stream_settings_ui.update_subscription_elements(sub);
}
if (property === "can_administer_channel_group") {
Expand Down
5 changes: 4 additions & 1 deletion web/src/stream_popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,15 @@ function build_stream_popover(opts: {elt: HTMLElement; stream_id: number}): void
const stream_unread = unread.unread_count_info_for_stream(stream_id);
const stream_unread_count = stream_unread.unmuted_count + stream_unread.muted_count;
const has_unread_messages = stream_unread_count > 0;
const stream_subscription = sub_store.get(stream_id);
assert(stream_subscription !== undefined);
const content = render_left_sidebar_stream_actions_popover({
stream: {
...sub_store.get(stream_id),
...stream_subscription,
url: browser_history.get_full_url(stream_hash),
list_of_topics_view_url: hash_util.by_channel_topic_list_url(stream_id),
},
should_display_unsubscribe_button: stream_data.can_unsubscribe(stream_subscription),
has_unread_messages,
show_go_to_channel_feed,
show_go_to_list_of_topics,
Expand Down
2 changes: 2 additions & 0 deletions web/src/stream_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const stream_permission_group_settings_schema = z.enum([
"can_resolve_topics_group",
"can_send_message_group",
"can_subscribe_group",
"can_unsubscribe_group",
]);
export type StreamPermissionGroupSetting = z.infer<typeof stream_permission_group_settings_schema>;

Expand All @@ -44,6 +45,7 @@ export const stream_schema = z.object({
can_resolve_topics_group: group_setting_value_schema,
can_send_message_group: group_setting_value_schema,
can_subscribe_group: group_setting_value_schema,
can_unsubscribe_group: group_setting_value_schema,
creator_id: z.nullable(z.number()),
date_created: z.number(),
description: z.string(),
Expand Down
10 changes: 8 additions & 2 deletions web/src/stream_ui_updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function update_private_stream_privacy_option_state(
.toggleClass("default_stream_private_tooltip", is_default_stream);
}

export function initialize_cant_subscribe_popover(): void {
export function initialize_subscription_toggle_disabled_popover(): void {
const $button_wrapper = $(".settings .stream_settings_header .sub_unsub_button_wrapper");
settings_components.initialize_disable_button_hint_popover($button_wrapper, undefined);
}
Expand Down Expand Up @@ -210,6 +210,7 @@ export function update_settings_button_for_sub(sub: StreamSubscription): void {
.addClass("unsubscribed action-button-quiet-brand")
.removeClass("action-button-quiet-neutral");
}
const $parent = $settings_button.parent();
if (stream_data.can_toggle_subscription(sub)) {
$settings_button.prop("disabled", false);
const $parent_element: tippy.ReferenceElement & HTMLElement = util.the(
Expand All @@ -220,7 +221,12 @@ export function update_settings_button_for_sub(sub: StreamSubscription): void {
$settings_button.addClass("toggle-subscription-tooltip");
} else {
$settings_button.attr("title", "");
initialize_cant_subscribe_popover();
if (sub.subscribed) {
$parent.attr("data-tooltip-template-id", "cannot-unsubscribe-tooltip-template");
} else {
$parent.attr("data-tooltip-template-id", "cannot-subscribe-tooltip-template");
}
initialize_subscription_toggle_disabled_popover();
$settings_button.prop("disabled", true);
$settings_button.removeClass("toggle-subscription-tooltip");
}
Expand Down
3 changes: 2 additions & 1 deletion web/src/user_profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ export function get_user_unsub_streams(user_id: number): dropdown_widget.Option[

function format_user_stream_list_item_html(stream: StreamSubscription, user: User): string {
const show_unsubscribe_button =
people.can_admin_user(user) || stream_data.can_unsubscribe_others(stream);
stream_data.can_unsubscribe_others(stream) ||
(people.is_my_user_id(user.user_id) && stream_data.can_unsubscribe(stream));
const show_private_stream_unsub_tooltip =
people.is_my_user_id(user.user_id) && stream.invite_only;
const show_last_user_in_private_stream_unsub_tooltip =
Expand Down
8 changes: 8 additions & 0 deletions web/styles/subscriptions.css
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,14 @@ h4.user_group_setting_subsection_title {
color: var(--color-stream-group-row-plus-icon-disabled);
}

&.checked svg {
opacity: 0.5;
}

&.checked svg {
opacity: 0.5;
}

.sub-unsub-icon {
color: var(--color-stream-group-row-checked-icon-disabled);
cursor: not-allowed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@
</a>
</li>
{{#unless stream.is_archived}}
{{#if should_display_unsubscribe_button}}
<li role="none" class="link-item popover-menu-list-item hidden-for-spectators">
<a role="menuitem" class="popover_sub_unsub_button popover-menu-link" tabindex="0">
<i class="popover-menu-icon zulip-icon zulip-icon-circle-x" aria-hidden="true"></i>
<span class="popover-menu-label">{{t "Unsubscribe"}}</span>
</a>
</li>
{{/if}}
<li role="separator" class="popover-menu-separator hidden-for-spectators"></li>
<li role="none" class="link-item popover-menu-list-item hidden-for-spectators">
<a role="menuitem" class="choose_stream_color popover-menu-link" data-stream-id="{{ stream.stream_id }}" tabindex="0">
Expand Down
22 changes: 19 additions & 3 deletions web/templates/stream_settings/browse_streams_list_item.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<div class="stream-row" data-stream-id="{{stream_id}}" data-stream-name="{{name}}">

{{#if subscribed}}
<div class="check checked sub_unsub_button">
<div class="check checked sub_unsub_button {{#unless should_display_subscription_button}}disabled{{/unless}}">

<div class="tippy-zulip-tooltip" data-tooltip-template-id="unsubscribe-from-{{stream_id}}-stream-tooltip-template">
<template id="unsubscribe-from-{{stream_id}}-stream-tooltip-template">
<div class="tippy-zulip-tooltip" data-tooltip-template-id="{{#if should_display_subscription_button}}unsubscribe-from-{{stream_id}}-stream-tooltip-template{{else}}cannot-unsubscribe-from-{{stream_id}}-stream-tooltip-template{{/if}}">
<template id="unsubscribe-from-{{name}}-stream-tooltip-template">
<span>
{{#tr}}
Unsubscribe from <z-stream></z-stream>
Expand All @@ -14,6 +14,22 @@
</span>
</template>

<template id="cannot-unsubscribe-from-{{name}}-stream-tooltip-template">
<span>
{{#tr}}
You are not allowed to unsubscribe from this channel.
{{/tr}}
</span>
</template>

<template id="cannot-unsubscribe-from-{{name}}-stream-tooltip-template">
<span>
{{#tr}}
You are not allowed to unsubscribe from this channel.
{{/tr}}
</span>
</template>

<i class="zulip-icon zulip-icon-subscriber-check sub-unsub-icon"></i>
</div>
<div class='sub_unsub_status'></div>
Expand Down
5 changes: 5 additions & 0 deletions web/templates/stream_settings/stream_permissions.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@
label=group_setting_labels.can_add_subscribers_group
prefix=prefix }}

{{> ../settings/group_setting_value_pill_input
setting_name="can_unsubscribe_group"
label=group_setting_labels.can_unsubscribe_group
prefix=prefix }}

{{> ../settings/group_setting_value_pill_input
setting_name="can_remove_subscribers_group"
label=group_setting_labels.can_remove_subscribers_group
Expand Down
9 changes: 8 additions & 1 deletion web/templates/stream_settings/stream_settings.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
<div class="tab-container"></div>
{{#with sub}}
<div class="button-group">
<div class="sub_unsub_button_wrapper inline-block {{#unless should_display_subscription_button }}cannot-subscribe-tooltip{{/unless}}" data-tooltip-template-id="cannot-subscribe-tooltip-template">
<div class="sub_unsub_button_wrapper inline-block" data-tooltip-template-id="{{#if subscribed}}cannot-unsubscribe-tooltip-template{{else}}cannot-subscribe-tooltip-template{{/if}}">
<template id="cannot-unsubscribe-tooltip-template">
<span>
{{#tr}}
You are not allowed to unsubscribe from this channel.
{{/tr}}
</span>
</template>
<template id="cannot-subscribe-tooltip-template">
<span>
{{#tr}}
Expand Down
8 changes: 8 additions & 0 deletions web/tests/lib/example_settings.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ exports.server_supported_permission_settings = {
default_group_name: "role:nobody",
allowed_system_groups: [],
},
can_unsubscribe_group: {
require_system_group: false,
allow_internet_group: false,
allow_nobody_group: true,
allow_everyone_group: true,
default_group_name: "role:nobody",
allowed_system_groups: [],
},
can_resolve_topics_group: {
require_system_group: false,
allow_internet_group: false,
Expand Down
Loading
Loading