Compare commits
1 commit
main
...
feature/gi
Author | SHA1 | Date | |
---|---|---|---|
|
ac70e6955e |
15 changed files with 326 additions and 8 deletions
|
@ -69,6 +69,7 @@ export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'
|
||||||
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
||||||
|
|
||||||
export const COMPOSE_DOODLE_SET = 'COMPOSE_DOODLE_SET';
|
export const COMPOSE_DOODLE_SET = 'COMPOSE_DOODLE_SET';
|
||||||
|
export const COMPOSE_TENOR_SET = 'COMPOSE_TENOR_SET';
|
||||||
|
|
||||||
export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
|
export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
|
||||||
export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
|
export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
|
||||||
|
@ -305,7 +306,14 @@ export function doodleSet(options) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uploadCompose(files) {
|
export function tenorSet(options) {
|
||||||
|
return {
|
||||||
|
type: COMPOSE_TENOR_SET,
|
||||||
|
options: options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uploadCompose(files, alt = '') {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const uploadLimit = 4;
|
const uploadLimit = 4;
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
|
@ -332,6 +340,7 @@ export function uploadCompose(files) {
|
||||||
resizeImage(f).then(file => {
|
resizeImage(f).then(file => {
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
data.append('description', alt);
|
||||||
// Account for disparity in size of original image and resized data
|
// Account for disparity in size of original image and resized data
|
||||||
total += file.size - f.size;
|
total += file.size - f.size;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import GifBoxIcon from '@/material-icons/400-24px/gif_box.svg?react';
|
||||||
import PhotoLibraryIcon from '@/material-icons/400-20px/photo_library.svg?react';
|
import PhotoLibraryIcon from '@/material-icons/400-20px/photo_library.svg?react';
|
||||||
import BrushIcon from '@/material-icons/400-24px/brush.svg?react';
|
import BrushIcon from '@/material-icons/400-24px/brush.svg?react';
|
||||||
import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
|
import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
|
||||||
|
@ -15,6 +16,7 @@ import { DropdownIconButton } from './dropdown_icon_button';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
upload: { id: 'upload_button.label', defaultMessage: 'Add images, a video or an audio file' },
|
upload: { id: 'upload_button.label', defaultMessage: 'Add images, a video or an audio file' },
|
||||||
doodle: { id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
|
doodle: { id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
|
||||||
|
gif: { id: 'compose.attach.gif', defaultMessage: 'Upload GIF' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -31,6 +33,9 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
onSelectFile: PropTypes.func.isRequired,
|
onSelectFile: PropTypes.func.isRequired,
|
||||||
onDoodleOpen: PropTypes.func.isRequired,
|
onDoodleOpen: PropTypes.func.isRequired,
|
||||||
|
onEmbedTenor: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
onModalOpen: PropTypes.func.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
resetFileKey: PropTypes.number,
|
resetFileKey: PropTypes.number,
|
||||||
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
|
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
|
||||||
|
@ -46,8 +51,10 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
handleSelect = (value) => {
|
handleSelect = (value) => {
|
||||||
if (value === 'upload') {
|
if (value === 'upload') {
|
||||||
this.fileElement.click();
|
this.fileElement.click();
|
||||||
} else {
|
} else if (value === 'doodle') {
|
||||||
this.props.onDoodleOpen();
|
this.props.onDoodleOpen();
|
||||||
|
} else if (value === 'gif') {
|
||||||
|
this.props.onEmbedTenor();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,6 +80,12 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
value: 'doodle',
|
value: 'doodle',
|
||||||
text: intl.formatMessage(messages.doodle),
|
text: intl.formatMessage(messages.doodle),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'gif-box',
|
||||||
|
iconComponent: GifBoxIcon,
|
||||||
|
value: 'gif',
|
||||||
|
text: intl.formatMessage(messages.gif),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { uploadCompose } from '../../../actions/compose';
|
import { uploadCompose } from '../../../actions/compose';
|
||||||
import { openModal } from '../../../actions/modal';
|
import { openModal, closeModal } from '../../../actions/modal';
|
||||||
import UploadButton from '../components/upload_button';
|
import UploadButton from '../components/upload_button';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -21,6 +21,24 @@ const mapDispatchToProps = dispatch => ({
|
||||||
modalProps: { noEsc: true, noClose: true },
|
modalProps: { noEsc: true, noClose: true },
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onEmbedTenor() {
|
||||||
|
dispatch(openModal({
|
||||||
|
modalType: 'TENOR',
|
||||||
|
modalProps: { noEsc: true },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalClose() {
|
||||||
|
dispatch(closeModal({
|
||||||
|
modalType: undefined,
|
||||||
|
ignoreFocus: false,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalOpen(props) {
|
||||||
|
dispatch(openModal({ modalType: 'ACTIONS', modalProps: props }));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UploadButton);
|
export default connect(mapStateToProps, mapDispatchToProps)(UploadButton);
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
import Tenor from 'react-tenor';
|
||||||
|
|
||||||
|
import { tenorSet, uploadCompose } from 'flavours/glitch/actions/compose';
|
||||||
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
search: { id: 'tenor.search', defaultMessage: 'Search for GIFs' },
|
||||||
|
error: { id: 'tenor.error', defaultMessage: 'Oops! Something went wrong. Please, try again.' },
|
||||||
|
loading: { id: 'tenor.loading', defaultMessage: 'Loading...' },
|
||||||
|
nomatches: { id: 'tenor.nomatches', defaultMessage: 'No matches found.' },
|
||||||
|
close: { id: 'settings.close', defaultMessage: 'Close' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Utility for converting base64 image to binary for upload
|
||||||
|
// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
|
||||||
|
function dataURLtoFile(dataurl, filename) {
|
||||||
|
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
|
||||||
|
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
|
||||||
|
while(n--){
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
return new File([u8arr], filename, { type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
options: state.getIn(['compose', 'tenor']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
/**
|
||||||
|
* Set options in the redux store
|
||||||
|
* @param {Object} opts
|
||||||
|
*/
|
||||||
|
setOpt: (opts) => dispatch(tenorSet(opts)),
|
||||||
|
/**
|
||||||
|
* Submit GIF for upload
|
||||||
|
* @param {File} file
|
||||||
|
* @param {string} alt
|
||||||
|
*/
|
||||||
|
submit: (file, alt) => dispatch(uploadCompose([file], alt)),
|
||||||
|
});
|
||||||
|
|
||||||
|
class GIFModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
options: ImmutablePropTypes.map,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
setOpt: PropTypes.func.isRequired,
|
||||||
|
submit: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
onDoneButton = (result) => {
|
||||||
|
const url = result.media[0].mp4.url;
|
||||||
|
const alt = result.content_description;
|
||||||
|
var modal = this;
|
||||||
|
// eslint-disable-next-line promise/catch-or-return
|
||||||
|
fetch(url).then(function(response) {
|
||||||
|
return response.blob();
|
||||||
|
}).then(function(blob) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
reader.onloadend = function() {
|
||||||
|
var dataUrl = reader.result;
|
||||||
|
const file = dataURLtoFile(dataUrl, 'tenor.mp4');
|
||||||
|
modal.props.submit(file, alt);
|
||||||
|
modal.props.onClose(); // close dialog
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
|
||||||
|
const { intl } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal tenor-modal'>
|
||||||
|
<div className='tenor-modal__container'>
|
||||||
|
<IconButton title={intl.formatMessage(messages.close)} icon='close' iconComponent={CloseIcon} size='16' onClick={this.props.onClose} style={{ float: 'right' }} />
|
||||||
|
<Tenor
|
||||||
|
token='FJBKNQSVF2DD'
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onSelect={result => this.onDoneButton(result)}
|
||||||
|
autoFocus='true'
|
||||||
|
searchPlaceholder={intl.formatMessage(messages.search)}
|
||||||
|
messageError={intl.formatMessage(messages.error)}
|
||||||
|
messageLoading={intl.formatMessage(messages.loading)}
|
||||||
|
messageNoMatches={intl.formatMessage(messages.nomatches)}
|
||||||
|
contentFilter='off'
|
||||||
|
/>
|
||||||
|
<br /><img src='/tenor.svg' alt='Tenor logo' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(GIFModal));
|
|
@ -32,6 +32,7 @@ import DeprecatedSettingsModal from './deprecated_settings_modal';
|
||||||
import DoodleModal from './doodle_modal';
|
import DoodleModal from './doodle_modal';
|
||||||
import FavouriteModal from './favourite_modal';
|
import FavouriteModal from './favourite_modal';
|
||||||
import FocalPointModal from './focal_point_modal';
|
import FocalPointModal from './focal_point_modal';
|
||||||
|
import GIFModal from './gif_modal';
|
||||||
import ImageModal from './image_modal';
|
import ImageModal from './image_modal';
|
||||||
import MediaModal from './media_modal';
|
import MediaModal from './media_modal';
|
||||||
import ModalLoading from './modal_loading';
|
import ModalLoading from './modal_loading';
|
||||||
|
@ -45,6 +46,7 @@ export const MODAL_COMPONENTS = {
|
||||||
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
||||||
'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }),
|
'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }),
|
||||||
'DOODLE': () => Promise.resolve({ default: DoodleModal }),
|
'DOODLE': () => Promise.resolve({ default: DoodleModal }),
|
||||||
|
'TENOR': () => Promise.resolve({ default: GIFModal }),
|
||||||
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
||||||
'MUTE': MuteModal,
|
'MUTE': MuteModal,
|
||||||
'BLOCK': BlockModal,
|
'BLOCK': BlockModal,
|
||||||
|
@ -92,7 +94,7 @@ export default class ModalRoot extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderLoading = modalId => () => {
|
renderLoading = modalId => () => {
|
||||||
return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'TENOR', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderError = (props) => {
|
renderError = (props) => {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"column_subheading.navigation": "Navigation",
|
"column_subheading.navigation": "Navigation",
|
||||||
"community.column_settings.allow_local_only": "Show local-only toots",
|
"community.column_settings.allow_local_only": "Show local-only toots",
|
||||||
"compose.attach.doodle": "Draw something",
|
"compose.attach.doodle": "Draw something",
|
||||||
|
"compose.attach.gif": "Upload GIF",
|
||||||
"compose.change_federation": "Change federation settings",
|
"compose.change_federation": "Change federation settings",
|
||||||
"compose.content-type.change": "Change advanced formatting options",
|
"compose.content-type.change": "Change advanced formatting options",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
|
@ -154,5 +155,9 @@
|
||||||
"status.is_poll": "This toot is a poll",
|
"status.is_poll": "This toot is a poll",
|
||||||
"status.local_only": "Only visible from your instance",
|
"status.local_only": "Only visible from your instance",
|
||||||
"status.uncollapse": "Uncollapse",
|
"status.uncollapse": "Uncollapse",
|
||||||
"suggestions.dismiss": "Dismiss suggestion"
|
"suggestions.dismiss": "Dismiss suggestion",
|
||||||
|
"tenor.error": "Oops! Something went wrong. Please, try again.",
|
||||||
|
"tenor.loading": "Loading...",
|
||||||
|
"tenor.nomatches": "No matches found.",
|
||||||
|
"tenor.search": "Search for GIFs"
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import {
|
||||||
COMPOSE_UPLOAD_CHANGE_SUCCESS,
|
COMPOSE_UPLOAD_CHANGE_SUCCESS,
|
||||||
COMPOSE_UPLOAD_CHANGE_FAIL,
|
COMPOSE_UPLOAD_CHANGE_FAIL,
|
||||||
COMPOSE_DOODLE_SET,
|
COMPOSE_DOODLE_SET,
|
||||||
|
COMPOSE_TENOR_SET,
|
||||||
COMPOSE_RESET,
|
COMPOSE_RESET,
|
||||||
COMPOSE_POLL_ADD,
|
COMPOSE_POLL_ADD,
|
||||||
COMPOSE_POLL_REMOVE,
|
COMPOSE_POLL_REMOVE,
|
||||||
|
@ -100,6 +101,7 @@ const initialState = ImmutableMap({
|
||||||
resetFileKey: Math.floor((Math.random() * 0x10000)),
|
resetFileKey: Math.floor((Math.random() * 0x10000)),
|
||||||
idempotencyKey: null,
|
idempotencyKey: null,
|
||||||
tagHistory: ImmutableList(),
|
tagHistory: ImmutableList(),
|
||||||
|
tenor: null,
|
||||||
media_modal: ImmutableMap({
|
media_modal: ImmutableMap({
|
||||||
id: null,
|
id: null,
|
||||||
description: '',
|
description: '',
|
||||||
|
@ -573,6 +575,8 @@ export default function compose(state = initialState, action) {
|
||||||
}));
|
}));
|
||||||
case COMPOSE_DOODLE_SET:
|
case COMPOSE_DOODLE_SET:
|
||||||
return state.mergeIn(['doodle'], action.options);
|
return state.mergeIn(['doodle'], action.options);
|
||||||
|
case COMPOSE_TENOR_SET:
|
||||||
|
return state.mergeIn(['tenor'], action.options);
|
||||||
case REDRAFT:
|
case REDRAFT:
|
||||||
const do_not_federate = !!action.status.get('local_only');
|
const do_not_federate = !!action.status.get('local_only');
|
||||||
let text = action.raw_text || unescapeHTML(expandMentions(action.status));
|
let text = action.raw_text || unescapeHTML(expandMentions(action.status));
|
||||||
|
|
|
@ -6192,6 +6192,7 @@ a.status-card {
|
||||||
}
|
}
|
||||||
|
|
||||||
.doodle-modal,
|
.doodle-modal,
|
||||||
|
.tenor-modal,
|
||||||
.boost-modal,
|
.boost-modal,
|
||||||
.confirmation-modal,
|
.confirmation-modal,
|
||||||
.report-modal,
|
.report-modal,
|
||||||
|
@ -10213,3 +10214,124 @@ noscript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tenor-modal__container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenor-modal__container .icon-button {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor-active {
|
||||||
|
box-shadow: 0 0 5px 1px rgba(0, 0, 0, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--search {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #555;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
line-height: 1.3;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--search[type='search']::-webkit-search-cancel-button,
|
||||||
|
.react-tenor--search[type='search']::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--search:focus {
|
||||||
|
box-shadow: 0 0 2px 2px #6a89af;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--autocomplete {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--autocomplete span {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--spinner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--suggestions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--results {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--result {
|
||||||
|
background: rgba(87, 87, 131, 15%);
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
flex-basis: 25%;
|
||||||
|
height: 120px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--result span {
|
||||||
|
animation: react-tenor-fade-in 0.2s;
|
||||||
|
background-size: cover;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--result:focus {
|
||||||
|
box-shadow: 0 0 2px 2px #6a89af;
|
||||||
|
border: 1px solid #f7f7f7;
|
||||||
|
outline: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--result:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (width <= 480px) {
|
||||||
|
.react-tenor--result {
|
||||||
|
flex-basis: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tenor--page-left,
|
||||||
|
.react-tenor--page-right {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes react-tenor-fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes react-tenor-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1
app/javascript/material-icons/400-24px/gif_box-fill.svg
Normal file
1
app/javascript/material-icons/400-24px/gif_box-fill.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M340-400h40q17 0 28.5-11.5T420-440v-40h-40v40h-40v-80h80q0-17-11.5-28.5T380-560h-40q-17 0-28.5 11.5T300-520v80q0 17 11.5 28.5T340-400Zm120 0h40v-160h-40v160Zm80 0h40v-60h60v-40h-60v-20h80v-40H540v160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Z"/></svg>
|
After Width: | Height: | Size: 425 B |
1
app/javascript/material-icons/400-24px/gif_box.svg
Normal file
1
app/javascript/material-icons/400-24px/gif_box.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M340-400h40q17 0 28.5-11.5T420-440v-40h-40v40h-40v-80h80q0-17-11.5-28.5T380-560h-40q-17 0-28.5 11.5T300-520v80q0 17 11.5 28.5T340-400Zm120 0h40v-160h-40v160Zm80 0h40v-60h60v-40h-60v-20h80v-40H540v160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>
|
After Width: | Height: | Size: 463 B |
|
@ -10,7 +10,7 @@ class ContentSecurityPolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_hosts
|
def media_hosts
|
||||||
[assets_host, cdn_host_value, paperclip_root_url].concat(extra_data_hosts).compact
|
[assets_host, cdn_host_value, paperclip_root_url, 'https://media.tenor.com'].concat(extra_data_hosts).compact
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -54,10 +54,10 @@ Rails.application.config.content_security_policy do |p|
|
||||||
webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public])
|
webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public])
|
||||||
front_end_build_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{webpacker_public_host}" }
|
front_end_build_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{webpacker_public_host}" }
|
||||||
|
|
||||||
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, *front_end_build_urls
|
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, 'https://api.tenor.com', *front_end_build_urls
|
||||||
p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host
|
p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host
|
||||||
else
|
else
|
||||||
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url
|
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, 'https://api.tenor.com'
|
||||||
p.script_src :self, assets_host, "'wasm-unsafe-eval'"
|
p.script_src :self, assets_host, "'wasm-unsafe-eval'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -111,6 +111,7 @@
|
||||||
"react-select": "^5.7.3",
|
"react-select": "^5.7.3",
|
||||||
"react-sparklines": "^1.7.0",
|
"react-sparklines": "^1.7.0",
|
||||||
"react-swipeable-views": "^0.14.0",
|
"react-swipeable-views": "^0.14.0",
|
||||||
|
"react-tenor": "^2.2.0",
|
||||||
"react-textarea-autosize": "^8.4.1",
|
"react-textarea-autosize": "^8.4.1",
|
||||||
"react-toggle": "^4.1.3",
|
"react-toggle": "^4.1.3",
|
||||||
"redux-immutable": "^4.0.0",
|
"redux-immutable": "^4.0.0",
|
||||||
|
|
22
public/tenor.svg
Normal file
22
public/tenor.svg
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="50px" height="14px" viewBox="0 0 1584 447" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>TENOR_VECTOR</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="TENOR_VECTOR" fill="#007add">
|
||||||
|
<g id="TENOR_GREY">
|
||||||
|
<g id="Layer_1">
|
||||||
|
<g id="Group">
|
||||||
|
<path d="M314.8,295.7 C317.2,313.7 322.4,329.8 331.6,344.3 C353.2,378.4 384.6,394.6 424.8,394.3 C457.8,394.1 486.1,382.1 510.5,360.1 C523.3,348.5 541.3,350.7 550.3,364.6 C556.8,374.6 555.5,386.7 546.5,395.7 C519.2,423 486.7,440 448.2,444.7 C423,447.8 397.9,447.2 373.4,439.6 C317.6,422.3 280.5,385.3 264,329.7 C244.7,264.4 255,203.7 300.5,151.5 C331.4,116.2 371.4,99.4 418.5,100.2 C459.1,100.9 494.5,114.7 523,144.2 C546.7,168.7 559.8,198.6 565.8,231.8 C568.1,244.4 569.1,257.2 569.7,270 C570.4,284.1 559.4,295.1 545.3,295.8 C543.3,295.9 541.3,295.8 539.3,295.8 L321.9,295.8 C319.9,295.7 317.8,295.7 314.8,295.7 L314.8,295.7 Z M510,245.4 C508.5,232.4 504.8,220.4 499.9,208.7 C470.7,140 394.8,138.2 355.8,171.7 C335.3,189.3 323.2,211.8 317.2,237.8 C316.6,240.2 316.4,242.6 315.9,245.4 L510,245.4 L510,245.4 Z" id="Shape"></path>
|
||||||
|
<path d="M970.4,271.3 C970.5,182.9 1039,99.8 1143.8,100 C1241.5,100.1 1316.4,175.8 1316.2,274.2 C1316,370.9 1239.6,446.3 1141.7,446.4 C1047.4,446.4 970.2,367.6 970.4,271.3 L970.4,271.3 Z M1257,275.9 C1256.5,241.6 1246.6,213 1224.9,188.8 C1180.6,139.3 1099.7,141.2 1057.2,192.2 C1021.8,234.7 1019.3,309.7 1060.4,355.9 C1097,397.1 1162.8,405.6 1207.6,373.6 C1241.7,349.2 1256,314.7 1257,275.9 L1257,275.9 Z" id="Shape"></path>
|
||||||
|
<path d="M683.5,148.6 C689.8,142.6 694.9,137.5 700.2,132.7 C725.8,109.3 756.4,99.7 790.5,100 C817.4,100.3 842.7,106.3 865.1,121.9 C892.9,141.3 908,168.8 913.9,201.5 C915.8,212.1 916.8,223 916.9,233.8 C917.2,295 917,356.1 917,417.3 C917,429 909.6,440.1 898.7,444.1 C887.3,448.3 876.6,446.6 867.8,437.8 C861.6,431.7 858.9,424 858.9,415.2 C859,358.5 859.2,301.9 858.7,245.2 C858.6,234 857,222.5 854.3,211.7 C845.8,178.6 822.1,158.5 788.2,154.6 C764.1,151.8 741.2,155.6 721.1,170.2 C694.4,189.6 683.7,217.2 683.6,249.2 C683.4,305 683.6,360.9 683.5,416.7 C683.5,438.1 662.7,452 642.9,443.9 C632.4,439.6 625.3,428.2 625.3,415.4 L625.3,326.9 L625.3,129.4 C625.3,118.1 630.5,109.5 640.1,103.9 C650,98.1 660.3,98.4 670,104.5 C678.3,109.8 683.3,117.5 683.5,127.7 C683.6,134.3 683.5,140.7 683.5,148.6 L683.5,148.6 Z" id="Shape"></path>
|
||||||
|
<path d="M124.9,106.8 L131.4,106.8 L203.4,106.8 C214.2,106.8 223,110.3 228.6,120 C237.9,136.3 228.4,156 209.8,158.8 C206.9,159.2 203.8,159.3 200.8,159.3 L131.3,159.3 L124.9,159.3 L124.9,164.9 L124.9,343.4 C124.9,355.4 126.8,366.9 134.9,376.7 C141.5,384.7 150.4,388.1 160.1,389.6 C174.6,391.9 189,390.7 203.1,386.3 C216.5,382 229.7,388 234.2,400.5 C238.8,413.2 232.3,425.7 219.9,431.7 C203.1,439.9 185.5,443.5 166.9,444.5 C118.7,446.9 75.4,411.9 68.1,364.1 C67.1,357.9 66.8,351.5 66.8,345.2 C66.7,285.7 66.7,226.2 66.7,166.7 L66.7,159.8 C64.5,159.7 62.7,159.5 61,159.5 C50,159.5 39,159.7 28,159.4 C10,158.9 -1.6,145.4 1.1,128.4 C3,116.4 13.9,107.4 27.6,107 C38.4,106.7 49.3,106.9 60.1,106.9 C66.7,106.9 66.7,106.9 66.7,100 L66.7,30.5 C66.7,13.5 79,0.5 95.4,0.3 C111.8,0.1 124.7,13.2 124.8,30.4 C124.9,53.7 124.8,77.1 124.8,100.4 C124.9,102.3 124.9,104.3 124.9,106.8 L124.9,106.8 Z" id="Shape"></path>
|
||||||
|
<path d="M1432.4,170.1 C1441,160.3 1448.6,150.6 1457.4,142 C1477.5,122.4 1501.1,108.7 1528.9,103.2 C1537.3,101.5 1546.1,101.1 1554.7,101.5 C1571,102.4 1583.2,116 1583.2,132 C1583.2,147.8 1570.8,161.2 1554.7,162.7 C1542.8,163.8 1530.7,164 1519,166.5 C1486.9,173.4 1464.2,192.8 1449.6,221.9 C1436.8,247.3 1432.5,274.7 1432.4,302.8 C1432.2,340.3 1432.4,377.8 1432.4,415.3 C1432.4,432.7 1419.6,446.2 1403.1,446.1 C1386.5,446 1374,433.1 1374,415.7 L1374,128.8 C1374,115.8 1383.2,104 1395.6,100.9 C1409.2,97.5 1422.4,103.1 1428.3,115.6 C1430.6,120.4 1431.9,126 1432.1,131.4 C1432.7,142.5 1432.3,153.7 1432.3,164.9 C1432.4,166.6 1432.4,168.2 1432.4,170.1 L1432.4,170.1 Z" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
11
yarn.lock
11
yarn.lock
|
@ -2446,6 +2446,7 @@ __metadata:
|
||||||
react-select: "npm:^5.7.3"
|
react-select: "npm:^5.7.3"
|
||||||
react-sparklines: "npm:^1.7.0"
|
react-sparklines: "npm:^1.7.0"
|
||||||
react-swipeable-views: "npm:^0.14.0"
|
react-swipeable-views: "npm:^0.14.0"
|
||||||
|
react-tenor: "npm:^2.2.0"
|
||||||
react-test-renderer: "npm:^18.2.0"
|
react-test-renderer: "npm:^18.2.0"
|
||||||
react-textarea-autosize: "npm:^8.4.1"
|
react-textarea-autosize: "npm:^8.4.1"
|
||||||
react-toggle: "npm:^4.1.3"
|
react-toggle: "npm:^4.1.3"
|
||||||
|
@ -13807,6 +13808,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-tenor@npm:^2.2.0":
|
||||||
|
version: 2.2.0
|
||||||
|
resolution: "react-tenor@npm:2.2.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16
|
||||||
|
react-dom: ^16
|
||||||
|
checksum: d6c46a7b4666916b4d22bc46ffe7667384d2958620cb843a820c8c24243e775b4a0e4b5146a19ec676a43f24236fddbea5fbe92ca14559794d1d1cd752a13587
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-test-renderer@npm:^18.2.0":
|
"react-test-renderer@npm:^18.2.0":
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
resolution: "react-test-renderer@npm:18.2.0"
|
resolution: "react-test-renderer@npm:18.2.0"
|
||||||
|
|
Loading…
Reference in a new issue