mirror of
https://github.com/wassname/talk.git
synced 2026-08-11 05:54:07 +08:00
[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:
co-authored by
nick-funk
Wyatt Johnson
parent
f8234e53ed
commit
c59c345756
@@ -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" },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user