feat: parse youtube, simplecast and custom iframe embeds on server-side

This commit is contained in:
Hossein Mehrabi 2023-08-28 15:26:50 +03:30
parent 77e4369176
commit bd0406eac5
No known key found for this signature in database
GPG Key ID: 45C04964191AFAA1
5 changed files with 78 additions and 28 deletions

View File

@ -4,7 +4,6 @@ import {
extractIdFromFirstTag, extractIdFromFirstTag,
extractInnerHtml, extractInnerHtml,
} from '@/utils/html.utils' } from '@/utils/html.utils'
import { convertToIframe } from '@/utils/string.utils'
import { HeadingElementsRef } from '@/utils/ui.utils' import { HeadingElementsRef } from '@/utils/ui.utils'
import { Typography } from '@acid-info/lsd-react' import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled' import styled from '@emotion/styled'
@ -40,31 +39,18 @@ export const RenderArticleBlock = ({
) )
} }
case 'p': { case 'p': {
const isIframeRegex = /<iframe[^>]*>(?:<\/iframe>|[^]*?<\/iframe>)/ const isIframe = block.embed && block.labels.includes('embed')
const isIframe = isIframeRegex.test(block.text)
const isYoutubeRegex = return block.embed && isIframe ? (
/^(https?\:\/\/)?((www\.)?youtube\.com|youtu\.?be)\/.+$/ block.labels.includes('youtube_embed') ? (
<ReactPlayer url={block.embed.src} />
const isYoutube = isYoutubeRegex.test(block.text) ) : (
const youtubeLink = block.text.match(isYoutubeRegex) ?? []
const isSimplecastRegex =
/^https?:\/\/([a-zA-Z0-9-]+\.)*simplecast\.com\/[^?\s]+(\?[\s\S]*)?$/
const isSimplecast = isSimplecastRegex.test(block.text)
const simplecastLink = block.text.match(isSimplecastRegex) ?? []
return isIframe ? (
<IframeContainer dangerouslySetInnerHTML={{ __html: block.text }} />
) : isYoutube ? (
<ReactPlayer url={youtubeLink[0]} />
) : isSimplecast ? (
<IframeContainer <IframeContainer
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: convertToIframe(simplecastLink[0] ?? ''), __html: block.embed.html,
}} }}
/> />
)
) : ( ) : (
<Paragraph <Paragraph
variant="body1" variant="body1"

View File

@ -17,7 +17,12 @@ const ArticleBlocks = ({ data }: Props) => {
const headingElementsRef = useIntersectionObserver(setTocId) const headingElementsRef = useIntersectionObserver(setTocId)
const blocks = useMemo( const blocks = useMemo(
() => data.content.filter((b) => b.labels.length === 0), () =>
data.content.filter(
(b) =>
b.labels.length === 0 ||
b.labels.includes(LPE.Post.ContentBlockLabels.Embed),
),
[data.content], [data.content],
) )

View File

@ -18,7 +18,6 @@ const ArticlePage = ({ data, errors, why }: ArticleProps) => {
if (!data) return null if (!data) return null
if (errors) return <div>{errors}</div> if (errors) return <div>{errors}</div>
return ( return (
<> <>
<SEO <SEO

View File

@ -1,4 +1,5 @@
import { LPE } from '../../../types/lpe.types' import { LPE } from '../../../types/lpe.types'
import { convertToIframe } from '../../../utils/string.utils'
import { UnbodyResGoogleDocData, UnbodyResTextBlockData } from '../unbody.types' import { UnbodyResGoogleDocData, UnbodyResTextBlockData } from '../unbody.types'
import { UnbodyDataTypeConfig } from './types' import { UnbodyDataTypeConfig } from './types'
@ -15,6 +16,55 @@ export const TextBlockDataType: UnbodyDataTypeConfig<
isMatch: (helpers, data, original, root) => data.__typename === 'TextBlock', isMatch: (helpers, data, original, root) => data.__typename === 'TextBlock',
transform: (helpers, data, original, root) => { transform: (helpers, data, original, root) => {
const { text = '', html = '' } = data
const labels: LPE.Post.ContentBlockLabel[] = []
let embed: LPE.Post.TextBlockEmbed | null = null
if (text.length > 0 || html.length > 0) {
const isLink =
/^<p[^>]*><span[^>]*><a[^>]*href="(https):\/\/[^ "]+"[^>]*>.*<\/a><\/span><\/p>$/.test(
html,
)
const isIframe = /<iframe[^>]*>(?:<\/iframe>|[^]*?<\/iframe>)/.test(text)
if (isLink) {
const youtube = html.match(
/(https?\:\/\/)?((www\.)?youtube\.com|youtu\.?be)\/[^ "]+/gi,
)
const simplecast = html.match(
/(https?\:\/\/)?((player\.)?simplecast\.com)\/[^ "]+/gi,
)
const label = youtube?.[0]
? LPE.Post.ContentBlockLabels.YoutubeEmbed
: LPE.Post.ContentBlockLabels.SimplecastEmbed
const src = youtube?.[0] || simplecast?.[0]
if (src && src.length > 0) {
labels.push(label)
labels.push(LPE.Post.ContentBlockLabels.Embed)
embed = {
src,
html: convertToIframe(src),
}
}
}
if (isIframe) {
const src = text.match(/(?<=src=").*?(?=[\?"])/g)?.[0]
if (src) {
labels.push(LPE.Post.ContentBlockLabels.Embed)
embed = {
html: text,
src,
}
}
}
}
return { return {
id: data?._additional?.id || `${data.order}`, id: data?._additional?.id || `${data.order}`,
type: 'text', type: 'text',
@ -24,7 +74,8 @@ export const TextBlockDataType: UnbodyDataTypeConfig<
footnotes: data.footnotesObj, footnotes: data.footnotesObj,
order: data.order, order: data.order,
tagName: data.tagName, tagName: data.tagName,
labels: [], labels,
...(embed ? { embed } : {}),
} }
}, },
} }

View File

@ -61,6 +61,9 @@ export namespace LPE {
Footnote: 'footnote', Footnote: 'footnote',
Paragraph: 'paragraph', Paragraph: 'paragraph',
CoverImage: 'cover_image', CoverImage: 'cover_image',
Embed: 'embed',
YoutubeEmbed: 'youtube_embed',
SimplecastEmbed: 'simplecast_embed',
} as const } as const
export type ContentBlockLabel = DictValues<typeof ContentBlockLabels> export type ContentBlockLabel = DictValues<typeof ContentBlockLabels>
@ -72,6 +75,11 @@ export namespace LPE {
document?: D document?: D
} }
export type TextBlockEmbed = {
src: string
html: string
}
export type TextBlock<D = any> = ContentBlockCommon<D> & { export type TextBlock<D = any> = ContentBlockCommon<D> & {
text: string text: string
html: string html: string
@ -79,6 +87,7 @@ export namespace LPE {
classNames: string[] classNames: string[]
type: Extract<ContentBlockType, 'text'> type: Extract<ContentBlockType, 'text'>
footnotes: Post.Footnotes footnotes: Post.Footnotes
embed?: TextBlockEmbed
} }
export type ImageBlock<D = any> = ContentBlockCommon<D> & export type ImageBlock<D = any> = ContentBlockCommon<D> &