diff --git a/content/posts/JOURNAL/2025-recap.mdx b/content/posts/JOURNAL/2025-recap.mdx
index cb89006..9696a05 100644
--- a/content/posts/JOURNAL/2025-recap.mdx
+++ b/content/posts/JOURNAL/2025-recap.mdx
@@ -241,16 +241,26 @@ draft: false
caption="팀 프로젝트"
/>
- - COMMA : 오락실 감성을 담은 미니게임과 커뮤니티로 ‘잠깐의 쉼표’를 제공하는
- 플랫폼 `Vue`, `Kaplay`
- - RoomE : 몰입감 있는 3D 공간에서 ‘나’를 표현하고, 취향
- 기반으로 연결되는 힐링 커뮤니티 플랫폼 `React` `R3F`
- - RoomE-BO : RoomE의
- 백오피스, GA를 통한 데이터 수집 후 시각화 통계 자료 제공 `React` , `Recharts`
- - Slice : 오늘의 할 일을 작은 조각으로 나누어 가볍게 완성해가는 Todo 서비스
- `Next`, `Storybook` , `Tanstak`
+
+
+• COMMA : 오락실 감성을 담은 미니게임과 커뮤니티로 ‘잠깐의 쉼표’를 제공하는 플랫폼{" "}
+COMMA
+
+
+• RoomE : 몰입감 있는 3D 공간에서 ‘나’를 표현하고 취향 기반으로 연결되는 힐링 커뮤니티 플랫폼{" "}
+RoomE
+
+
+• RoomE-BO : RoomE의 백오피스, 데이터 수집 및 시각화 제공{" "}
+RoomE-BO
+
+
+• Slice : 오늘의 할 일을 작은 조각으로 나누어 완성해가는 Todo 서비스{" "}
+Slice
+
+
- - MediaWave : 지금 뜨는 영화·시리즈를 한눈에, 취향 맞춤 추천과 솔직한 리뷰로
- 즐기는 커뮤니티 `Firebase`, `Redux`
- - RoomE 리팩토링 : 기존 2D로 제작되었던
- 음악 페이지를 `R3F` + `Three.js` 도입하여 3D로 리팩토링 진행
- - B-log : 개인
- 기술 블로그 `Next` , `Supabase` , `Velite`
+
+
+• MediaWave : 지금 뜨는 영화·시리즈를 한눈에, 취향 맞춤 추천과 솔직한 리뷰로 즐기는 커뮤니티{" "}
+MediaWave
+
+
+• RoomE 리팩토링 : 기존 2D로 제작되었던 음악 페이지를 3D 기술을 도입하여 리팩토링 진행 {" "}
+RoomE
+
+
+• B-log : 개인 기술 블로그{" "}
+B-log
---
diff --git a/package.json b/package.json
index c13df27..2f2bbf7 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,8 @@
"react-dom": "19.2.0",
"react-icons": "^5.5.0",
"rehype-autolink-headings": "^7.1.0",
- "rehype-slug": "^6.0.0"
+ "rehype-slug": "^6.0.0",
+ "remark-link-card-plus": "^0.7.3"
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d978830..93f059f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -50,6 +50,9 @@ importers:
rehype-slug:
specifier: ^6.0.0
version: 6.0.0
+ remark-link-card-plus:
+ specifier: ^0.7.3
+ version: 0.7.3
devDependencies:
'@svgr/webpack':
specifier: ^8.1.0
@@ -767,6 +770,9 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+ '@borewit/text-codec@0.2.1':
+ resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==}
+
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -1818,6 +1824,13 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
+ '@tokenizer/inflate@0.4.1':
+ resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
+ engines: {node: '>=18'}
+
+ '@tokenizer/token@0.3.0':
+ resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
+
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
@@ -2384,6 +2397,16 @@ packages:
character-reference-invalid@2.0.1:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+ chardet@2.1.1:
+ resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
+
+ cheerio-select@2.1.0:
+ resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
+
+ cheerio@1.1.2:
+ resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==}
+ engines: {node: '>=20.18.1'}
+
ci-info@4.3.1:
resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}
engines: {node: '>=8'}
@@ -2640,6 +2663,9 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ encoding-sniffer@0.2.1:
+ resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
+
enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
@@ -2911,6 +2937,10 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
+ file-type@21.3.0:
+ resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==}
+ engines: {node: '>=20'}
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -3138,6 +3168,12 @@ packages:
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+ htmlparser2@10.0.0:
+ resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==}
+
+ htmlparser2@8.0.2:
+ resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
+
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
@@ -3159,6 +3195,13 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
+ iconv-lite@0.7.2:
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
+ engines: {node: '>=0.10.0'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -3292,6 +3335,10 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-plain-object@5.0.0:
+ resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+ engines: {node: '>=0.10.0'}
+
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@@ -4050,6 +4097,10 @@ packages:
oniguruma-to-es@4.3.4:
resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
+ open-graph-scraper@6.11.0:
+ resolution: {integrity: sha512-KkO3qMMzJj9KYGtCl19dRtncb+RuBiG/P9BgukcAG4p2w9wSAWTE90vL6/xqth1K9ThkYF/+xfTGrVvU79TJtQ==}
+ engines: {node: '>=20.0.0'}
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -4099,6 +4150,15 @@ packages:
parse-numeric-range@1.3.0:
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
+ parse-srcset@1.0.2:
+ resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
+
+ parse5-htmlparser2-tree-adapter@7.1.0:
+ resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
+
+ parse5-parser-stream@7.1.2:
+ resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
+
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
@@ -4336,6 +4396,10 @@ packages:
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+ remark-link-card-plus@0.7.3:
+ resolution: {integrity: sha512-abWy0PeihkWYvQP2rPSORdXviJrpn/v7K1P7PQyhqz6JIQs55Jwb0QHcnykCmjIdLYKUL2UmTGfva2yB8iakUw==}
+ engines: {node: '>=20'}
+
remark-mdx@3.1.1:
resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==}
@@ -4401,6 +4465,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ sanitize-html@2.17.0:
+ resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==}
+
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
@@ -4607,6 +4674,10 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ strtok3@10.3.4:
+ resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==}
+ engines: {node: '>=18'}
+
style-to-js@1.1.19:
resolution: {integrity: sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ==}
@@ -4691,6 +4762,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ token-types@6.1.2:
+ resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==}
+ engines: {node: '>=14.16'}
+
tough-cookie@5.1.2:
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
engines: {node: '>=16'}
@@ -4807,6 +4882,10 @@ packages:
engines: {node: '>=0.8.0'}
hasBin: true
+ uint8array-extras@1.5.0:
+ resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
+ engines: {node: '>=18'}
+
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -4814,6 +4893,10 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici@7.18.2:
+ resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==}
+ engines: {node: '>=20.18.1'}
+
unicode-canonical-property-names-ecmascript@2.0.1:
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
engines: {node: '>=4'}
@@ -5861,6 +5944,8 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
+ '@borewit/text-codec@0.2.1': {}
+
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -6902,6 +6987,15 @@ snapshots:
dependencies:
'@testing-library/dom': 10.4.1
+ '@tokenizer/inflate@0.4.1':
+ dependencies:
+ debug: 4.4.3
+ token-types: 6.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tokenizer/token@0.3.0': {}
+
'@trysound/sax@0.2.0': {}
'@tsconfig/node10@1.0.11': {}
@@ -7496,6 +7590,31 @@ snapshots:
character-reference-invalid@2.0.1: {}
+ chardet@2.1.1: {}
+
+ cheerio-select@2.1.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-select: 5.2.2
+ css-what: 6.2.2
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+
+ cheerio@1.1.2:
+ dependencies:
+ cheerio-select: 2.1.0
+ dom-serializer: 2.0.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ encoding-sniffer: 0.2.1
+ htmlparser2: 10.0.0
+ parse5: 7.3.0
+ parse5-htmlparser2-tree-adapter: 7.1.0
+ parse5-parser-stream: 7.1.2
+ undici: 7.18.2
+ whatwg-mimetype: 4.0.0
+
ci-info@4.3.1: {}
cjs-module-lexer@2.1.1: {}
@@ -7734,6 +7853,11 @@ snapshots:
emoji-regex@9.2.2: {}
+ encoding-sniffer@0.2.1:
+ dependencies:
+ iconv-lite: 0.6.3
+ whatwg-encoding: 3.1.1
+
enhanced-resolve@5.18.3:
dependencies:
graceful-fs: 4.2.11
@@ -8202,6 +8326,15 @@ snapshots:
dependencies:
flat-cache: 4.0.1
+ file-type@21.3.0:
+ dependencies:
+ '@tokenizer/inflate': 0.4.1
+ strtok3: 10.3.4
+ token-types: 6.1.2
+ uint8array-extras: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -8494,6 +8627,20 @@ snapshots:
html-void-elements@3.0.0: {}
+ htmlparser2@10.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 6.0.1
+
+ htmlparser2@8.0.2:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 4.5.0
+
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
@@ -8516,6 +8663,12 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
+ iconv-lite@0.7.2:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ieee754@1.2.1: {}
+
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -8641,6 +8794,8 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-plain-object@5.0.0: {}
+
is-potential-custom-element-name@1.0.1: {}
is-regex@1.2.1:
@@ -9856,6 +10011,13 @@ snapshots:
regex: 6.1.0
regex-recursion: 6.0.2
+ open-graph-scraper@6.11.0:
+ dependencies:
+ chardet: 2.1.1
+ cheerio: 1.1.2
+ iconv-lite: 0.7.2
+ undici: 7.18.2
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -9919,6 +10081,17 @@ snapshots:
parse-numeric-range@1.3.0: {}
+ parse-srcset@1.0.2: {}
+
+ parse5-htmlparser2-tree-adapter@7.1.0:
+ dependencies:
+ domhandler: 5.0.3
+ parse5: 7.3.0
+
+ parse5-parser-stream@7.1.2:
+ dependencies:
+ parse5: 7.3.0
+
parse5@7.3.0:
dependencies:
entities: 6.0.1
@@ -10194,6 +10367,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ remark-link-card-plus@0.7.3:
+ dependencies:
+ file-type: 21.3.0
+ open-graph-scraper: 6.11.0
+ sanitize-html: 2.17.0
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
remark-mdx@3.1.1:
dependencies:
mdast-util-mdx: 3.0.0
@@ -10277,6 +10460,15 @@ snapshots:
safer-buffer@2.1.2: {}
+ sanitize-html@2.17.0:
+ dependencies:
+ deepmerge: 4.3.1
+ escape-string-regexp: 4.0.0
+ htmlparser2: 8.0.2
+ is-plain-object: 5.0.0
+ parse-srcset: 1.0.2
+ postcss: 8.5.6
+
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
@@ -10550,6 +10742,10 @@ snapshots:
strip-json-comments@3.1.1: {}
+ strtok3@10.3.4:
+ dependencies:
+ '@tokenizer/token': 0.3.0
+
style-to-js@1.1.19:
dependencies:
style-to-object: 1.0.12
@@ -10631,6 +10827,12 @@ snapshots:
dependencies:
is-number: 7.0.0
+ token-types@6.1.2:
+ dependencies:
+ '@borewit/text-codec': 0.2.1
+ '@tokenizer/token': 0.3.0
+ ieee754: 1.2.1
+
tough-cookie@5.1.2:
dependencies:
tldts: 6.1.86
@@ -10753,6 +10955,8 @@ snapshots:
uglify-js@3.19.3:
optional: true
+ uint8array-extras@1.5.0: {}
+
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -10762,6 +10966,8 @@ snapshots:
undici-types@6.21.0: {}
+ undici@7.18.2: {}
+
unicode-canonical-property-names-ecmascript@2.0.1: {}
unicode-match-property-ecmascript@2.0.0:
diff --git a/src/components/mdx/LinkBadge.tsx b/src/components/mdx/LinkBadge.tsx
new file mode 100644
index 0000000..402bfc3
--- /dev/null
+++ b/src/components/mdx/LinkBadge.tsx
@@ -0,0 +1,47 @@
+import { ArrowUpRight } from "lucide-react";
+import Link from "next/link";
+import { ReactNode } from "react";
+import { FaGithub } from "react-icons/fa";
+import { PiRocketLaunchFill } from "react-icons/pi";
+import { SiVelog } from "react-icons/si";
+
+type LinkBadgeProps = {
+ href: string;
+ children: ReactNode;
+ icon?: "github" | "deploy" | "velog";
+ variant?: "default" | "blue" | "green";
+};
+
+export default function LinkBadge({
+ href,
+ children,
+ icon,
+ variant = "default",
+}: LinkBadgeProps) {
+ const icons = {
+ github: ,
+ deploy: ,
+ velog: ,
+ };
+
+ const variantClass = {
+ default:
+ "border-neutral-300/70 text-neutral-800 bg-white dark:bg-neutral-900 dark:text-neutral-200 dark:border-neutral-700",
+ blue: "border-blue-300/70 text-blue-700 bg-blue-50 dark:text-blue-300 dark:bg-blue-900/30 dark:border-blue-700",
+ green:
+ "border-emerald-300/70 text-emerald-700 bg-emerald-50 dark:text-emerald-300 dark:bg-emerald-900/30 dark:border-emerald-700",
+ }[variant];
+
+ return (
+
+ {icon && {icons[icon]}}
+ {children}
+
+
+ );
+}
diff --git a/src/components/mdx/PostMention.tsx b/src/components/mdx/PostMention.tsx
new file mode 100644
index 0000000..59f139c
--- /dev/null
+++ b/src/components/mdx/PostMention.tsx
@@ -0,0 +1,30 @@
+import { velitePosts } from "@/lib/posts";
+import Link from "next/link";
+
+type Props = {
+ slug: string;
+};
+
+export default function PostMention({ slug }: Props) {
+ const post = velitePosts.find((p) => p.slug === slug);
+
+ if (!post) {
+ return (
+
+ @{slug}
+
+ );
+ }
+
+ return (
+
+ {post.title}
+
+ {post.category} · {post.date}
+
+
+ );
+}
diff --git a/src/components/mdx/index.ts b/src/components/mdx/index.ts
index 3f038ed..78a34bc 100644
--- a/src/components/mdx/index.ts
+++ b/src/components/mdx/index.ts
@@ -1,4 +1,6 @@
export { default as Callout } from "./Callout";
+export { default as CodeBlockFigure } from "./CodeBlock";
export { default as Figure } from "./Figure";
+export { default as LinkBadge } from "./LinkBadge";
+export { default as PostMention } from "./PostMention";
export { default as Quote } from "./Quote";
-export { default as CodeBlockFigure } from "./CodeBlock";
diff --git a/src/styles/mdx.css b/src/styles/mdx.css
index 9d2605c..68fa566 100644
--- a/src/styles/mdx.css
+++ b/src/styles/mdx.css
@@ -107,3 +107,7 @@ Inline code
.prose :where(code):not(:where(pre code)) {
white-space: normal;
}
+
+.prose a.no-underline {
+ text-decoration: none !important;
+}
\ No newline at end of file
diff --git a/velite.config.ts b/velite.config.ts
index d41964e..05d5f0f 100644
--- a/velite.config.ts
+++ b/velite.config.ts
@@ -3,6 +3,7 @@ import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import remarkGfm from "remark-gfm";
+import remarkLinkCard from "remark-link-card-plus";
import { defineCollection, defineConfig, s } from "velite";
const capitalizeFirst = (v: string) => {
@@ -57,7 +58,15 @@ export default defineConfig({
}),
},
mdx: {
- remarkPlugins: [remarkGfm],
+ remarkPlugins: [
+ remarkGfm,
+ [
+ remarkLinkCard,
+ {
+ className: "link-card",
+ },
+ ],
+ ],
rehypePlugins: [
rehypeSlug,
[