Remove t from utils and most components (#27274)
* Remove t from utils/utils * Remove t from utils/url * Remove t from utils/constants * Remove t from components/widgets * Remove t from components/copy_button * Remove t from components/searchable_channel_list * Fix mocking of React in some tests
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
9187c772b6
Коммит
0da473b9f8
@@ -8,6 +8,7 @@ import useGetHighestThresholdCloudLimit from './useGetHighestThresholdCloudLimit
|
|||||||
import type {LimitSummary} from './useGetHighestThresholdCloudLimit';
|
import type {LimitSummary} from './useGetHighestThresholdCloudLimit';
|
||||||
|
|
||||||
jest.mock('react', () => ({
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
useMemo: (fn: () => LimitSummary) => fn(),
|
useMemo: (fn: () => LimitSummary) => fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import useGetMultiplesExceededCloudLimit from './useGetMultiplesExceededCloudLim
|
|||||||
import type {LimitSummary} from './useGetMultiplesExceededCloudLimit';
|
import type {LimitSummary} from './useGetMultiplesExceededCloudLimit';
|
||||||
|
|
||||||
jest.mock('react', () => ({
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
useMemo: (fn: () => LimitSummary) => fn(),
|
useMemo: (fn: () => LimitSummary) => fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,16 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, {useRef, useState} from 'react';
|
import React, {useRef, useState} from 'react';
|
||||||
import {Tooltip} from 'react-bootstrap';
|
import {Tooltip} from 'react-bootstrap';
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
import {FormattedMessage, defineMessages, useIntl} from 'react-intl';
|
||||||
|
|
||||||
import OverlayTrigger from 'components/overlay_trigger';
|
import OverlayTrigger from 'components/overlay_trigger';
|
||||||
|
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
import {copyToClipboard} from 'utils/utils';
|
import {copyToClipboard} from 'utils/utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: string;
|
content: string;
|
||||||
beforeCopyText?: string;
|
isForText?: boolean;
|
||||||
afterCopyText?: string;
|
|
||||||
placement?: string;
|
placement?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@@ -41,26 +39,18 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
|||||||
copyToClipboard(props.content);
|
copyToClipboard(props.content);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getId = () => {
|
let tooltipMessage;
|
||||||
if (isCopied) {
|
if (isCopied) {
|
||||||
return t('copied.message');
|
tooltipMessage = messages.copied;
|
||||||
}
|
} else if (props.isForText) {
|
||||||
return props.beforeCopyText ? t('copy.text.message') : t('copy.code.message');
|
tooltipMessage = messages.copyText;
|
||||||
};
|
} else {
|
||||||
|
tooltipMessage = messages.copyCode;
|
||||||
const getDefaultMessage = () => {
|
}
|
||||||
if (isCopied) {
|
|
||||||
return props.afterCopyText;
|
|
||||||
}
|
|
||||||
return props.beforeCopyText ?? 'Copy code';
|
|
||||||
};
|
|
||||||
|
|
||||||
const tooltip = (
|
const tooltip = (
|
||||||
<Tooltip id='copyButton'>
|
<Tooltip id='copyButton'>
|
||||||
<FormattedMessage
|
<FormattedMessage {...tooltipMessage}/>
|
||||||
id={getId()}
|
|
||||||
defaultMessage={getDefaultMessage()}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -70,13 +60,13 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
|||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
shouldUpdatePosition={true}
|
shouldUpdatePosition={true}
|
||||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||||
placement={props.placement}
|
placement={props.placement ?? 'top'}
|
||||||
overlay={tooltip}
|
overlay={tooltip}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={spanClassName}
|
className={spanClassName}
|
||||||
onClick={copyText}
|
onClick={copyText}
|
||||||
aria-label={intl.formatMessage({id: getId(), defaultMessage: getDefaultMessage()})}
|
aria-label={intl.formatMessage(tooltipMessage)}
|
||||||
role='button'
|
role='button'
|
||||||
>
|
>
|
||||||
{!isCopied &&
|
{!isCopied &&
|
||||||
@@ -94,9 +84,19 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CopyButton.defaultProps = {
|
const messages = defineMessages({
|
||||||
afterCopyText: 'Copied',
|
copied: {
|
||||||
placement: 'top',
|
id: 'copied.message',
|
||||||
};
|
defaultMessage: 'Copied',
|
||||||
|
},
|
||||||
|
copyCode: {
|
||||||
|
id: 'copy.code.message',
|
||||||
|
defaultMessage: 'Copy code',
|
||||||
|
},
|
||||||
|
copyText: {
|
||||||
|
id: 'copy.text.message',
|
||||||
|
defaultMessage: 'Copy text',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default CopyButton;
|
export default CopyButton;
|
||||||
|
|||||||
@@ -145,15 +145,11 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
|||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
);
|
);
|
||||||
const getBeforeCopyText = () => {
|
|
||||||
const fileType = getFileType(props.fileInfo.extension);
|
|
||||||
return fileType === FileTypes.TEXT ? 'Copy text' : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const copy = (
|
const copy = (
|
||||||
<CopyButton
|
<CopyButton
|
||||||
className='file-preview-modal-main-actions__action-item'
|
className='file-preview-modal-main-actions__action-item'
|
||||||
beforeCopyText={getBeforeCopyText()}
|
isForText={getFileType(props.fileInfo.extension) === FileTypes.TEXT}
|
||||||
placement={tooltipPlacement}
|
placement={tooltipPlacement}
|
||||||
content={props.content}
|
content={props.content}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl';
|
import {FormattedMessage, defineMessages, injectIntl, type WrappedComponentProps} from 'react-intl';
|
||||||
|
|
||||||
import {ArchiveOutlineIcon, CheckIcon, ChevronDownIcon, GlobeIcon, LockOutlineIcon, AccountOutlineIcon, GlobeCheckedIcon} from '@mattermost/compass-icons/components';
|
import {ArchiveOutlineIcon, CheckIcon, ChevronDownIcon, GlobeIcon, LockOutlineIcon, AccountOutlineIcon, GlobeCheckedIcon} from '@mattermost/compass-icons/components';
|
||||||
import type {Channel, ChannelMembership} from '@mattermost/types/channels';
|
import type {Channel, ChannelMembership} from '@mattermost/types/channels';
|
||||||
@@ -21,7 +21,6 @@ import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
|
|||||||
|
|
||||||
import {isArchivedChannel} from 'utils/channel_utils';
|
import {isArchivedChannel} from 'utils/channel_utils';
|
||||||
import Constants, {ModalIdentifiers} from 'utils/constants';
|
import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
import {isKeyPressed} from 'utils/keyboard';
|
import {isKeyPressed} from 'utils/keyboard';
|
||||||
import * as UserAgent from 'utils/user_agent';
|
import * as UserAgent from 'utils/user_agent';
|
||||||
import {localizeMessage, localizeAndFormatMessage} from 'utils/utils';
|
import {localizeMessage, localizeAndFormatMessage} from 'utils/utils';
|
||||||
@@ -162,8 +161,8 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const channelPurposeContainerAriaLabel = localizeAndFormatMessage(
|
const channelPurposeContainerAriaLabel = localizeAndFormatMessage(
|
||||||
t('more_channels.channel_purpose'),
|
messages.channelPurpose.id,
|
||||||
'Channel Information: Membership Indicator: Joined, Member count {memberCount}, Purpose: {channelPurpose}',
|
messages.channelPurpose.defaultMessage,
|
||||||
{memberCount, channelPurpose: channel.purpose || ''},
|
{memberCount, channelPurpose: channel.purpose || ''},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -358,7 +357,7 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
|||||||
listContent = (
|
listContent = (
|
||||||
<div
|
<div
|
||||||
className='no-channel-message'
|
className='no-channel-message'
|
||||||
aria-label={this.state.channelSearchValue.length > 0 ? localizeAndFormatMessage(t('more_channels.noMore'), 'No results for {text}', {text: this.state.channelSearchValue}) : localizeMessage('widgets.channels_input.empty', 'No channels found')
|
aria-label={this.state.channelSearchValue.length > 0 ? localizeAndFormatMessage(messages.noMore.id, messages.noMore.defaultMessage, {text: this.state.channelSearchValue}) : localizeMessage('widgets.channels_input.empty', 'No channels found')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MagnifyingGlassSVG/>
|
<MagnifyingGlassSVG/>
|
||||||
@@ -547,7 +546,7 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
|||||||
} else if (channels.length === 1) {
|
} else if (channels.length === 1) {
|
||||||
channelCountLabel = localizeMessage('more_channels.count_one', '1 Result');
|
channelCountLabel = localizeMessage('more_channels.count_one', '1 Result');
|
||||||
} else if (channels.length > 1) {
|
} else if (channels.length > 1) {
|
||||||
channelCountLabel = localizeAndFormatMessage(t('more_channels.count'), '0 Results', {count: channels.length});
|
channelCountLabel = localizeAndFormatMessage(messages.channelCount.id, messages.channelCount.defaultMessage, {count: channels.length});
|
||||||
} else {
|
} else {
|
||||||
channelCountLabel = localizeMessage('more_channels.count_zero', '0 Results');
|
channelCountLabel = localizeMessage('more_channels.count_zero', '0 Results');
|
||||||
}
|
}
|
||||||
@@ -588,4 +587,19 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
channelCount: {
|
||||||
|
id: 'more_channels.count',
|
||||||
|
defaultMessage: '{count} Results',
|
||||||
|
},
|
||||||
|
channelPurpose: {
|
||||||
|
id: 'more_channels.channel_purpose',
|
||||||
|
defaultMessage: 'Channel Information: Membership Indicator: Joined, Member count {memberCount} , Purpose: {channelPurpose}',
|
||||||
|
},
|
||||||
|
noMore: {
|
||||||
|
id: 'more_channels.noMore',
|
||||||
|
defaultMessage: 'No results for {text}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default injectIntl(SearchableChannelList);
|
export default injectIntl(SearchableChannelList);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {defineMessages} from 'react-intl';
|
||||||
|
|
||||||
import type {Group} from '@mattermost/types/groups';
|
import type {Group} from '@mattermost/types/groups';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
@@ -458,3 +460,26 @@ export default class AtMentionProvider extends Provider {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineMessages({
|
||||||
|
groupDivider: {
|
||||||
|
id: 'suggestion.search.group',
|
||||||
|
defaultMessage: 'Group Mentions',
|
||||||
|
},
|
||||||
|
memberDivider: {
|
||||||
|
id: 'suggestion.mention.members',
|
||||||
|
defaultMessage: 'Channel Members',
|
||||||
|
},
|
||||||
|
moreMembersDivider: {
|
||||||
|
id: 'suggestion.mention.moremembers',
|
||||||
|
defaultMessage: 'Other Members',
|
||||||
|
},
|
||||||
|
nonmemberDivider: {
|
||||||
|
id: 'suggestion.mention.nonmembers',
|
||||||
|
defaultMessage: 'Not in Channel',
|
||||||
|
},
|
||||||
|
specialDivider: {
|
||||||
|
id: 'suggestion.mention.special',
|
||||||
|
defaultMessage: 'Special Mentions',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {defineMessages} from 'react-intl';
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
@@ -260,3 +261,10 @@ export default class ChannelMentionProvider extends Provider {
|
|||||||
this.lastPrefixWithNoResults = '';
|
this.lastPrefixWithNoResults = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineMessages({
|
||||||
|
myChannelsDivider: {
|
||||||
|
id: 'suggestion.mention.channels',
|
||||||
|
defaultMessage: 'My Channels',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {defineMessages} from 'react-intl';
|
||||||
import type {Store} from 'redux';
|
import type {Store} from 'redux';
|
||||||
|
|
||||||
import {DockWindowIcon} from '@mattermost/compass-icons/components';
|
import {DockWindowIcon} from '@mattermost/compass-icons/components';
|
||||||
@@ -288,3 +289,10 @@ export default class CommandProvider extends Provider {
|
|||||||
return matches.findIndex((match) => match.Complete === complete) !== -1;
|
return matches.findIndex((match) => match.Complete === complete) !== -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineMessages({
|
||||||
|
commandsDivider: {
|
||||||
|
id: 'suggestion.commands',
|
||||||
|
defaultMessage: 'Commands',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {defineMessages} from 'react-intl';
|
||||||
|
|
||||||
import type {Emoji} from '@mattermost/types/emojis';
|
import type {Emoji} from '@mattermost/types/emojis';
|
||||||
|
|
||||||
@@ -11,7 +12,6 @@ import {getEmojiImageUrl, isSystemEmoji} from 'mattermost-redux/utils/emoji_util
|
|||||||
import {getEmojiMap, getRecentEmojisNames} from 'selectors/emojis';
|
import {getEmojiMap, getRecentEmojisNames} from 'selectors/emojis';
|
||||||
import store from 'stores/redux_store';
|
import store from 'stores/redux_store';
|
||||||
|
|
||||||
import {Preferences} from 'utils/constants';
|
|
||||||
import {compareEmojis, emojiMatchesSkin} from 'utils/emoji_utils';
|
import {compareEmojis, emojiMatchesSkin} from 'utils/emoji_utils';
|
||||||
import * as Emoticons from 'utils/emoticons';
|
import * as Emoticons from 'utils/emoticons';
|
||||||
|
|
||||||
@@ -29,6 +29,8 @@ type EmojiItem = {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const suggestionTypeEmoji = 'emoji';
|
||||||
|
|
||||||
const EmoticonSuggestion = React.forwardRef<HTMLDivElement, SuggestionProps<EmojiItem>>((props, ref) => {
|
const EmoticonSuggestion = React.forwardRef<HTMLDivElement, SuggestionProps<EmojiItem>>((props, ref) => {
|
||||||
const text = props.term;
|
const text = props.term;
|
||||||
const emoji = props.item.emoji;
|
const emoji = props.item.emoji;
|
||||||
@@ -131,7 +133,7 @@ export default class EmoticonProvider extends Provider {
|
|||||||
|
|
||||||
// if the emoji has skin, only add those that match with the user selected skin.
|
// if the emoji has skin, only add those that match with the user selected skin.
|
||||||
if (emojiMatchesSkin(emoji, skintone)) {
|
if (emojiMatchesSkin(emoji, skintone)) {
|
||||||
matchedArray.push({name: alias, emoji, type: Preferences.CATEGORY_EMOJI});
|
matchedArray.push({name: alias, emoji, type: suggestionTypeEmoji});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -145,7 +147,7 @@ export default class EmoticonProvider extends Provider {
|
|||||||
|
|
||||||
const matchedArray = recentEmojis.includes(name) ? recentMatched : matched;
|
const matchedArray = recentEmojis.includes(name) ? recentMatched : matched;
|
||||||
|
|
||||||
matchedArray.push({name, emoji, type: Preferences.CATEGORY_EMOJI});
|
matchedArray.push({name, emoji, type: suggestionTypeEmoji});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,3 +178,10 @@ export default class EmoticonProvider extends Provider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineMessages({
|
||||||
|
emojisDivider: {
|
||||||
|
id: 'suggestion.emoji',
|
||||||
|
defaultMessage: 'Emoji',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {defineMessages} from 'react-intl';
|
||||||
import {connect, useSelector} from 'react-redux';
|
import {connect, useSelector} from 'react-redux';
|
||||||
|
|
||||||
import type {Channel, ChannelMembership, ChannelType} from '@mattermost/types/channels';
|
import type {Channel, ChannelMembership, ChannelType} from '@mattermost/types/channels';
|
||||||
@@ -863,3 +864,22 @@ export default class SwitchChannelProvider extends Provider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineMessages({
|
||||||
|
moreChannels: {
|
||||||
|
id: 'suggestion.mention.morechannels',
|
||||||
|
defaultMessage: 'Other Channels',
|
||||||
|
},
|
||||||
|
privateChannelsDivider: {
|
||||||
|
id: 'suggestion.mention.private.channels',
|
||||||
|
defaultMessage: 'Private Channels',
|
||||||
|
},
|
||||||
|
recentChannels: {
|
||||||
|
id: 'suggestion.mention.recent.channels',
|
||||||
|
defaultMessage: 'Recent',
|
||||||
|
},
|
||||||
|
unreadChannels: {
|
||||||
|
id: 'suggestion.mention.unread',
|
||||||
|
defaultMessage: 'Unread',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
|
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
|
|
||||||
export default function CloseIcon(props: React.HTMLAttributes<HTMLSpanElement>) {
|
export default function CloseIcon(props: React.HTMLAttributes<HTMLSpanElement>) {
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
return (
|
return (
|
||||||
@@ -15,7 +13,7 @@ export default function CloseIcon(props: React.HTMLAttributes<HTMLSpanElement>)
|
|||||||
height='24px'
|
height='24px'
|
||||||
viewBox='0 0 24 24'
|
viewBox='0 0 24 24'
|
||||||
role='img'
|
role='img'
|
||||||
aria-label={formatMessage({id: t('generic_icons.close'), defaultMessage: 'Close Icon'})}
|
aria-label={formatMessage({id: 'generic_icons.close', defaultMessage: 'Close Icon'})}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule='nonzero'
|
fillRule='nonzero'
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {RefObject} from 'react';
|
import type {RefObject} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import type {MessageDescriptor} from 'react-intl';
|
||||||
|
import {FormattedMessage, defineMessages} from 'react-intl';
|
||||||
import {components} from 'react-select';
|
import {components} from 'react-select';
|
||||||
import type {ValueType, ActionMeta, InputActionMeta} from 'react-select';
|
import type {ValueType, ActionMeta, InputActionMeta} from 'react-select';
|
||||||
import type {Async} from 'react-select/async';
|
import type {Async} from 'react-select/async';
|
||||||
@@ -19,7 +20,6 @@ import PrivateChannelIcon from 'components/widgets/icons/lock_icon';
|
|||||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
import {Constants} from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
|
|
||||||
import './channels_input.scss';
|
import './channels_input.scss';
|
||||||
|
|
||||||
@@ -31,22 +31,29 @@ type Props = {
|
|||||||
value: Channel[];
|
value: Channel[];
|
||||||
onInputChange: (change: string) => void;
|
onInputChange: (change: string) => void;
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
loadingMessageId?: string;
|
loadingMessage?: MessageDescriptor;
|
||||||
loadingMessageDefault?: string;
|
noOptionsMessage?: MessageDescriptor;
|
||||||
noOptionsMessageId?: string;
|
|
||||||
noOptionsMessageDefault?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
options: Channel[];
|
options: Channel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
loading: {
|
||||||
|
id: 'widgets.channels_input.loading',
|
||||||
|
defaultMessage: 'Loading',
|
||||||
|
},
|
||||||
|
noOptions: {
|
||||||
|
id: 'widgets.channels_input.empty',
|
||||||
|
defaultMessage: 'No channels found',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default class ChannelsInput extends React.PureComponent<Props, State> {
|
export default class ChannelsInput extends React.PureComponent<Props, State> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
loadingMessageId: t('widgets.channels_input.loading'),
|
loadingMessage: messages.loading,
|
||||||
loadingMessageDefault: 'Loading',
|
noOptionsMessage: messages.noOptions,
|
||||||
noOptionsMessageId: t('widgets.channels_input.empty'),
|
|
||||||
noOptionsMessageDefault: 'No channels found',
|
|
||||||
};
|
};
|
||||||
private selectRef: RefObject<Async<Channel> & {handleInputChange: (newValue: string, actionMeta: InputActionMeta | {action: 'custom'}) => string}>;
|
private selectRef: RefObject<Async<Channel> & {handleInputChange: (newValue: string, actionMeta: InputActionMeta | {action: 'custom'}) => string}>;
|
||||||
|
|
||||||
@@ -89,8 +96,7 @@ export default class ChannelsInput extends React.PureComponent<Props, State> {
|
|||||||
loadingMessage = () => {
|
loadingMessage = () => {
|
||||||
const text = (
|
const text = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={this.props.loadingMessageId}
|
{...this.props.loadingMessage}
|
||||||
defaultMessage={this.props.loadingMessageDefault}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -108,8 +114,7 @@ export default class ChannelsInput extends React.PureComponent<Props, State> {
|
|||||||
<div className='channels-input__option channels-input__option--no-matches'>
|
<div className='channels-input__option channels-input__option--no-matches'>
|
||||||
<Msg {...props}>
|
<Msg {...props}>
|
||||||
<FormattedMarkdownMessage
|
<FormattedMarkdownMessage
|
||||||
id={this.props.noOptionsMessageId}
|
{...this.props.noOptionsMessage}
|
||||||
defaultMessage={this.props.noOptionsMessageDefault}
|
|
||||||
values={{text: inputValue}}
|
values={{text: inputValue}}
|
||||||
/>
|
/>
|
||||||
</Msg>
|
</Msg>
|
||||||
|
|||||||
@@ -4,14 +4,13 @@
|
|||||||
import type {PrimitiveType, FormatXMLElementFn} from 'intl-messageformat';
|
import type {PrimitiveType, FormatXMLElementFn} from 'intl-messageformat';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {ReactNode} from 'react';
|
import type {ReactNode} from 'react';
|
||||||
import {useIntl} from 'react-intl';
|
import {defineMessage, useIntl} from 'react-intl';
|
||||||
|
|
||||||
import type {LimitSummary} from 'components/common/hooks/useGetHighestThresholdCloudLimit';
|
import type {LimitSummary} from 'components/common/hooks/useGetHighestThresholdCloudLimit';
|
||||||
import useOpenPricingModal from 'components/common/hooks/useOpenPricingModal';
|
import useOpenPricingModal from 'components/common/hooks/useOpenPricingModal';
|
||||||
import NotifyAdminCTA from 'components/notify_admin_cta/notify_admin_cta';
|
import NotifyAdminCTA from 'components/notify_admin_cta/notify_admin_cta';
|
||||||
|
|
||||||
import {MattermostFeatures, LicenseSkus} from 'utils/constants';
|
import {MattermostFeatures, LicenseSkus} from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
import {limitThresholds, asGBString, inK, LimitTypes} from 'utils/limits';
|
import {limitThresholds, asGBString, inK, LimitTypes} from 'utils/limits';
|
||||||
|
|
||||||
interface Words {
|
interface Words {
|
||||||
@@ -82,36 +81,50 @@ export default function useWords(highestLimit: LimitSummary | false, isAdminUser
|
|||||||
|
|
||||||
switch (highestLimit.id) {
|
switch (highestLimit.id) {
|
||||||
case LimitTypes.messageHistory: {
|
case LimitTypes.messageHistory: {
|
||||||
let id = t('workspace_limits.menu_limit.warn.messages_history');
|
let description = defineMessage({
|
||||||
let defaultMessage = 'You’re getting closer to the free {limit} message limit. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.warn.messages_history',
|
||||||
|
defaultMessage: 'You’re getting closer to the free {limit} message limit. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
values.limit = intl.formatNumber(highestLimit.limit);
|
values.limit = intl.formatNumber(highestLimit.limit);
|
||||||
if (usageRatio >= limitThresholds.danger) {
|
if (usageRatio >= limitThresholds.danger) {
|
||||||
if (isAdminUser) {
|
if (isAdminUser) {
|
||||||
id = t('workspace_limits.menu_limit.critical.messages_history');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’re close to hitting the free {limit} message history limit <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.critical.messages_history',
|
||||||
|
defaultMessage: 'You’re close to hitting the free {limit} message history limit <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
id = t('workspace_limits.menu_limit.critical.messages_history_non_admin');
|
description = defineMessage({
|
||||||
defaultMessage = 'You\'re almost at the message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.critical.messages_history_non_admin',
|
||||||
|
defaultMessage: 'You\'re almost at the message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (usageRatio >= limitThresholds.reached) {
|
if (usageRatio >= limitThresholds.reached) {
|
||||||
if (isAdminUser) {
|
if (isAdminUser) {
|
||||||
id = t('workspace_limits.menu_limit.reached.messages_history');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’ve reached the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.reached.messages_history',
|
||||||
|
defaultMessage: 'You’ve reached the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
values.limit = inK(highestLimit.limit);
|
values.limit = inK(highestLimit.limit);
|
||||||
} else {
|
} else {
|
||||||
id = t('workspace_limits.menu_limit.reached.messages_history_non_admin');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’ve reached your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.reached.messages_history_non_admin',
|
||||||
|
defaultMessage: 'You’ve reached your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (usageRatio >= limitThresholds.exceeded) {
|
if (usageRatio >= limitThresholds.exceeded) {
|
||||||
if (isAdminUser) {
|
if (isAdminUser) {
|
||||||
id = t('workspace_limits.menu_limit.over.messages_history');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’re over the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.over.messages_history',
|
||||||
|
defaultMessage: 'You’re over the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
values.limit = inK(highestLimit.limit);
|
values.limit = inK(highestLimit.limit);
|
||||||
} else {
|
} else {
|
||||||
id = t('workspace_limits.menu_limit.over.messages_history_non_admin');
|
description = defineMessage({
|
||||||
defaultMessage = 'You\'re over your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.over.messages_history_non_admin',
|
||||||
|
defaultMessage: 'You\'re over your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -120,30 +133,35 @@ export default function useWords(highestLimit: LimitSummary | false, isAdminUser
|
|||||||
defaultMessage: 'Total messages',
|
defaultMessage: 'Total messages',
|
||||||
}),
|
}),
|
||||||
description: intl.formatMessage<ReactNode>(
|
description: intl.formatMessage<ReactNode>(
|
||||||
{
|
description,
|
||||||
id,
|
|
||||||
defaultMessage,
|
|
||||||
},
|
|
||||||
values,
|
values,
|
||||||
),
|
),
|
||||||
status: inK(highestLimit.usage),
|
status: inK(highestLimit.usage),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case LimitTypes.fileStorage: {
|
case LimitTypes.fileStorage: {
|
||||||
let id = t('workspace_limits.menu_limit.warn.files_storage');
|
let description = defineMessage({
|
||||||
let defaultMessage = 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.warn.files_storage',
|
||||||
|
defaultMessage: 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
values.limit = asGBString(highestLimit.limit, intl.formatNumber);
|
values.limit = asGBString(highestLimit.limit, intl.formatNumber);
|
||||||
if (usageRatio >= limitThresholds.danger) {
|
if (usageRatio >= limitThresholds.danger) {
|
||||||
id = t('workspace_limits.menu_limit.critical.files_storage');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.critical.files_storage',
|
||||||
|
defaultMessage: 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (usageRatio >= limitThresholds.reached) {
|
if (usageRatio >= limitThresholds.reached) {
|
||||||
id = t('workspace_limits.menu_limit.reached.files_storage');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’ve reached the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.reached.files_storage',
|
||||||
|
defaultMessage: 'You’ve reached the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (usageRatio >= limitThresholds.exceeded) {
|
if (usageRatio >= limitThresholds.exceeded) {
|
||||||
id = t('workspace_limits.menu_limit.over.files_storage');
|
description = defineMessage({
|
||||||
defaultMessage = 'You’re over the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>';
|
id: 'workspace_limits.menu_limit.over.files_storage',
|
||||||
|
defaultMessage: 'You’re over the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -152,10 +170,7 @@ export default function useWords(highestLimit: LimitSummary | false, isAdminUser
|
|||||||
defaultMessage: 'File storage limit',
|
defaultMessage: 'File storage limit',
|
||||||
}),
|
}),
|
||||||
description: intl.formatMessage<ReactNode>(
|
description: intl.formatMessage<ReactNode>(
|
||||||
{
|
description,
|
||||||
id,
|
|
||||||
defaultMessage,
|
|
||||||
},
|
|
||||||
values,
|
values,
|
||||||
),
|
),
|
||||||
status: asGBString(highestLimit.usage, intl.formatNumber),
|
status: asGBString(highestLimit.usage, intl.formatNumber),
|
||||||
|
|||||||
@@ -5043,7 +5043,6 @@
|
|||||||
"suggestion.mention.all": "Notifies everyone in this channel",
|
"suggestion.mention.all": "Notifies everyone in this channel",
|
||||||
"suggestion.mention.channel": "Notifies everyone in this channel",
|
"suggestion.mention.channel": "Notifies everyone in this channel",
|
||||||
"suggestion.mention.channels": "My Channels",
|
"suggestion.mention.channels": "My Channels",
|
||||||
"suggestion.mention.groups": "Group Mentions",
|
|
||||||
"suggestion.mention.here": "Notifies everyone online in this channel",
|
"suggestion.mention.here": "Notifies everyone online in this channel",
|
||||||
"suggestion.mention.members": "Channel Members",
|
"suggestion.mention.members": "Channel Members",
|
||||||
"suggestion.mention.morechannels": "Other Channels",
|
"suggestion.mention.morechannels": "Other Channels",
|
||||||
@@ -5053,7 +5052,6 @@
|
|||||||
"suggestion.mention.recent.channels": "Recent",
|
"suggestion.mention.recent.channels": "Recent",
|
||||||
"suggestion.mention.special": "Special Mentions",
|
"suggestion.mention.special": "Special Mentions",
|
||||||
"suggestion.mention.unread": "Unread",
|
"suggestion.mention.unread": "Unread",
|
||||||
"suggestion.mention.unread.channels": "Unread Channels",
|
|
||||||
"suggestion.private": "Private channels",
|
"suggestion.private": "Private channels",
|
||||||
"suggestion.public": "Public channels",
|
"suggestion.public": "Public channels",
|
||||||
"suggestion.search.direct": "Direct Messages",
|
"suggestion.search.direct": "Direct Messages",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import solarizedLightCSS from 'highlight.js/styles/base16/solarized-light.css';
|
|||||||
import githubCSS from 'highlight.js/styles/github.css';
|
import githubCSS from 'highlight.js/styles/github.css';
|
||||||
import monokaiCSS from 'highlight.js/styles/monokai.css';
|
import monokaiCSS from 'highlight.js/styles/monokai.css';
|
||||||
import keyMirror from 'key-mirror';
|
import keyMirror from 'key-mirror';
|
||||||
|
import {defineMessage, defineMessages} from 'react-intl';
|
||||||
|
|
||||||
import {CustomStatusDuration} from '@mattermost/types/users';
|
import {CustomStatusDuration} from '@mattermost/types/users';
|
||||||
|
|
||||||
@@ -29,7 +30,6 @@ import monokaiIcon from 'images/themes/code_themes/monokai.png';
|
|||||||
import solarizedDarkIcon from 'images/themes/code_themes/solarized-dark.png';
|
import solarizedDarkIcon from 'images/themes/code_themes/solarized-dark.png';
|
||||||
import solarizedLightIcon from 'images/themes/code_themes/solarized-light.png';
|
import solarizedLightIcon from 'images/themes/code_themes/solarized-light.png';
|
||||||
import logoWebhook from 'images/webhook_icon.jpg';
|
import logoWebhook from 'images/webhook_icon.jpg';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
|
|
||||||
export const SettingsTypes = {
|
export const SettingsTypes = {
|
||||||
TYPE_TEXT: 'text' as const,
|
TYPE_TEXT: 'text' as const,
|
||||||
@@ -913,16 +913,52 @@ export const AnnouncementBarTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AnnouncementBarMessages = {
|
export const AnnouncementBarMessages = {
|
||||||
EMAIL_VERIFICATION_REQUIRED: t('announcement_bar.error.email_verification_required'),
|
EMAIL_VERIFICATION_REQUIRED: 'announcement_bar.error.email_verification_required',
|
||||||
EMAIL_VERIFIED: t('announcement_bar.notification.email_verified'),
|
EMAIL_VERIFIED: 'announcement_bar.notification.email_verified',
|
||||||
LICENSE_EXPIRED: t('announcement_bar.error.license_expired'),
|
LICENSE_EXPIRED: 'announcement_bar.error.license_expired',
|
||||||
LICENSE_EXPIRING: t('announcement_bar.error.license_expiring'),
|
LICENSE_EXPIRING: 'announcement_bar.error.license_expiring',
|
||||||
LICENSE_PAST_GRACE: t('announcement_bar.error.past_grace'),
|
LICENSE_PAST_GRACE: 'announcement_bar.error.past_grace',
|
||||||
PREVIEW_MODE: t('announcement_bar.error.preview_mode'),
|
PREVIEW_MODE: 'announcement_bar.error.preview_mode',
|
||||||
WEBSOCKET_PORT_ERROR: t('channel_loader.socketError'),
|
WEBSOCKET_PORT_ERROR: 'channel_loader.socketError',
|
||||||
TRIAL_LICENSE_EXPIRING: t('announcement_bar.error.trial_license_expiring'),
|
TRIAL_LICENSE_EXPIRING: 'announcement_bar.error.trial_license_expiring',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// These messages correspond to AnnouncementBarMessages above
|
||||||
|
defineMessages({
|
||||||
|
emailVerificationRequired: {
|
||||||
|
id: 'announcement_bar.error.email_verification_required',
|
||||||
|
defaultMessage: 'Check your email inbox to verify the address.',
|
||||||
|
},
|
||||||
|
emailVerified: {
|
||||||
|
id: 'announcement_bar.notification.email_verified',
|
||||||
|
defaultMessage: 'Email verified',
|
||||||
|
},
|
||||||
|
licenseExpired: {
|
||||||
|
id: 'announcement_bar.error.license_expired',
|
||||||
|
defaultMessage: '{licenseSku} license is expired and some features may be disabled.',
|
||||||
|
},
|
||||||
|
licenseExpiring: {
|
||||||
|
id: 'announcement_bar.error.license_expiring',
|
||||||
|
defaultMessage: '{licenseSku} license expires on {date, date, long}.',
|
||||||
|
},
|
||||||
|
pastGrace: {
|
||||||
|
id: 'announcement_bar.error.past_grace',
|
||||||
|
defaultMessage: '{licenseSku} license is expired and some features may be disabled. Please contact your System Administrator for details.',
|
||||||
|
},
|
||||||
|
previewMode: {
|
||||||
|
id: 'announcement_bar.error.preview_mode',
|
||||||
|
defaultMessage: 'Preview Mode: Email notifications have not been configured.',
|
||||||
|
},
|
||||||
|
socketError: {
|
||||||
|
id: 'channel_loader.socketError',
|
||||||
|
defaultMessage: 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to [check WebSocket port](!https://docs.mattermost.com/install/troubleshooting.html#please-check-connection-mattermost-unreachable-if-issue-persists-ask-administrator-to-check-websocket-port).',
|
||||||
|
},
|
||||||
|
trialLicenseExpiring: {
|
||||||
|
id: 'announcement_bar.error.trial_license_expiring',
|
||||||
|
defaultMessage: 'There are {days} days left on your free trial.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const VerifyEmailErrors = {
|
export const VerifyEmailErrors = {
|
||||||
FAILED_EMAIL_VERIFICATION: 'failed_email_verification',
|
FAILED_EMAIL_VERIFICATION: 'failed_email_verification',
|
||||||
FAILED_USER_STATE_GET: 'failed_get_user_state',
|
FAILED_USER_STATE_GET: 'failed_get_user_state',
|
||||||
@@ -2070,43 +2106,25 @@ export const WindowSizes = {
|
|||||||
|
|
||||||
export const AcceptedProfileImageTypes = ['image/jpeg', 'image/png', 'image/bmp'];
|
export const AcceptedProfileImageTypes = ['image/jpeg', 'image/png', 'image/bmp'];
|
||||||
|
|
||||||
export const searchHintOptions = [{searchTerm: 'From:', message: {id: t('search_list_option.from'), defaultMessage: 'Messages from a user'}},
|
export const searchHintOptions = [{searchTerm: 'From:', message: defineMessage({id: 'search_list_option.from', defaultMessage: 'Messages from a user'})},
|
||||||
{searchTerm: 'In:', message: {id: t('search_list_option.in'), defaultMessage: 'Messages in a channel'}},
|
{searchTerm: 'In:', message: defineMessage({id: 'search_list_option.in', defaultMessage: 'Messages in a channel'})},
|
||||||
{searchTerm: 'On:', message: {id: t('search_list_option.on'), defaultMessage: 'Messages on a date'}},
|
{searchTerm: 'On:', message: defineMessage({id: 'search_list_option.on', defaultMessage: 'Messages on a date'})},
|
||||||
{searchTerm: 'Before:', message: {id: t('search_list_option.before'), defaultMessage: 'Messages before a date'}},
|
{searchTerm: 'Before:', message: defineMessage({id: 'search_list_option.before', defaultMessage: 'Messages before a date'})},
|
||||||
{searchTerm: 'After:', message: {id: t('search_list_option.after'), defaultMessage: 'Messages after a date'}},
|
{searchTerm: 'After:', message: defineMessage({id: 'search_list_option.after', defaultMessage: 'Messages after a date'})},
|
||||||
{searchTerm: '-', message: {id: t('search_list_option.exclude'), defaultMessage: 'Exclude search terms'}, additionalDisplay: '—'},
|
{searchTerm: '-', message: defineMessage({id: 'search_list_option.exclude', defaultMessage: 'Exclude search terms'}), additionalDisplay: '—'},
|
||||||
{searchTerm: '""', message: {id: t('search_list_option.phrases'), defaultMessage: 'Messages with phrases'}},
|
{searchTerm: '""', message: defineMessage({id: 'search_list_option.phrases', defaultMessage: 'Messages with phrases'})},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const searchFilesHintOptions = [{searchTerm: 'From:', message: {id: t('search_files_list_option.from'), defaultMessage: 'Files from a user'}},
|
export const searchFilesHintOptions = [{searchTerm: 'From:', message: defineMessage({id: 'search_files_list_option.from', defaultMessage: 'Files from a user'})},
|
||||||
{searchTerm: 'In:', message: {id: t('search_files_list_option.in'), defaultMessage: 'Files in a channel'}},
|
{searchTerm: 'In:', message: defineMessage({id: 'search_files_list_option.in', defaultMessage: 'Files in a channel'})},
|
||||||
{searchTerm: 'On:', message: {id: t('search_files_list_option.on'), defaultMessage: 'Files on a date'}},
|
{searchTerm: 'On:', message: defineMessage({id: 'search_files_list_option.on', defaultMessage: 'Files on a date'})},
|
||||||
{searchTerm: 'Before:', message: {id: t('search_files_list_option.before'), defaultMessage: 'Files before a date'}},
|
{searchTerm: 'Before:', message: defineMessage({id: 'search_files_list_option.before', defaultMessage: 'Files before a date'})},
|
||||||
{searchTerm: 'After:', message: {id: t('search_files_list_option.after'), defaultMessage: 'Files after a date'}},
|
{searchTerm: 'After:', message: defineMessage({id: 'search_files_list_option.after', defaultMessage: 'Files after a date'})},
|
||||||
{searchTerm: 'Ext:', message: {id: t('search_files_list_option.ext'), defaultMessage: 'Files with a extension'}},
|
{searchTerm: 'Ext:', message: defineMessage({id: 'search_files_list_option.ext', defaultMessage: 'Files with a extension'})},
|
||||||
{searchTerm: '-', message: {id: t('search_files_list_option.exclude'), defaultMessage: 'Exclude search terms'}, additionalDisplay: '—'},
|
{searchTerm: '-', message: defineMessage({id: 'search_files_list_option.exclude', defaultMessage: 'Exclude search terms'}), additionalDisplay: '—'},
|
||||||
{searchTerm: '""', message: {id: t('search_files_list_option.phrases'), defaultMessage: 'Files with phrases'}},
|
{searchTerm: '""', message: defineMessage({id: 'search_files_list_option.phrases', defaultMessage: 'Files with phrases'})},
|
||||||
];
|
];
|
||||||
|
|
||||||
// adding these rtranslations here so the weblate CI step will not fail with empty translation strings
|
|
||||||
t('suggestion.archive');
|
|
||||||
t('suggestion.mention.channels');
|
|
||||||
t('suggestion.mention.morechannels');
|
|
||||||
t('suggestion.mention.unread.channels');
|
|
||||||
t('suggestion.mention.unread');
|
|
||||||
t('suggestion.mention.members');
|
|
||||||
t('suggestion.mention.moremembers');
|
|
||||||
t('suggestion.mention.nonmembers');
|
|
||||||
t('suggestion.mention.private.channels');
|
|
||||||
t('suggestion.mention.recent.channels');
|
|
||||||
t('suggestion.mention.special');
|
|
||||||
t('suggestion.mention.groups');
|
|
||||||
t('suggestion.search.public');
|
|
||||||
t('suggestion.search.group');
|
|
||||||
t('suggestion.commands');
|
|
||||||
t('suggestion.emoji');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
DONT_CLEAR,
|
DONT_CLEAR,
|
||||||
THIRTY_MINUTES,
|
THIRTY_MINUTES,
|
||||||
@@ -2118,40 +2136,41 @@ const {
|
|||||||
CUSTOM_DATE_TIME,
|
CUSTOM_DATE_TIME,
|
||||||
} = CustomStatusDuration;
|
} = CustomStatusDuration;
|
||||||
|
|
||||||
export const durationValues = {
|
export const durationValues = defineMessages({
|
||||||
[DONT_CLEAR]: {
|
[DONT_CLEAR]: {
|
||||||
id: t('custom_status.expiry_dropdown.dont_clear'),
|
id: 'custom_status.expiry_dropdown.dont_clear',
|
||||||
defaultMessage: "Don't clear",
|
defaultMessage: "Don't clear",
|
||||||
},
|
},
|
||||||
[THIRTY_MINUTES]: {
|
[THIRTY_MINUTES]: {
|
||||||
id: t('custom_status.expiry_dropdown.thirty_minutes'),
|
id: 'custom_status.expiry_dropdown.thirty_minutes',
|
||||||
defaultMessage: '30 minutes',
|
defaultMessage: '30 minutes',
|
||||||
},
|
},
|
||||||
[ONE_HOUR]: {
|
[ONE_HOUR]: {
|
||||||
id: t('custom_status.expiry_dropdown.one_hour'),
|
id: 'custom_status.expiry_dropdown.one_hour',
|
||||||
defaultMessage: '1 hour',
|
defaultMessage: '1 hour',
|
||||||
},
|
},
|
||||||
[FOUR_HOURS]: {
|
[FOUR_HOURS]: {
|
||||||
id: t('custom_status.expiry_dropdown.four_hours'),
|
id: 'custom_status.expiry_dropdown.four_hours',
|
||||||
defaultMessage: '4 hours',
|
defaultMessage: '4 hours',
|
||||||
},
|
},
|
||||||
[TODAY]: {
|
[TODAY]: {
|
||||||
id: t('custom_status.expiry_dropdown.today'),
|
id: 'custom_status.expiry_dropdown.today',
|
||||||
defaultMessage: 'Today',
|
defaultMessage: 'Today',
|
||||||
},
|
},
|
||||||
[THIS_WEEK]: {
|
[THIS_WEEK]: {
|
||||||
id: t('custom_status.expiry_dropdown.this_week'),
|
id: 'custom_status.expiry_dropdown.this_week',
|
||||||
defaultMessage: 'This week',
|
defaultMessage: 'This week',
|
||||||
},
|
},
|
||||||
[DATE_AND_TIME]: {
|
[DATE_AND_TIME]: {
|
||||||
id: t('custom_status.expiry_dropdown.date_and_time'),
|
id: 'custom_status.expiry_dropdown.date_and_time',
|
||||||
defaultMessage: 'Custom Date and Time',
|
defaultMessage: 'Custom Date and Time',
|
||||||
},
|
},
|
||||||
[CUSTOM_DATE_TIME]: {
|
[CUSTOM_DATE_TIME]: {
|
||||||
id: t('custom_status.expiry_dropdown.date_and_time'),
|
id: 'custom_status.expiry_dropdown.date_and_time',
|
||||||
defaultMessage: 'Custom Date and Time',
|
defaultMessage: 'Custom Date and Time',
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
export enum ClaimErrors {
|
export enum ClaimErrors {
|
||||||
MFA_VALIDATE_TOKEN_AUTHENTICATE = 'mfa.validate_token.authenticate.app_error',
|
MFA_VALIDATE_TOKEN_AUTHENTICATE = 'mfa.validate_token.authenticate.app_error',
|
||||||
ENT_LDAP_LOGIN_USER_NOT_REGISTERED = 'ent.ldap.do_login.user_not_registered.app_error',
|
ENT_LDAP_LOGIN_USER_NOT_REGISTERED = 'ent.ldap.do_login.user_not_registered.app_error',
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage, defineMessage} from 'react-intl';
|
||||||
import type {IntlShape} from 'react-intl';
|
import type {IntlShape, MessageDescriptor} from 'react-intl';
|
||||||
|
|
||||||
import {getModule} from 'module_registry';
|
import {getModule} from 'module_registry';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
import {latinise} from 'utils/latinise';
|
import {latinise} from 'utils/latinise';
|
||||||
import * as TextFormatting from 'utils/text_formatting';
|
import * as TextFormatting from 'utils/text_formatting';
|
||||||
|
|
||||||
@@ -106,18 +105,19 @@ export function getScheme(url: string): string | null {
|
|||||||
return match && match[1];
|
return match && match[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function formattedError(id: string, message: string, intl?: IntlShape): React.ReactElement | string {
|
function formattedError(message: MessageDescriptor, intl?: IntlShape): React.ReactElement | string {
|
||||||
if (intl) {
|
if (intl) {
|
||||||
return intl.formatMessage({id, defaultMessage: message});
|
return intl.formatMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<span key={id}>
|
return (
|
||||||
<FormattedMessage
|
<span key={message.id}>
|
||||||
id={id}
|
<FormattedMessage
|
||||||
defaultMessage={message}
|
{...message}
|
||||||
/>
|
/>
|
||||||
<br/>
|
<br/>
|
||||||
</span>);
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateChannelUrl(url: string, intl?: IntlShape): Array<React.ReactElement | string> {
|
export function validateChannelUrl(url: string, intl?: IntlShape): Array<React.ReactElement | string> {
|
||||||
@@ -134,34 +134,82 @@ export function validateChannelUrl(url: string, intl?: IntlShape): Array<React.R
|
|||||||
|
|
||||||
if (cleanedURL !== url || !urlMatched || urlMatched[0] !== url || isDirectMessageFormat || urlLonger || urlShorter) {
|
if (cleanedURL !== url || !urlMatched || urlMatched[0] !== url || isDirectMessageFormat || urlLonger || urlShorter) {
|
||||||
if (urlLonger) {
|
if (urlLonger) {
|
||||||
errors.push(formattedError(t('change_url.longer'), 'URLs must have at least 2 characters.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.longer',
|
||||||
|
defaultMessage: 'URLs must have at least 2 characters.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlShorter) {
|
if (urlShorter) {
|
||||||
errors.push(formattedError(t('change_url.shorter'), 'URLs must have maximum 64 characters.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.shorter',
|
||||||
|
defaultMessage: 'URLs must have maximum 64 characters.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.match(/[^A-Za-z0-9-_]/)) {
|
if (url.match(/[^A-Za-z0-9-_]/)) {
|
||||||
errors.push(formattedError(t('change_url.noSpecialChars'), 'URLs cannot use special characters.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.noSpecialChars',
|
||||||
|
defaultMessage: 'URLs cannot use special characters.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDirectMessageFormat) {
|
if (isDirectMessageFormat) {
|
||||||
errors.push(formattedError(t('change_url.invalidDirectMessage'), 'User IDs are not allowed in channel URLs.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.invalidDirectMessage',
|
||||||
|
defaultMessage: 'User IDs are not allowed in channel URLs.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const startsWithoutLetter = url.charAt(0) === '-' || url.charAt(0) === '_';
|
const startsWithoutLetter = url.charAt(0) === '-' || url.charAt(0) === '_';
|
||||||
const endsWithoutLetter = url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_');
|
const endsWithoutLetter = url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_');
|
||||||
if (startsWithoutLetter && endsWithoutLetter) {
|
if (startsWithoutLetter && endsWithoutLetter) {
|
||||||
errors.push(formattedError(t('change_url.startAndEndWithLetter'), 'URLs must start and end with a lowercase letter or number.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.startAndEndWithLetter',
|
||||||
|
defaultMessage: 'URLs must start and end with a lowercase letter or number.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
} else if (startsWithoutLetter) {
|
} else if (startsWithoutLetter) {
|
||||||
errors.push(formattedError(t('change_url.startWithLetter'), 'URLs must start with a lowercase letter or number.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.startWithLetter',
|
||||||
|
defaultMessage: 'URLs must start with a lowercase letter or number.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
} else if (endsWithoutLetter) {
|
} else if (endsWithoutLetter) {
|
||||||
errors.push(formattedError(t('change_url.endWithLetter'), 'URLs must end with a lowercase letter or number.', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.endWithLetter',
|
||||||
|
defaultMessage: 'URLs must end with a lowercase letter or number.',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of error we don't detect
|
// In case of error we don't detect
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
errors.push(formattedError(t('change_url.invalidUrl'), 'Invalid URL', intl));
|
errors.push(formattedError(
|
||||||
|
defineMessage({
|
||||||
|
id: 'change_url.invalidUrl',
|
||||||
|
defaultMessage: 'Invalid URL',
|
||||||
|
}),
|
||||||
|
intl,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ import type {TextboxElement} from 'components/textbox';
|
|||||||
import {getHistory} from 'utils/browser_history';
|
import {getHistory} from 'utils/browser_history';
|
||||||
import Constants, {FileTypes, ValidationErrors, A11yCustomEventTypes} from 'utils/constants';
|
import Constants, {FileTypes, ValidationErrors, A11yCustomEventTypes} from 'utils/constants';
|
||||||
import type {A11yFocusEventDetail} from 'utils/constants';
|
import type {A11yFocusEventDetail} from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
|
||||||
import * as Keyboard from 'utils/keyboard';
|
import * as Keyboard from 'utils/keyboard';
|
||||||
import * as UserAgent from 'utils/user_agent';
|
import * as UserAgent from 'utils/user_agent';
|
||||||
|
|
||||||
@@ -1236,7 +1235,8 @@ export function getPasswordConfig(config: Partial<ClientConfig>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isValidPassword(password: string, passwordConfig: ReturnType<typeof getPasswordConfig>, intl?: IntlShape) {
|
export function isValidPassword(password: string, passwordConfig: ReturnType<typeof getPasswordConfig>, intl?: IntlShape) {
|
||||||
let errorId = t('user.settings.security.passwordError');
|
// The translation strings used by this function are defined in admin_console/password_settings
|
||||||
|
let errorId = 'user.settings.security.passwordError';
|
||||||
const telemetryErrorIds = [];
|
const telemetryErrorIds = [];
|
||||||
let valid = true;
|
let valid = true;
|
||||||
const minimumLength = passwordConfig.minimumLength || Constants.MIN_PASSWORD_LENGTH;
|
const minimumLength = passwordConfig.minimumLength || Constants.MIN_PASSWORD_LENGTH;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user