From 5217d00161f1d24dbe57f320bedba3a9afd3fcaf Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Fri, 27 Mar 2026 01:00:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Callout=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=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/Callout.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/entities/post/detail/Callout.tsx diff --git a/app/entities/post/detail/Callout.tsx b/app/entities/post/detail/Callout.tsx new file mode 100644 index 0000000..5fdfb10 --- /dev/null +++ b/app/entities/post/detail/Callout.tsx @@ -0,0 +1,22 @@ +'use client'; +import { ReactNode } from 'react'; + +interface CalloutProps { + emoji?: string; + children?: ReactNode; +} + +const Callout = ({ emoji, children }: CalloutProps) => { + return ( +
+ {emoji && ( + {emoji} + )} +
+ {children} +
+
+ ); +}; + +export default Callout; From 9f6ce735d08666cf8c9950e9f2800b9a5d63fc15 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Fri, 27 Mar 2026 01:01:06 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20Callout=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20reh?= =?UTF-8?q?ype=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EB=8A=94=20=EC=9C=A0=ED=8B=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/detail/PostBody.tsx | 7 +++++-- app/entities/post/write/BlogForm.tsx | 9 ++++++-- app/lib/utils/rehypeUtils.ts | 30 +++++++++++---------------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/entities/post/detail/PostBody.tsx b/app/entities/post/detail/PostBody.tsx index 6204292..455baf1 100644 --- a/app/entities/post/detail/PostBody.tsx +++ b/app/entities/post/detail/PostBody.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import LoadingIndicator from '@/app/entities/common/Loading/LoadingIndicator'; import ImageZoomOverlayContainer from '@/app/entities/common/Overlay/Image/ImageZoomOverlayContainer'; import Overlay from '@/app/entities/common/Overlay/Overlay'; +import Callout from '@/app/entities/post/detail/Callout'; import OgLinkCard from '@/app/entities/post/detail/OgLinkCard'; import PostTOC from '@/app/entities/post/detail/PostTOC'; import TagBox from '@/app/entities/post/tags/TagBox'; @@ -10,7 +11,7 @@ import useOverlay from '@/app/hooks/common/useOverlay'; import useTheme from '@/app/hooks/useTheme'; import MDEditor from '@uiw/react-md-editor'; import { - asideStyleRewrite, + asideToCallout, addDescriptionUnderImage, renderYoutubeEmbed, renderOpenGraph, @@ -79,9 +80,11 @@ const PostBody = ({ content, tags, loading }: Props) => { components={{ ogcard: ({ href }: { href?: string }) => href ? : null, + callout: ({ emoji, children }: { emoji?: string; children?: React.ReactNode }) => + {children}, } as any} rehypeRewrite={(node, index?, parent?) => { - asideStyleRewrite(node); + asideToCallout(node); renderOpenGraph(node, index, parent as Element | undefined); renderYoutubeEmbed( node, diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index 76efb49..880370b 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -20,8 +20,9 @@ import { usePasteImageUpload } from '@/app/hooks/post/usePasteImageUpload'; import usePost from '@/app/hooks/post/usePost'; import useTheme from '@/app/hooks/useTheme'; import useToast from '@/app/hooks/useToast'; +import Callout from '@/app/entities/post/detail/Callout'; import { - asideStyleRewrite, + asideToCallout, addDescriptionUnderImage, renderYoutubeEmbed, createImageClickHandler, @@ -289,8 +290,12 @@ const BlogForm = () => { wrapperElement: { 'data-color-mode': theme, }, + components: { + callout: ({ emoji, children }: { emoji?: string; children?: React.ReactNode }) => + {children}, + } as any, rehypeRewrite: (node, index?, parent?) => { - asideStyleRewrite(node); + asideToCallout(node); renderYoutubeEmbed( node, index || 0, diff --git a/app/lib/utils/rehypeUtils.ts b/app/lib/utils/rehypeUtils.ts index f9cede1..b27bb2c 100644 --- a/app/lib/utils/rehypeUtils.ts +++ b/app/lib/utils/rehypeUtils.ts @@ -5,27 +5,21 @@ import { SelectedImage } from '../../entities/post/detail/PostBody'; /** - * aside 태그의 첫 번째 텍스트 노드를 emoji span으로 래핑 + * aside 태그를 callout 커스텀 컴포넌트 노드로 변환 + * 첫 번째 텍스트 노드를 emoji prop으로 추출하고 나머지는 children으로 유지 */ -export const asideStyleRewrite = (node: any) => { +export const asideToCallout = (node: any) => { if (node.type === 'element' && node.tagName === 'aside') { - for (const child of [...node.children]) { - if (node.children[0] === child && child.type === 'text') { - node.children[0] = { - type: 'element', - tagName: 'span', - properties: { - className: 'aside-emoji', - }, - children: [ - { - type: 'text', - value: child.value!, - }, - ], - }; - } + const firstChild = node.children[0]; + let emoji = ''; + + if (firstChild?.type === 'text') { + emoji = firstChild.value.trim(); + node.children = node.children.slice(1); } + + node.tagName = 'callout'; + node.properties = { ...node.properties, emoji }; } }; From 336ebe14720402ba6cbf96a7cc99e302c44fb0e4 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Fri, 27 Mar 2026 01:05:11 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20Callout=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A4=91=EB=B3=B5=20=EC=9E=84=ED=8F=AC?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/entities/post/write/BlogForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/entities/post/write/BlogForm.tsx b/app/entities/post/write/BlogForm.tsx index 880370b..3e94ad6 100644 --- a/app/entities/post/write/BlogForm.tsx +++ b/app/entities/post/write/BlogForm.tsx @@ -6,6 +6,7 @@ import { useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import ImageZoomOverlayContainer from '@/app/entities/common/Overlay/Image/ImageZoomOverlayContainer'; import Overlay from '@/app/entities/common/Overlay/Overlay'; +import Callout from '@/app/entities/post/detail/Callout'; import DraftListOverlay from '@/app/entities/post/write/DraftListOverlay'; import PostMetadataForm from '@/app/entities/post/write/PostMetadataForm'; import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons'; @@ -20,7 +21,6 @@ import { usePasteImageUpload } from '@/app/hooks/post/usePasteImageUpload'; import usePost from '@/app/hooks/post/usePost'; import useTheme from '@/app/hooks/useTheme'; import useToast from '@/app/hooks/useToast'; -import Callout from '@/app/entities/post/detail/Callout'; import { asideToCallout, addDescriptionUnderImage,