diff --git a/app/entities/post/detail/PostBody.tsx b/app/entities/post/detail/PostBody.tsx index 53031c4..b2fd342 100644 --- a/app/entities/post/detail/PostBody.tsx +++ b/app/entities/post/detail/PostBody.tsx @@ -35,6 +35,167 @@ const PostBody = ({ content, tags, loading }: Props) => { } } }; + + const renderOpenGraph = (node: any, index?: number, parent?: Element) => { + if (node.type === 'element' && node.tagName === 'p' && node.children) { + const aTag = node.children.find( + (node: any) => node.type === 'element' && node.tagName === 'a' + ); + if (!aTag) return; + + const href = aTag.properties?.href; + if (href && href.startsWith('/')) { + // 부모가 존재하고 children 배열이 있는 경우 + const opengraph = createOpenGraph(href); + if ( + index !== undefined && + parent && + parent.children && + Array.isArray(parent.children) + ) { + // 현재 a 태그 다음 위치에 div 삽입 s + parent.children.splice(index + 1, 0, opengraph); + } else return; + } + } + }; + + const renderYoutubeEmbed = (node: any, index?: number, parent?: Element) => { + if (node.type === 'element' && node.tagName === 'p' && node.children) { + const aTag = node.children.find( + (node: any) => node.type === 'element' && node.tagName === 'a' + ); + if (!aTag) return; + + const href = aTag.properties?.href; + const isYoutubeLink = + href && + (href.startsWith('https://www.youtube.com/watch') || + href.startsWith('https://youtu.be/')); + + if (isYoutubeLink) { + const urlType = href.startsWith('https://www.youtube.com/watch') + ? 'watch' + : 'be'; + + const videoId = + urlType === 'watch' + ? new URL(href).searchParams.get('v') + : href.split('/').pop(); + + if (videoId) { + const youtubeEmbed = createYoutubeIframe(videoId, 736, 414); + // 부모가 존재하고 children 배열이 있는 경우 + if ( + index && + parent && + parent.children && + Array.isArray(parent.children) + ) { + parent.children.splice(index + 1, 0, youtubeEmbed); + } else return; + } + } + } + }; + + const createYoutubeIframe = ( + videoId: string, + width: number, + height: number + ) => { + return { + type: 'element', + tagName: 'iframe', + properties: { + src: `https://www.youtube.com/embed/${videoId}`, + width: width.toString(), + height: height.toString(), + frameBorder: '0', + allowFullScreen: true, + className: 'youtube-embed', + }, + children: [], + }; + }; + + const createOpenGraph = (href: string) => { + return { + type: 'element', + tagName: 'a', + properties: { + className: 'open-graph', + href: href, + }, + children: [ + { + type: 'element', + tagName: 'img', + properties: { + src: `${href}`, + alt: 'Open Graph Image', + className: 'og-image', + }, + children: [], + }, + { + type: 'element', + tagName: 'div', + properties: { + className: 'og-container', + }, + children: [ + { + type: 'element', + tagName: 'h4', + properties: { + className: 'og-title', + }, + children: [ + { + type: 'text', + value: decodeURIComponent(href.split('/').pop()!).replaceAll( + '-', + ' ' + ), + }, + ], + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'og-content', + }, + children: [ + // { + // type: 'text', + // value: decodeURIComponent(href.split('/').pop()!).replaceAll( + // '-', + // ' ' + // ), + // }, + ], + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'og-domain', + }, + children: [ + { + type: 'text', + value: '', + }, + ], + }, + ], + }, + ], + }; + }; + return (
{ wrapperElement={{ 'data-color-mode': theme, }} - rehypeRewrite={(node) => { + rehypeRewrite={(node, index?, parent?) => { asideStyleRewrite(node); + // renderOpenGraph(node, index || 0, parent as Element | undefined); + renderYoutubeEmbed( + node, + index || 0, + parent as Element | undefined + ); }} /> diff --git a/app/globals.css b/app/globals.css index 1c1660b..1e60717 100644 --- a/app/globals.css +++ b/app/globals.css @@ -167,12 +167,57 @@ article.post .post-body hr::before { content: ''; } +article.post .post-body pre { + box-shadow: 0 0 4px rgba(0, 0, 0, 0.05); +} + .shadow-top { box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1), 0 -2px 4px -1px rgba(0, 0, 0, 0.06); } +.post-body .open-graph { + display: flex; + height: 120px; + background-color: #ededed; + overflow: hidden; + flex-direction: row; + justify-content: center; + border-radius: 8px; + margin-bottom: 0.5em; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.1); + text-decoration: none; + color: var(--text-default); +} + +.post-body .open-graph img.og-image { + flex-grow: 1; + border-radius: 0; + margin: 0; + padding: 0.25em; + background-color: white; +} + +.open-graph .og-container { + flex-grow: 2; + display: flex; + flex-direction: column; + justify-content: center; + padding: 0.5em; + gap: 4px; +} + +.post-body .youtube-embed { + position: relative; + margin: 1em auto; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + border-radius: 1em; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.05); +} + .post-body aside { background-color: rgba(231, 241, 231); border-radius: 4px; @@ -193,6 +238,7 @@ article.post .post-body hr::before { .post-body aside p { margin: 0; } + .dark .post-body p > code, .dark .post-body li > code, .dark .post-body strong > code,