Replace Gfycat with GIPHY in emoji/gif picker (#24236)
* Added the package * design * add styled-component alias * add contrast ratio * added the tab icon * review comments * rev comments * key added * key added * trans * Added giphy sdk test key for playwright tests config --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: maria.nunez <maria.nunez@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8418eefb75
Коммит
bc11b29807
@@ -129,6 +129,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
EnableGifPicker: true,
|
EnableGifPicker: true,
|
||||||
GfycatAPIKey: '2_KtH_W5',
|
GfycatAPIKey: '2_KtH_W5',
|
||||||
GfycatAPISecret: '3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof',
|
GfycatAPISecret: '3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof',
|
||||||
|
GiphySdkKey: 's0glxvzVg9azvPipKxcPLpXV0q1x1fVP',
|
||||||
EnableCustomEmoji: true,
|
EnableCustomEmoji: true,
|
||||||
EnableEmojiPicker: true,
|
EnableEmojiPicker: true,
|
||||||
PostEditTimeLimit: -1,
|
PostEditTimeLimit: -1,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
|||||||
props["EnableGifPicker"] = strconv.FormatBool(*c.ServiceSettings.EnableGifPicker)
|
props["EnableGifPicker"] = strconv.FormatBool(*c.ServiceSettings.EnableGifPicker)
|
||||||
props["GfycatApiKey"] = *c.ServiceSettings.GfycatAPIKey
|
props["GfycatApiKey"] = *c.ServiceSettings.GfycatAPIKey
|
||||||
props["GfycatApiSecret"] = *c.ServiceSettings.GfycatAPISecret
|
props["GfycatApiSecret"] = *c.ServiceSettings.GfycatAPISecret
|
||||||
|
props["GiphySdkKey"] = *c.ServiceSettings.GiphySdkKey
|
||||||
props["MaxFileSize"] = strconv.FormatInt(*c.FileSettings.MaxFileSize, 10)
|
props["MaxFileSize"] = strconv.FormatInt(*c.FileSettings.MaxFileSize, 10)
|
||||||
|
|
||||||
props["MaxNotificationsPerChannel"] = strconv.FormatInt(*c.TeamSettings.MaxNotificationsPerChannel, 10)
|
props["MaxNotificationsPerChannel"] = strconv.FormatInt(*c.TeamSettings.MaxNotificationsPerChannel, 10)
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ const (
|
|||||||
ServiceSettingsDefaultListenAndAddress = ":8065"
|
ServiceSettingsDefaultListenAndAddress = ":8065"
|
||||||
ServiceSettingsDefaultGfycatAPIKey = "2_KtH_W5"
|
ServiceSettingsDefaultGfycatAPIKey = "2_KtH_W5"
|
||||||
ServiceSettingsDefaultGfycatAPISecret = "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof"
|
ServiceSettingsDefaultGfycatAPISecret = "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof"
|
||||||
|
ServiceSettingsDefaultGiphySdkKey = "yaRojIWaxmKhtSMBaT3uLCAHm0kpMLKw"
|
||||||
|
ServiceSettingsDefaultGiphySdkKeyTest = "s0glxvzVg9azvPipKxcPLpXV0q1x1fVP"
|
||||||
ServiceSettingsDefaultDeveloperFlags = ""
|
ServiceSettingsDefaultDeveloperFlags = ""
|
||||||
|
|
||||||
TeamSettingsDefaultSiteName = "Mattermost"
|
TeamSettingsDefaultSiteName = "Mattermost"
|
||||||
@@ -348,6 +350,7 @@ type ServiceSettings struct {
|
|||||||
EnableGifPicker *bool `access:"integrations_gif"`
|
EnableGifPicker *bool `access:"integrations_gif"`
|
||||||
GfycatAPIKey *string `access:"integrations_gif"`
|
GfycatAPIKey *string `access:"integrations_gif"`
|
||||||
GfycatAPISecret *string `access:"integrations_gif"`
|
GfycatAPISecret *string `access:"integrations_gif"`
|
||||||
|
GiphySdkKey *string `access:"integrations_gif"`
|
||||||
EnableCustomEmoji *bool `access:"site_emoji"`
|
EnableCustomEmoji *bool `access:"site_emoji"`
|
||||||
EnableEmojiPicker *bool `access:"site_emoji"`
|
EnableEmojiPicker *bool `access:"site_emoji"`
|
||||||
PostEditTimeLimit *int `access:"user_management_permissions"`
|
PostEditTimeLimit *int `access:"user_management_permissions"`
|
||||||
@@ -740,6 +743,15 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
|||||||
s.GfycatAPISecret = NewString(ServiceSettingsDefaultGfycatAPISecret)
|
s.GfycatAPISecret = NewString(ServiceSettingsDefaultGfycatAPISecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.GiphySdkKey == nil {
|
||||||
|
switch GetServiceEnvironment() {
|
||||||
|
case ServiceEnvironmentProduction:
|
||||||
|
s.GiphySdkKey = NewString(ServiceSettingsDefaultGiphySdkKey)
|
||||||
|
case ServiceEnvironmentTest, ServiceEnvironmentDev:
|
||||||
|
s.GiphySdkKey = NewString(ServiceSettingsDefaultGiphySdkKeyTest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.ExperimentalEnableAuthenticationTransfer == nil {
|
if s.ExperimentalEnableAuthenticationTransfer == nil {
|
||||||
s.ExperimentalEnableAuthenticationTransfer = NewBool(true)
|
s.ExperimentalEnableAuthenticationTransfer = NewBool(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react-dom": "1.0.0",
|
"@floating-ui/react-dom": "1.0.0",
|
||||||
"@floating-ui/react-dom-interactions": "0.10.3",
|
"@floating-ui/react-dom-interactions": "0.10.3",
|
||||||
|
"@giphy/js-fetch-api": "5.1.0",
|
||||||
|
"@giphy/react-components": "8.1.0",
|
||||||
"@guyplusplus/turndown-plugin-gfm": "1.0.7",
|
"@guyplusplus/turndown-plugin-gfm": "1.0.7",
|
||||||
"@mattermost/client": "*",
|
"@mattermost/client": "*",
|
||||||
"@mattermost/compass-components": "^0.2.12",
|
"@mattermost/compass-components": "^0.2.12",
|
||||||
|
|||||||
@@ -6100,36 +6100,7 @@ const AdminDefinition = {
|
|||||||
label: t('admin.customization.enableGifPickerTitle'),
|
label: t('admin.customization.enableGifPickerTitle'),
|
||||||
label_default: 'Enable GIF Picker:',
|
label_default: 'Enable GIF Picker:',
|
||||||
help_text: t('admin.customization.enableGifPickerDesc'),
|
help_text: t('admin.customization.enableGifPickerDesc'),
|
||||||
help_text_default: 'Allow users to select GIFs from the emoji picker via a Gfycat integration.',
|
help_text_default: 'Allows users to select GIFs from the emoji picker.',
|
||||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: Constants.SettingsTypes.TYPE_TEXT,
|
|
||||||
key: 'ServiceSettings.GfycatAPIKey',
|
|
||||||
label: t('admin.customization.gfycatApiKey'),
|
|
||||||
label_default: 'Gfycat API Key:',
|
|
||||||
help_text: t('admin.customization.gfycatApiKeyDescription'),
|
|
||||||
help_text_default: 'Request an API key at <link>https://developers.gfycat.com/signup/#</link>. Enter the client ID you receive via email to this field. When blank, uses the default API key provided by Gfycat.',
|
|
||||||
help_text_markdown: false,
|
|
||||||
help_text_values: {
|
|
||||||
link: (msg) => (
|
|
||||||
<ExternalLink
|
|
||||||
location='admin_console'
|
|
||||||
href='https://developers.gfycat.com/signup/#'
|
|
||||||
>
|
|
||||||
{msg}
|
|
||||||
</ExternalLink>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: Constants.SettingsTypes.TYPE_TEXT,
|
|
||||||
key: 'ServiceSettings.GfycatAPISecret',
|
|
||||||
label: t('admin.customization.gfycatApiSecret'),
|
|
||||||
label_default: 'Gfycat API Secret:',
|
|
||||||
help_text: t('admin.customization.gfycatApiSecretDescription'),
|
|
||||||
help_text_default: 'The API secret generated by Gfycat for your API key. When blank, uses the default API secret provided by Gfycat.',
|
|
||||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
|
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React, {useRef, useState, useEffect, useCallback, memo, useMemo} from 're
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import type {FixedSizeList} from 'react-window';
|
import type {FixedSizeList} from 'react-window';
|
||||||
import type InfiniteLoader from 'react-window-infinite-loader';
|
import type InfiniteLoader from 'react-window-infinite-loader';
|
||||||
import {throttle} from 'lodash';
|
import throttle from 'lodash/throttle';
|
||||||
|
|
||||||
import {Emoji, EmojiCategory} from '@mattermost/types/emojis';
|
import {Emoji, EmojiCategory} from '@mattermost/types/emojis';
|
||||||
import {isSystemEmoji} from 'mattermost-redux/utils/emoji_utils';
|
import {isSystemEmoji} from 'mattermost-redux/utils/emoji_utils';
|
||||||
@@ -34,7 +34,6 @@ import type {PropsFromRedux} from './index';
|
|||||||
|
|
||||||
interface Props extends PropsFromRedux {
|
interface Props extends PropsFromRedux {
|
||||||
filter: string;
|
filter: string;
|
||||||
visible: boolean;
|
|
||||||
onEmojiClick: (emoji: Emoji) => void;
|
onEmojiClick: (emoji: Emoji) => void;
|
||||||
handleFilterChange: (filter: string) => void;
|
handleFilterChange: (filter: string) => void;
|
||||||
handleEmojiPickerClose: () => void;
|
handleEmojiPickerClose: () => void;
|
||||||
@@ -42,7 +41,6 @@ interface Props extends PropsFromRedux {
|
|||||||
|
|
||||||
const EmojiPicker = ({
|
const EmojiPicker = ({
|
||||||
filter,
|
filter,
|
||||||
visible,
|
|
||||||
onEmojiClick,
|
onEmojiClick,
|
||||||
handleFilterChange,
|
handleFilterChange,
|
||||||
handleEmojiPickerClose,
|
handleEmojiPickerClose,
|
||||||
@@ -125,10 +123,9 @@ const EmojiPicker = ({
|
|||||||
throttledSearchCustomEmoji.current(filter, customEmojisEnabled);
|
throttledSearchCustomEmoji.current(filter, customEmojisEnabled);
|
||||||
}, [filter, shouldRunCreateCategoryAndEmojiRows.current, customEmojisEnabled]);
|
}, [filter, shouldRunCreateCategoryAndEmojiRows.current, customEmojisEnabled]);
|
||||||
|
|
||||||
// Hack for getting focus on search input when tab changes to emoji from gifs
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
searchInputRef.current?.focus();
|
searchInputRef.current?.focus();
|
||||||
}, [visible]);
|
}, []);
|
||||||
|
|
||||||
// clear out the active category on search input
|
// clear out the active category on search input
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
// 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 React, {CSSProperties, PureComponent} from 'react';
|
import React, {CSSProperties, PureComponent, createRef, RefObject} from 'react';
|
||||||
import {Tab, Tabs} from 'react-bootstrap';
|
import {Tab, Tabs} from 'react-bootstrap';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import EmojiIcon from 'components/widgets/icons/emoji_icon';
|
|
||||||
import GfycatIcon from 'components/widgets/icons/gfycat_icon';
|
|
||||||
import {makeAsyncComponent} from 'components/async_load';
|
import {makeAsyncComponent} from 'components/async_load';
|
||||||
import EmojiPicker from 'components/emoji_picker';
|
import EmojiPicker from 'components/emoji_picker';
|
||||||
import EmojiPickerHeader from 'components/emoji_picker/components/emoji_picker_header';
|
import EmojiPickerHeader from 'components/emoji_picker/components/emoji_picker_header';
|
||||||
|
import EmojiIcon from 'components/widgets/icons/emoji_icon';
|
||||||
|
import GifIcon from 'components/widgets/icons/giphy_icon';
|
||||||
|
|
||||||
import {Emoji} from '@mattermost/types/emojis';
|
import {Emoji} from '@mattermost/types/emojis';
|
||||||
|
|
||||||
@@ -32,6 +33,8 @@ type State = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
||||||
|
private rootPickerNodeRef: RefObject<HTMLDivElement>;
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
rightOffset: 0,
|
rightOffset: 0,
|
||||||
topOffset: 0,
|
topOffset: 0,
|
||||||
@@ -45,20 +48,10 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
|||||||
emojiTabVisible: true,
|
emojiTabVisible: true,
|
||||||
filter: '',
|
filter: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.rootPickerNodeRef = createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEnterEmojiTab = () => {
|
|
||||||
this.setState({
|
|
||||||
emojiTabVisible: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleExitEmojiTab = () => {
|
|
||||||
this.setState({
|
|
||||||
emojiTabVisible: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEmojiPickerClose = () => {
|
handleEmojiPickerClose = () => {
|
||||||
this.props.onEmojiClose();
|
this.props.onEmojiClose();
|
||||||
};
|
};
|
||||||
@@ -67,6 +60,10 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
|||||||
this.setState({filter});
|
this.setState({filter});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getRootPickerNode = () => {
|
||||||
|
return this.rootPickerNodeRef.current;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let pickerStyle;
|
let pickerStyle;
|
||||||
if (this.props.style && !(this.props.style.left === 0 && this.props.style.top === 0)) {
|
if (this.props.style && !(this.props.style.left === 0 && this.props.style.top === 0)) {
|
||||||
@@ -99,33 +96,38 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
if (this.props.enableGifPicker && typeof this.props.onGifClick != 'undefined') {
|
if (this.props.enableGifPicker && typeof this.props.onGifClick != 'undefined') {
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<div
|
||||||
defaultActiveKey={1}
|
ref={this.rootPickerNodeRef}
|
||||||
id='emoji-picker-tabs'
|
|
||||||
style={pickerStyle}
|
style={pickerStyle}
|
||||||
className={pickerClass}
|
className={pickerClass}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
id='emoji-picker-tabs'
|
||||||
|
defaultActiveKey={1}
|
||||||
justified={true}
|
justified={true}
|
||||||
|
mountOnEnter={true}
|
||||||
|
unmountOnExit={true}
|
||||||
>
|
>
|
||||||
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
|
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey={1}
|
eventKey={1}
|
||||||
onEnter={this.handleEnterEmojiTab}
|
|
||||||
onExit={this.handleExitEmojiTab}
|
|
||||||
title={
|
title={
|
||||||
<div className={'custom-emoji-tab__icon__text'}>
|
<div className={'custom-emoji-tab__icon__text'}>
|
||||||
<EmojiIcon
|
<EmojiIcon
|
||||||
className='custom-emoji-tab__icon'
|
className='custom-emoji-tab__icon'
|
||||||
|
aria-hidden={true}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='emoji_gif_picker.tabs.emojis'
|
||||||
|
defaultMessage='Emojis'
|
||||||
/>
|
/>
|
||||||
<div>
|
|
||||||
{'Emojis'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
unmountOnExit={true}
|
||||||
tabClassName={'custom-emoji-tab'}
|
tabClassName={'custom-emoji-tab'}
|
||||||
>
|
>
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
filter={this.state.filter}
|
filter={this.state.filter}
|
||||||
visible={this.state.emojiTabVisible}
|
|
||||||
onEmojiClick={this.props.onEmojiClick}
|
onEmojiClick={this.props.onEmojiClick}
|
||||||
handleFilterChange={this.handleFilterChange}
|
handleFilterChange={this.handleFilterChange}
|
||||||
handleEmojiPickerClose={this.handleEmojiPickerClose}
|
handleEmojiPickerClose={this.handleEmojiPickerClose}
|
||||||
@@ -133,17 +135,30 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
|||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey={2}
|
eventKey={2}
|
||||||
title={<GfycatIcon/>}
|
title={
|
||||||
|
<div className={'custom-emoji-tab__icon__text'}>
|
||||||
|
<GifIcon
|
||||||
|
className='custom-emoji-tab__icon'
|
||||||
|
aria-hidden={true}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='emoji_gif_picker.tabs.gifs'
|
||||||
|
defaultMessage='GIFs'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
unmountOnExit={true}
|
unmountOnExit={true}
|
||||||
tabClassName={'custom-emoji-tab'}
|
tabClassName={'custom-emoji-tab'}
|
||||||
>
|
>
|
||||||
<GifPicker
|
<GifPicker
|
||||||
|
filter={this.state.filter}
|
||||||
|
getRootPickerNode={this.getRootPickerNode}
|
||||||
onGifClick={this.props.onGifClick}
|
onGifClick={this.props.onGifClick}
|
||||||
defaultSearchText={this.state.filter}
|
handleFilterChange={this.handleFilterChange}
|
||||||
handleSearchTextChange={this.handleFilterChange}
|
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +171,6 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
|
|||||||
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
|
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
filter={this.state.filter}
|
filter={this.state.filter}
|
||||||
visible={this.state.emojiTabVisible}
|
|
||||||
onEmojiClick={this.props.onEmojiClick}
|
onEmojiClick={this.props.onEmojiClick}
|
||||||
handleFilterChange={this.handleFilterChange}
|
handleFilterChange={this.handleFilterChange}
|
||||||
handleEmojiPickerClose={this.handleEmojiPickerClose}
|
handleEmojiPickerClose={this.handleEmojiPickerClose}
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import {connect} from 'react-redux';
|
|||||||
import {saveAppProps} from 'mattermost-redux/actions/gifs';
|
import {saveAppProps} from 'mattermost-redux/actions/gifs';
|
||||||
|
|
||||||
import Header from 'components/gif_picker/components/Header';
|
import Header from 'components/gif_picker/components/Header';
|
||||||
import {appProps} from 'components/gif_picker/gif_picker';
|
|
||||||
|
|
||||||
const mapDispatchToProps = ({
|
const mapDispatchToProps = ({
|
||||||
saveAppProps,
|
saveAppProps,
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appProps: typeof appProps;
|
appProps: any;
|
||||||
action: string;
|
action: string;
|
||||||
onCategories: () => void;
|
onCategories: () => void;
|
||||||
onSearch?: () => void;
|
onSearch?: () => void;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {trackEvent} from 'actions/telemetry_actions.jsx';
|
|||||||
import {getImageSrc} from 'utils/post_utils';
|
import {getImageSrc} from 'utils/post_utils';
|
||||||
|
|
||||||
import InfiniteScroll from 'components/gif_picker/components/InfiniteScroll';
|
import InfiniteScroll from 'components/gif_picker/components/InfiniteScroll';
|
||||||
import {appProps} from 'components/gif_picker/gif_picker';
|
|
||||||
|
|
||||||
import './Categories.scss';
|
import './Categories.scss';
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ const mapDispatchToProps = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appProps: typeof appProps;
|
appProps: any;
|
||||||
gifs?: Record<string, GfycatAPIItem>;
|
gifs?: Record<string, GfycatAPIItem>;
|
||||||
hasMore?: boolean;
|
hasMore?: boolean;
|
||||||
onSearch: () => void;
|
onSearch: () => void;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import GifTrendingIcon from 'components/widgets/icons/gif_trending_icon';
|
|||||||
import GifReactionsIcon from 'components/widgets/icons/gif_reactions_icon';
|
import GifReactionsIcon from 'components/widgets/icons/gif_reactions_icon';
|
||||||
import './Header.scss';
|
import './Header.scss';
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
import {appProps} from 'components/gif_picker/gif_picker';
|
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState) {
|
||||||
return {
|
return {
|
||||||
@@ -57,7 +56,7 @@ const getStyle = makeStyleFromTheme((theme) => {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
action: string;
|
action: string;
|
||||||
appProps: typeof appProps;
|
appProps: any;
|
||||||
saveSearchBarText: (searchBarText: string) => void;
|
saveSearchBarText: (searchBarText: string) => void;
|
||||||
searchTextUpdate: (searchText: string) => void;
|
searchTextUpdate: (searchText: string) => void;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
@@ -102,7 +101,7 @@ export class Header extends PureComponent<Props, State> {
|
|||||||
renderTabs(props: Props, style: Style) {
|
renderTabs(props: Props, style: Style) {
|
||||||
const {appProps, onTrending, onCategories} = props;
|
const {appProps, onTrending, onCategories} = props;
|
||||||
const {header} = appProps;
|
const {header} = appProps;
|
||||||
return header.tabs.map((tab, index) => {
|
return header.tabs.map((tab: any, index: any) => {
|
||||||
let link;
|
let link;
|
||||||
if (tab === constants.Tab.TRENDING) {
|
if (tab === constants.Tab.TRENDING) {
|
||||||
link = this.renderTab({name: 'trending', callback: onTrending, Icon: GifTrendingIcon, index, style});
|
link = this.renderTab({name: 'trending', callback: onTrending, Icon: GifTrendingIcon, index, style});
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {GfycatAPIItem} from '@mattermost/types/gifs';
|
|||||||
import {searchIfNeededInitial, searchGfycat} from 'mattermost-redux/actions/gifs';
|
import {searchIfNeededInitial, searchGfycat} from 'mattermost-redux/actions/gifs';
|
||||||
|
|
||||||
import SearchGrid from 'components/gif_picker/components/SearchGrid';
|
import SearchGrid from 'components/gif_picker/components/SearchGrid';
|
||||||
import {appProps} from 'components/gif_picker/gif_picker';
|
|
||||||
|
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ const mapDispatchToProps = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appProps: typeof appProps;
|
appProps: any;
|
||||||
onCategories?: () => void;
|
onCategories?: () => void;
|
||||||
handleItemClick: (gif: GfycatAPIItem) => void;
|
handleItemClick: (gif: GfycatAPIItem) => void;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
} from 'mattermost-redux/actions/gifs';
|
} from 'mattermost-redux/actions/gifs';
|
||||||
|
|
||||||
import SearchGrid from 'components/gif_picker/components/SearchGrid';
|
import SearchGrid from 'components/gif_picker/components/SearchGrid';
|
||||||
import {appProps} from 'components/gif_picker/gif_picker';
|
|
||||||
|
|
||||||
const mapDispatchToProps = ({
|
const mapDispatchToProps = ({
|
||||||
searchCategory,
|
searchCategory,
|
||||||
@@ -22,7 +21,7 @@ const mapDispatchToProps = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appProps: typeof appProps;
|
appProps: any;
|
||||||
searchIfNeededInitial: (searchText: string) => void;
|
searchIfNeededInitial: (searchText: string) => void;
|
||||||
onCategories: () => void;
|
onCategories: () => void;
|
||||||
saveSearchScrollPosition: (scrollPosition: number) => void;
|
saveSearchScrollPosition: (scrollPosition: number) => void;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {memo, useCallback} from 'react';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import {EmojiVariationsListProps, Grid} from '@giphy/react-components';
|
||||||
|
import {GifsResult} from '@giphy/js-fetch-api';
|
||||||
|
|
||||||
|
import {getGiphyFetchInstance} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
|
import NoResultsIndicator from 'components/no_results_indicator';
|
||||||
|
import {NoResultsVariant} from 'components/no_results_indicator/types';
|
||||||
|
|
||||||
|
const GUTTER_BETWEEN_GIFS = 8;
|
||||||
|
const NUM_OF_GIFS_COLUMNS = 2;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width: number;
|
||||||
|
filter: string;
|
||||||
|
onClick: EmojiVariationsListProps['onGifClick'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function GifPickerItems(props: Props) {
|
||||||
|
const giphyFetch = useSelector(getGiphyFetchInstance);
|
||||||
|
|
||||||
|
const fetchGifs = useCallback(async (offset: number) => {
|
||||||
|
if (!giphyFetch) {
|
||||||
|
return {} as GifsResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We dont have to throttled the fetching as the library does it for us
|
||||||
|
if (props.filter.length > 0) {
|
||||||
|
const filteredResult = await giphyFetch.search(props.filter, {offset, limit: 10});
|
||||||
|
return filteredResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trendingResult = await giphyFetch.trending({offset, limit: 10});
|
||||||
|
return trendingResult;
|
||||||
|
}, [props.filter, giphyFetch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='emoji-picker__items gif-picker__items'>
|
||||||
|
<Grid
|
||||||
|
key={props.filter.length === 0 ? 'trending' : props.filter}
|
||||||
|
columns={NUM_OF_GIFS_COLUMNS}
|
||||||
|
gutter={GUTTER_BETWEEN_GIFS}
|
||||||
|
hideAttribution={true}
|
||||||
|
width={props.width}
|
||||||
|
noResultsMessage={
|
||||||
|
<NoResultsIndicator
|
||||||
|
variant={NoResultsVariant.ChannelSearch}
|
||||||
|
titleValues={{channelName: `"${props.filter}"`}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fetchGifs={fetchGifs}
|
||||||
|
onGifClick={props.onClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(GifPickerItems);
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {ChangeEvent, memo, useCallback, useMemo} from 'react';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
|
|
||||||
|
import giphyWhiteImage from 'images/gif_picker/powered-by-giphy-white.png';
|
||||||
|
import giphyBlackImage from 'images/gif_picker/powered-by-giphy-black.png';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GifPickerSearch(props: Props) {
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
|
||||||
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
|
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// remove trailing and leading colons
|
||||||
|
const value = event.target?.value?.replace(/^:|:$/g, '') ?? '';
|
||||||
|
props.onChange(value);
|
||||||
|
}, [props.onChange]);
|
||||||
|
|
||||||
|
const shouldUseWhiteLogo = useMemo(() => {
|
||||||
|
const WHITE_COLOR = '#FFFFFF';
|
||||||
|
const BLACK_COLOR = '#000000';
|
||||||
|
|
||||||
|
const mostReadableColor = tinycolor.mostReadable(theme.centerChannelBg, [WHITE_COLOR, BLACK_COLOR], {includeFallbackColors: false});
|
||||||
|
|
||||||
|
if (mostReadableColor.isLight()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [theme.centerChannelBg]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='emoji-picker__search-container'>
|
||||||
|
<div className='emoji-picker__text-container'>
|
||||||
|
<span className='icon-magnify icon emoji-picker__search-icon'/>
|
||||||
|
<input
|
||||||
|
id='emojiPickerSearch'
|
||||||
|
className='emoji-picker__search'
|
||||||
|
aria-label={formatMessage({id: 'gif_picker.input.label', defaultMessage: 'Search for GIFs'})}
|
||||||
|
placeholder={formatMessage({id: 'gif_picker.input.placeholder', defaultMessage: 'Search GIPHY'})}
|
||||||
|
type='text'
|
||||||
|
autoFocus={true}
|
||||||
|
autoComplete='off'
|
||||||
|
onChange={handleChange}
|
||||||
|
value={props.value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='gif-attribution'>
|
||||||
|
<img
|
||||||
|
src={shouldUseWhiteLogo ? giphyWhiteImage : giphyBlackImage}
|
||||||
|
alt={formatMessage({id: 'gif_picker.attribution.alt', defaultMessage: 'Powered by GIPHY'})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(GifPickerSearch);
|
||||||
@@ -1,90 +1,48 @@
|
|||||||
// 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 React, {useState} from 'react';
|
import React, {SyntheticEvent, useCallback, useMemo} from 'react';
|
||||||
|
import {IGif} from '@giphy/js-types';
|
||||||
|
|
||||||
import {GifsAppState, GfycatAPIItem} from '@mattermost/types/gifs';
|
import GifPickerSearch from './components/gif_picker_search';
|
||||||
|
import GifPickerItems from './components/gif_picker_items';
|
||||||
|
|
||||||
import App from 'components/gif_picker/components/App';
|
const GIF_DEFAULT_WIDTH = 350;
|
||||||
import Categories from 'components/gif_picker/components/Categories';
|
const GIF_MARGIN_ENDS = 12;
|
||||||
import Search from 'components/gif_picker/components/Search';
|
|
||||||
import Trending from 'components/gif_picker/components/Trending';
|
|
||||||
import constants from 'components/gif_picker/utils/constants';
|
|
||||||
|
|
||||||
export const appProps: GifsAppState = {
|
|
||||||
appName: constants.appName.mattermost,
|
|
||||||
basePath: '/mattermost',
|
|
||||||
itemTapType: constants.ItemTapAction.SHARE,
|
|
||||||
appClassName: 'gfycat',
|
|
||||||
shareEvent: 'shareMattermost',
|
|
||||||
appId: 'mattermostwebviews',
|
|
||||||
enableHistory: true,
|
|
||||||
header: {
|
|
||||||
tabs: [constants.Tab.TRENDING, constants.Tab.REACTIONS],
|
|
||||||
displayText: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
filter: string;
|
||||||
onGifClick?: (gif: string) => void;
|
onGifClick?: (gif: string) => void;
|
||||||
defaultSearchText?: string;
|
handleFilterChange: (filter: string) => void;
|
||||||
handleSearchTextChange: (text: string) => void;
|
getRootPickerNode: () => HTMLDivElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GifPicker = (props: Props) => {
|
const GifPicker = (props: Props) => {
|
||||||
const [action, setAction] = useState(props.defaultSearchText ? 'search' : 'trending');
|
const handleItemClick = useCallback((gif: IGif, event: SyntheticEvent<HTMLElement, Event>) => {
|
||||||
|
if (props.onGifClick) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
const handleTrending = () => setAction('trending');
|
const imageWithMarkdown = ``;
|
||||||
const handleCategories = () => setAction('reactions');
|
props.onGifClick(imageWithMarkdown);
|
||||||
const handleSearch = () => setAction('search');
|
|
||||||
|
|
||||||
const handleItemClick = (gif: GfycatAPIItem) => {
|
|
||||||
props.onGifClick?.('![' + gif.title?.replace(/]|\[/g, '\\$&') + '](' + gif.max5mbGif + ')');
|
|
||||||
};
|
|
||||||
|
|
||||||
let component;
|
|
||||||
switch (action) {
|
|
||||||
case 'reactions':
|
|
||||||
component = (
|
|
||||||
<Categories
|
|
||||||
appProps={appProps}
|
|
||||||
onTrending={handleTrending}
|
|
||||||
onSearch={handleSearch}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'search':
|
|
||||||
component = (
|
|
||||||
<Search
|
|
||||||
appProps={appProps}
|
|
||||||
onCategories={handleCategories}
|
|
||||||
handleItemClick={handleItemClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'trending':
|
|
||||||
component = (
|
|
||||||
<Trending
|
|
||||||
appProps={appProps}
|
|
||||||
onCategories={handleCategories}
|
|
||||||
handleItemClick={handleItemClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}, [props.onGifClick]);
|
||||||
|
|
||||||
|
const pickerWidth = useMemo(() => {
|
||||||
|
const pickerWidth = props.getRootPickerNode?.()?.getBoundingClientRect()?.width ?? GIF_DEFAULT_WIDTH;
|
||||||
|
return (pickerWidth - (2 * GIF_MARGIN_ENDS));
|
||||||
|
}, [props.getRootPickerNode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<App
|
<GifPickerSearch
|
||||||
appProps={appProps}
|
value={props.filter}
|
||||||
action={action}
|
onChange={props.handleFilterChange}
|
||||||
onTrending={handleTrending}
|
/>
|
||||||
onCategories={handleCategories}
|
<GifPickerItems
|
||||||
onSearch={handleSearch}
|
width={pickerWidth}
|
||||||
defaultSearchText={props.defaultSearchText}
|
filter={props.filter}
|
||||||
handleSearchTextChange={props.handleSearchTextChange}
|
onClick={handleItemClick}
|
||||||
>
|
/>
|
||||||
{component}
|
|
||||||
</App>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
28
webapp/channels/src/components/widgets/icons/giphy_icon.tsx
Обычный файл
28
webapp/channels/src/components/widgets/icons/giphy_icon.tsx
Обычный файл
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {HTMLAttributes} from 'react';
|
||||||
|
|
||||||
|
function GiphyIcon(props: HTMLAttributes<HTMLSpanElement>) {
|
||||||
|
return (
|
||||||
|
<span {...props}>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
width='16px'
|
||||||
|
height='16px'
|
||||||
|
viewBox='0 0 20 20'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d='M16 10V18H4V2H7.73654L9.73654 0H2V20H18V8L16 10Z'
|
||||||
|
fill='inherit'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d='M11 0H13.3333V2.33325H15.6667V4.66675H18V7.00008H11V0Z'
|
||||||
|
fill='inherit'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GiphyIcon;
|
||||||
@@ -653,7 +653,7 @@
|
|||||||
"admin.customization.enableCustomEmojiTitle": "Enable Custom Emoji:",
|
"admin.customization.enableCustomEmojiTitle": "Enable Custom Emoji:",
|
||||||
"admin.customization.enableEmojiPickerDesc": "The emoji picker allows users to select emoji to add as reactions or use in messages. Enabling the emoji picker with a large number of custom emoji may slow down performance.",
|
"admin.customization.enableEmojiPickerDesc": "The emoji picker allows users to select emoji to add as reactions or use in messages. Enabling the emoji picker with a large number of custom emoji may slow down performance.",
|
||||||
"admin.customization.enableEmojiPickerTitle": "Enable Emoji Picker:",
|
"admin.customization.enableEmojiPickerTitle": "Enable Emoji Picker:",
|
||||||
"admin.customization.enableGifPickerDesc": "Allow users to select GIFs from the emoji picker via a Gfycat integration.",
|
"admin.customization.enableGifPickerDesc": "Allows users to select GIFs from the emoji picker.",
|
||||||
"admin.customization.enableGifPickerTitle": "Enable GIF Picker:",
|
"admin.customization.enableGifPickerTitle": "Enable GIF Picker:",
|
||||||
"admin.customization.enableInlineLatexDesc": "Enable rendering of inline Latex code. If false, Latex can only be rendered in a code block using syntax highlighting. Please review our <link>documentation</link> for details about text formatting.",
|
"admin.customization.enableInlineLatexDesc": "Enable rendering of inline Latex code. If false, Latex can only be rendered in a code block using syntax highlighting. Please review our <link>documentation</link> for details about text formatting.",
|
||||||
"admin.customization.enableInlineLatexTitle": "Enable Inline Latex Rendering:",
|
"admin.customization.enableInlineLatexTitle": "Enable Inline Latex Rendering:",
|
||||||
@@ -665,10 +665,6 @@
|
|||||||
"admin.customization.enablePermalinkPreviewsTitle": "Enable message link previews:",
|
"admin.customization.enablePermalinkPreviewsTitle": "Enable message link previews:",
|
||||||
"admin.customization.enableSVGsDesc": "Enable previews for SVG file attachments and allow them to appear in messages.\n\nEnabling SVGs is not recommended in environments where not all users are trusted.",
|
"admin.customization.enableSVGsDesc": "Enable previews for SVG file attachments and allow them to appear in messages.\n\nEnabling SVGs is not recommended in environments where not all users are trusted.",
|
||||||
"admin.customization.enableSVGsTitle": "Enable SVGs:",
|
"admin.customization.enableSVGsTitle": "Enable SVGs:",
|
||||||
"admin.customization.gfycatApiKey": "Gfycat API Key:",
|
|
||||||
"admin.customization.gfycatApiKeyDescription": "Request an API key at <link>https://developers.gfycat.com/signup/#</link>. Enter the client ID you receive via email to this field. When blank, uses the default API key provided by Gfycat.",
|
|
||||||
"admin.customization.gfycatApiSecret": "Gfycat API Secret:",
|
|
||||||
"admin.customization.gfycatApiSecretDescription": "The API secret generated by Gfycat for your API key. When blank, uses the default API secret provided by Gfycat.",
|
|
||||||
"admin.customization.iosAppDownloadLinkDesc": "Add a link to download the iOS app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.",
|
"admin.customization.iosAppDownloadLinkDesc": "Add a link to download the iOS app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.",
|
||||||
"admin.customization.iosAppDownloadLinkTitle": "iOS App Download Link:",
|
"admin.customization.iosAppDownloadLinkTitle": "iOS App Download Link:",
|
||||||
"admin.customization.restrictLinkPreviewsDesc": "Link previews and image link previews will not be shown for the above list of comma-separated domains.",
|
"admin.customization.restrictLinkPreviewsDesc": "Link previews and image link previews will not be shown for the above list of comma-separated domains.",
|
||||||
@@ -3354,6 +3350,8 @@
|
|||||||
"email_verify.return": "Return to log in",
|
"email_verify.return": "Return to log in",
|
||||||
"email_verify.sending": "Sending email…",
|
"email_verify.sending": "Sending email…",
|
||||||
"email_verify.sent": "Verification email sent",
|
"email_verify.sent": "Verification email sent",
|
||||||
|
"emoji_gif_picker.tabs.emojis": "Emojis",
|
||||||
|
"emoji_gif_picker.tabs.gifs": "GIFs",
|
||||||
"emoji_list.actions": "Actions",
|
"emoji_list.actions": "Actions",
|
||||||
"emoji_list.add": "Add Custom Emoji",
|
"emoji_list.add": "Add Custom Emoji",
|
||||||
"emoji_list.creator": "Creator",
|
"emoji_list.creator": "Creator",
|
||||||
@@ -3618,7 +3616,10 @@
|
|||||||
"get_link.copy": "Copy Link",
|
"get_link.copy": "Copy Link",
|
||||||
"get_public_link_modal.help": "The link below allows anyone to see this file without being registered on this server.",
|
"get_public_link_modal.help": "The link below allows anyone to see this file without being registered on this server.",
|
||||||
"get_public_link_modal.title": "Copy Public Link",
|
"get_public_link_modal.title": "Copy Public Link",
|
||||||
|
"gif_picker.attribution.alt": "Powered by GIPHY",
|
||||||
"gif_picker.gfycat": "Search Gfycat",
|
"gif_picker.gfycat": "Search Gfycat",
|
||||||
|
"gif_picker.input.label": "Search for GIFs",
|
||||||
|
"gif_picker.input.placeholder": "Search GIPHY",
|
||||||
"global_header.productSettings": "Settings",
|
"global_header.productSettings": "Settings",
|
||||||
"global_header.productSwitchMenu": "Product switch menu",
|
"global_header.productSwitchMenu": "Product switch menu",
|
||||||
"globalThreads.heading": "Followed threads",
|
"globalThreads.heading": "Followed threads",
|
||||||
|
|||||||
Двоичные данные
webapp/channels/src/images/gif_picker/powered-by-giphy-black.png
Обычный файл
Двоичные данные
webapp/channels/src/images/gif_picker/powered-by-giphy-black.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
webapp/channels/src/images/gif_picker/powered-by-giphy-white.png
Обычный файл
Двоичные данные
webapp/channels/src/images/gif_picker/powered-by-giphy-white.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 1.4 KiB |
@@ -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 {GiphyFetch} from '@giphy/js-fetch-api';
|
||||||
|
|
||||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||||
import {General} from 'mattermost-redux/constants';
|
import {General} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
@@ -112,3 +114,16 @@ export const isMarketplaceEnabled: (state: GlobalState) => boolean = createSelec
|
|||||||
return config.PluginsEnabled === 'true' && config.EnableMarketplace === 'true';
|
return config.PluginsEnabled === 'true' && config.EnableMarketplace === 'true';
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getGiphyFetchInstance: (state: GlobalState) => GiphyFetch | null = createSelector(
|
||||||
|
'getGiphyFetchInstance',
|
||||||
|
(state) => getConfig(state).GiphySdkKey,
|
||||||
|
(giphySdkKey) => {
|
||||||
|
if (giphySdkKey) {
|
||||||
|
const giphyFetch = new GiphyFetch(giphySdkKey);
|
||||||
|
return giphyFetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -54,6 +54,8 @@
|
|||||||
.custom-emoji-tab__icon__text {
|
.custom-emoji-tab__icon__text {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-emoji-tab {
|
.custom-emoji-tab {
|
||||||
|
|||||||
@@ -186,7 +186,7 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
margin: 8px 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
|
|
||||||
.emoji-picker__category {
|
.emoji-picker__category {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-color: transparent !important;
|
border-color: transparent !important;
|
||||||
margin: 12px 12px 0 12px;
|
margin: 12px;
|
||||||
|
|
||||||
.skin-tones-animation {
|
.skin-tones-animation {
|
||||||
&-enter {
|
&-enter {
|
||||||
@@ -296,6 +296,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--center-channel-bg);
|
background: var(--center-channel-bg);
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -351,10 +352,9 @@
|
|||||||
.emoji-picker__text-container {
|
.emoji-picker__text-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
width: 284px;
|
width: 100%;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.24);
|
border: 1px solid rgba(var(--center-channel-color-rgb), 0.24);
|
||||||
margin-right: 8px;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
@@ -456,7 +456,22 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gif-attribution {
|
||||||
|
padding: 5px 0 5px 5px;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
|
||||||
|
& > img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$emoji-picker-footer-margin: 12px;
|
||||||
|
$emoji-footer-border-width: 1px;
|
||||||
|
$emoji-half-height: 33px;
|
||||||
|
$emoji-footer-height: $emoji-footer-border-width + $emoji-half-height + $emoji-picker-footer-margin * 2;
|
||||||
|
|
||||||
.emoji-picker__items {
|
.emoji-picker__items {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -469,6 +484,13 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&.gif-picker__items {
|
||||||
|
display: flex;
|
||||||
|
height: 380px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 12px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.emoji-picker__container {
|
.emoji-picker__container {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 325px;
|
min-width: 325px;
|
||||||
@@ -547,17 +569,12 @@
|
|||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
|
|
||||||
$emoji-picker-footer-margin: 12px;
|
|
||||||
$emoji-half-height: 33px;
|
|
||||||
|
|
||||||
.emoji-picker__footer {
|
.emoji-picker__footer {
|
||||||
$border-width: 1px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: $border-width + $emoji-half-height + $emoji-picker-footer-margin * 2; // prevents tiny layout shift when emoji replaces placeholder
|
min-height: $emoji-footer-height; // prevents tiny layout shift when emoji replaces placeholder
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-top: solid $border-width rgba(61, 60, 64, 0.2);
|
border-top: solid $emoji-footer-border-width rgba(61, 60, 64, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker__custom {
|
.emoji-picker__custom {
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
|
|
||||||
/* eslint-disable no-console, no-process-env */
|
/* eslint-disable no-console, no-process-env */
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const ExternalTemplateRemotesPlugin = require('external-remotes-plugin');
|
const ExternalTemplateRemotesPlugin = require('external-remotes-plugin');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
@@ -134,6 +131,9 @@ var config = {
|
|||||||
'mattermost-redux/test': 'packages/mattermost-redux/test',
|
'mattermost-redux/test': 'packages/mattermost-redux/test',
|
||||||
'mattermost-redux': 'packages/mattermost-redux/src',
|
'mattermost-redux': 'packages/mattermost-redux/src',
|
||||||
'@mui/styled-engine': '@mui/styled-engine-sc',
|
'@mui/styled-engine': '@mui/styled-engine-sc',
|
||||||
|
|
||||||
|
// This alias restricts single version of styled components acros all packages
|
||||||
|
'styled-components': path.resolve(__dirname, '..', 'node_modules', 'styled-components'),
|
||||||
},
|
},
|
||||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||||
fallback: {
|
fallback: {
|
||||||
|
|||||||
1316
webapp/package-lock.json
сгенерированный
1316
webapp/package-lock.json
сгенерированный
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -123,6 +123,7 @@ export type ClientConfig = {
|
|||||||
ForgotPasswordLink: string;
|
ForgotPasswordLink: string;
|
||||||
GfycatAPIKey: string;
|
GfycatAPIKey: string;
|
||||||
GfycatAPISecret: string;
|
GfycatAPISecret: string;
|
||||||
|
GiphySdkKey: string;
|
||||||
GoogleDeveloperKey: string;
|
GoogleDeveloperKey: string;
|
||||||
GuestAccountsEnforceMultifactorAuthentication: string;
|
GuestAccountsEnforceMultifactorAuthentication: string;
|
||||||
HasImageProxy: string;
|
HasImageProxy: string;
|
||||||
@@ -336,6 +337,7 @@ export type ServiceSettings = {
|
|||||||
EnableGifPicker: boolean;
|
EnableGifPicker: boolean;
|
||||||
GfycatAPIKey: string;
|
GfycatAPIKey: string;
|
||||||
GfycatAPISecret: string;
|
GfycatAPISecret: string;
|
||||||
|
GiphySdkKey: string;
|
||||||
PostEditTimeLimit: number;
|
PostEditTimeLimit: number;
|
||||||
TimeBetweenUserTypingUpdatesMilliseconds: number;
|
TimeBetweenUserTypingUpdatesMilliseconds: number;
|
||||||
EnablePostSearch: boolean;
|
EnablePostSearch: boolean;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user