groups-ui: Add option to copy membership from group.

Instead of adding group as a subgroup, we now provide option
to add direct members and direct subgroups of a group to a
user group by providing an expand button in the group pill.

Fixes #32335.
This commit is contained in:
Sahil Batra
2025-04-17 12:13:01 +05:30
committed by Tim Abbott
parent 592ee2de1c
commit 53cdfddf5b
10 changed files with 91 additions and 10 deletions

View File

@@ -283,6 +283,11 @@ Source: https://lucide.dev/icons/plus
Copyright: 2013-2022 Cole Bemis
License: ISC License
Files: web/shared/icons/expand-both-diagonals.svg
Source: https://lucide.dev/icons/expand
Copyright: 2013-2022 Cole Bemis
License: ISC License
Files: web/third/bootstrap/css/bootstrap.app.css
Copyright: 2012 Twitter, Inc.
License: Apache-2.0

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m 15,14 a 1,1 0 0 0 -0.707031,0.292969 1,1 0 0 0 0,1.414062 l 6,6 a 1,1 0 0 0 1.414062,0 1,1 0 0 0 0,-1.414062 l -6,-6 A 1,1 0 0 0 15,14 Z" />
<path d="m 20.292969,2.2929688 -6,6 a 1,1 0 0 0 0,1.4140625 1,1 0 0 0 1.414062,0 l 6,-6 a 1,1 0 0 0 0,-1.4140625 1,1 0 0 0 -1.414062,0 z" />
<path d="m 21,15.199219 a 1,1 0 0 0 -1,1 V 20 h -3.800781 a 1,1 0 0 0 -1,1 1,1 0 0 0 1,1 H 21 a 1.0001,1.0001 0 0 0 1,-1 v -4.800781 a 1,1 0 0 0 -1,-1 z" />
<path d="m 16.199219,2 a 1,1 0 0 0 -1,1 1,1 0 0 0 1,1 H 20 v 3.8007812 a 1,1 0 0 0 1,1.0000001 1,1 0 0 0 1,-1.0000001 V 3 A 1.0001,1.0001 0 0 0 21,2 Z" />
<path d="m 3,15.199219 a 1,1 0 0 0 -1,1 V 21 a 1.0001,1.0001 0 0 0 1,1 H 7.8007812 A 1,1 0 0 0 8.8007813,21 1,1 0 0 0 7.8007812,20 H 4 v -3.800781 a 1,1 0 0 0 -1,-1 z" />
<path d="m 9,14 a 1,1 0 0 0 -0.7070312,0.292969 l -6,6 a 1,1 0 0 0 0,1.414062 1,1 0 0 0 1.4140625,0 l 6,-6 a 1,1 0 0 0 0,-1.414062 A 1,1 0 0 0 9,14 Z" />
<path d="M 3,2 A 1.0001,1.0001 0 0 0 2,3 V 7.8007812 A 1,1 0 0 0 3,8.8007813 1,1 0 0 0 4,7.8007812 V 4 H 7.8007812 A 1,1 0 0 0 8.8007813,3 1,1 0 0 0 7.8007812,2 Z" />
<path d="m 3,2 a 1,1 0 0 0 -0.7070312,0.2929688 1,1 0 0 0 0,1.4140625 l 6,6 a 1,1 0 0 0 1.4140625,0 1,1 0 0 0 0,-1.4140625 l -6,-6 A 1,1 0 0 0 3,2 Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -3,6 +3,7 @@ import assert from "minimalistic-assert";
import * as add_subscribers_pill from "./add_subscribers_pill.ts";
import * as input_pill from "./input_pill.ts";
import * as keydown_util from "./keydown_util.ts";
import * as people from "./people.ts";
import type {User} from "./people.ts";
import * as stream_pill from "./stream_pill.ts";
import type {CombinedPill, CombinedPillContainer} from "./typeahead_helper.ts";
@@ -24,6 +25,30 @@ function get_pill_group_ids(pill_widget: CombinedPillContainer): number[] {
return group_user_ids;
}
function expand_group_pill($pill_elem: JQuery, pill_widget: CombinedPillContainer): void {
const group_id = Number.parseInt($pill_elem.attr("data-user-group-id")!, 10);
const group = user_groups.get_user_group_from_id(group_id);
const direct_subgroup_ids = group.direct_subgroup_ids;
const direct_member_ids = group.members;
const taken_group_ids = user_group_pill.get_group_ids(pill_widget);
const taken_user_ids = user_pill.get_user_ids(pill_widget);
for (const member_id of direct_member_ids) {
if (!taken_user_ids.includes(member_id)) {
const user = people.get_by_user_id(member_id);
user_pill.append_user(user, pill_widget, false);
}
}
for (const group_id of direct_subgroup_ids) {
if (!taken_group_ids.includes(group_id)) {
const subgroup = user_groups.get_user_group_from_id(group_id);
user_group_pill.append_user_group(subgroup, pill_widget, false, true);
}
}
}
export function create_item_from_text(
text: string,
current_items: CombinedPill[],
@@ -42,6 +67,8 @@ export function create_item_from_text(
const group_item = user_group_pill.create_item_from_group_name(text, current_items);
if (group_item) {
const subgroup = user_groups.get_user_group_from_id(group_item.group_id);
group_item.show_expand_button =
subgroup.members.size > 0 || subgroup.direct_subgroup_ids.size > 0;
const current_group_id = user_group_components.active_group_id;
assert(current_group_id !== undefined);
const current_group = user_groups.get_user_group_from_id(current_group_id);
@@ -88,6 +115,10 @@ export function create({
return user_group_pill.filter_taken_groups(potential_groups, pill_widget);
}
pill_widget.onPillExpand((pill) => {
expand_group_pill(pill, pill_widget);
});
add_subscribers_pill.set_up_pill_typeahead({
pill_widget,
$pill_container,
@@ -136,6 +167,10 @@ export function create_without_add_button({
onPillRemoveAction(get_pill_user_ids(pill_widget), get_pill_group_ids(pill_widget));
});
pill_widget.onPillExpand((pill) => {
expand_group_pill(pill, pill_widget);
});
add_subscribers_pill.set_up_pill_typeahead({
pill_widget,
$pill_container,

View File

@@ -58,6 +58,7 @@ type InputPillStore<ItemType> = {
on_pill_exit: InputPillCreateOptions<ItemType>["on_pill_exit"];
onPillCreate?: () => void;
onPillRemove?: (pill: InputPill<ItemType>, trigger: RemovePillTrigger) => void;
onPillExpand?: (pill: JQuery) => void;
createPillonPaste?: () => void;
split_text_on_comma: boolean;
convert_to_pill_on_enter: boolean;
@@ -78,6 +79,7 @@ export type InputPillContainer<ItemType> = {
onPillRemove: (
callback: (pill: InputPill<ItemType>, trigger: RemovePillTrigger) => void,
) => void;
onPillExpand: (callback: (pill: JQuery) => void) => void;
onTextInputHook: (callback: () => void) => void;
createPillonPaste: (callback: () => void) => void;
clear: (quiet?: boolean) => void;
@@ -469,6 +471,15 @@ export function create<ItemType extends {type: string}>(
store.$input.trigger("focus");
});
store.$parent.on("click", ".expand", function (this: HTMLElement, e) {
assert(store.onPillExpand !== undefined);
e.stopPropagation();
store.onPillExpand($(this).closest(".pill"));
const pill = util.the($(this).closest(".pill"));
funcs.removePill(pill, "close");
store.$input.trigger("focus");
});
store.$parent.on("click", function (e) {
if ($(e.target).is(".pill-container")) {
$(this).find(".input").trigger("focus");
@@ -501,6 +512,10 @@ export function create<ItemType extends {type: string}>(
store.onPillRemove = callback;
},
onPillExpand(callback) {
store.onPillExpand = callback;
},
onTextInputHook(callback) {
store.onTextInputHook = callback;
},

View File

@@ -394,7 +394,10 @@ export function set_up_combined(
if (include_streams(query) && item.type === "stream") {
stream_pill.append_stream(item, pills);
} else if (include_user_groups && item.type === "user_group") {
user_group_pill.append_user_group(item, pills);
const show_expand_button =
!opts.for_stream_subscribers &&
(item.members.size > 0 || item.direct_subgroup_ids.size > 0);
user_group_pill.append_user_group(item, pills, true, show_expand_button);
} else if (
include_users &&
item.type === "user" &&

View File

@@ -17,6 +17,7 @@ export type UserGroupPill = {
type: "user_group";
group_id: number;
group_name: string;
show_expand_button?: boolean;
};
export type UserGroupPillWidget = InputPillContainer<UserGroupPill>;
@@ -34,6 +35,7 @@ export function generate_pill_html(item: UserGroupPill): string {
group_id: item.group_id,
show_group_members_count: true,
group_members_count: group_members.length,
show_expand_button: item.show_expand_button ?? false,
});
}
@@ -87,12 +89,14 @@ export function append_user_group(
group: UserGroup,
pill_widget: CombinedPillContainer | GroupSettingPillContainer | UserGroupPillWidget,
execute_oncreate_callback = true,
show_expand_button = false,
): void {
pill_widget.appendValidatedData(
{
type: "user_group",
group_id: group.id,
group_name: group.name,
show_expand_button,
},
false,
!execute_oncreate_callback,

View File

@@ -70,7 +70,8 @@
line-height: 1.5;
}
.pill-close-button {
.pill-close-button,
.pill-expand-button {
font-size: 0.7142em; /* 10px at 14px em */
text-decoration: none;
/* Let the close button's box stretch,
@@ -92,20 +93,22 @@
opacity: 0.7;
}
.exit {
.exit,
.expand {
width: var(--length-input-pill-exit);
height: var(--length-input-pill-exit);
display: flex;
justify-content: center;
margin-right: 2px;
border-radius: 2px;
}
.exit:hover {
background: var(--color-background-input-pill-exit-hover);
&:hover {
background: var(--color-background-input-pill-exit-hover);
.pill-close-button {
opacity: 1;
.pill-close-button,
.pill-expand-button {
opacity: 1;
}
}
}

View File

@@ -33,6 +33,11 @@
&nbsp;<span class="group-members-count">({{group_members_count}})</span>
{{~/if~}}
</span>
{{#if show_expand_button}}
<div class="expand">
<a role="button" class="zulip-icon zulip-icon-expand-both-diagonals pill-expand-button"></a>
</div>
{{/if}}
{{#unless disabled}}
<div class="exit">
<a role="button" class="zulip-icon zulip-icon-close pill-close-button"></a>

View File

@@ -94,14 +94,14 @@ const admins = {
name: "Admins",
description: "foo",
id: 1,
members: [jill.user_id, mark.user_id, me.user_id],
members: new Set([jill.user_id, mark.user_id, me.user_id]),
};
const admins_item = user_group_item(admins);
const testers = {
name: "Testers",
description: "bar",
id: 2,
members: [mark.user_id, fred.user_id, me.user_id],
members: new Set([mark.user_id, fred.user_id, me.user_id]),
};
const testers_item = user_group_item(testers);

View File

@@ -77,6 +77,7 @@ const testers_pill = {
group_id: testers.id,
group_name: testers.name,
type: "user_group",
show_expand_button: false,
};
const everyone_pill = {
group_id: everyone.id,