diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index 6665ff58eb..9d29b934c6 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -67,7 +67,7 @@ export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
export const COMPOSE_DOODLE_SET = 'COMPOSE_DOODLE_SET';
-export const COMPOSE_GIPHY_SET = 'COMPOSE_GIPHY_SET';
+export const COMPOSE_TENOR_SET = 'COMPOSE_TENOR_SET';
export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
@@ -287,9 +287,9 @@ export function doodleSet(options) {
};
}
-export function giphySet(options) {
+export function tenorSet(options) {
return {
- type: COMPOSE_GIPHY_SET,
+ type: COMPOSE_TENOR_SET,
options: options,
};
};
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index e6ad2a3f59..e572ac5a52 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -133,7 +133,7 @@ class ComposerOptions extends ImmutablePureComponent {
onChangeContentType: PropTypes.func,
onTogglePoll: PropTypes.func,
onDoodleOpen: PropTypes.func,
- onEmbedGiphy: PropTypes.func,
+ onEmbedTenor: PropTypes.func,
onModalClose: PropTypes.func,
onModalOpen: PropTypes.func,
onToggleSpoiler: PropTypes.func,
@@ -156,7 +156,7 @@ class ComposerOptions extends ImmutablePureComponent {
// Handles attachment clicks.
handleClickAttach = (name) => {
const { fileElement } = this;
- const { onDoodleOpen, onEmbedGiphy } = this.props;
+ const { onDoodleOpen, onEmbedTenor } = this.props;
// We switch over the name of the option.
switch (name) {
@@ -171,8 +171,8 @@ class ComposerOptions extends ImmutablePureComponent {
}
return;
case 'gif':
- if (onEmbedGiphy) {
- onEmbedGiphy();
+ if (onEmbedTenor) {
+ onEmbedTenor();
}
return;
}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
index 655780affd..cf1dac545b 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
@@ -49,8 +49,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(openModal('DOODLE', { noEsc: true, noClose: true }));
},
- onEmbedGiphy() {
- dispatch(openModal('GIPHY', { noEsc: true }));
+ onEmbedTenor() {
+ dispatch(openModal('TENOR', { noEsc: true }));
},
onModalClose() {
diff --git a/app/javascript/flavours/glitch/features/ui/components/gif_modal.js b/app/javascript/flavours/glitch/features/ui/components/gif_modal.js
index a13bfd60cb..d9e22a5a22 100644
--- a/app/javascript/flavours/glitch/features/ui/components/gif_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/gif_modal.js
@@ -3,16 +3,16 @@ import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { giphySet, uploadCompose } from 'flavours/glitch/actions/compose';
+import { tenorSet, uploadCompose } from 'flavours/glitch/actions/compose';
import IconButton from 'flavours/glitch/components/icon_button';
-import ReactGiphySearchbox from 'react-giphy-searchbox'
+import Tenor from 'react-tenor';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
- search: { id: 'giphy.search', defaultMessage: 'Search for GIFs' },
- error: { id: 'giphy.error', defaultMessage: 'Oops! Something went wrong. Please, try again.' },
- loading: { id: 'giphy.loading', defaultMessage: 'Loading...'},
- nomatches: { id: 'giphy.nomatches', defaultMessage: 'No matches found.' },
+ 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' },
});
@@ -28,12 +28,12 @@ function dataURLtoFile(dataurl, filename) {
}
const mapStateToProps = state => ({
- options: state.getIn(['compose', 'giphy']),
+ options: state.getIn(['compose', 'tenor']),
});
const mapDispatchToProps = dispatch => ({
/** Set options in the redux store */
- setOpt: (opts) => dispatch(giphySet(opts)),
+ setOpt: (opts) => dispatch(tenorSet(opts)),
/** Submit GIF for upload */
submit: (file) => dispatch(uploadCompose([file])),
});
@@ -50,9 +50,10 @@ class GIFModal extends ImmutablePureComponent {
submit: PropTypes.func.isRequired,
};
- onDoneButton = (item) => {
- const url = item["images"]["original"]["mp4"];
+ onDoneButton = (result) => {
+ const url = result.media[0].mp4.url;
var modal = this;
+ // eslint-disable-next-line promise/catch-or-return
fetch(url).then(function(response) {
return response.blob();
}).then(function(blob) {
@@ -60,7 +61,7 @@ class GIFModal extends ImmutablePureComponent {
reader.readAsDataURL(blob);
reader.onloadend = function() {
var dataUrl = reader.result;
- const file = dataURLtoFile(dataUrl, 'giphy.mp4');
+ const file = dataURLtoFile(dataUrl, 'tenor.mp4');
modal.props.submit(file);
modal.props.onClose(); // close dialog
};
@@ -72,23 +73,26 @@ class GIFModal extends ImmutablePureComponent {
const { intl } = this.props;
return (
-
-
-
-
this.onDoneButton(item)}
- masonryConfig={[
- { columns: 2, imageWidth: 190, gutter: 5 },
- { mq: "700px", columns: 2, imageWidth: 210, gutter: 5 }
- ]}
- autoFocus="true"
- searchPlaceholder={intl.formatMessage(messages.search)}
- messageError={intl.formatMessage(messages.error)}
- messageLoading={intl.formatMessage(messages.loading)}
- messageNoMatches={intl.formatMessage(messages.nomatches)}
- wrapperClassName="giphy-modal__searchbox"
- />
+
+
+
+
+ this.onDoneButton(result)}
+ masonryConfig={[
+ { columns: 2, imageWidth: 190, gutter: 5 },
+ { mq: '700px', columns: 2, imageWidth: 210, gutter: 5 }
+ ]}
+ autoFocus='true'
+ searchPlaceholder={intl.formatMessage(messages.search)}
+ messageError={intl.formatMessage(messages.error)}
+ messageLoading={intl.formatMessage(messages.loading)}
+ messageNoMatches={intl.formatMessage(messages.nomatches)}
+ />
+
+
);
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
index c98f6038b9..10fdbad33e 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
@@ -44,7 +44,7 @@ const MODAL_COMPONENTS = {
'BOOST': () => Promise.resolve({ default: BoostModal }),
'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }),
'DOODLE': () => Promise.resolve({ default: DoodleModal }),
- 'GIPHY': () => Promise.resolve({ default: GIFModal }),
+ 'TENOR': () => Promise.resolve({ default: GIFModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'MUTE': MuteModal,
'BLOCK': BlockModal,
@@ -92,7 +92,7 @@ export default class ModalRoot extends React.PureComponent {
};
renderLoading = modalId => () => {
- return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'GIPHY', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null;
+ return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'TENOR', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null;
};
renderError = (props) => {
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index 13b47a08e5..cf746cdeac 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -38,7 +38,7 @@ import {
COMPOSE_UPLOAD_CHANGE_SUCCESS,
COMPOSE_UPLOAD_CHANGE_FAIL,
COMPOSE_DOODLE_SET,
- COMPOSE_GIPHY_SET,
+ COMPOSE_TENOR_SET,
COMPOSE_RESET,
COMPOSE_POLL_ADD,
COMPOSE_POLL_REMOVE,
@@ -107,6 +107,7 @@ const initialState = ImmutableMap({
resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null,
tagHistory: ImmutableList(),
+ tenor: null,
media_modal: ImmutableMap({
id: null,
description: '',
@@ -114,7 +115,6 @@ const initialState = ImmutableMap({
focusY: 0,
dirty: false,
}),
- giphy: null,
doodle: ImmutableMap({
fg: 'rgb( 0, 0, 0)',
bg: 'rgb(255, 255, 255)',
@@ -560,8 +560,8 @@ export default function compose(state = initialState, action) {
}));
case COMPOSE_DOODLE_SET:
return state.mergeIn(['doodle'], action.options);
- case COMPOSE_GIPHY_SET:
- return state.mergeIn(['giphy'], action.options);
+ case COMPOSE_TENOR_SET:
+ return state.mergeIn(['tenor'], action.options);
case REDRAFT:
const do_not_federate = !!action.status.get('local_only');
let text = action.raw_text || unescapeHTML(expandMentions(action.status));
diff --git a/app/javascript/flavours/glitch/styles/components/giphy.scss b/app/javascript/flavours/glitch/styles/components/giphy.scss
deleted file mode 100644
index 71c1de3204..0000000000
--- a/app/javascript/flavours/glitch/styles/components/giphy.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.giphy-modal {
- @extend .boost-modal;
- width: 500px;
-}
-
-.giphy-modal__container {
- text-align: center;
- padding: 20px;
-}
-
-.giphy-modal__searchbox {
- width: 450px !important;
-}
\ No newline at end of file
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 79128d66f9..bc1c40fe97 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1814,7 +1814,7 @@ noscript {
@import 'search';
@import 'emoji';
@import 'doodle';
-@import 'giphy';
+@import 'tenor';
@import 'drawer';
@import 'media';
@import 'sensitive';
diff --git a/app/javascript/flavours/glitch/styles/components/tenor.scss b/app/javascript/flavours/glitch/styles/components/tenor.scss
new file mode 100644
index 0000000000..69bd24945e
--- /dev/null
+++ b/app/javascript/flavours/glitch/styles/components/tenor.scss
@@ -0,0 +1,217 @@
+.tenor-modal {
+ @extend .boost-modal;
+ width: 500px;
+}
+
+.tenor-modal__container {
+ text-align: center;
+ padding: 20px;
+}
+
+.tenor-modal__searchbox {
+ width: 450px !important;
+}
+
+.react-tenor {
+ background-color: #f7f7f7;
+ border: 1px solid #ccc;
+ max-width: 480px;
+}
+
+.react-tenor-active {
+ box-shadow: 0 0 5px 1px rgba(0, 0, 0, .2);
+}
+
+.react-tenor--search-bar {
+ position: relative;
+}
+
+.react-tenor--search {
+ background-color: white;
+ border: 1px solid #f7f7f7;
+ box-sizing: border-box;
+ color: #555;
+ font-family: Arial;
+ font-size: 1em;
+ line-height: 1.3;
+ overflow: visible;
+ padding: .25em .5em;
+ width: 100%;
+}
+
+.react-tenor--search:focus {
+ box-shadow: 0 0 2px 2px #6a89af;
+ outline: none;
+}
+
+.react-tenor--autocomplete {
+ box-sizing: border-box;
+ color: #aaa;
+ font-family: Arial;
+ font-size: 1em;
+ line-height: 1.3;
+ left: 0;
+ padding: .25em .5em;
+ pointer-events: none;
+ position: absolute;
+ top: 1px;
+}
+
+.react-tenor--autocomplete span {
+ visibility: hidden;
+}
+
+.react-tenor--spinner {
+ animation: react-tenor-spin 1s linear infinite;
+ height: 22px;
+ position: absolute;
+ right: 4px;
+ top: 3px;
+ width: 22px;
+}
+
+.react-tenor--spinner path {
+ fill: #999;
+}
+
+.react-tenor--suggestions {
+ overflow-x: auto;
+ padding: .5em .5em;
+ white-space: nowrap;
+}
+
+.react-tenor--suggestions button {
+ background: #6a89af;
+ border: 1px solid #f7f7f7;
+ border-radius: 5px;
+ color: white;
+ cursor: pointer;
+ display: inline-block;
+ font-size: 1em;
+ padding: 3px 5px;
+}
+
+.react-tenor--suggestions button:focus {
+ box-shadow: 0 0 2px 2px #6a89af;
+ outline: none;
+}
+
+.react-tenor--suggestions button + button {
+ margin-left: .5em;
+}
+
+.react-tenor--results {
+ display: flex;
+ flex-wrap: wrap;
+ position: relative;
+}
+
+.react-tenor--result {
+ background: #6a89af;
+ background-image: repeating-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, .1),
+ rgba(255, 255, 255, .1) 15px,
+ transparent 0,
+ transparent 30px
+ );
+ border: 0;
+ cursor: pointer;
+ display: inline-block;
+ flex-basis: 25%;
+ height: 120px;
+ opacity: 1;
+ padding: 0;
+ transition: opacity .3s;
+ width: 120px;
+}
+
+.react-tenor--result span {
+ animation: react-tenor-fade-in .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: .5;
+}
+
+@media screen and (max-width: 480px) {
+ .react-tenor--result {
+ flex-basis: 33%;
+ }
+}
+
+.react-tenor--page-left,
+.react-tenor--page-right {
+ background: #6a89af;
+ border: 0;
+ cursor: pointer;
+ height: 1.8em;
+ position: absolute;
+ top: calc(50% - .9em);
+ opacity: .5;
+ position: absolute;
+ transition: opacity .2s, left .2s, right .2s;
+ width: 1.8em;
+ z-index: -1;
+}
+
+.react-tenor--results:hover .react-tenor--page-left,
+.react-tenor--results:hover .react-tenor--page-right {
+ opacity: 1;
+ z-index: 1;
+}
+
+.react-tenor--results:hover .react-tenor--page-left {
+ left: -1em;
+}
+
+.react-tenor--results:hover .react-tenor--page-right {
+ right: -1em;
+}
+
+.react-tenor--page-left div,
+.react-tenor--page-right div {
+ background: inherit;
+ height: 1.6em;
+ transform: rotate(45deg);
+ top: .1em;
+ position: absolute;
+ width: 1.6em;
+}
+
+.react-tenor--page-left {
+ left: -.3em;
+}
+
+.react-tenor--page-left div {
+ left: -.7em;
+}
+
+.react-tenor--page-right {
+ right: -.3em;
+}
+
+.react-tenor--page-right div {
+ right: -.7em;
+}
+
+@keyframes react-tenor-fade-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes react-tenor-spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
diff --git a/package.json b/package.json
index a738f5806d..32fa9f7833 100644
--- a/package.json
+++ b/package.json
@@ -36,8 +36,8 @@
"@rails/ujs": "^6.1.7",
"abortcontroller-polyfill": "^1.7.5",
"array-includes": "^3.1.6",
- "atrament": "0.2.4",
"arrow-key-navigation": "^1.2.0",
+ "atrament": "0.2.4",
"autoprefixer": "^9.8.8",
"axios": "^1.3.2",
"babel-loader": "^8.3.0",
@@ -93,7 +93,6 @@
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-helmet": "^6.1.0",
- "react-giphy-searchbox": "^1.5.4",
"react-hotkeys": "^1.1.4",
"react-immutable-proptypes": "^2.2.0",
"react-immutable-pure-component": "^2.2.2",
@@ -108,6 +107,7 @@
"react-select": "^5.7.0",
"react-sparklines": "^1.7.0",
"react-swipeable-views": "^0.14.0",
+ "react-tenor": "^2.2.0",
"react-textarea-autosize": "^8.4.0",
"react-toggle": "^4.1.3",
"redis": "^4.0.6 <4.1.0",
diff --git a/public/tenor.svg b/public/tenor.svg
new file mode 100644
index 0000000000..bed2e8bc73
--- /dev/null
+++ b/public/tenor.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 5cd6dd4e82..7e12ca474a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2865,7 +2865,7 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-bricks.js@^1.7.0, bricks.js@^1.8.0:
+bricks.js@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.8.0.tgz#8fdeb3c0226af251f4d5727a7df7f9ac0092b4b2"
integrity sha1-j96zwCJq8lH01XJ6fff5rACStLI=
@@ -9058,7 +9058,7 @@ react-immutable-pure-component@^2.2.2:
resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz#3014d3e20cd5a7a4db73b81f1f1464f4d351684b"
integrity sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==
-react-infinite-scroller@^1.0.12, react-infinite-scroller@^1.2.4:
+react-infinite-scroller@^1.0.12:
version "1.2.4"
resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9"
integrity sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==
@@ -9254,6 +9254,11 @@ react-swipeable-views@^0.14.0:
react-swipeable-views-utils "^0.14.0"
warning "^4.0.1"
+react-tenor@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/react-tenor/-/react-tenor-2.2.0.tgz#98326868cc199165bacb911abdbbb48306232b6f"
+ integrity sha512-hs0KomduflTLU05fvKAtrf9f/aAj3wN2kPF2cl5cAOFPkhuaVJuWLNxRSAQN1m+aKHinujEQZ1fXp+gL1KgYhg==
+
react-test-renderer@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"