Files
mattermost-mobile/app/utils/channel/index.ts
Javier Aguirre ac8a18bbfb [MM-43844][MM-42809] Integration Selector (#6716)
* Activating screens

* Registering the screen

* Adding default themes for components

* Porting items rows, WIP

* WIP Custom List

* Pasting old code to integration_selector, WIP

* No TS errors on components

* Adding selector options

* fix types

* Adding state with hooks

* Page loading with no results

* fix search timeout

* Getting channels, not painting them yet

* searching for profiles

* tuning user and channel remote calls

* Fix radioButton error

* channels being loaded

* rendering options

* Rendering users

* Preparing search results

* Added onPress events for everybody!

* single select working for all selectors

* Remove dirty empty data fix

* remove unused data on custom list row

* fic touchableOpacity styling

* Adding extra info to userlistRow

* Search results (channels and users)

* filter options!

* Adding i18n

* Adding username as name

* move code to effects

* fix typing onRow

* multiselect selection working, missing a "Done" button

* commenting out the selector icons, moving selected options to func

* Added button for multiselect submit

* Fixing data types on selector

* 💄 data sources check

* cleaning custom_list_row

* Fix onLoadMore bug

* ordering setLoading

* eslinting all the things

* more eslint

* multiselect

* fix autocomplete format

* FIx eslint

* fix renderIcon

* fix section type

* actions not being used

* now we have user avatars

* Fix icon checks on multiselect

* handling select for multiple selections

* Moving to its respective folders

* components should render

* Added some test cases

* Multiple fixes from @mickmister feedback

* changing lock icon to padlock on channel row

* Fix children lint errors

* fix useEffect function eslint error

* Adding useCallback to profiles, channels and multiselections

* Fixing @larkox suggestions

* type checking fixes

* Fix onLoadMore

* Multiple hook and functionality fixes

* 🔥 extraData and setting loading channels better

* fix teammate display

* Fix multiselect button selection

* Fix returning selection to autocomplete selector

* Using typography

* Updating snapshots due to typography changes

* removing UserListRow, modifying the existing one

* Extract key for data sources

* Multiselect selection refactor

* fix setNext loop

* refactoring searchprofiles and channels

* Using refs for next and page

* Callback and other fixes

* Multiple fixes

* Add callback to multiselect selected items

* integration selector fixes

* Filter option search

* fix useCallback, timeout

* Remove initial page, fix selection data type

Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com>
2022-11-23 11:17:47 +01:00

160 lines
5.9 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IntlShape} from 'react-intl';
import {Channel, General, Permissions} from '@constants';
import {t, DEFAULT_LOCALE} from '@i18n';
import {hasPermission} from '@utils/role';
import {generateId} from '../general';
import {cleanUpUrlable} from '../url';
import type ChannelModel from '@typings/database/models/servers/channel';
export function getDirectChannelName(id: string, otherId: string): string {
let handle;
if (otherId > id) {
handle = id + '__' + otherId;
} else {
handle = otherId + '__' + id;
}
return handle;
}
export function isDMorGM(channel: Channel | ChannelModel): boolean {
return isTypeDMorGM(channel.type);
}
const DIRECT_TYPES: string[] = [General.GM_CHANNEL, General.DM_CHANNEL];
export function isTypeDMorGM(channelType: ChannelType | undefined): boolean {
return Boolean(channelType && DIRECT_TYPES.includes(channelType));
}
export function isArchived(channel: Channel | ChannelModel): boolean {
const deleteAt = 'delete_at' in channel ? channel.delete_at : channel.deleteAt;
return deleteAt > 0;
}
export function selectDefaultChannelForTeam<T extends Channel|ChannelModel>(channels: T[], memberships: ChannelMembership[], teamId: string, roles?: Role[], locale = DEFAULT_LOCALE) {
let channel: T|undefined;
let canIJoinPublicChannelsInTeam = false;
if (roles) {
canIJoinPublicChannelsInTeam = hasPermission(roles, Permissions.JOIN_PUBLIC_CHANNELS);
}
const defaultChannel = channels?.find((c) => c.name === General.DEFAULT_CHANNEL);
const membershipIds = new Set(memberships.map((m) => m.channel_id));
const iAmMemberOfTheTeamDefaultChannel = Boolean(defaultChannel && membershipIds.has(defaultChannel.id));
const myFirstTeamChannel = channels?.filter((c) =>
(('team_id' in c) ? c.team_id : c.teamId) === teamId &&
c.type === General.OPEN_CHANNEL &&
membershipIds.has(c.id),
).sort(sortChannelsByDisplayName.bind(null, locale))[0];
if (iAmMemberOfTheTeamDefaultChannel || canIJoinPublicChannelsInTeam) {
channel = defaultChannel;
} else {
channel = myFirstTeamChannel || defaultChannel;
}
return channel;
}
export function sortChannelsByDisplayName<T extends Channel|ChannelModel>(locale: string, a: T, b: T): number {
// if both channels have the display_name defined
const aDisplayName = 'display_name' in a ? a.display_name : a.displayName;
const bDisplayName = 'display_name' in b ? b.display_name : b.displayName;
if (aDisplayName && bDisplayName && aDisplayName !== bDisplayName) {
return aDisplayName.toLowerCase().localeCompare(bDisplayName.toLowerCase(), locale, {numeric: true});
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, {numeric: true});
}
export function sortChannelsModelByDisplayName(locale: string, a: ChannelModel, b: ChannelModel): number {
// if both channels have the display_name defined
if (a.displayName && b.displayName && a.displayName !== b.displayName) {
return a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase(), locale, {numeric: true});
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, {numeric: true});
}
const displayNameValidationMessages = {
display_name_required: {
id: t('mobile.rename_channel.display_name_required'),
defaultMessage: 'Channel name is required',
},
display_name_maxLength: {
id: t('mobile.rename_channel.display_name_maxLength'),
defaultMessage: 'Channel name must be less than {maxLength, number} characters',
},
display_name_minLength: {
id: t('mobile.rename_channel.display_name_minLength'),
defaultMessage: 'Channel name must be {minLength, number} or more characters',
},
};
export const validateDisplayName = (intl: IntlShape, displayName: string): {error: string} => {
let errorMessage;
switch (true) {
case !displayName:
errorMessage = intl.formatMessage(displayNameValidationMessages.display_name_required);
break;
case displayName.length > Channel.MAX_CHANNEL_NAME_LENGTH:
errorMessage = intl.formatMessage(
displayNameValidationMessages.display_name_maxLength,
{maxLength: Channel.MAX_CHANNEL_NAME_LENGTH});
break;
case displayName.length < Channel.MIN_CHANNEL_NAME_LENGTH:
errorMessage = intl.formatMessage(
displayNameValidationMessages.display_name_minLength,
{minLength: Channel.MIN_CHANNEL_NAME_LENGTH});
break;
default:
errorMessage = '';
}
return {error: errorMessage};
};
export function generateChannelNameFromDisplayName(displayName: string) {
let name = cleanUpUrlable(displayName);
if (name === '') {
name = generateId();
}
return name;
}
export function compareNotifyProps(propsA: Partial<ChannelNotifyProps>, propsB: Partial<ChannelNotifyProps>): boolean {
if (
propsA.desktop !== propsB.desktop ||
propsA.email !== propsB.email ||
propsA.mark_unread !== propsB.mark_unread ||
propsA.push !== propsB.push ||
propsA.ignore_channel_mentions !== propsB.ignore_channel_mentions
) {
return false;
}
return true;
}
export function filterChannelsMatchingTerm(channels: Channel[], term: string): Channel[] {
const lowercasedTerm = term.toLowerCase();
return channels.filter((channel: Channel): boolean => {
if (!channel) {
return false;
}
const name = (channel.name || '').toLowerCase();
const displayName = (channel.display_name || '').toLowerCase();
return name.startsWith(lowercasedTerm) ||
displayName.startsWith(lowercasedTerm);
});
}