Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/entities/post/detail/Callout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';
import { ReactNode } from 'react';

interface CalloutProps {
emoji?: string;
children?: ReactNode;
}

const Callout = ({ emoji, children }: CalloutProps) => {
return (
<div className="flex gap-3 rounded-lg border border-neutral-200 dark:border-neutral-700 bg-neutral-100 dark:bg-neutral-800/60 px-4 py-3 my-4 not-prose">
{emoji && (
<span className="shrink-0 text-xl leading-7 select-none">{emoji}</span>
)}
<div className="min-w-0 flex-1 text-sm leading-7 text-neutral-800 dark:text-neutral-200 [&>p:last-child]:mb-0 [&>p]:mb-1">
{children}
</div>
</div>
);
};

export default Callout;
7 changes: 5 additions & 2 deletions app/entities/post/detail/PostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ 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';
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,
Expand Down Expand Up @@ -79,9 +80,11 @@ const PostBody = ({ content, tags, loading }: Props) => {
components={{
ogcard: ({ href }: { href?: string }) =>
href ? <OgLinkCard href={href} /> : null,
callout: ({ emoji, children }: { emoji?: string; children?: React.ReactNode }) =>
<Callout emoji={emoji}>{children}</Callout>,
} as any}
rehypeRewrite={(node, index?, parent?) => {
asideStyleRewrite(node);
asideToCallout(node);
renderOpenGraph(node, index, parent as Element | undefined);
renderYoutubeEmbed(
node,
Expand Down
9 changes: 7 additions & 2 deletions app/entities/post/write/BlogForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -21,7 +22,7 @@ import usePost from '@/app/hooks/post/usePost';
import useTheme from '@/app/hooks/useTheme';
import useToast from '@/app/hooks/useToast';
import {
asideStyleRewrite,
asideToCallout,
addDescriptionUnderImage,
renderYoutubeEmbed,
createImageClickHandler,
Expand Down Expand Up @@ -289,8 +290,12 @@ const BlogForm = () => {
wrapperElement: {
'data-color-mode': theme,
},
components: {
callout: ({ emoji, children }: { emoji?: string; children?: React.ReactNode }) =>
<Callout emoji={emoji}>{children}</Callout>,
} as any,
rehypeRewrite: (node, index?, parent?) => {
asideStyleRewrite(node);
asideToCallout(node);
renderYoutubeEmbed(
node,
index || 0,
Expand Down
30 changes: 12 additions & 18 deletions app/lib/utils/rehypeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}
};

Expand Down
Loading