From f688d3c5d356eb6805cd57349b7ce66ea7e58712 Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Thu, 11 Aug 2022 10:31:17 -0700 Subject: [PATCH] Use a single place to embed iframe, Youtube, and Tweets --- web/components/editor.tsx | 100 +----------------- .../{tweetModal.tsx => embed-modal.tsx} | 55 ++++++++-- 2 files changed, 48 insertions(+), 107 deletions(-) rename web/components/editor/{tweetModal.tsx => embed-modal.tsx} (57%) diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 1da14703..1f4b970a 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -23,12 +23,7 @@ import { DisplayMention } from './editor/mention' import Iframe from 'common/util/tiptap-iframe' import TiptapTweet from 'common/util/tiptap-tweet' import { CodeIcon, PhotographIcon } from '@heroicons/react/solid' -import { Modal } from './layout/modal' -import { Col } from './layout/col' -import { Button } from './button' -import { Row } from './layout/row' -import { Spacer } from './layout/spacer' -import { TweetModal } from './editor/tweetModal' +import { EmbedModal } from './editor/embed-modal' const DisplayImage = Image.configure({ HTMLAttributes: { @@ -128,13 +123,6 @@ function isValidIframe(text: string) { return /^$/.test(text) } -function isValidUrl(text: string) { - // Conjured by Codex, not sure if it's actually good - return /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test( - text - ) -} - export function TextEditor(props: { editor: Editor | null upload: ReturnType @@ -142,7 +130,6 @@ export function TextEditor(props: { }) { const { editor, upload, children } = props const [iframeOpen, setIframeOpen] = useState(false) - const [tweetOpen, setTweetOpen] = useState(false) return ( <> @@ -167,7 +154,7 @@ export function TextEditor(props: { onClick={() => setIframeOpen(true)} className="-m-2.5 flex h-10 w-10 items-center justify-center rounded-full text-gray-400 hover:text-gray-500" > - Embed an iframe -
- -
{/* Spacer that also focuses editor on click */}
void -}) { - const { editor, open, setOpen } = props - const [input, setInput] = useState('') - const valid = isValidIframe(input) || isValidUrl(input) - const embedCode = isValidIframe(input) ? input : `' - value={input} - onChange={(e) => setInput(e.target.value)} - /> - - {/* Preview the embed if it's valid */} - {valid ? : } - - - - - - - - ) -} - const useUploadMutation = (editor: Editor | null) => useMutation( (files: File[]) => @@ -294,7 +201,7 @@ const useUploadMutation = (editor: Editor | null) => } ) -function RichContent(props: { +export function RichContent(props: { content: JSONContent | string smallImage?: boolean }) { @@ -307,6 +214,7 @@ function RichContent(props: { DisplayLink, DisplayMention, Iframe, + TiptapTweet, ], content, editable: false, diff --git a/web/components/editor/tweetModal.tsx b/web/components/editor/embed-modal.tsx similarity index 57% rename from web/components/editor/tweetModal.tsx rename to web/components/editor/embed-modal.tsx index a6fac5db..26d53edd 100644 --- a/web/components/editor/tweetModal.tsx +++ b/web/components/editor/embed-modal.tsx @@ -2,29 +2,62 @@ import { Editor } from '@tiptap/react' import { useState } from 'react' import { TwitterTweetEmbed } from 'react-twitter-embed' import { Button } from '../button' +import { RichContent } from '../editor' import { Col } from '../layout/col' import { Modal } from '../layout/modal' import { Row } from '../layout/row' import { Spacer } from '../layout/spacer' +function isValidIframe(text: string) { + return /^$/.test(text) +} + // A valid tweet URL looks like 'https://twitter.com/username/status/123456789' // Return the tweetId if the URL is valid, otherwise return null. function getTweetId(text: string) { - const match = text.match(/^https?:\/\/twitter\.com\/.*\/status\/(\d+)$/) + const match = text.match(/^https?:\/\/twitter\.com\/.*\/status\/(\d+)/) return match ? match[1] : null } -export function TweetModal(props: { +// A valid YouTube URL looks like 'https://www.youtube.com/watch?v=ziq7FUKpCS8' +function getYoutubeId(text: string) { + const match = text.match(/^https?:\/\/www\.youtube\.com\/watch\?v=([^&]+)/) + return match ? match[1] : null +} + +function isValidUrl(text: string) { + // Conjured by Codex + return /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test( + text + ) +} + +function embedCode(text: string) { + if (isValidIframe(text)) { + return text + } else if (getTweetId(text)) { + // Append a leading 't', to prevent tweetId from being interpreted as a number. + // If it's a number, there may be numeric precision issues. + return `` + } else if (getYoutubeId(text)) { + return `` + } else if (isValidUrl(text)) { + return `` + } + // Return null if the text is not embeddable. + return null +} + +export function EmbedModal(props: { editor: Editor | null open: boolean setOpen: (open: boolean) => void }) { const { editor, open, setOpen } = props const [input, setInput] = useState('') - const tweetId = getTweetId(input) - // Append a leading 't', to prevent tweetId from being interpreted as a number. - // If it's a number, there may be numeric precision issues. - const tweetCode = `` + const embed = embedCode(input) return ( @@ -33,7 +66,7 @@ export function TweetModal(props: { htmlFor="embed" className="block text-sm font-medium text-gray-700" > - Paste a tweet link + Embed a Youtube video, Tweet, or other link {/* Preview the embed if it's valid */} - {tweetId ? : } + {embed ? : }