mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(app): list friends with closed DMs in the forward modal (#2787)
This commit is contained in:
1 parent
951e39da3d
commit
c5aaf65a10
5 files changed
+255
-88
No files matched your search
@@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import {
|
||||||
|
ForwardChannelIndex,
|
||||||
|
type ForwardChannelObservation,
|
||||||
|
} from '@app/features/app/components/dialogs/shared/ForwardChannelIndex';
|
||||||
|
import type {Channel} from '@app/features/channel/models/Channel';
|
||||||
|
import type {User} from '@app/features/user/models/User';
|
||||||
|
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||||
|
import {setupI18n} from '@lingui/core';
|
||||||
|
import {describe, expect, it, vi} from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('@lingui/core/macro', () => ({msg: (descriptor: unknown) => descriptor}));
|
||||||
|
vi.mock('@app/features/channel/components/SlowmodeIndicator', () => ({formatSlowmodeTime: () => ''}));
|
||||||
|
vi.mock('@app/features/permissions/utils/PermissionUtils', () => ({formatPermissionLabel: () => ''}));
|
||||||
|
|
||||||
|
const i18n = setupI18n({locale: 'en', messages: {en: {}}});
|
||||||
|
|
||||||
|
function observation(overrides: Partial<ForwardChannelObservation>): ForwardChannelObservation {
|
||||||
|
return {
|
||||||
|
canAttachFiles: true,
|
||||||
|
canEmbedLinks: true,
|
||||||
|
canSendMessages: true,
|
||||||
|
categoryName: null,
|
||||||
|
channel: null,
|
||||||
|
displayName: '',
|
||||||
|
guildMessagesDisabled: false,
|
||||||
|
guildName: null,
|
||||||
|
key: '',
|
||||||
|
memberTimedOut: false,
|
||||||
|
recipient: null,
|
||||||
|
searchAliases: [],
|
||||||
|
slowmodeEnabled: false,
|
||||||
|
slowmodeRemainingMs: 0,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const general = observation({
|
||||||
|
channel: {id: 'channel-1', name: 'general', type: ChannelTypes.GUILD_TEXT} as unknown as Channel,
|
||||||
|
displayName: 'general',
|
||||||
|
key: 'channel-1',
|
||||||
|
});
|
||||||
|
const closedFriendDM = observation({
|
||||||
|
displayName: 'Ada Lovelace',
|
||||||
|
key: 'user:user-1',
|
||||||
|
recipient: {id: 'user-1'} as unknown as User,
|
||||||
|
searchAliases: ['ada_dev'],
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildIndex(observations: ReadonlyArray<ForwardChannelObservation>): ForwardChannelIndex {
|
||||||
|
return new ForwardChannelIndex({
|
||||||
|
excludedChannelId: 'channel-1',
|
||||||
|
i18n,
|
||||||
|
mediaSelection: {hasAttachments: true, hasEmbeds: true},
|
||||||
|
observations,
|
||||||
|
recentChannelIds: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('forward channel index', () => {
|
||||||
|
it('lists a friend whose DM is closed ahead of the source channel', () => {
|
||||||
|
const keys = buildIndex([general, closedFriendDM])
|
||||||
|
.filter('')
|
||||||
|
.map((option) => option.key);
|
||||||
|
expect(keys).toEqual(['user:user-1', 'channel-1']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds a friend whose DM is closed by username', () => {
|
||||||
|
const matches = buildIndex([general, closedFriendDM]).filter('ada_dev');
|
||||||
|
expect(matches.map((option) => option.key)).toEqual(['user:user-1']);
|
||||||
|
expect(matches[0]?.channel).toBeNull();
|
||||||
|
expect(matches[0]?.disableReason).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('selects destinations by key', () => {
|
||||||
|
const selected = buildIndex([general, closedFriendDM]).select(new Set(['user:user-1', 'channel-1', 'missing']));
|
||||||
|
expect(selected.map((option) => option.key)).toEqual(['user:user-1', 'channel-1']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drops an observation with neither a channel nor a recipient', () => {
|
||||||
|
const keys = buildIndex([observation({displayName: 'ghost', key: 'user:ghost'})])
|
||||||
|
.filter('')
|
||||||
|
.map((option) => option.key);
|
||||||
|
expect(keys).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,6 +7,7 @@ import type {Channel} from '@app/features/channel/models/Channel';
|
|||||||
import {PERSONAL_NOTES_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
import {PERSONAL_NOTES_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||||
import type {Message} from '@app/features/messaging/models/MessagingMessage';
|
import type {Message} from '@app/features/messaging/models/MessagingMessage';
|
||||||
import {formatPermissionLabel} from '@app/features/permissions/utils/PermissionUtils';
|
import {formatPermissionLabel} from '@app/features/permissions/utils/PermissionUtils';
|
||||||
|
import type {User} from '@app/features/user/models/User';
|
||||||
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||||
import type {I18n} from '@lingui/core';
|
import type {I18n} from '@lingui/core';
|
||||||
import {msg} from '@lingui/core/macro';
|
import {msg} from '@lingui/core/macro';
|
||||||
@@ -88,11 +89,13 @@ export interface ForwardChannelObservation {
|
|||||||
readonly canEmbedLinks: boolean;
|
readonly canEmbedLinks: boolean;
|
||||||
readonly canSendMessages: boolean;
|
readonly canSendMessages: boolean;
|
||||||
readonly categoryName: string | null;
|
readonly categoryName: string | null;
|
||||||
readonly channel: Channel;
|
readonly channel: Channel | null;
|
||||||
readonly displayName: string;
|
readonly displayName: string;
|
||||||
readonly guildMessagesDisabled: boolean;
|
readonly guildMessagesDisabled: boolean;
|
||||||
readonly guildName: string | null;
|
readonly guildName: string | null;
|
||||||
|
readonly key: string;
|
||||||
readonly memberTimedOut: boolean;
|
readonly memberTimedOut: boolean;
|
||||||
|
readonly recipient: User | null;
|
||||||
readonly searchAliases: ReadonlyArray<string>;
|
readonly searchAliases: ReadonlyArray<string>;
|
||||||
readonly slowmodeEnabled: boolean;
|
readonly slowmodeEnabled: boolean;
|
||||||
readonly slowmodeRemainingMs: number;
|
readonly slowmodeRemainingMs: number;
|
||||||
@@ -100,10 +103,12 @@ export interface ForwardChannelObservation {
|
|||||||
|
|
||||||
export interface ForwardChannelOption {
|
export interface ForwardChannelOption {
|
||||||
readonly categoryName: string | null;
|
readonly categoryName: string | null;
|
||||||
readonly channel: Channel;
|
readonly channel: Channel | null;
|
||||||
readonly disableReason: string | null;
|
readonly disableReason: string | null;
|
||||||
readonly displayName: string;
|
readonly displayName: string;
|
||||||
readonly guildName: string | null;
|
readonly guildName: string | null;
|
||||||
|
readonly key: string;
|
||||||
|
readonly recipient: User | null;
|
||||||
readonly slowmodeEnabled: boolean;
|
readonly slowmodeEnabled: boolean;
|
||||||
readonly slowmodeRemainingMs: number;
|
readonly slowmodeRemainingMs: number;
|
||||||
}
|
}
|
||||||
@@ -129,13 +134,18 @@ interface BuildForwardChannelIndexRequest {
|
|||||||
interface ForwardChannelSelectionDisabledRequest {
|
interface ForwardChannelSelectionDisabledRequest {
|
||||||
readonly maxSelections: number;
|
readonly maxSelections: number;
|
||||||
readonly option: ForwardChannelOption;
|
readonly option: ForwardChannelOption;
|
||||||
readonly selectedChannelIds: ReadonlySet<string>;
|
readonly selectedKeys: ReadonlySet<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isForwardableObservation(observation: ForwardChannelObservation): boolean {
|
||||||
|
if (observation.channel == null) return observation.recipient != null;
|
||||||
|
return isForwardableChannelType(observation.channel.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ForwardChannelIndex {
|
export class ForwardChannelIndex {
|
||||||
private readonly i18n: I18n;
|
private readonly i18n: I18n;
|
||||||
private readonly options: ReadonlyArray<IndexedForwardChannelOption>;
|
private readonly options: ReadonlyArray<IndexedForwardChannelOption>;
|
||||||
private readonly optionsByChannelId: ReadonlyMap<string, IndexedForwardChannelOption>;
|
private readonly optionsByKey: ReadonlyMap<string, IndexedForwardChannelOption>;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
excludedChannelId,
|
excludedChannelId,
|
||||||
@@ -147,11 +157,11 @@ export class ForwardChannelIndex {
|
|||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
const recentRanks = ForwardChannelIndex.buildRecentRanks(recentChannelIds);
|
const recentRanks = ForwardChannelIndex.buildRecentRanks(recentChannelIds);
|
||||||
const options = observations
|
const options = observations
|
||||||
.filter((observation) => isForwardableChannelType(observation.channel.type))
|
.filter(isForwardableObservation)
|
||||||
.map((observation) => this.buildOption({excludedChannelId, mediaSelection, observation, recentRanks}));
|
.map((observation) => this.buildOption({excludedChannelId, mediaSelection, observation, recentRanks}));
|
||||||
options.sort((left, right) => this.compareOptions(left, right));
|
options.sort((left, right) => this.compareOptions(left, right));
|
||||||
this.options = Object.freeze(options);
|
this.options = Object.freeze(options);
|
||||||
this.optionsByChannelId = new Map(options.map((option) => [option.channel.id, option]));
|
this.optionsByKey = new Map(options.map((option) => [option.key, option]));
|
||||||
}
|
}
|
||||||
|
|
||||||
filter(searchQuery: string): ReadonlyArray<ForwardChannelOption> {
|
filter(searchQuery: string): ReadonlyArray<ForwardChannelOption> {
|
||||||
@@ -169,16 +179,16 @@ export class ForwardChannelIndex {
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelectionDisabled({maxSelections, option, selectedChannelIds}: ForwardChannelSelectionDisabledRequest): boolean {
|
isSelectionDisabled({maxSelections, option, selectedKeys}: ForwardChannelSelectionDisabledRequest): boolean {
|
||||||
if (option.disableReason != null) return true;
|
if (option.disableReason != null) return true;
|
||||||
if (selectedChannelIds.has(option.channel.id)) return false;
|
if (selectedKeys.has(option.key)) return false;
|
||||||
return selectedChannelIds.size >= maxSelections;
|
return selectedKeys.size >= maxSelections;
|
||||||
}
|
}
|
||||||
|
|
||||||
select(selectedChannelIds: ReadonlySet<string>): ReadonlyArray<ForwardChannelOption> {
|
select(selectedKeys: ReadonlySet<string>): ReadonlyArray<ForwardChannelOption> {
|
||||||
const selected: Array<IndexedForwardChannelOption> = [];
|
const selected: Array<IndexedForwardChannelOption> = [];
|
||||||
for (const channelId of selectedChannelIds) {
|
for (const key of selectedKeys) {
|
||||||
const option = this.optionsByChannelId.get(channelId);
|
const option = this.optionsByKey.get(key);
|
||||||
if (option != null) selected.push(option);
|
if (option != null) selected.push(option);
|
||||||
}
|
}
|
||||||
return selected;
|
return selected;
|
||||||
@@ -213,25 +223,29 @@ export class ForwardChannelIndex {
|
|||||||
}): IndexedForwardChannelOption {
|
}): IndexedForwardChannelOption {
|
||||||
const permissionIssue = this.resolvePermissionIssue(observation, mediaSelection);
|
const permissionIssue = this.resolvePermissionIssue(observation, mediaSelection);
|
||||||
const disableReason = this.resolveDisableReason(observation, permissionIssue);
|
const disableReason = this.resolveDisableReason(observation, permissionIssue);
|
||||||
const recentRankValue = recentRanks.get(observation.channel.id);
|
let recentRankValue: number | undefined;
|
||||||
|
if (observation.channel != null) recentRankValue = recentRanks.get(observation.channel.id);
|
||||||
let recentRank: number | null = null;
|
let recentRank: number | null = null;
|
||||||
if (recentRankValue !== undefined) recentRank = recentRankValue;
|
if (recentRankValue !== undefined) recentRank = recentRankValue;
|
||||||
|
const channel = observation.channel;
|
||||||
let channelNameSearchValue = '';
|
let channelNameSearchValue = '';
|
||||||
if (observation.channel.name) channelNameSearchValue = observation.channel.name.toLowerCase();
|
if (channel?.name) channelNameSearchValue = channel.name.toLowerCase();
|
||||||
let guildNameSearchValue = '';
|
let guildNameSearchValue = '';
|
||||||
if (observation.guildName != null) guildNameSearchValue = observation.guildName.toLowerCase();
|
if (observation.guildName != null) guildNameSearchValue = observation.guildName.toLowerCase();
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
categoryName: observation.categoryName,
|
categoryName: observation.categoryName,
|
||||||
channel: observation.channel,
|
channel,
|
||||||
channelNameSearchValue,
|
channelNameSearchValue,
|
||||||
disableReason,
|
disableReason,
|
||||||
displayName: observation.displayName,
|
displayName: observation.displayName,
|
||||||
displayNameSearchValue: observation.displayName.toLowerCase(),
|
displayNameSearchValue: observation.displayName.toLowerCase(),
|
||||||
guildName: observation.guildName,
|
guildName: observation.guildName,
|
||||||
guildNameSearchValue,
|
guildNameSearchValue,
|
||||||
isPersonalNotes: observation.channel.type === ChannelTypes.DM_PERSONAL_NOTES,
|
isPersonalNotes: channel != null && channel.type === ChannelTypes.DM_PERSONAL_NOTES,
|
||||||
isSource: observation.channel.id === excludedChannelId,
|
isSource: channel != null && channel.id === excludedChannelId,
|
||||||
|
key: observation.key,
|
||||||
recentRank,
|
recentRank,
|
||||||
|
recipient: observation.recipient,
|
||||||
searchAliasValues: Object.freeze(observation.searchAliases.map((alias) => alias.toLowerCase())),
|
searchAliasValues: Object.freeze(observation.searchAliases.map((alias) => alias.toLowerCase())),
|
||||||
slowmodeEnabled: observation.slowmodeEnabled,
|
slowmodeEnabled: observation.slowmodeEnabled,
|
||||||
slowmodeRemainingMs: observation.slowmodeRemainingMs,
|
slowmodeRemainingMs: observation.slowmodeRemainingMs,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '@app/features/app/components/dialogs/shared/ForwardChannelIndex';
|
} from '@app/features/app/components/dialogs/shared/ForwardChannelIndex';
|
||||||
import {useForwardChannelObservations} from '@app/features/app/components/dialogs/shared/UseForwardChannelObservations';
|
import {useForwardChannelObservations} from '@app/features/app/components/dialogs/shared/UseForwardChannelObservations';
|
||||||
import {useShallowStableArray} from '@app/features/app/hooks/useShallowStableArray';
|
import {useShallowStableArray} from '@app/features/app/hooks/useShallowStableArray';
|
||||||
|
import type {Channel} from '@app/features/channel/models/Channel';
|
||||||
import Channels from '@app/features/channel/state/Channels';
|
import Channels from '@app/features/channel/state/Channels';
|
||||||
import type {Message} from '@app/features/messaging/models/MessagingMessage';
|
import type {Message} from '@app/features/messaging/models/MessagingMessage';
|
||||||
import SelectedChannel from '@app/features/navigation/state/SelectedChannel';
|
import SelectedChannel from '@app/features/navigation/state/SelectedChannel';
|
||||||
@@ -24,30 +25,27 @@ interface UseForwardChannelSelectionOptions {
|
|||||||
|
|
||||||
export interface ForwardChannelSelectionState {
|
export interface ForwardChannelSelectionState {
|
||||||
readonly filteredChannels: ReadonlyArray<ForwardChannelOption>;
|
readonly filteredChannels: ReadonlyArray<ForwardChannelOption>;
|
||||||
readonly handleToggleChannel: (channelId: string) => void;
|
readonly handleToggleChannel: (key: string) => void;
|
||||||
readonly isChannelSelectionDisabled: (option: ForwardChannelOption) => boolean;
|
readonly isChannelSelectionDisabled: (option: ForwardChannelOption) => boolean;
|
||||||
readonly maxSelections: number;
|
readonly maxSelections: number;
|
||||||
readonly mostRecentlySelectedChannelId: string | null;
|
readonly mostRecentlySelectedChannel: Channel | null;
|
||||||
readonly searchQuery: string;
|
readonly searchQuery: string;
|
||||||
readonly selectedChannelIds: ReadonlySet<string>;
|
readonly selectedChannelOptions: ReadonlyArray<ForwardChannelOption>;
|
||||||
|
readonly selectedKeys: ReadonlySet<string>;
|
||||||
readonly setSearchQuery: Dispatch<SetStateAction<string>>;
|
readonly setSearchQuery: Dispatch<SetStateAction<string>>;
|
||||||
readonly slowmodeActiveSelectedChannelOptions: ReadonlyArray<ForwardChannelOption>;
|
readonly slowmodeActiveSelectedChannelOptions: ReadonlyArray<ForwardChannelOption>;
|
||||||
readonly slowmodeEnabledSelectedChannelOptions: ReadonlyArray<ForwardChannelOption>;
|
readonly slowmodeEnabledSelectedChannelOptions: ReadonlyArray<ForwardChannelOption>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleForwardChannelSelection(
|
function toggleForwardChannelSelection(key: string, maxSelections: number, previousKeys: Set<string>): Set<string> {
|
||||||
channelId: string,
|
const nextKeys = new Set(previousKeys);
|
||||||
maxSelections: number,
|
if (nextKeys.has(key)) {
|
||||||
previousChannelIds: Set<string>,
|
nextKeys.delete(key);
|
||||||
): Set<string> {
|
return nextKeys;
|
||||||
const nextChannelIds = new Set(previousChannelIds);
|
|
||||||
if (nextChannelIds.has(channelId)) {
|
|
||||||
nextChannelIds.delete(channelId);
|
|
||||||
return nextChannelIds;
|
|
||||||
}
|
}
|
||||||
if (nextChannelIds.size >= maxSelections) return previousChannelIds;
|
if (nextKeys.size >= maxSelections) return previousKeys;
|
||||||
nextChannelIds.add(channelId);
|
nextKeys.add(key);
|
||||||
return nextChannelIds;
|
return nextKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useForwardChannelSelection({
|
export function useForwardChannelSelection({
|
||||||
@@ -79,24 +77,19 @@ export function useForwardChannelSelection({
|
|||||||
[excludedChannelId, i18n, locale, observations, recentChannelIds, resolvedMediaSelection],
|
[excludedChannelId, i18n, locale, observations, recentChannelIds, resolvedMediaSelection],
|
||||||
);
|
);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [selectedChannelIds, setSelectedChannelIds] = useState<Set<string>>(new Set());
|
const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
|
||||||
const filteredChannels = useMemo(() => channelIndex.filter(searchQuery), [channelIndex, searchQuery]);
|
const filteredChannels = useMemo(() => channelIndex.filter(searchQuery), [channelIndex, searchQuery]);
|
||||||
const handleToggleChannel = useCallback(
|
const handleToggleChannel = useCallback(
|
||||||
(channelId: string) => {
|
(key: string) => {
|
||||||
setSelectedChannelIds((previousChannelIds) =>
|
setSelectedKeys((previousKeys) => toggleForwardChannelSelection(key, maxSelections, previousKeys));
|
||||||
toggleForwardChannelSelection(channelId, maxSelections, previousChannelIds),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[maxSelections],
|
[maxSelections],
|
||||||
);
|
);
|
||||||
const isChannelSelectionDisabled = useCallback(
|
const isChannelSelectionDisabled = useCallback(
|
||||||
(option: ForwardChannelOption) => channelIndex.isSelectionDisabled({maxSelections, option, selectedChannelIds}),
|
(option: ForwardChannelOption) => channelIndex.isSelectionDisabled({maxSelections, option, selectedKeys}),
|
||||||
[maxSelections, channelIndex, selectedChannelIds],
|
[maxSelections, channelIndex, selectedKeys],
|
||||||
);
|
|
||||||
const selectedChannelOptions = useMemo(
|
|
||||||
() => channelIndex.select(selectedChannelIds),
|
|
||||||
[channelIndex, selectedChannelIds],
|
|
||||||
);
|
);
|
||||||
|
const selectedChannelOptions = useMemo(() => channelIndex.select(selectedKeys), [channelIndex, selectedKeys]);
|
||||||
const slowmodeEnabledSelectedChannelOptions = useMemo(
|
const slowmodeEnabledSelectedChannelOptions = useMemo(
|
||||||
() => selectedChannelOptions.filter((option) => option.slowmodeEnabled),
|
() => selectedChannelOptions.filter((option) => option.slowmodeEnabled),
|
||||||
[selectedChannelOptions],
|
[selectedChannelOptions],
|
||||||
@@ -105,22 +98,23 @@ export function useForwardChannelSelection({
|
|||||||
() => selectedChannelOptions.filter((option) => option.slowmodeRemainingMs > 0),
|
() => selectedChannelOptions.filter((option) => option.slowmodeRemainingMs > 0),
|
||||||
[selectedChannelOptions],
|
[selectedChannelOptions],
|
||||||
);
|
);
|
||||||
const mostRecentlySelectedChannelId = useMemo(() => {
|
const mostRecentlySelectedChannel = useMemo(() => {
|
||||||
let mostRecent: string | null = null;
|
let mostRecent: Channel | null = null;
|
||||||
for (const channelId of selectedChannelIds) {
|
for (const option of selectedChannelOptions) {
|
||||||
mostRecent = channelId;
|
mostRecent = option.channel;
|
||||||
}
|
}
|
||||||
return mostRecent;
|
return mostRecent;
|
||||||
}, [selectedChannelIds]);
|
}, [selectedChannelOptions]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filteredChannels,
|
filteredChannels,
|
||||||
handleToggleChannel,
|
handleToggleChannel,
|
||||||
isChannelSelectionDisabled,
|
isChannelSelectionDisabled,
|
||||||
maxSelections,
|
maxSelections,
|
||||||
mostRecentlySelectedChannelId,
|
mostRecentlySelectedChannel,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
selectedChannelIds,
|
selectedChannelOptions,
|
||||||
|
selectedKeys,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
slowmodeActiveSelectedChannelOptions,
|
slowmodeActiveSelectedChannelOptions,
|
||||||
slowmodeEnabledSelectedChannelOptions,
|
slowmodeEnabledSelectedChannelOptions,
|
||||||
|
|||||||
+68
-5
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import type {ForwardChannelObservation} from '@app/features/app/components/dialogs/shared/ForwardChannelIndex';
|
import type {ForwardChannelObservation} from '@app/features/app/components/dialogs/shared/ForwardChannelIndex';
|
||||||
import {useShallowStableArray} from '@app/features/app/hooks/useShallowStableArray';
|
import {useShallowStableArray} from '@app/features/app/hooks/useShallowStableArray';
|
||||||
|
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||||
import type {Channel} from '@app/features/channel/models/Channel';
|
import type {Channel} from '@app/features/channel/models/Channel';
|
||||||
import Channels from '@app/features/channel/state/Channels';
|
import Channels from '@app/features/channel/state/Channels';
|
||||||
import * as ChannelUtils from '@app/features/channel/utils/ChannelUtils';
|
import * as ChannelUtils from '@app/features/channel/utils/ChannelUtils';
|
||||||
@@ -12,11 +13,13 @@ import Permission from '@app/features/permissions/state/Permission';
|
|||||||
import Relationships from '@app/features/relationship/state/Relationships';
|
import Relationships from '@app/features/relationship/state/Relationships';
|
||||||
import Slowmode from '@app/features/slowmode/state/Slowmode';
|
import Slowmode from '@app/features/slowmode/state/Slowmode';
|
||||||
import {useNow} from '@app/features/ui/state/Tick';
|
import {useNow} from '@app/features/ui/state/Tick';
|
||||||
|
import type {User} from '@app/features/user/models/User';
|
||||||
import Users from '@app/features/user/state/Users';
|
import Users from '@app/features/user/state/Users';
|
||||||
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
|
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
|
||||||
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||||
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
|
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
|
||||||
import {CHANNEL_RATE_LIMIT_PER_USER_MAX} from '@fluxer/constants/src/LimitConstants';
|
import {CHANNEL_RATE_LIMIT_PER_USER_MAX} from '@fluxer/constants/src/LimitConstants';
|
||||||
|
import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';
|
||||||
import type {I18n} from '@lingui/core';
|
import type {I18n} from '@lingui/core';
|
||||||
import {msg} from '@lingui/core/macro';
|
import {msg} from '@lingui/core/macro';
|
||||||
import {computed, type IComputedValue} from 'mobx';
|
import {computed, type IComputedValue} from 'mobx';
|
||||||
@@ -40,11 +43,13 @@ interface ForwardChannelObservationBase {
|
|||||||
readonly canEmbedLinks: boolean;
|
readonly canEmbedLinks: boolean;
|
||||||
readonly canSendMessages: boolean;
|
readonly canSendMessages: boolean;
|
||||||
readonly categoryName: string | null;
|
readonly categoryName: string | null;
|
||||||
readonly channel: Channel;
|
readonly channel: Channel | null;
|
||||||
readonly displayName: string;
|
readonly displayName: string;
|
||||||
readonly guildMessagesDisabled: boolean;
|
readonly guildMessagesDisabled: boolean;
|
||||||
readonly guildName: string | null;
|
readonly guildName: string | null;
|
||||||
|
readonly key: string;
|
||||||
readonly memberTimedOut: boolean;
|
readonly memberTimedOut: boolean;
|
||||||
|
readonly recipient: User | null;
|
||||||
readonly searchAliases: ReadonlyArray<string>;
|
readonly searchAliases: ReadonlyArray<string>;
|
||||||
readonly slowmodeEnabled: boolean;
|
readonly slowmodeEnabled: boolean;
|
||||||
}
|
}
|
||||||
@@ -78,13 +83,28 @@ function resolveChannelDisplayName(channel: Channel, i18n: I18n): string {
|
|||||||
return i18n._(CHANNEL_DESCRIPTOR, {id: channel.id});
|
return i18n._(CHANNEL_DESCRIPTOR, {id: channel.id});
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectRecipientSearchAliases(channel: Channel): ReadonlyArray<string> {
|
function resolveForwardChannelKey(channel: Channel): string {
|
||||||
|
if (channel.type === ChannelTypes.DM && channel.recipientIds.length > 0) {
|
||||||
|
return resolveForwardRecipientKey(channel.recipientIds[0]);
|
||||||
|
}
|
||||||
|
return channel.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveForwardRecipientKey(userId: string): string {
|
||||||
|
return `user:${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectChannelSearchAliases(channel: Channel): ReadonlyArray<string> {
|
||||||
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
||||||
return EMPTY_SEARCH_ALIASES;
|
return EMPTY_SEARCH_ALIASES;
|
||||||
}
|
}
|
||||||
|
return collectRecipientSearchAliases(channel.recipientIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectRecipientSearchAliases(recipientIds: ReadonlyArray<string>): ReadonlyArray<string> {
|
||||||
const aliases = new Set<string>();
|
const aliases = new Set<string>();
|
||||||
const guilds = Guilds.getGuilds();
|
const guilds = Guilds.getGuilds();
|
||||||
for (const recipientId of channel.recipientIds) {
|
for (const recipientId of recipientIds) {
|
||||||
const recipient = Users.getUser(recipientId);
|
const recipient = Users.getUser(recipientId);
|
||||||
if (!recipient) continue;
|
if (!recipient) continue;
|
||||||
aliases.add(recipient.username);
|
aliases.add(recipient.username);
|
||||||
@@ -128,6 +148,7 @@ function areForwardChannelObservationBasesEqual(
|
|||||||
right: ForwardChannelObservationBase,
|
right: ForwardChannelObservationBase,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (left.channel !== right.channel || left.displayName !== right.displayName) return false;
|
if (left.channel !== right.channel || left.displayName !== right.displayName) return false;
|
||||||
|
if (left.key !== right.key || left.recipient !== right.recipient) return false;
|
||||||
if (left.guildName !== right.guildName || left.categoryName !== right.categoryName) return false;
|
if (left.guildName !== right.guildName || left.categoryName !== right.categoryName) return false;
|
||||||
if (left.guildMessagesDisabled !== right.guildMessagesDisabled || left.memberTimedOut !== right.memberTimedOut) {
|
if (left.guildMessagesDisabled !== right.guildMessagesDisabled || left.memberTimedOut !== right.memberTimedOut) {
|
||||||
return false;
|
return false;
|
||||||
@@ -195,9 +216,12 @@ class ForwardChannelObservationsOwner {
|
|||||||
private buildSelection(): ForwardChannelObservationSelection {
|
private buildSelection(): ForwardChannelObservationSelection {
|
||||||
const bases: Array<ForwardChannelObservationBase> = [];
|
const bases: Array<ForwardChannelObservationBase> = [];
|
||||||
const slowmodeChannels: Array<ForwardSlowmodeChannel> = [];
|
const slowmodeChannels: Array<ForwardSlowmodeChannel> = [];
|
||||||
|
const keys = new Set<string>();
|
||||||
for (const channel of this.request.channels) {
|
for (const channel of this.request.channels) {
|
||||||
const index = bases.length;
|
const index = bases.length;
|
||||||
const base = this.buildBase(channel);
|
const base = this.buildBase(channel);
|
||||||
|
if (keys.has(base.key)) continue;
|
||||||
|
keys.add(base.key);
|
||||||
bases.push(base);
|
bases.push(base);
|
||||||
if (base.slowmodeEnabled) {
|
if (base.slowmodeEnabled) {
|
||||||
slowmodeChannels.push(
|
slowmodeChannels.push(
|
||||||
@@ -205,9 +229,42 @@ class ForwardChannelObservationsOwner {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const base of this.buildClosedFriendDMBases()) {
|
||||||
|
if (keys.has(base.key)) continue;
|
||||||
|
keys.add(base.key);
|
||||||
|
bases.push(base);
|
||||||
|
}
|
||||||
return Object.freeze({bases: Object.freeze(bases), slowmodeChannels: Object.freeze(slowmodeChannels)});
|
return Object.freeze({bases: Object.freeze(bases), slowmodeChannels: Object.freeze(slowmodeChannels)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildClosedFriendDMBases(): ReadonlyArray<ForwardChannelObservationBase> {
|
||||||
|
if (RuntimeConfig.directMessagesDisabled) return [];
|
||||||
|
const bases: Array<ForwardChannelObservationBase> = [];
|
||||||
|
for (const relationship of Relationships.getRelationships()) {
|
||||||
|
if (relationship.type !== RelationshipTypes.FRIEND) continue;
|
||||||
|
const recipient = Users.getUser(relationship.userId);
|
||||||
|
if (!recipient) continue;
|
||||||
|
bases.push(
|
||||||
|
Object.freeze({
|
||||||
|
canAttachFiles: true,
|
||||||
|
canEmbedLinks: true,
|
||||||
|
canSendMessages: true,
|
||||||
|
categoryName: null,
|
||||||
|
channel: null,
|
||||||
|
displayName: NicknameUtils.getNickname(recipient, null),
|
||||||
|
guildMessagesDisabled: false,
|
||||||
|
guildName: null,
|
||||||
|
key: resolveForwardRecipientKey(recipient.id),
|
||||||
|
memberTimedOut: false,
|
||||||
|
recipient,
|
||||||
|
searchAliases: collectRecipientSearchAliases([recipient.id]),
|
||||||
|
slowmodeEnabled: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return bases;
|
||||||
|
}
|
||||||
|
|
||||||
private buildBase(channel: Channel): ForwardChannelObservationBase {
|
private buildBase(channel: Channel): ForwardChannelObservationBase {
|
||||||
const guildId = channel.guildId;
|
const guildId = channel.guildId;
|
||||||
if (!guildId) {
|
if (!guildId) {
|
||||||
@@ -220,8 +277,10 @@ class ForwardChannelObservationsOwner {
|
|||||||
displayName: resolveChannelDisplayName(channel, this.request.i18n),
|
displayName: resolveChannelDisplayName(channel, this.request.i18n),
|
||||||
guildMessagesDisabled: false,
|
guildMessagesDisabled: false,
|
||||||
guildName: null,
|
guildName: null,
|
||||||
|
key: resolveForwardChannelKey(channel),
|
||||||
memberTimedOut: false,
|
memberTimedOut: false,
|
||||||
searchAliases: collectRecipientSearchAliases(channel),
|
recipient: null,
|
||||||
|
searchAliases: collectChannelSearchAliases(channel),
|
||||||
slowmodeEnabled: false,
|
slowmodeEnabled: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -249,8 +308,10 @@ class ForwardChannelObservationsOwner {
|
|||||||
displayName: resolveChannelDisplayName(channel, this.request.i18n),
|
displayName: resolveChannelDisplayName(channel, this.request.i18n),
|
||||||
guildMessagesDisabled,
|
guildMessagesDisabled,
|
||||||
guildName,
|
guildName,
|
||||||
|
key: resolveForwardChannelKey(channel),
|
||||||
memberTimedOut: isCurrentMemberTimedOut(channel, this.request.currentUserId),
|
memberTimedOut: isCurrentMemberTimedOut(channel, this.request.currentUserId),
|
||||||
searchAliases: collectRecipientSearchAliases(channel),
|
recipient: null,
|
||||||
|
searchAliases: collectChannelSearchAliases(channel),
|
||||||
slowmodeEnabled,
|
slowmodeEnabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -281,8 +342,10 @@ function buildForwardChannelObservations(
|
|||||||
displayName: base.displayName,
|
displayName: base.displayName,
|
||||||
guildMessagesDisabled: base.guildMessagesDisabled,
|
guildMessagesDisabled: base.guildMessagesDisabled,
|
||||||
guildName: base.guildName,
|
guildName: base.guildName,
|
||||||
|
key: base.key,
|
||||||
searchAliases: base.searchAliases,
|
searchAliases: base.searchAliases,
|
||||||
memberTimedOut: base.memberTimedOut,
|
memberTimedOut: base.memberTimedOut,
|
||||||
|
recipient: base.recipient,
|
||||||
slowmodeEnabled: base.slowmodeEnabled,
|
slowmodeEnabled: base.slowmodeEnabled,
|
||||||
slowmodeRemainingMs: remainingMs === undefined ? 0 : remainingMs,
|
slowmodeRemainingMs: remainingMs === undefined ? 0 : remainingMs,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {useForwardChannelSelection} from '@app/features/app/components/dialogs/s
|
|||||||
import selectorStyles from '@app/features/app/components/dialogs/shared/SelectorModalStyles.module.css';
|
import selectorStyles from '@app/features/app/components/dialogs/shared/SelectorModalStyles.module.css';
|
||||||
import {GroupDMAvatar} from '@app/features/app/components/shared/GroupDMAvatar';
|
import {GroupDMAvatar} from '@app/features/app/components/shared/GroupDMAvatar';
|
||||||
import {Limits} from '@app/features/app/utils/UserLimits';
|
import {Limits} from '@app/features/app/utils/UserLimits';
|
||||||
|
import * as PrivateChannelCommands from '@app/features/channel/commands/PrivateChannelCommands';
|
||||||
import {MessageCharacterCounter} from '@app/features/channel/components/MessageCharacterCounter';
|
import {MessageCharacterCounter} from '@app/features/channel/components/MessageCharacterCounter';
|
||||||
import type {Channel} from '@app/features/channel/models/Channel';
|
import type {Channel} from '@app/features/channel/models/Channel';
|
||||||
import Channels from '@app/features/channel/state/Channels';
|
import Channels from '@app/features/channel/state/Channels';
|
||||||
@@ -162,6 +163,21 @@ function resolveForwardReferenceGuildId(channel: Channel | null, message: Messag
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openForwardChannels(options: ReadonlyArray<ForwardChannelOption>): Promise<Array<string>> {
|
||||||
|
const channelIds: Array<string> = [];
|
||||||
|
for (const option of options) {
|
||||||
|
if (option.channel != null) {
|
||||||
|
channelIds.push(option.channel.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (option.recipient == null) {
|
||||||
|
throw new Error(`Forward destination ${option.key} has no channel or recipient`);
|
||||||
|
}
|
||||||
|
channelIds.push(await PrivateChannelCommands.ensureDMChannel(option.recipient.id));
|
||||||
|
}
|
||||||
|
return channelIds;
|
||||||
|
}
|
||||||
|
|
||||||
function focusForwardComment(handle: LexicalRichInputHandle | null): void {
|
function focusForwardComment(handle: LexicalRichInputHandle | null): void {
|
||||||
if (handle != null) {
|
if (handle != null) {
|
||||||
handle.focus();
|
handle.focus();
|
||||||
@@ -185,9 +201,10 @@ export const ForwardModal = observer(
|
|||||||
filteredChannels,
|
filteredChannels,
|
||||||
handleToggleChannel,
|
handleToggleChannel,
|
||||||
isChannelSelectionDisabled,
|
isChannelSelectionDisabled,
|
||||||
mostRecentlySelectedChannelId,
|
mostRecentlySelectedChannel,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
selectedChannelIds,
|
selectedChannelOptions,
|
||||||
|
selectedKeys,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
maxSelections,
|
maxSelections,
|
||||||
slowmodeActiveSelectedChannelOptions,
|
slowmodeActiveSelectedChannelOptions,
|
||||||
@@ -218,13 +235,6 @@ export const ForwardModal = observer(
|
|||||||
});
|
});
|
||||||
}, [i18n]);
|
}, [i18n]);
|
||||||
const sourceForwardChannel = resolveForwardSourceChannel(sourceChannel, message.channelId);
|
const sourceForwardChannel = resolveForwardSourceChannel(sourceChannel, message.channelId);
|
||||||
const composerChannel = useMemo(() => {
|
|
||||||
if (mostRecentlySelectedChannelId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const selectedChannel = Channels.getChannel(mostRecentlySelectedChannelId);
|
|
||||||
return selectedChannel == null ? null : selectedChannel;
|
|
||||||
}, [mostRecentlySelectedChannelId]);
|
|
||||||
const handleCommentChange = useCallback((_display: string, _segments: Array<MentionSegment>, wire: string) => {
|
const handleCommentChange = useCallback((_display: string, _segments: Array<MentionSegment>, wire: string) => {
|
||||||
setActualOptionalMessage(dropTrailingEmptyBlockquoteLines(wire));
|
setActualOptionalMessage(dropTrailingEmptyBlockquoteLines(wire));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -266,7 +276,7 @@ export const ForwardModal = observer(
|
|||||||
}
|
}
|
||||||
const isCommentCounterVisible = actualOptionalMessage.length > user.maxMessageLength * 0.8;
|
const isCommentCounterVisible = actualOptionalMessage.length > user.maxMessageLength * 0.8;
|
||||||
const handleForward = async (skipNavigation = false) => {
|
const handleForward = async (skipNavigation = false) => {
|
||||||
if (selectedChannelIds.size === 0) return;
|
if (selectedChannelOptions.length === 0) return;
|
||||||
if (isForwarding) return;
|
if (isForwarding) return;
|
||||||
if (isSendBlockedBySlowmode) return;
|
if (isSendBlockedBySlowmode) return;
|
||||||
if (!isCommentComposerDisabled && isCommentOverLimit) {
|
if (!isCommentComposerDisabled && isCommentOverLimit) {
|
||||||
@@ -287,7 +297,7 @@ export const ForwardModal = observer(
|
|||||||
attachmentIds = mediaSelection.attachmentIds;
|
attachmentIds = mediaSelection.attachmentIds;
|
||||||
embedIndices = mediaSelection.embedIndices;
|
embedIndices = mediaSelection.embedIndices;
|
||||||
}
|
}
|
||||||
const forwardedChannelIds = Array.from(selectedChannelIds);
|
const forwardedChannelIds = await openForwardChannels(selectedChannelOptions);
|
||||||
const forwarded = await MessageCommands.forward(
|
const forwarded = await MessageCommands.forward(
|
||||||
forwardedChannelIds,
|
forwardedChannelIds,
|
||||||
{
|
{
|
||||||
@@ -335,21 +345,11 @@ export const ForwardModal = observer(
|
|||||||
setIsForwarding(false);
|
setIsForwarding(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getChannelIcon = (ch: Channel) => {
|
const getChannelIcon = (option: ForwardChannelOption) => {
|
||||||
const iconSize = 32;
|
const iconSize = 32;
|
||||||
if (ch.type === ChannelTypes.DM_PERSONAL_NOTES) {
|
const ch = option.channel;
|
||||||
return (
|
if (ch == null || ch.type === ChannelTypes.DM) {
|
||||||
<NotePencilIcon
|
const user = ch == null ? option.recipient : Users.getUser(ch.recipientIds[0]);
|
||||||
className={selectorStyles.itemIcon}
|
|
||||||
weight="fill"
|
|
||||||
size={remFromPx(iconSize)}
|
|
||||||
data-flx="messaging.forward-modal.get-channel-icon.note-pencil-icon"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (ch.type === ChannelTypes.DM) {
|
|
||||||
const recipientId = ch.recipientIds[0];
|
|
||||||
const user = Users.getUser(recipientId);
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
return (
|
return (
|
||||||
<div className={selectorStyles.avatar} data-flx="messaging.forward-modal.get-channel-icon.div">
|
<div className={selectorStyles.avatar} data-flx="messaging.forward-modal.get-channel-icon.div">
|
||||||
@@ -361,6 +361,16 @@ export const ForwardModal = observer(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (ch.type === ChannelTypes.DM_PERSONAL_NOTES) {
|
||||||
|
return (
|
||||||
|
<NotePencilIcon
|
||||||
|
className={selectorStyles.itemIcon}
|
||||||
|
weight="fill"
|
||||||
|
size={remFromPx(iconSize)}
|
||||||
|
data-flx="messaging.forward-modal.get-channel-icon.note-pencil-icon"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (ch.type === ChannelTypes.GROUP_DM) {
|
if (ch.type === ChannelTypes.GROUP_DM) {
|
||||||
return (
|
return (
|
||||||
<div className={selectorStyles.avatar} data-flx="messaging.forward-modal.get-channel-icon.div--2">
|
<div className={selectorStyles.avatar} data-flx="messaging.forward-modal.get-channel-icon.div--2">
|
||||||
@@ -428,21 +438,20 @@ export const ForwardModal = observer(
|
|||||||
) : (
|
) : (
|
||||||
<div className={selectorStyles.itemList} data-flx="messaging.forward-modal.div--4">
|
<div className={selectorStyles.itemList} data-flx="messaging.forward-modal.div--4">
|
||||||
{filteredChannels.map((option: ForwardChannelOption) => {
|
{filteredChannels.map((option: ForwardChannelOption) => {
|
||||||
const ch = option.channel;
|
const isSelected = selectedKeys.has(option.key);
|
||||||
const isSelected = selectedChannelIds.has(ch.id);
|
|
||||||
const isDisabledForSelection = isChannelSelectionDisabled(option);
|
const isDisabledForSelection = isChannelSelectionDisabled(option);
|
||||||
const isDisabled = isDisabledForSelection && !isSelected;
|
const isDisabled = isDisabledForSelection && !isSelected;
|
||||||
const {categoryName, disableReason, displayName, guildName} = option;
|
const {categoryName, disableReason, displayName, guildName} = option;
|
||||||
return (
|
return (
|
||||||
<FocusRing
|
<FocusRing
|
||||||
key={ch.id}
|
key={option.key}
|
||||||
offset={-2}
|
offset={-2}
|
||||||
enabled={!isDisabled}
|
enabled={!isDisabled}
|
||||||
data-flx="messaging.forward-modal.focus-ring"
|
data-flx="messaging.forward-modal.focus-ring"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => !isDisabled && handleToggleChannel(ch.id)}
|
onClick={() => !isDisabled && handleToggleChannel(option.key)}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@@ -453,7 +462,7 @@ export const ForwardModal = observer(
|
|||||||
data-flx="messaging.forward-modal.button"
|
data-flx="messaging.forward-modal.button"
|
||||||
>
|
>
|
||||||
<div className={selectorStyles.itemContent} data-flx="messaging.forward-modal.div--5">
|
<div className={selectorStyles.itemContent} data-flx="messaging.forward-modal.div--5">
|
||||||
{getChannelIcon(ch)}
|
{getChannelIcon(option)}
|
||||||
<div className={selectorStyles.itemInfo} data-flx="messaging.forward-modal.div--6">
|
<div className={selectorStyles.itemInfo} data-flx="messaging.forward-modal.div--6">
|
||||||
<span className={selectorStyles.itemName} data-flx="messaging.forward-modal.span">
|
<span className={selectorStyles.itemName} data-flx="messaging.forward-modal.span">
|
||||||
{displayName}
|
{displayName}
|
||||||
@@ -517,7 +526,7 @@ export const ForwardModal = observer(
|
|||||||
initialValue=""
|
initialValue=""
|
||||||
initialSegments={EMPTY_FORWARD_COMMENT_SEGMENTS}
|
initialSegments={EMPTY_FORWARD_COMMENT_SEGMENTS}
|
||||||
className={modalStyles.richInput}
|
className={modalStyles.richInput}
|
||||||
channel={composerChannel}
|
channel={mostRecentlySelectedChannel}
|
||||||
disabled={isCommentComposerDisabled}
|
disabled={isCommentComposerDisabled}
|
||||||
markdown={true}
|
markdown={true}
|
||||||
singleLine={false}
|
singleLine={false}
|
||||||
@@ -638,7 +647,7 @@ export const ForwardModal = observer(
|
|||||||
<Button
|
<Button
|
||||||
onClick={(event: MouseEvent<HTMLButtonElement>) => handleForward(event.shiftKey)}
|
onClick={(event: MouseEvent<HTMLButtonElement>) => handleForward(event.shiftKey)}
|
||||||
disabled={
|
disabled={
|
||||||
selectedChannelIds.size === 0 ||
|
selectedChannelOptions.length === 0 ||
|
||||||
isForwarding ||
|
isForwarding ||
|
||||||
isSendBlockedBySlowmode ||
|
isSendBlockedBySlowmode ||
|
||||||
(!isCommentComposerDisabled && isCommentOverLimit)
|
(!isCommentComposerDisabled && isCommentOverLimit)
|
||||||
@@ -646,7 +655,7 @@ export const ForwardModal = observer(
|
|||||||
data-flx="messaging.forward-modal.button.forward"
|
data-flx="messaging.forward-modal.button.forward"
|
||||||
>
|
>
|
||||||
{i18n._(SEND_SELECTED_COUNT_DESCRIPTOR, {
|
{i18n._(SEND_SELECTED_COUNT_DESCRIPTOR, {
|
||||||
selectedCount: selectedChannelIds.size,
|
selectedCount: selectedKeys.size,
|
||||||
selectionLimit: maxSelections,
|
selectionLimit: maxSelections,
|
||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in new issue
Block a user