From ca7d15fe668894ed57e10cda1b8e37e14a984d5c Mon Sep 17 00:00:00 2001
From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com>
Date: Wed, 30 Jul 2025 19:12:51 +0900
Subject: [PATCH 1/4] =?UTF-8?q?feat:=20youtube=20embed=20rehype=20?=
=?UTF-8?q?=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/entities/post/detail/PostBody.tsx | 158 +++++++++++++++++++++++++-
app/globals.css | 42 +++++++
2 files changed, 199 insertions(+), 1 deletion(-)
diff --git a/app/entities/post/detail/PostBody.tsx b/app/entities/post/detail/PostBody.tsx
index 53031c4..c61d88e 100644
--- a/app/entities/post/detail/PostBody.tsx
+++ b/app/entities/post/detail/PostBody.tsx
@@ -35,6 +35,156 @@ 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('/')) {
+ const opengraph: Element = {
+ 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: '',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ // 부모가 존재하고 children 배열이 있는 경우
+ if (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: [],
+ };
+ };
+
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..744269a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -173,6 +173,47 @@ article.post .post-body hr::before {
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 +234,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,
From c30480332a60e97b7a211a083630b05271202d85 Mon Sep 17 00:00:00 2001
From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com>
Date: Wed, 30 Jul 2025 19:14:23 +0900
Subject: [PATCH 2/4] style: code block shadow
---
app/globals.css | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/globals.css b/app/globals.css
index 744269a..1e60717 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -167,6 +167,10 @@ 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),
From ec8c49825c4bb8dd4f8fa60bd6e05cb67c7ad6cf Mon Sep 17 00:00:00 2001
From: JeongwooSeo <98446924+ShipFriend0516@users.noreply.github.com>
Date: Wed, 30 Jul 2025 19:18:04 +0900
Subject: [PATCH 3/4] =?UTF-8?q?fix:=20element=20=EA=B4=80=EB=A0=A8=20type?=
=?UTF-8?q?=20error=20=ED=95=B4=EA=B2=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/entities/post/detail/PostBody.tsx | 157 ++++++++++++++------------
1 file changed, 84 insertions(+), 73 deletions(-)
diff --git a/app/entities/post/detail/PostBody.tsx b/app/entities/post/detail/PostBody.tsx
index c61d88e..3f80ffa 100644
--- a/app/entities/post/detail/PostBody.tsx
+++ b/app/entities/post/detail/PostBody.tsx
@@ -45,80 +45,14 @@ const PostBody = ({ content, tags, loading }: Props) => {
const href = aTag.properties?.href;
if (href && href.startsWith('/')) {
- const opengraph: Element = {
- 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: '',
- },
- ],
- },
- ],
- },
- ],
- };
// 부모가 존재하고 children 배열이 있는 경우
- if (parent && parent.children && Array.isArray(parent.children)) {
+ const opengraph = createOpenGraph(href);
+ if (
+ index &&
+ parent &&
+ parent.children &&
+ Array.isArray(parent.children)
+ ) {
// 현재 a 태그 다음 위치에 div 삽입 s
parent.children.splice(index + 1, 0, opengraph);
} else return;
@@ -185,6 +119,83 @@ const PostBody = ({ content, tags, loading }: Props) => {
};
};
+ 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 (
Date: Wed, 30 Jul 2025 19:56:14 +0900
Subject: [PATCH 4/4] =?UTF-8?q?chore:=20og=20=EC=9D=BC=EB=8B=A8=20?=
=?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/entities/post/detail/PostBody.tsx | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/app/entities/post/detail/PostBody.tsx b/app/entities/post/detail/PostBody.tsx
index 3f80ffa..b2fd342 100644
--- a/app/entities/post/detail/PostBody.tsx
+++ b/app/entities/post/detail/PostBody.tsx
@@ -48,7 +48,7 @@ const PostBody = ({ content, tags, loading }: Props) => {
// 부모가 존재하고 children 배열이 있는 경우
const opengraph = createOpenGraph(href);
if (
- index &&
+ index !== undefined &&
parent &&
parent.children &&
Array.isArray(parent.children)
@@ -168,13 +168,13 @@ const PostBody = ({ content, tags, loading }: Props) => {
className: 'og-content',
},
children: [
- {
- type: 'text',
- value: decodeURIComponent(href.split('/').pop()!).replaceAll(
- '-',
- ' '
- ),
- },
+ // {
+ // type: 'text',
+ // value: decodeURIComponent(href.split('/').pop()!).replaceAll(
+ // '-',
+ // ' '
+ // ),
+ // },
],
},
{
@@ -221,7 +221,7 @@ const PostBody = ({ content, tags, loading }: Props) => {
}}
rehypeRewrite={(node, index?, parent?) => {
asideStyleRewrite(node);
- renderOpenGraph(node, index || 0, parent as Element | undefined);
+ // renderOpenGraph(node, index || 0, parent as Element | undefined);
renderYoutubeEmbed(
node,
index || 0,