From c59c345756db463d8abc0f4c62e500f171f145fe Mon Sep 17 00:00:00 2001 From: Tessa Thornton Date: Tue, 14 Jul 2020 22:16:06 -0400 Subject: [PATCH] [CORL-1108] Media Embeds (#3010) * Create Twitter and YouTube embed components Uses the `/api/oembed` endpoint to proxy the oembed requests that the embed components drop into an iframe. CORL-1012 * Create preliminary embed link parsing/storage CORL-1012 * Create preliminary embed section on comments CORL-1012 * Preliminarily add admin embed config options CORL-1012 * Show a "missing" message when embed is unavailable CORL-1012 * Simplify naming of embeds in schema embedLinks -> embed CORL-1012 * Rename oEmbedHandler to oembedHandler CORL-1012 * add backend services for giphy * search gifs on frontend * display selected gif * show gif previews * display giphy attribution and no results text * save a gif to a comment * use embeds feature for gif embeds * clean up gif/video/tweet display * style and configure post comment form * preview and confirm twitter and youtube embeds * moderate embeds on server * update reply and edit forms * update snaps * fix some of the tests * fix tests and types * fix tests * fix types * show gifs in moderate cards * correctly attach embeds to comments * make gif rating configurable * make gif rating configurable * configure giphy api key * refactor comment form * only allow embeds if settings enabled * scale youtube * resize embeds if necessary * make tweets and videos responsive * set maxwidth on tweet embeds * update copy for embed config * force gif search results to fit container * prevent double posting of gifs * undo hiding html if empmty because now it doesn't contain random break tags * use downsampled preview images * update fixtures and snapshots * remove unused css * add i18n string * remove console logs * Fix styles on logged-out comment form * click to pause gif in moderation * style youtube and twitter embeds in mod stream" * use mp4s for stream gifs * use mp4 for moderation gifs * clean up commentform * fix dom tests * update rte * import oembed module with correct casing * bump rte * add correct return type for setInterval * add migration for embeds config * catch errors from gif search * return early from iframe container size calculation if width and height are set * remove unused classnames * make giphy api key protected * reorganize tenant embed settings schema * update schema on backend to support single comment embed instead of array * move findEmbedLinks to common * wrap error * return function for linkify instead of ternary * remove unused url param * clean up oembed service * remove conditional in repeat post check * use joi to validate giphy responses * fix types for embeds * fix optimistic responses * move attachEmbeds function * update snapshots * fix: improved repeatPost checking * force case change on oembed * force rename file name * feat: Rename Embed -> Media * fix: cleanup of service functions * fix: moved types * fix: fixed logic bug * fix: fixed translation * show embeds on history comments * fix: fixed iframe csp and query param bug * correct validation for twitter oembed * feat: save youtube still * fix: typeerror * fix: fixed errors related to final form * fix: fixed issue with types * fix: added docs to the schema * fix: linting + tests Co-authored-by: nick-funk Co-authored-by: Wyatt Johnson --- .eslintrc.js | 2 + package-lock.json | 189 ++- package.json | 2 +- .../components/ModerateCard/GiphyMedia.tsx | 63 + .../admin/components/ModerateCard/Media.css | 33 + .../ModerateCard/MediaContainer.tsx | 87 ++ .../components/ModerateCard/ModerateCard.tsx | 17 +- .../ModerateCard/ModerateCardContainer.tsx | 1 + .../components/ModerateCard/TwitterMedia.tsx | 26 + .../components/ModerateCard/YouTubeMedia.tsx | 63 + .../__snapshots__/ModerateCard.spec.tsx.snap | 292 ++-- .../General/GeneralConfigContainer.tsx | 4 +- .../sections/General/MediaLinksConfig.css | 7 + .../sections/General/MediaLinksConfig.tsx | 273 ++++ .../__snapshots__/general.spec.tsx.snap | 405 +++++ .../__snapshots__/regularQueue.spec.tsx.snap | 112 +- .../__snapshots__/rejectedQueue.spec.tsx.snap | 64 +- .../__snapshots__/singleComment.spec.tsx.snap | 48 +- .../addEmailAddress.spec.tsx.snap | 10 +- .../createUsername.spec.tsx.snap | 6 +- .../forgotPassword.spec.tsx.snap | 2 +- .../test/__snapshots__/signIn.spec.tsx.snap | 4 +- .../test/__snapshots__/signUp.spec.tsx.snap | 8 +- src/core/client/framework/lib/validation.tsx | 12 + src/core/client/stream/classes.ts | 4 + .../stream/common/HTMLContent/HTMLContent.css | 1 + .../client/stream/common/Media/GiphyMedia.tsx | 32 + .../client/stream/common/Media/OEmbed.css | 23 + .../client/stream/common/Media/OEmbed.tsx | 111 ++ .../stream/common/Media/TwitterMedia.tsx | 15 + .../stream/common/Media/YouTubeMedia.tsx | 29 + src/core/client/stream/common/Media/index.ts | 3 + .../stream/tabs/Comments/Comment/Comment.tsx | 2 + .../Comments/Comment/CommentContainer.tsx | 7 + .../EditCommentForm/EditCommentForm.css | 6 + .../EditCommentForm/EditCommentForm.tsx | 222 +-- .../EditCommentFormContainer.tsx | 46 +- .../EditCommentForm/EditCommentMutation.ts | 23 +- .../MediaConfirmation/MediaConfirmPrompt.css | 61 + .../MediaConfirmation/MediaConfirmPrompt.tsx | 96 ++ .../MediaConfirmation/MediaConfirmation.css | 23 + .../MediaConfirmation/MediaConfirmation.tsx | 59 + .../MediaConfirmationIcon.css | 4 + .../MediaConfirmationIcon.tsx | 24 + .../MediaConfirmation/MediaPreview.css | 25 + .../MediaConfirmation/MediaPreview.tsx | 96 ++ .../Comment/MediaConfirmation/index.ts | 3 + .../Comment/MediaConfirmation/twitter.png | Bin 0 -> 752 bytes .../MediaSection/MediaSectionContainer.css | 3 + .../MediaSection/MediaSectionContainer.tsx | 181 +++ .../Comments/Comment/MediaSection/index.ts | 4 + .../CreateCommentReplyMutation.ts | 5 + .../ReplyCommentForm/ReplyCommentForm.css | 7 + .../ReplyCommentForm/ReplyCommentForm.tsx | 182 +-- .../ReplyCommentFormContainer.spec.tsx | 9 + .../ReplyCommentFormContainer.tsx | 17 + .../ReplyCommentFormContainer.spec.tsx.snap | 56 + .../CommentContainer.spec.tsx.snap | 298 ++++ .../tabs/Comments/GifSelector/GifPreview.css | 17 + .../tabs/Comments/GifSelector/GifPreview.tsx | 38 + .../Comments/GifSelector/GifSearchFetch.ts | 30 + .../tabs/Comments/GifSelector/GifSelector.css | 47 + .../tabs/Comments/GifSelector/GifSelector.tsx | 226 +++ .../Comments/GifSelector/GiphyAttribution.css | 3 + .../Comments/GifSelector/GiphyAttribution.tsx | 27 + .../Comments/GifSelector/giphyAttribution.png | Bin 0 -> 1570 bytes .../stream/tabs/Comments/GifSelector/index.ts | 2 + .../client/stream/tabs/Comments/RTE/RTE.css | 16 +- .../client/stream/tabs/Comments/RTE/RTE.tsx | 16 + .../stream/tabs/Comments/RTE/RTEContainer.tsx | 1 + .../RTE/__snapshots__/RTE.spec.tsx.snap | 2 +- .../Stream/CommentForm/CommentForm.css | 18 + .../Stream/CommentForm/CommentForm.tsx | 322 ++++ .../Stream/CommentForm/MediaField.tsx | 101 ++ .../tabs/Comments/Stream/CommentForm/index.ts | 1 + .../FeaturedCommentContainer.tsx | 18 +- .../PostCommentForm/CreateCommentMutation.ts | 11 +- .../PostCommentForm/PostCommentForm.css | 7 + .../PostCommentForm/PostCommentForm.tsx | 191 +-- .../PostCommentFormContainer.spec.tsx | 11 +- .../PostCommentFormContainer.tsx | 29 +- .../PostCommentForm/PostCommentFormFake.css | 6 + .../PostCommentForm/PostCommentFormFake.tsx | 2 +- .../PostCommentForm/PostCommentInput.css | 7 + .../PostCommentFormContainer.spec.tsx.snap | 91 ++ .../PostCommentFormFake.spec.tsx.snap | 4 +- .../helpers/getCommentBodyValidators.ts | 34 +- .../MyComments/DownloadCommentsContainer.css | 2 - .../MyComments/DownloadCommentsContainer.tsx | 2 +- .../MyComments/HistoryComment.spec.tsx | 1 + .../Profile/MyComments/HistoryComment.tsx | 18 +- .../MyComments/HistoryCommentContainer.tsx | 9 + .../HistoryComment.spec.tsx.snap | 18 +- .../renderFeaturedStream.spec.tsx.snap | 42 +- .../__snapshots__/editComment.spec.tsx.snap | 1354 +++++++++-------- .../postLocalReply.spec.tsx.snap | 299 ++-- .../__snapshots__/postReply.spec.tsx.snap | 299 ++-- .../renderCommunityGuidelines.spec.tsx.snap | 6 +- .../renderMessageBox.spec.tsx.snap | 230 +-- .../__snapshots__/renderStream.spec.tsx.snap | 6 +- .../stream/charCountReplyComment.spec.tsx | 25 +- .../test/comments/stream/editComment.spec.tsx | 3 +- src/core/client/stream/test/fixtures.ts | 1 + .../client/stream/test/helpers/fixture.ts | 4 + .../__snapshots__/myComments.spec.tsx.snap | 36 +- .../client/ui/components/v2/Button/Button.css | 43 + .../client/ui/components/v2/Button/Button.tsx | 3 +- .../ui/components/v2/TextField/TextField.css | 1 + .../ui/components/v2/TextField/TextField.tsx | 2 +- src/core/common/constants.ts | 4 + src/core/common/helpers/findMediaLinks.ts | 42 + src/core/common/helpers/getHTMLPlainText.ts | 8 +- src/core/common/rest/external/giphy.ts | 42 + src/core/server/app/handlers/api/index.ts | 1 + .../server/app/handlers/api/oembed/oembed.ts | 110 ++ .../app/handlers/api/remoteMedia/index.ts | 30 + src/core/server/app/middleware/csp/tenant.ts | 7 + src/core/server/app/router/api/index.ts | 11 + src/core/server/app/router/api/remoteMedia.ts | 13 + src/core/server/graph/mutators/Comments.ts | 13 +- .../server/graph/resolvers/CommentMedia.ts | 21 + .../server/graph/resolvers/CommentRevision.ts | 1 + .../graph/resolvers/MediaConfiguration.ts | 10 + src/core/server/graph/resolvers/Settings.ts | 15 + src/core/server/graph/resolvers/index.ts | 14 +- src/core/server/graph/schema/schema.graphql | 245 +++ src/core/server/helpers/index.ts | 1 + src/core/server/helpers/validateSchema.ts | 19 + src/core/server/models/comment/comment.ts | 9 +- src/core/server/models/comment/revision.ts | 34 + src/core/server/models/settings/settings.ts | 1 + src/core/server/models/tenant/tenant.ts | 21 + src/core/server/services/comments/index.ts | 1 + src/core/server/services/comments/media.ts | 139 ++ .../services/comments/pipeline/helpers.ts | 4 +- .../comments/pipeline/phases/commentLength.ts | 11 +- .../comments/pipeline/phases/linkify.ts | 11 +- .../comments/pipeline/phases/repeatPost.ts | 50 +- .../services/comments/pipeline/pipeline.ts | 7 +- src/core/server/services/giphy/giphy.ts | 173 +++ src/core/server/services/giphy/index.ts | 1 + src/core/server/services/oembed/index.ts | 1 + src/core/server/services/oembed/oembed.ts | 69 + src/core/server/stacks/createComment.ts | 24 +- src/core/server/stacks/editComment.ts | 22 +- src/locales/en-US/admin.ftl | 32 + src/locales/en-US/stream.ftl | 31 + 147 files changed, 6366 insertions(+), 1971 deletions(-) create mode 100644 src/core/client/admin/components/ModerateCard/GiphyMedia.tsx create mode 100644 src/core/client/admin/components/ModerateCard/Media.css create mode 100644 src/core/client/admin/components/ModerateCard/MediaContainer.tsx create mode 100644 src/core/client/admin/components/ModerateCard/TwitterMedia.tsx create mode 100644 src/core/client/admin/components/ModerateCard/YouTubeMedia.tsx create mode 100644 src/core/client/admin/routes/Configure/sections/General/MediaLinksConfig.css create mode 100644 src/core/client/admin/routes/Configure/sections/General/MediaLinksConfig.tsx create mode 100644 src/core/client/stream/common/Media/GiphyMedia.tsx create mode 100644 src/core/client/stream/common/Media/OEmbed.css create mode 100644 src/core/client/stream/common/Media/OEmbed.tsx create mode 100644 src/core/client/stream/common/Media/TwitterMedia.tsx create mode 100644 src/core/client/stream/common/Media/YouTubeMedia.tsx create mode 100644 src/core/client/stream/common/Media/index.ts create mode 100644 src/core/client/stream/tabs/Comments/Comment/EditCommentForm/EditCommentForm.css create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmPrompt.css create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmPrompt.tsx create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmation.css create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmation.tsx create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmationIcon.css create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmationIcon.tsx create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaPreview.css create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaPreview.tsx create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/index.ts create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/twitter.png create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaSection/MediaSectionContainer.css create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaSection/MediaSectionContainer.tsx create mode 100644 src/core/client/stream/tabs/Comments/Comment/MediaSection/index.ts create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GifPreview.css create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GifPreview.tsx create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GifSearchFetch.ts create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GifSelector.css create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GifSelector.tsx create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GiphyAttribution.css create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/GiphyAttribution.tsx create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/giphyAttribution.png create mode 100644 src/core/client/stream/tabs/Comments/GifSelector/index.ts create mode 100644 src/core/client/stream/tabs/Comments/Stream/CommentForm/CommentForm.css create mode 100644 src/core/client/stream/tabs/Comments/Stream/CommentForm/CommentForm.tsx create mode 100644 src/core/client/stream/tabs/Comments/Stream/CommentForm/MediaField.tsx create mode 100644 src/core/client/stream/tabs/Comments/Stream/CommentForm/index.ts create mode 100644 src/core/client/stream/tabs/Comments/Stream/PostCommentForm/PostCommentInput.css create mode 100644 src/core/common/helpers/findMediaLinks.ts create mode 100644 src/core/common/rest/external/giphy.ts create mode 100644 src/core/server/app/handlers/api/oembed/oembed.ts create mode 100644 src/core/server/app/handlers/api/remoteMedia/index.ts create mode 100644 src/core/server/app/router/api/remoteMedia.ts create mode 100644 src/core/server/graph/resolvers/CommentMedia.ts create mode 100644 src/core/server/graph/resolvers/MediaConfiguration.ts create mode 100644 src/core/server/helpers/validateSchema.ts create mode 100644 src/core/server/services/comments/media.ts create mode 100644 src/core/server/services/giphy/giphy.ts create mode 100644 src/core/server/services/giphy/index.ts create mode 100644 src/core/server/services/oembed/index.ts create mode 100644 src/core/server/services/oembed/oembed.ts diff --git a/.eslintrc.js b/.eslintrc.js index 0b2059c41..85f0cfd18 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -92,6 +92,8 @@ const typescriptOverrides = { "react/prop-types": "off", "react/no-unescaped-entities": "off", "no-empty-function": "off", + // (tessalt) disabled because video elements are only used to display gifs, which have no audio + "jsx-a11y/media-has-caption": "off", } ), }; diff --git a/package-lock.json b/package-lock.json index 3d2ba7617..52c5d5cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5087,9 +5087,9 @@ } }, "@coralproject/rte": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-1.0.0.tgz", - "integrity": "sha512-Z+NMytOSdz2qqphcevI9+s3CUzsW/LPzXPxVHAd9qKSlgJ+lr4pvht/gzgcvDvKy1hT88CompJwkqlIBxk0y9w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-1.1.1.tgz", + "integrity": "sha512-r70xg7arHttiJr4pXCZTTWA86bjT9M+XHDeaYjNpMQrW5rIL+kw7sjoRuiyPLzi88xINMbxffurdKep2Z68bcg==", "dev": true, "requires": { "classnames": "^2.2.6", @@ -22068,6 +22068,13 @@ "concat-map": "0.0.1" } }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true, + "optional": true + }, "code-point-at": { "version": "1.1.0", "resolved": false, @@ -22127,12 +22134,28 @@ "dev": true, "optional": true }, - "fs.realpath": { - "version": "1.0.0", - "resolved": false, - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, - "optional": true + "optional": true, + "requires": { + "minipass": "^2.6.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } }, "gauge": { "version": "2.7.4", @@ -22245,14 +22268,42 @@ "brace-expansion": "^1.1.7" } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, "requires": { - "minimist": "^1.2.5" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" } }, "ms": { @@ -22534,6 +22585,22 @@ "dev": true, "optional": true }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": false, @@ -22557,6 +22624,13 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true, + "optional": true } } }, @@ -29139,7 +29213,6 @@ "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "requires": { "chownr": "^1.0.1", - "minizlib": "^1.1.0", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.1", "yallist": "^3.0.2" @@ -38530,35 +38603,6 @@ "minipass": "^3.0.0" } }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "requires": { - "minipass": "^2.9.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -49223,9 +49267,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "squire-rte": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/squire-rte/-/squire-rte-1.10.1.tgz", - "integrity": "sha512-BGTSZmwY3BFFvLaGSz6qFwzdCbRc0EhTW9rw3xNwlEIVHKBFzImDdzT1aM4dWZYyvNvAHknUuPvr/elNU7Nt1g==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/squire-rte/-/squire-rte-1.10.2.tgz", + "integrity": "sha512-/IUZbc3+pdZUL+kP6wsUI8brDKtiP+EiIlw6Hj6q1n384NzUqQNa8iMLkeYZKC4o96ToyteY7fhv/DeS1GkWRw==", "dev": true }, "sshpk": { @@ -50805,59 +50849,6 @@ "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", "dev": true }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "optional": true - } - } - }, "tar-stream": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz", diff --git a/package.json b/package.json index 2c7a403c1..fa579a0f6 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@babel/preset-typescript": "^7.10.1", "@babel/runtime-corejs3": "^7.10.3", "@coralproject/npm-run-all": "^4.1.5", - "@coralproject/rte": "^1.0.0", + "@coralproject/rte": "^1.1.1", "@fluent/react": "^0.11.1", "@intervolga/optimize-cssnano-plugin": "^1.0.6", "@types/archiver": "^3.1.0", diff --git a/src/core/client/admin/components/ModerateCard/GiphyMedia.tsx b/src/core/client/admin/components/ModerateCard/GiphyMedia.tsx new file mode 100644 index 000000000..6bca430b9 --- /dev/null +++ b/src/core/client/admin/components/ModerateCard/GiphyMedia.tsx @@ -0,0 +1,63 @@ +import { Localized } from "@fluent/react/compat"; +import React, { FunctionComponent, useCallback, useState } from "react"; + +import { BaseButton, Flex, Icon } from "coral-ui/components/v2"; + +import styles from "./Media.css"; + +interface Props { + still: string | null; + title: string | null; + width: number | null; + height: number | null; + video: string | null; +} + +const GiphyMedia: FunctionComponent = ({ + still, + title, + width, + height, + video, +}) => { + const [showAnimated, setShowAnimated] = useState(false); + const toggleImage = useCallback(() => { + setShowAnimated(!showAnimated); + }, [showAnimated]); + return ( +
+ {!showAnimated && still && ( + + {title + + + play_circle_outline + + +

Play GIF

+
+
+
+ )} + {showAnimated && video && ( + + + + )} +
+ ); +}; + +export default GiphyMedia; diff --git a/src/core/client/admin/components/ModerateCard/Media.css b/src/core/client/admin/components/ModerateCard/Media.css new file mode 100644 index 000000000..20c2e5644 --- /dev/null +++ b/src/core/client/admin/components/ModerateCard/Media.css @@ -0,0 +1,33 @@ +.embed { + margin: var(--v2-spacing-2) 0; +} + +.toggle { + position: relative; +} + +.toggleTrigger { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: rgba(101, 105, 107, 0.6); +} + +.playIcon { + color: var(--v2-colors-pure-white); +} + +.playText { + color: var(--v2-colors-pure-white); + font-size: var(--v2-font-size-2); + font-weight: var(--v2-font-weight-primary-bold); + font-family: var(--v2-font-family-primary); + text-transform: uppercase; + margin: 0; +} + +.image { + display: block; +} diff --git a/src/core/client/admin/components/ModerateCard/MediaContainer.tsx b/src/core/client/admin/components/ModerateCard/MediaContainer.tsx new file mode 100644 index 000000000..974863a70 --- /dev/null +++ b/src/core/client/admin/components/ModerateCard/MediaContainer.tsx @@ -0,0 +1,87 @@ +import React, { FunctionComponent } from "react"; +import { graphql } from "react-relay"; + +import { withFragmentContainer } from "coral-framework/lib/relay"; +import {} from "coral-ui/components/v2"; + +import { MediaContainer_comment } from "coral-admin/__generated__/MediaContainer_comment.graphql"; + +import GiphyMedia from "./GiphyMedia"; +import TwitterMedia from "./TwitterMedia"; +import YouTubeMedia from "./YouTubeMedia"; + +interface Props { + comment: MediaContainer_comment; +} + +const MediaContainer: FunctionComponent = ({ comment }) => { + if (!comment || !comment.revision || !comment.revision.media) { + return null; + } + return ( + <> + {comment.revision.media.__typename === "GiphyMedia" && ( + + )} + {comment.revision.media.__typename === "TwitterMedia" && ( + + )} + {comment.revision.media.__typename === "YouTubeMedia" && ( + + )} + + ); +}; + +const enhanced = withFragmentContainer({ + comment: graphql` + fragment MediaContainer_comment on Comment { + site { + id + } + revision { + media { + __typename + ... on GiphyMedia { + url + title + width + height + still + video + } + ... on TwitterMedia { + url + width + } + ... on YouTubeMedia { + url + still + title + width + height + } + } + } + } + `, +})(MediaContainer); + +export default enhanced; diff --git a/src/core/client/admin/components/ModerateCard/ModerateCard.tsx b/src/core/client/admin/components/ModerateCard/ModerateCard.tsx index 7a54986a0..cb3500241 100644 --- a/src/core/client/admin/components/ModerateCard/ModerateCard.tsx +++ b/src/core/client/admin/components/ModerateCard/ModerateCard.tsx @@ -29,6 +29,7 @@ import ApproveButton from "./ApproveButton"; import CommentAuthorContainer from "./CommentAuthorContainer"; import FeatureButton from "./FeatureButton"; import MarkersContainer from "./MarkersContainer"; +import MediaContainer from "./MediaContainer"; import RejectButton from "./RejectButton"; import styles from "./ModerateCard.css"; @@ -44,7 +45,8 @@ interface Props { username: string | null; } | null; comment: PropTypesOf["comment"] & - PropTypesOf["comment"]; + PropTypesOf["comment"] & + PropTypesOf["comment"]; settings: PropTypesOf["settings"]; status: "approved" | "rejected" | "undecided"; featured: boolean; @@ -238,13 +240,12 @@ const ModerateCard: FunctionComponent = ({ )}
- - {commentBody} - +
+ + {commentBody} + + +
{onConversationClick && (