[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 <nick.funk@outlook.com>
Co-authored-by: Wyatt Johnson <me@wyattjoh.ca>
This commit is contained in:
Tessa Thornton
2020-07-15 02:16:06 +00:00
committed by GitHub
co-authored by nick-funk Wyatt Johnson
parent f8234e53ed
commit c59c345756
147 changed files with 6366 additions and 1971 deletions
+11 -2
View File
@@ -11,6 +11,7 @@ import {
removeDontAgree,
removeReaction,
} from "coral-server/services/comments/actions";
import { CreateCommentMediaInput } from "coral-server/services/comments/media";
import { publishCommentFeatured } from "coral-server/services/events";
import {
approveComment,
@@ -41,6 +42,7 @@ export const Comments = (ctx: GraphContext) => ({
create: ({
clientMutationId,
nudge = false,
media,
...comment
}: GQLCreateCommentInput | GQLCreateCommentReplyInput) =>
mapFieldsetToErrorCodes(
@@ -51,7 +53,12 @@ export const Comments = (ctx: GraphContext) => ({
ctx.broker,
ctx.tenant,
ctx.user!,
{ authorID: ctx.user!.id, ...comment },
{
...comment,
authorID: ctx.user!.id,
// TODO: (wyattjoh) check this type to get it to match.
media: media as CreateCommentMediaInput,
},
nudge,
ctx.now,
ctx.req
@@ -65,7 +72,7 @@ export const Comments = (ctx: GraphContext) => ({
"input.storyID": [ERROR_CODES.STORY_NOT_FOUND],
}
),
edit: ({ commentID, body }: GQLEditCommentInput) =>
edit: ({ commentID, body, media }: GQLEditCommentInput) =>
mapFieldsetToErrorCodes(
editComment(
ctx.mongo,
@@ -77,6 +84,8 @@ export const Comments = (ctx: GraphContext) => ({
{
id: commentID,
body,
// TODO: (wyattjoh) check this type to get it to match.
media: media as CreateCommentMediaInput,
},
ctx.now,
ctx.req
@@ -0,0 +1,21 @@
import { GQLCommentMediaTypeResolver } from "coral-server/graph/schema/__generated__/types";
import * as comment from "coral-server/models/comment";
const resolveType: GQLCommentMediaTypeResolver<comment.CommentMedia> = (
embed
) => {
switch (embed.type) {
case "giphy":
return "GiphyMedia";
case "youtube":
return "YouTubeMedia";
case "twitter":
return "TwitterMedia";
default:
// TODO: replace with better error.
throw new Error("invalid embed type");
}
};
export const CommentMedia = {
__resolveType: resolveType,
};
@@ -17,4 +17,5 @@ export const CommentRevision: Required<GQLCommentRevisionTypeResolver<
// Defaults to an empty object if not set on the revision.
metadata: (w) => w.revision.metadata || {},
createdAt: (w) => w.revision.createdAt,
media: (w) => w.revision.media,
};
@@ -0,0 +1,10 @@
import {
GQLMediaConfiguration,
GQLMediaConfigurationTypeResolver,
} from "coral-server/graph/schema/__generated__/types";
export const MediaConfiguration: GQLMediaConfigurationTypeResolver<GQLMediaConfiguration> = {
twitter: ({ twitter }) => (twitter ? twitter : false),
youtube: ({ youtube }) => (youtube ? youtube : false),
giphy: ({ giphy }) => (giphy ? giphy : false),
};
@@ -31,4 +31,19 @@ export const Settings: GQLSettingsTypeResolver<Tenant> = {
},
webhookEvents: () => Object.values(GQLWEBHOOK_EVENT_NAME),
rte: ({ rte = defaultRTEConfiguration }) => rte,
media: ({ media }) => {
if (!media) {
return {
twitter: { enabled: false },
youtube: { enabled: false },
giphy: { enabled: false },
};
}
return {
twitter: media.twitter || { enabled: false },
youtube: media.youtube || { enabled: false },
giphy: media.giphy || { enabled: false, maxRating: "g" },
};
},
};
+9 -5
View File
@@ -14,6 +14,7 @@ import { CommentCounts } from "./CommentCounts";
import { CommentCreatedPayload } from "./CommentCreatedPayload";
import { CommentEnteredModerationQueuePayload } from "./CommentEnteredModerationQueuePayload";
import { CommentLeftModerationQueuePayload } from "./CommentLeftModerationQueuePayload";
import { CommentMedia } from "./CommentMedia";
import { CommentModerationAction } from "./CommentModerationAction";
import { CommentReleasedPayload } from "./CommentReleasedPayload";
import { CommentReplyCreatedPayload } from "./CommentReplyCreatedPayload";
@@ -27,6 +28,7 @@ import { Flag } from "./Flag";
import { GoogleAuthIntegration } from "./GoogleAuthIntegration";
import { Invite } from "./Invite";
import { LiveConfiguration } from "./LiveConfiguration";
import { MediaConfiguration } from "./MediaConfiguration";
import { ModerationQueue } from "./ModerationQueue";
import { ModerationQueues } from "./ModerationQueues";
import { ModeratorNote } from "./ModeratorNote";
@@ -69,6 +71,7 @@ const Resolvers: GQLResolver = {
CommentCreatedPayload,
CommentEnteredModerationQueuePayload,
CommentLeftModerationQueuePayload,
CommentMedia,
CommentModerationAction,
CommentReleasedPayload,
CommentReplyCreatedPayload,
@@ -84,6 +87,7 @@ const Resolvers: GQLResolver = {
Invite,
LiveConfiguration,
Locale,
MediaConfiguration,
ModerationQueue,
ModerationQueues,
ModeratorNote,
@@ -93,11 +97,15 @@ const Resolvers: GQLResolver = {
PremodStatusHistory,
Profile,
Query,
Queue,
Queues,
RecentCommentHistory,
RejectCommentPayload,
SSOAuthIntegration,
Settings,
SigningSecret,
Site,
SlackConfiguration,
SSOAuthIntegration,
Story,
StorySettings,
Subscription,
@@ -107,13 +115,9 @@ const Resolvers: GQLResolver = {
Time,
User,
UserModerationScopes,
Queue,
Queues,
UsernameHistory,
UsernameStatus,
UserStatus,
Settings,
SlackConfiguration,
WebhookEndpoint,
};
+245
View File
@@ -1278,6 +1278,58 @@ type StoryMessageBox {
content: String
}
################################################################################
## Embed Links
################################################################################
type GiphyMediaConfiguration {
"""
enabled is true when gif search via giphy and giphy media objects are enabled.
"""
enabled: Boolean!
"""
maximum allowed rating for gifs, g, pg, pg-13, r.
"""
maxRating: String
"""
key is the API key for Giphy.
"""
key: String @auth(roles: [ADMIN])
}
type TwitterMediaConfiguration {
"""
enabled is true when twitter media objects are enabled.
"""
enabled: Boolean!
}
type YouTubeMediaConfiguration {
"""
enabled is true when youtube media objects are enabled.
"""
enabled: Boolean!
}
type MediaConfiguration {
"""
twitter is the configuration for Twitter support.
"""
twitter: TwitterMediaConfiguration!
"""
youtube is the configuration for YouTube support.
"""
youtube: YouTubeMediaConfiguration!
"""
giphy is the configuration for Giphy support.
"""
giphy: GiphyMediaConfiguration!
}
################################################################################
## Settings
################################################################################
@@ -1688,6 +1740,11 @@ type Settings {
"""
stories: StoryConfiguration! @auth(roles: [ADMIN])
"""
media is the configuration media content attached to Comment's.
"""
media: MediaConfiguration
"""
featureFlags provides the enabled feature flags.
"""
@@ -1703,7 +1760,11 @@ type Settings {
"""
newCommenters: NewCommentersConfiguration! @auth(roles: [ADMIN])
"""
announcement is the currently active Announcement.
"""
announcement: Announcement
"""
multisite is whether multiple sites exist for this tenant.
"""
@@ -2551,6 +2612,96 @@ type CommentRevisionMetadata {
perspective: CommentRevisionPerspectiveMetadata
}
"""
GiphyMedia is a particular GIF that is provided by the Giphy platform.
"""
type GiphyMedia {
"""
url is the URL to a image of the GIF.
"""
url: String!
"""
original is the URL to a image of the GIF.
"""
original: String!
"""
still is a thumbnail preview of the GIF.
"""
still: String!
"""
video is a URL to the mp4 video of the GIF.
"""
video: String!
"""
width is the width of the GIF in pixels.
"""
width: Int
"""
height is the height of the GIF in pixels.
"""
height: Int
"""
title is the title of the GIF.
"""
title: String
}
"""
TwitterMedia is a specific Twitter Tweet.
"""
type TwitterMedia {
"""
url is the URL of a Twitter tweet.
"""
url: String!
"""
width is the width of the Twitter tweet in pixels.
"""
width: Int
}
"""
YouTubeMedia is a specific YouTube video.
"""
type YouTubeMedia {
"""
url is the URL of a YouTube video.
"""
url: String!
"""
still is the thumbnail of the YouTube video.
"""
still: String!
"""
title is the title of the YouTube video.
"""
title: String
"""
width is the width of the YouTube video in pixels.
"""
width: Int
"""
height is the height of the YouTube video in pixels.
"""
height: Int
}
"""
CommentMedia is the various media types that can be attached to a Comment.
"""
union CommentMedia = GiphyMedia | TwitterMedia | YouTubeMedia
type CommentRevision {
"""
id is the identifier of the CommentRevision.
@@ -2575,6 +2726,11 @@ type CommentRevision {
"""
body: String
"""
media is the optional media object attached to this revision.
"""
media: CommentMedia
"""
metadata stores details on a CommentRevision.
"""
@@ -3463,6 +3619,11 @@ input CreateCommentInput {
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
media is the optional media attachment to be added to a Comment.
"""
media: CreateCommentMediaInput
}
"""
@@ -3481,6 +3642,27 @@ type CreateCommentPayload {
clientMutationId: String!
}
"""
CreateCommentMediaInput is used for creating media to be attached to a Comment.
"""
input CreateCommentMediaInput {
"""
type specifies the type of media that can be attached. Valid values for this
are: `giphy`, `twitter`, and `youtube`.
"""
type: String!
"""
id refers to a foreign id for the specific media provider.
"""
id: String
"""
url is the URL to the media resource.
"""
url: String!
}
##################
## createCommentReply
##################
@@ -3520,6 +3702,11 @@ input CreateCommentReplyInput {
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
media is the optional media attachment to be added to a Comment.
"""
media: CreateCommentMediaInput
}
"""
@@ -3560,6 +3747,11 @@ input EditCommentInput {
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
media is the optional media attachment to be added to a Comment.
"""
media: CreateCommentMediaInput
}
"""
@@ -4201,6 +4393,54 @@ input SlackConfigurationInput {
channels: [SlackChannelConfigurationInput!]
}
input TwitterMediaConfigurationInput {
"""
enabled is true when twitter media objects are enabled.
"""
enabled: Boolean
}
input YouTubeMediaConfigurationInput {
"""
enabled is true when youtube media objects are enabled.
"""
enabled: Boolean
}
input GiphyMediaConfigurationInput {
"""
enabled is true when gif search via giphy and giphy media objects are enabled.
"""
enabled: Boolean
"""
maximum allowed rating for gifs, g, pg, pg-13, r
"""
maxRating: String!
"""
key is the API key for Giphy.
"""
key: String
}
input MediaConfigurationInput {
"""
twitter is the configuration for Twitter support.
"""
twitter: TwitterMediaConfigurationInput
"""
youtube is the configuration for YouTube support.
"""
youtube: YouTubeMediaConfigurationInput
"""
giphy is the configuration for Giphy support.
"""
giphy: GiphyMediaConfigurationInput
}
"""
NewCommenterConfigurationInput specifies the features that apply to new commenters
"""
@@ -4361,6 +4601,11 @@ input SettingsInput {
rte is the configuration of the Rich-Text-Editor.
"""
rte: RTEConfigurationInput
"""
media is the configuration media content attached to Comment's.
"""
media: MediaConfigurationInput
}
"""