From 603b708f8b98c9cc9f5a2ad2b8c1d79da1e79aff Mon Sep 17 00:00:00 2001 From: spde3289 Date: Wed, 11 Mar 2026 19:41:17 +0900 Subject: [PATCH 01/11] =?UTF-8?q?chore:=20rehype-pretty-code,=20shiki=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 456 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 2 files changed, 458 insertions(+) diff --git a/package-lock.json b/package-lock.json index 54bf44a..d007f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,11 @@ "react-highlight": "^0.15.0", "react-icons": "^5.4.0", "react-syntax-highlighter": "^15.6.1", + "rehype-pretty-code": "^0.14.3", "remark": "^15.0.1", "remark-gfm": "^4.0.0", "remark-html": "^16.0.1", + "shiki": "^4.0.2", "tailwindcss-filters": "^3.0.0" }, "devDependencies": { @@ -1363,6 +1365,133 @@ "dev": true, "license": "MIT" }, + "node_modules/@shikijs/core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/core/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", + "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", + "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", + "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2935,6 +3064,18 @@ "dev": true, "license": "MIT" }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -4137,6 +4278,128 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-from-parse5/node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-from-parse5/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/hast-util-parse-selector": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", @@ -4239,6 +4502,28 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -6286,6 +6571,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6385,6 +6687,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6853,6 +7173,30 @@ "node": ">=6" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -6874,6 +7218,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-pretty-code": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.14.3.tgz", + "integrity": "sha512-Cz692FeYusTjT5cfFWLc4r7JhgC3/JlJptgUh4iffBxWxUnWW1oqbWFi7jGCeq00DYUm8yzoTnvpocaYa5x82g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "rehype-parse": "^9.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "shiki": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rehype-pretty-code/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/remark": { "version": "15.0.1", "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", @@ -7231,6 +7628,35 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", + "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/core": "4.0.2", + "@shikijs/engine-javascript": "4.0.2", + "@shikijs/engine-oniguruma": "4.0.2", + "@shikijs/langs": "4.0.2", + "@shikijs/themes": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -8182,6 +8608,26 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/vfile-message": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", @@ -8208,6 +8654,16 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 0f628a1..6a443a2 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "react-highlight": "^0.15.0", "react-icons": "^5.4.0", "react-syntax-highlighter": "^15.6.1", + "rehype-pretty-code": "^0.14.3", "remark": "^15.0.1", "remark-gfm": "^4.0.0", "remark-html": "^16.0.1", + "shiki": "^4.0.2", "tailwindcss-filters": "^3.0.0" }, "devDependencies": { From 6e5e31570f77a691389c3db8fd6d0cf272e162ae Mon Sep 17 00:00:00 2001 From: spde3289 Date: Thu, 12 Mar 2026 15:45:00 +0900 Subject: [PATCH 02/11] =?UTF-8?q?chore:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 126 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 10 +++- 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d007f69..9845cd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@next/third-parties": "^15.1.5", "framer-motion": "^12.5.0", + "github-markdown-css": "^5.9.0", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", "lucide-react": "^0.563.0", @@ -20,11 +21,16 @@ "react-icons": "^5.4.0", "react-syntax-highlighter": "^15.6.1", "rehype-pretty-code": "^0.14.3", + "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", "remark": "^15.0.1", - "remark-gfm": "^4.0.0", + "remark-gfm": "^4.0.1", "remark-html": "^16.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", "shiki": "^4.0.2", - "tailwindcss-filters": "^3.0.0" + "tailwindcss-filters": "^3.0.0", + "unified": "^11.0.5" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -4091,6 +4097,24 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-markdown-css": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.9.0.tgz", + "integrity": "sha512-tmT5sY+zvg2302XLYEfH2mtkViIM1SWf2nvYoF5N1ZsO0V6B2qZTiw3GOzw4vpjLygK/KG35qRlPFweHqfzz5w==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4400,6 +4424,28 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/hast-util-parse-selector": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", @@ -7271,6 +7317,56 @@ "@types/unist": "*" } }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/remark": { "version": "15.0.1", "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", @@ -7338,6 +7434,32 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/remark-stringify": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", diff --git a/package.json b/package.json index 6a443a2..44f8fd4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@next/third-parties": "^15.1.5", "framer-motion": "^12.5.0", + "github-markdown-css": "^5.9.0", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", "lucide-react": "^0.563.0", @@ -22,11 +23,16 @@ "react-icons": "^5.4.0", "react-syntax-highlighter": "^15.6.1", "rehype-pretty-code": "^0.14.3", + "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", "remark": "^15.0.1", - "remark-gfm": "^4.0.0", + "remark-gfm": "^4.0.1", "remark-html": "^16.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", "shiki": "^4.0.2", - "tailwindcss-filters": "^3.0.0" + "tailwindcss-filters": "^3.0.0", + "unified": "^11.0.5" }, "devDependencies": { "@eslint/eslintrc": "^3", From a6ba85498612e2e0921153acddbea1837c2c1c3c Mon Sep 17 00:00:00 2001 From: spde3289 Date: Thu, 12 Mar 2026 15:54:49 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=ED=85=8C=EC=9D=BC=EC=9C=88?= =?UTF-8?q?=EB=93=9C=20=EC=9C=A0=ED=8B=B8=ED=95=A8=EC=88=98=20cn=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 21 +++++++++++++++++++++ package.json | 2 ++ src/utils/cn.ts | 6 ++++++ 3 files changed, 29 insertions(+) create mode 100644 src/utils/cn.ts diff --git a/package-lock.json b/package-lock.json index 9845cd2..af0b702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.6.0", "dependencies": { "@next/third-parties": "^15.1.5", + "clsx": "^2.1.1", "framer-motion": "^12.5.0", "github-markdown-css": "^5.9.0", "gray-matter": "^4.0.3", @@ -29,6 +30,7 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "shiki": "^4.0.2", + "tailwind-merge": "^3.5.0", "tailwindcss-filters": "^3.0.0", "unified": "^11.0.5" }, @@ -2738,6 +2740,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -8160,6 +8171,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", diff --git a/package.json b/package.json index 44f8fd4..f738d65 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@next/third-parties": "^15.1.5", + "clsx": "^2.1.1", "framer-motion": "^12.5.0", "github-markdown-css": "^5.9.0", "gray-matter": "^4.0.3", @@ -31,6 +32,7 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "shiki": "^4.0.2", + "tailwind-merge": "^3.5.0", "tailwindcss-filters": "^3.0.0", "unified": "^11.0.5" }, diff --git a/src/utils/cn.ts b/src/utils/cn.ts new file mode 100644 index 0000000..36afe8b --- /dev/null +++ b/src/utils/cn.ts @@ -0,0 +1,6 @@ +import { ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export const cn = (...inputs: ClassValue[]) => { + return twMerge(clsx(inputs)); +}; From e2d87cf1566c0e54760bcc17f1e4fc03e7589a5d Mon Sep 17 00:00:00 2001 From: spde3289 Date: Thu, 12 Mar 2026 16:17:45 +0900 Subject: [PATCH 04/11] =?UTF-8?q?=EC=9E=91=EC=97=85=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 41 +- package.json | 2 +- .../[post]/_components/PostContainer.tsx | 68 +- .../[post]/_components/TableOfContents.tsx | 68 ++ src/components/CopyButton.tsx | 49 + src/components/HighlightCode.tsx | 92 +- src/components/ImageCard.tsx | 30 - src/lib/postParser.ts | 32 +- src/styles/github.css | 238 ---- src/styles/highlight.css | 1032 ----------------- src/styles/post.css | 56 +- tailwind.config.ts | 1 + 12 files changed, 262 insertions(+), 1447 deletions(-) create mode 100644 src/app/blog/[category]/[post]/_components/TableOfContents.tsx create mode 100644 src/components/CopyButton.tsx delete mode 100644 src/components/ImageCard.tsx delete mode 100644 src/styles/github.css delete mode 100644 src/styles/highlight.css diff --git a/package-lock.json b/package-lock.json index af0b702..a53fbe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@next/third-parties": "^15.1.5", "clsx": "^2.1.1", "framer-motion": "^12.5.0", - "github-markdown-css": "^5.9.0", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", "lucide-react": "^0.563.0", @@ -36,6 +35,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@tailwindcss/typography": "^0.5.19", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1515,6 +1515,33 @@ "tslib": "^2.8.0" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -4108,18 +4135,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-markdown-css": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.9.0.tgz", - "integrity": "sha512-tmT5sY+zvg2302XLYEfH2mtkViIM1SWf2nvYoF5N1ZsO0V6B2qZTiw3GOzw4vpjLygK/KG35qRlPFweHqfzz5w==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", diff --git a/package.json b/package.json index f738d65..f307dda 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "@next/third-parties": "^15.1.5", "clsx": "^2.1.1", "framer-motion": "^12.5.0", - "github-markdown-css": "^5.9.0", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", "lucide-react": "^0.563.0", @@ -38,6 +37,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@tailwindcss/typography": "^0.5.19", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/src/app/blog/[category]/[post]/_components/PostContainer.tsx b/src/app/blog/[category]/[post]/_components/PostContainer.tsx index 1333903..15526f6 100644 --- a/src/app/blog/[category]/[post]/_components/PostContainer.tsx +++ b/src/app/blog/[category]/[post]/_components/PostContainer.tsx @@ -1,76 +1,44 @@ "use client"; import HighlightCode from "@/components/HighlightCode"; -import "@/styles/github.css"; -import "@/styles/highlight.css"; -import "@/styles/post.css"; import type { PostMetaData } from "@/types/posts.types"; import { useEffect, useState } from "react"; +import TableOfContents, { type TocItem } from "./TableOfContents"; interface HighlightedCodeProps { contentHtml: string; metadata: PostMetaData; } -const buildHtmlAndToc = (contentHtml: string) => { +// HTML을 수정하지 않고, 단순히 목차 데이터만 뽑아내는 용도로 단순화 +const extractToc = (htmlString: string): TocItem[] => { const parser = new DOMParser(); - const doc = parser.parseFromString(contentHtml, "text/html"); + const doc = parser.parseFromString(htmlString, "text/html"); const headings = Array.from(doc.querySelectorAll("h2, h3")); - doc.querySelectorAll("h2, h3").forEach((el) => (el.id = el.innerHTML)); - - const toc: TocItem[] = headings.map((el, index) => { - const id = - el.textContent?.trim().replace(/\s+/g, "-") || `section-${index}`; - el.id = id; - return { - id, - text: el.textContent || "", - level: el.tagName === "H2" ? 2 : 3, - }; - }); - return { html: doc.body, toc }; -}; - -type TocItem = { - id: string; - text: string; - level: 2 | 3; + return headings.map((el) => ({ + id: el.id, // 서버(rehype-slug)가 이미 달아준 id를 그대로 가져옴 + text: el.textContent || "", + level: el.tagName === "H2" ? 2 : 3, + })); }; const PostContainer = ({ metadata, contentHtml }: HighlightedCodeProps) => { - const [html, setHtml] = useState(""); - const [sub, setSub] = useState([]); - + // // 이제 html 상태를 따로 관리할 필요가 없습니다! + const [toc, setToc] = useState([]); + const [html, setHtml] = useState(contentHtml); useEffect(() => { - const htmlTest = buildHtmlAndToc(contentHtml); - setHtml(htmlTest.html.innerHTML); - setSub(htmlTest.toc); + // 렌더링 후 목차 배열만 추출해서 상태에 저장 + setHtml(contentHtml); + setToc(extractToc(contentHtml)); }, [contentHtml]); return (
+ {/* 원본 HTML을 그대로 꽂아주기만 하면 끝 */} - {/* 목차 영역 */} -
- -
+ {/* Scroll Spy가 적용된 목차 컴포넌트 */} +
); }; diff --git a/src/app/blog/[category]/[post]/_components/TableOfContents.tsx b/src/app/blog/[category]/[post]/_components/TableOfContents.tsx new file mode 100644 index 0000000..1a00bcc --- /dev/null +++ b/src/app/blog/[category]/[post]/_components/TableOfContents.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export type TocItem = { + id: string; + text: string; + level: 2 | 3; +}; + +interface TableOfContentsProps { + toc: TocItem[]; +} + +const TableOfContents = ({ toc }: TableOfContentsProps) => { + const [activeId, setActiveId] = useState(""); + + useEffect(() => { + // 렌더링된 h2, h3 태그들을 모두 찾습니다. + const elements = document.querySelectorAll("h2, h3"); + + // Intersection Observer 설정 + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + }); + }, + { rootMargin: "-80px 0px -80% 0px", threshold: 0.1 }, + ); + + elements.forEach((el) => observer.observe(el)); + + return () => observer.disconnect(); + }, [toc]); + + if (toc.length === 0) return null; + + return ( +
+ +
+ ); +}; + +export default TableOfContents; diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx new file mode 100644 index 0000000..166a035 --- /dev/null +++ b/src/components/CopyButton.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { cn } from "@/utils/cn"; +import { Copy, CopyCheck } from "lucide-react"; +import { useState } from "react"; + +interface CopyButtonProps { + textToCopy: string; +} + +const CopyButton = ({ textToCopy }: CopyButtonProps) => { + const [isCopied, setIsCopied] = useState(false); + + const handleCopy = () => { + if (isCopied) return; + + navigator.clipboard.writeText(textToCopy).then(() => { + setIsCopied(true); + + setTimeout(() => { + setIsCopied(false); + }, 2000); + }); + }; + + return ( + + ); +}; + +export default CopyButton; diff --git a/src/components/HighlightCode.tsx b/src/components/HighlightCode.tsx index 2766f59..5bac9a1 100644 --- a/src/components/HighlightCode.tsx +++ b/src/components/HighlightCode.tsx @@ -1,60 +1,67 @@ "use client"; -import "@/styles/github.css"; -import "@/styles/highlight.css"; +import { useTheme } from "@/contexts/ThemeContext"; import "@/styles/post.css"; import type { PostMetaData } from "@/types/posts.types"; -import { useEffect } from "react"; -import Highlight from "react-highlight"; +import { useEffect, useRef, useState } from "react"; +import { createRoot } from "react-dom/client"; +import CopyButton from "./CopyButton"; interface HighlightedCodeProps { contentHtml: string; metadata: PostMetaData; } -const CopySvg = ``; -const CheckSvg = ``; - const HighlightCode = ({ metadata, contentHtml }: HighlightedCodeProps) => { + const { resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + const contentRef = useRef(null); + useEffect(() => { - const codeBlocks = document.querySelectorAll("pre"); - - codeBlocks.forEach((block) => { - if (block.querySelector(".copy-button")) return; - - const button = document.createElement("button"); - button.innerHTML = CopySvg; - button.className = "copy-button"; // CSS 클래스 이름 추가 - - // 복사 이벤트 핸들러 - button.addEventListener("click", () => { - console.log(button); - const code = block.querySelector("code")?.textContent || ""; - navigator.clipboard.writeText(code).then(() => { - button.innerHTML = CheckSvg; - button.classList.add("check-button"); // check-button 클래스 추가 - button.classList.remove("copy-button"); - - // 2초 후에 check-button 클래스를 제거 - setTimeout(() => { - button.innerHTML = CopySvg; - button.classList.remove("check-button"); - button.classList.add("copy-button"); - }, 2000); - }); + setMounted(true); + }, []); + + useEffect(() => { + const container = contentRef.current; + if (!container) return; + + const addButtons = () => { + const codeBlocks = container.querySelectorAll("pre"); + + codeBlocks.forEach((block) => { + if (block.querySelector(".copy-btn-wrapper")) return; + + const codeText = block.querySelector("code")?.textContent || ""; + + const wrapper = document.createElement("div"); + wrapper.className = "copy-btn-wrapper"; + + // pre 태그 안에 컨테이너 추가 + block.style.position = "relative"; + block.appendChild(wrapper); + + const root = createRoot(wrapper); + root.render(); }); + }; - block.style.position = "relative"; - block.appendChild(button); + addButtons(); + + const observer = new MutationObserver(() => { + addButtons(); }); + + observer.observe(container, { childList: true, subtree: true }); + + return () => observer.disconnect(); }, [contentHtml]); return ( -
+

{metadata.title} @@ -73,7 +80,12 @@ const HighlightCode = ({ metadata, contentHtml }: HighlightedCodeProps) => {

- {contentHtml} + {/* ✅ ref 속성을 추가하여 React 생명주기 내에서 DOM을 추적하도록 합니다. */} +
); }; diff --git a/src/components/ImageCard.tsx b/src/components/ImageCard.tsx deleted file mode 100644 index 96e9c72..0000000 --- a/src/components/ImageCard.tsx +++ /dev/null @@ -1,30 +0,0 @@ -interface ImageCardProps { - img: { - src: string; - alt: string; - options?: { - [key: string]: string | boolean; - }; - }; - size?: string; -} - -const ImageCard = ({ img, size = "size-10" }: ImageCardProps) => { - return ( -
-
- {img.alt} -
-
- {img.alt} -
-
- ); -}; - -export default ImageCard; diff --git a/src/lib/postParser.ts b/src/lib/postParser.ts index c006c93..e8a3622 100644 --- a/src/lib/postParser.ts +++ b/src/lib/postParser.ts @@ -1,9 +1,13 @@ import { BLOG_CONFIG } from "@/constants/blogConfig"; import type { PostMetaData } from "@/types/posts.types"; import matter from "gray-matter"; -import { remark } from "remark"; -import gfm from "remark-gfm"; -import html from "remark-html"; +import rehypePrettyCode from "rehype-pretty-code"; +import rehypeSlug from "rehype-slug"; +import rehypeStringify from "rehype-stringify"; +import remarkGfm from "remark-gfm"; +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import { unified } from "unified"; type Data = { [key: string]: string }; @@ -33,9 +37,25 @@ export const getPlainTextExcerpt = ( return text.slice(0, length); }; -export const convertMarkdownToHtml = async (md: string): Promise => { - const processed = await remark().use(gfm).use(html).process(md); - return String(processed); +// 기존의 convertMarkdownToHtml 함수를 대체합니다. +export const convertMarkdownToHtml = async (markdown: string) => { + const result = await unified() + .use(remarkParse) // 1. 마크다운을 AST(추상 구문 트리)로 파싱 + .use(remarkGfm) + .use(remarkRehype) // 2. 마크다운 트리를 HTML 트리로 변환 + .use(rehypeSlug) + .use(rehypePrettyCode, { + // 3. rehype-pretty-code 적용 (원하는 Shiki 테마 설정) + theme: { + light: "github-light", + dark: "github-dark", + }, + keepBackground: true, // 테마의 기본 배경색 유지 + }) + .use(rehypeStringify) // 4. HTML 트리를 최종 문자열로 변환 + .process(markdown); + + return result.toString(); }; export const parseMarkdownFrontmatter = (fileContents: string) => { diff --git a/src/styles/github.css b/src/styles/github.css deleted file mode 100644 index 0ccd8b6..0000000 --- a/src/styles/github.css +++ /dev/null @@ -1,238 +0,0 @@ -pre code.hljs { - display: block; - overflow-x: auto; - padding: 2em; -} - -code.hljs { - padding: 3px 5px; -} -/*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS -*/ -.hljs { - color: #24292e; - background: #f6f8fa; -} -.hljs-doctag, -.hljs-keyword, -.hljs-meta .hljs-keyword, -.hljs-template-tag, -.hljs-template-variable, -.hljs-type, -.hljs-variable.language_ { - /* prettylights-syntax-keyword */ - color: #d73a49; -} -.hljs-title, -.hljs-title.class_, -.hljs-title.class_.inherited__, -.hljs-title.function_ { - /* prettylights-syntax-entity */ - color: #6f42c1; -} -.hljs-attr, -.hljs-attribute, -.hljs-literal, -.hljs-meta, -.hljs-number, -.hljs-operator, -.hljs-variable, -.hljs-selector-attr, -.hljs-selector-class, -.hljs-selector-id { - /* prettylights-syntax-constant */ - color: #005cc5; -} -.hljs-regexp, -.hljs-string, -.hljs-meta .hljs-string { - /* prettylights-syntax-string */ - color: #032f62; -} -.hljs-built_in, -.hljs-symbol { - /* prettylights-syntax-variable */ - color: #e36209; -} -.hljs-comment, -.hljs-code, -.hljs-formula { - /* prettylights-syntax-comment */ - color: #6a737d; -} -.hljs-name, -.hljs-quote, -.hljs-selector-tag, -.hljs-selector-pseudo { - /* prettylights-syntax-entity-tag */ - color: #22863a; -} -.hljs-subst { - /* prettylights-syntax-storage-modifier-import */ - color: #24292e; -} -.hljs-section { - /* prettylights-syntax-markup-heading */ - color: #005cc5; - font-weight: bold; -} -.hljs-bullet { - /* prettylights-syntax-markup-list */ - color: #735c0f; -} -.hljs-emphasis { - /* prettylights-syntax-markup-italic */ - color: #24292e; - font-style: italic; -} -.hljs-strong { - /* prettylights-syntax-markup-bold */ - color: #24292e; - font-weight: bold; -} -.hljs-addition { - /* prettylights-syntax-markup-inserted */ - color: #22863a; - background-color: #f0fff4; -} -.hljs-deletion { - /* prettylights-syntax-markup-deleted */ - color: #b31d28; - background-color: #ffeef0; -} -/* .hljs-char.escape_, -.hljs-link, -.hljs-params, -.hljs-property, -.hljs-punctuation, -.hljs-tag { - -} */ - -.dark { - pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em; - } - code.hljs { - padding: 3px 5px; - } - /*! - Theme: GitHub Dark - Description: Dark theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-dark - Current colors taken from GitHub's CSS -*/ - .hljs { - color: #c9d1d9; - background: #0d1117; - } - .hljs-doctag, - .hljs-keyword, - .hljs-meta .hljs-keyword, - .hljs-template-tag, - .hljs-template-variable, - .hljs-type, - .hljs-variable.language_ { - /* prettylights-syntax-keyword */ - color: #ff7b72; - } - .hljs-title, - .hljs-title.class_, - .hljs-title.class_.inherited__, - .hljs-title.function_ { - /* prettylights-syntax-entity */ - color: #d2a8ff; - } - .hljs-attr, - .hljs-attribute, - .hljs-literal, - .hljs-meta, - .hljs-number, - .hljs-operator, - .hljs-variable, - .hljs-selector-attr, - .hljs-selector-class, - .hljs-selector-id { - /* prettylights-syntax-constant */ - color: #79c0ff; - } - .hljs-regexp, - .hljs-string, - .hljs-meta .hljs-string { - /* prettylights-syntax-string */ - color: #a5d6ff; - } - .hljs-built_in, - .hljs-symbol { - /* prettylights-syntax-variable */ - color: #ffa657; - } - .hljs-comment, - .hljs-code, - .hljs-formula { - /* prettylights-syntax-comment */ - color: #8b949e; - } - .hljs-name, - .hljs-quote, - .hljs-selector-tag, - .hljs-selector-pseudo { - /* prettylights-syntax-entity-tag */ - color: #7ee787; - } - .hljs-subst { - /* prettylights-syntax-storage-modifier-import */ - color: #c9d1d9; - } - .hljs-section { - /* prettylights-syntax-markup-heading */ - color: #1f6feb; - font-weight: bold; - } - .hljs-bullet { - /* prettylights-syntax-markup-list */ - color: #f2cc60; - } - .hljs-emphasis { - /* prettylights-syntax-markup-italic */ - color: #c9d1d9; - font-style: italic; - } - .hljs-strong { - /* prettylights-syntax-markup-bold */ - color: #c9d1d9; - font-weight: bold; - } - .hljs-addition { - /* prettylights-syntax-markup-inserted */ - color: #aff5b4; - background-color: #033a16; - } - .hljs-deletion { - /* prettylights-syntax-markup-deleted */ - color: #ffdcd7; - background-color: #67060c; - } - /* .hljs-char.escape_, - .hljs-link, - .hljs-params, - .hljs-property, - .hljs-punctuation, - .hljs-tag { - - } */ -} diff --git a/src/styles/highlight.css b/src/styles/highlight.css deleted file mode 100644 index e2b8f36..0000000 --- a/src/styles/highlight.css +++ /dev/null @@ -1,1032 +0,0 @@ -.dark .markdown-body { - color-scheme: dark; - --color-prettylights-syntax-comment: #8b949e; - --color-prettylights-syntax-constant: #79c0ff; - --color-prettylights-syntax-entity: #d2a8ff; - --color-prettylights-syntax-storage-modifier-import: #c9d1d9; - --color-prettylights-syntax-entity-tag: #7ee787; - --color-prettylights-syntax-keyword: #ff7b72; - --color-prettylights-syntax-string: #a5d6ff; - --color-prettylights-syntax-variable: #ffa657; - --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; - --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; - --color-prettylights-syntax-invalid-illegal-bg: #8e1519; - --color-prettylights-syntax-carriage-return-text: #f0f6fc; - --color-prettylights-syntax-carriage-return-bg: #b62324; - --color-prettylights-syntax-string-regexp: #7ee787; - --color-prettylights-syntax-markup-list: #f2cc60; - --color-prettylights-syntax-markup-heading: #1f6feb; - --color-prettylights-syntax-markup-italic: #c9d1d9; - --color-prettylights-syntax-markup-bold: #c9d1d9; - --color-prettylights-syntax-markup-deleted-text: #ffdcd7; - --color-prettylights-syntax-markup-deleted-bg: #67060c; - --color-prettylights-syntax-markup-inserted-text: #aff5b4; - --color-prettylights-syntax-markup-inserted-bg: #033a16; - --color-prettylights-syntax-markup-changed-text: #ffdfb6; - --color-prettylights-syntax-markup-changed-bg: #5a1e02; - --color-prettylights-syntax-markup-ignored-text: #c9d1d9; - --color-prettylights-syntax-markup-ignored-bg: #1158c7; - --color-prettylights-syntax-meta-diff-range: #d2a8ff; - --color-prettylights-syntax-brackethighlighter-angle: #8b949e; - --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; - --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; - --color-fg-default: #c9d1d9; - --color-fg-muted: #8b949e; - --color-fg-subtle: #484f58; - --color-canvas-default: inherit; - --color-canvas-subtle: #0d1117; - --color-border-default: #30363d; - --color-border-muted: #21262d; - --color-neutral-muted: rgba(110, 118, 129, 0.4); - --color-accent-fg: #58a6ff; - --color-accent-emphasis: #1f6feb; - --color-attention-subtle: rgba(187, 128, 9, 0.15); - --color-danger-fg: #f85149; -} - -h2, -h3 { - scroll-margin-top: 80px; -} - -.markdown-body { - color-scheme: light; - --color-prettylights-syntax-comment: #6e7781; - --color-prettylights-syntax-constant: #0550ae; - --color-prettylights-syntax-entity: #8250df; - --color-prettylights-syntax-storage-modifier-import: #24292f; - --color-prettylights-syntax-entity-tag: #116329; - --color-prettylights-syntax-keyword: #cf222e; - --color-prettylights-syntax-string: #0a3069; - --color-prettylights-syntax-variable: #953800; - --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; - --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; - --color-prettylights-syntax-invalid-illegal-bg: #82071e; - --color-prettylights-syntax-carriage-return-text: #f6f8fa; - --color-prettylights-syntax-carriage-return-bg: #cf222e; - --color-prettylights-syntax-string-regexp: #116329; - --color-prettylights-syntax-markup-list: #3b2300; - --color-prettylights-syntax-markup-heading: #0550ae; - --color-prettylights-syntax-markup-italic: #24292f; - --color-prettylights-syntax-markup-bold: #24292f; - --color-prettylights-syntax-markup-deleted-text: #82071e; - --color-prettylights-syntax-markup-deleted-bg: #ffebe9; - --color-prettylights-syntax-markup-inserted-text: #116329; - --color-prettylights-syntax-markup-inserted-bg: #dafbe1; - --color-prettylights-syntax-markup-changed-text: #953800; - --color-prettylights-syntax-markup-changed-bg: #ffd8b5; - --color-prettylights-syntax-markup-ignored-text: #eaeef2; - --color-prettylights-syntax-markup-ignored-bg: #0550ae; - --color-prettylights-syntax-meta-diff-range: #8250df; - --color-prettylights-syntax-brackethighlighter-angle: #57606a; - --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; - --color-prettylights-syntax-constant-other-reference-link: #0a3069; - --color-fg-default: #24292f; - --color-fg-muted: #57606a; - --color-fg-subtle: #6e7781; - --color-canvas-default: #ffffff; - --color-canvas-subtle: #f6f8fa; - --color-border-default: #d0d7de; - --color-border-muted: hsla(210, 18%, 87%, 1); - --color-neutral-muted: rgba(175, 184, 193, 0.2); - --color-accent-fg: #0969da; - --color-accent-emphasis: #0969da; - --color-attention-subtle: #fff8c5; - --color-danger-fg: #cf222e; -} - -.markdown-body { - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - margin: 0; - color: var(--color-fg-default); - background-color: var(--color-canvas-default); - font-size: 16px; - line-height: 1.5; - word-wrap: break-word; -} - -.markdown-body .octicon { - display: inline-block; - fill: currentColor; - vertical-align: text-bottom; -} - -.markdown-body h1:hover .anchor .octicon-link:before, -.markdown-body h2:hover .anchor .octicon-link:before, -.markdown-body h3:hover .anchor .octicon-link:before, -.markdown-body h4:hover .anchor .octicon-link:before, -.markdown-body h5:hover .anchor .octicon-link:before, -.markdown-body h6:hover .anchor .octicon-link:before { - width: 16px; - height: 16px; - content: " "; - display: inline-block; - background-color: currentColor; - -webkit-mask-image: url("data:image/svg+xml,"); - mask-image: url("data:image/svg+xml,"); -} - -.markdown-body details, -.markdown-body figcaption, -.markdown-body figure { - display: block; -} - -.markdown-body summary { - display: list-item; -} - -.markdown-body [hidden] { - display: none !important; -} - -.markdown-body a { - background-color: transparent; - color: var(--color-fg-muted); - text-decoration: none; -} - -.markdown-body a:active, -.markdown-body a:hover { - outline-width: 0; -} - -.markdown-body abbr[title] { - border-bottom: none; - text-decoration: underline dotted; -} - -.markdown-body b, -.markdown-body strong { - font-weight: 600; -} - -.markdown-body dfn { - font-style: italic; -} - -.markdown-body h1 { - margin: 0.67em 0; - font-weight: 600; - padding-bottom: 0.3em; - font-size: 2em; - border-bottom: 1px solid var(--color-border-muted); -} - -.markdown-body mark { - background-color: var(--color-attention-subtle); - color: var(--color-text-primary); -} - -.markdown-body small { - font-size: 90%; -} - -.markdown-body sub, -.markdown-body sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -.markdown-body sub { - bottom: -0.25em; -} - -.markdown-body sup { - top: -0.5em; -} - -.markdown-body img { - border-style: none; - max-width: 100%; - box-sizing: content-box; - background-color: var(--color-canvas-default); -} - -.markdown-body code, -.markdown-body kbd, -.markdown-body pre, -.markdown-body samp { - font-family: monospace, monospace; - font-size: 1em; -} - -.markdown-body figure { - margin: 1em 40px; -} - -.markdown-body hr { - box-sizing: content-box; - overflow: hidden; - background: transparent; - border-bottom: 1px solid var(--color-border-muted); - height: 0.25em; - padding: 0; - margin: 24px 0; - background-color: var(--color-border-default); - border: 0; -} - -.markdown-body input { - font: inherit; - margin: 0; - overflow: visible; - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -.markdown-body [type="button"]::-moz-focus-inner, -.markdown-body [type="reset"]::-moz-focus-inner, -.markdown-body [type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -.markdown-body [type="button"]:-moz-focusring, -.markdown-body [type="reset"]:-moz-focusring, -.markdown-body [type="submit"]:-moz-focusring { - outline: 1px dotted ButtonText; -} - -.markdown-body [type="checkbox"], -.markdown-body [type="radio"] { - box-sizing: border-box; - padding: 0; -} - -.markdown-body [type="number"]::-webkit-inner-spin-button, -.markdown-body [type="number"]::-webkit-outer-spin-button { - height: auto; -} - -.markdown-body [type="search"] { - outline-offset: -2px; -} - -.markdown-body [type="search"]::-webkit-search-cancel-button, -.markdown-body [type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -.markdown-body ::-webkit-input-placeholder { - color: inherit; - opacity: 0.54; -} - -.markdown-body ::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit; -} - -.markdown-body a:hover { - text-decoration: underline; -} - -.markdown-body hr::before { - display: table; - content: ""; -} - -.markdown-body hr::after { - display: table; - clear: both; - content: ""; -} - -.markdown-body table { - border-spacing: 0; - border-collapse: collapse; - display: block; - width: max-content; - max-width: 100%; - overflow: auto; -} - -.markdown-body td, -.markdown-body th { - padding: 0; -} - -.markdown-body details summary { - cursor: pointer; -} - -.markdown-body details:not([open]) > *:not(summary) { - display: none !important; -} - -.markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, - Liberation Mono, monospace; - line-height: 10px; - color: var(--color-fg-default); - vertical-align: middle; - background-color: var(--color-canvas-subtle); - border: solid 1px var(--color-neutral-muted); - border-bottom-color: var(--color-neutral-muted); - border-radius: 6px; - box-shadow: inset 0 -1px 0 var(--color-neutral-muted); -} - -.markdown-body h1, -.markdown-body h2, -.markdown-body h3, -.markdown-body h4, -.markdown-body h5, -.markdown-body h6 { - margin-top: 24px; - margin-bottom: 16px; - font-weight: 600; - line-height: 1.25; -} - -.markdown-body h2 { - font-weight: 600; - padding-bottom: 0.3em; - font-size: 1.5em; - border-bottom: 1px solid var(--color-border-muted); -} - -.markdown-body h3 { - font-weight: 600; - font-size: 1.25em; -} - -.markdown-body h4 { - font-weight: 600; - font-size: 1em; -} - -.markdown-body h5 { - font-weight: 600; - font-size: 0.875em; -} - -.markdown-body h6 { - font-weight: 600; - font-size: 0.85em; - color: var(--color-fg-muted); -} - -.markdown-body p { - margin-top: 0; - margin-bottom: 10px; -} - -.markdown-body blockquote { - margin: 0; - padding: 0 1em; - color: var(--color-fg-muted); - border-left: 0.25em solid var(--color-border-default); -} - -.markdown-body ul, -.markdown-body ol { - margin-top: 0; - margin-bottom: 0; - padding-left: 2em; -} - -.markdown-body ol ol, -.markdown-body ul ol { - list-style-type: lower-roman; -} - -.markdown-body ul ul ol, -.markdown-body ul ol ol, -.markdown-body ol ul ol, -.markdown-body ol ol ol { - list-style-type: lower-alpha; -} - -.markdown-body dd { - margin-left: 0; -} - -.markdown-body tt, -.markdown-body code { - font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, - Liberation Mono, monospace; - font-size: 12px; -} - -.markdown-body pre { - margin-top: 0; - margin-bottom: 0; - font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, - Liberation Mono, monospace; - font-size: 12px; - word-wrap: normal; -} - -.markdown-body .octicon { - display: inline-block; - overflow: visible !important; - vertical-align: text-bottom; - fill: currentColor; -} - -.markdown-body ::placeholder { - color: var(--color-fg-subtle); - opacity: 1; -} - -.markdown-body input::-webkit-outer-spin-button, -.markdown-body input::-webkit-inner-spin-button { - margin: 0; - -webkit-appearance: none; - appearance: none; -} - -.markdown-body .pl-c { - color: var(--color-prettylights-syntax-comment); -} - -.markdown-body .pl-c1, -.markdown-body .pl-s .pl-v { - color: var(--color-prettylights-syntax-constant); -} - -.markdown-body .pl-e, -.markdown-body .pl-en { - color: var(--color-prettylights-syntax-entity); -} - -.markdown-body .pl-smi, -.markdown-body .pl-s .pl-s1 { - color: var(--color-prettylights-syntax-storage-modifier-import); -} - -.markdown-body .pl-ent { - color: var(--color-prettylights-syntax-entity-tag); -} - -.markdown-body .pl-k { - color: var(--color-prettylights-syntax-keyword); -} - -.markdown-body .pl-s, -.markdown-body .pl-pds, -.markdown-body .pl-s .pl-pse .pl-s1, -.markdown-body .pl-sr, -.markdown-body .pl-sr .pl-cce, -.markdown-body .pl-sr .pl-sre, -.markdown-body .pl-sr .pl-sra { - color: var(--color-prettylights-syntax-string); -} - -.markdown-body .pl-v, -.markdown-body .pl-smw { - color: var(--color-prettylights-syntax-variable); -} - -.markdown-body .pl-bu { - color: var(--color-prettylights-syntax-brackethighlighter-unmatched); -} - -.markdown-body .pl-ii { - color: var(--color-prettylights-syntax-invalid-illegal-text); - background-color: var(--color-prettylights-syntax-invalid-illegal-bg); -} - -.markdown-body .pl-c2 { - color: var(--color-prettylights-syntax-carriage-return-text); - background-color: var(--color-prettylights-syntax-carriage-return-bg); -} - -.markdown-body .pl-sr .pl-cce { - font-weight: bold; - color: var(--color-prettylights-syntax-string-regexp); -} - -.markdown-body .pl-ml { - color: var(--color-prettylights-syntax-markup-list); -} - -.markdown-body .pl-mh, -.markdown-body .pl-mh .pl-en, -.markdown-body .pl-ms { - font-weight: bold; - color: var(--color-prettylights-syntax-markup-heading); -} - -.markdown-body .pl-mi { - font-style: italic; - color: var(--color-prettylights-syntax-markup-italic); -} - -.markdown-body .pl-mb { - font-weight: bold; - color: var(--color-prettylights-syntax-markup-bold); -} - -.markdown-body .pl-md { - color: var(--color-prettylights-syntax-markup-deleted-text); - background-color: var(--color-prettylights-syntax-markup-deleted-bg); -} - -.markdown-body .pl-mi1 { - color: var(--color-prettylights-syntax-markup-inserted-text); - background-color: var(--color-prettylights-syntax-markup-inserted-bg); -} - -.markdown-body .pl-mc { - color: var(--color-prettylights-syntax-markup-changed-text); - background-color: var(--color-prettylights-syntax-markup-changed-bg); -} - -.markdown-body .pl-mi2 { - color: var(--color-prettylights-syntax-markup-ignored-text); - background-color: var(--color-prettylights-syntax-markup-ignored-bg); -} - -.markdown-body .pl-mdr { - font-weight: bold; - color: var(--color-prettylights-syntax-meta-diff-range); -} - -.markdown-body .pl-ba { - color: var(--color-prettylights-syntax-brackethighlighter-angle); -} - -.markdown-body .pl-sg { - color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); -} - -.markdown-body .pl-corl { - text-decoration: underline; - color: var(--color-prettylights-syntax-constant-other-reference-link); -} - -.markdown-body [data-catalyst] { - display: block; -} - -.markdown-body g-emoji { - font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 1em; - font-style: normal !important; - font-weight: 400; - line-height: 1; - vertical-align: -0.075em; -} - -.markdown-body g-emoji img { - width: 1em; - height: 1em; -} - -.markdown-body::before { - display: table; - content: ""; -} - -.markdown-body::after { - display: table; - clear: both; - content: ""; -} - -.markdown-body > *:first-child { - margin-top: 0 !important; -} - -.markdown-body > *:last-child { - margin-bottom: 0 !important; -} - -.markdown-body a:not([href]) { - color: inherit; - text-decoration: none; -} - -.markdown-body .absent { - color: var(--color-danger-fg); -} - -.markdown-body .anchor { - float: left; - padding-right: 4px; - margin-left: -20px; - line-height: 1; -} - -.markdown-body .anchor:focus { - outline: none; -} - -.markdown-body p, -.markdown-body blockquote, -.markdown-body ul, -.markdown-body ol, -.markdown-body dl, -.markdown-body table, -.markdown-body pre, -.markdown-body details { - margin-top: 0; - margin-bottom: 16px; -} - -.markdown-body blockquote > :first-child { - margin-top: 0; -} - -.markdown-body blockquote > :last-child { - margin-bottom: 0; -} - -.markdown-body sup > a::before { - content: "["; -} - -.markdown-body sup > a::after { - content: "]"; -} - -.markdown-body h1 .octicon-link, -.markdown-body h2 .octicon-link, -.markdown-body h3 .octicon-link, -.markdown-body h4 .octicon-link, -.markdown-body h5 .octicon-link, -.markdown-body h6 .octicon-link { - color: var(--color-fg-default); - vertical-align: middle; - visibility: hidden; -} - -.markdown-body h1:hover .anchor, -.markdown-body h2:hover .anchor, -.markdown-body h3:hover .anchor, -.markdown-body h4:hover .anchor, -.markdown-body h5:hover .anchor, -.markdown-body h6:hover .anchor { - text-decoration: none; -} - -.markdown-body h1:hover .anchor .octicon-link, -.markdown-body h2:hover .anchor .octicon-link, -.markdown-body h3:hover .anchor .octicon-link, -.markdown-body h4:hover .anchor .octicon-link, -.markdown-body h5:hover .anchor .octicon-link, -.markdown-body h6:hover .anchor .octicon-link { - visibility: visible; -} - -.markdown-body h1 tt, -.markdown-body h1 code, -.markdown-body h2 tt, -.markdown-body h2 code, -.markdown-body h3 tt, -.markdown-body h3 code, -.markdown-body h4 tt, -.markdown-body h4 code, -.markdown-body h5 tt, -.markdown-body h5 code, -.markdown-body h6 tt, -.markdown-body h6 code { - padding: 0 0.2em; - font-size: inherit; -} - -.markdown-body ul.no-list, -.markdown-body ol.no-list { - padding: 0; - list-style-type: none; -} - -.markdown-body ol[type="1"] { - list-style-type: decimal; -} - -.markdown-body ol[type="a"] { - list-style-type: lower-alpha; -} - -.markdown-body ol[type="i"] { - list-style-type: lower-roman; -} - -.markdown-body div > ol:not([type]) { - list-style-type: decimal; -} - -.markdown-body ul ul, -.markdown-body ul ol, -.markdown-body ol ol, -.markdown-body ol ul { - margin-top: 0; - margin-bottom: 0; -} - -.markdown-body li > p { - margin-top: 16px; -} - -.markdown-body li + li { - margin-top: 0.25em; -} - -.markdown-body dl { - padding: 0; -} - -.markdown-body dl dt { - padding: 0; - margin-top: 16px; - font-size: 1em; - font-style: italic; - font-weight: 600; -} - -.markdown-body dl dd { - padding: 0 16px; - margin-bottom: 16px; -} - -.markdown-body table th { - font-weight: 600; -} - -.markdown-body table th, -.markdown-body table td { - padding: 6px 13px; - border: 1px solid var(--color-border-default); -} - -.markdown-body table tr { - background-color: var(--color-canvas-default); - border-top: 1px solid var(--color-border-muted); -} - -.markdown-body table tr:nth-child(2n) { - background-color: var(--color-canvas-subtle); -} - -.markdown-body table img { - background-color: transparent; -} - -.markdown-body img[align="right"] { - padding-left: 20px; -} - -.markdown-body img[align="left"] { - padding-right: 20px; -} - -.markdown-body .emoji { - max-width: none; - vertical-align: text-top; - background-color: transparent; -} - -.markdown-body span.frame { - display: block; - overflow: hidden; -} - -.markdown-body span.frame > span { - display: block; - float: left; - width: auto; - padding: 7px; - margin: 13px 0 0; - overflow: hidden; - border: 1px solid var(--color-border-default); -} - -.markdown-body span.frame span img { - display: block; - float: left; -} - -.markdown-body span.frame span span { - display: block; - padding: 5px 0 0; - clear: both; - color: var(--color-fg-default); -} - -.markdown-body span.align-center { - display: block; - overflow: hidden; - clear: both; -} - -.markdown-body span.align-center > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: center; -} - -.markdown-body span.align-center span img { - margin: 0 auto; - text-align: center; -} - -.markdown-body span.align-right { - display: block; - overflow: hidden; - clear: both; -} - -.markdown-body span.align-right > span { - display: block; - margin: 13px 0 0; - overflow: hidden; - text-align: right; -} - -.markdown-body span.align-right span img { - margin: 0; - text-align: right; -} - -.markdown-body span.float-left { - display: block; - float: left; - margin-right: 13px; - overflow: hidden; -} - -.markdown-body span.float-left span { - margin: 13px 0 0; -} - -.markdown-body span.float-right { - display: block; - float: right; - margin-left: 13px; - overflow: hidden; -} - -.markdown-body span.float-right > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: right; -} - -.markdown-body code, -.markdown-body tt { - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - background-color: var(--color-neutral-muted); - border-radius: 6px; -} - -.markdown-body code br, -.markdown-body tt br { - display: none; -} - -.markdown-body del code { - text-decoration: inherit; -} - -.markdown-body pre code { - font-size: 100%; -} - -.markdown-body pre > code { - padding: 0; - margin: 0; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; -} - -.markdown-body .highlight { - margin-bottom: 16px; -} - -.markdown-body .highlight pre { - margin-bottom: 0; - word-break: normal; -} - -.markdown-body .highlight pre, -.markdown-body pre { - padding: 16px; - overflow: auto; - font-size: 85%; - line-height: 1.45; - background-color: var(--color-canvas-subtle); - border-radius: 6px; -} - -.markdown-body pre code, -.markdown-body pre tt { - display: inline; - max-width: auto; - padding: 0; - margin: 0; - overflow: visible; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; -} - -.markdown-body .csv-data td, -.markdown-body .csv-data th { - padding: 5px; - overflow: hidden; - font-size: 12px; - line-height: 1; - text-align: left; - white-space: nowrap; -} - -.markdown-body .csv-data .blob-num { - padding: 10px 8px 9px; - text-align: right; - background: var(--color-canvas-default); - border: 0; -} - -.markdown-body .csv-data tr { - border-top: 0; -} - -.markdown-body .csv-data th { - font-weight: 600; - background: var(--color-canvas-subtle); - border-top: 0; -} - -.markdown-body .footnotes { - font-size: 12px; - color: var(--color-fg-muted); - border-top: 1px solid var(--color-border-default); -} - -.markdown-body .footnotes ol { - padding-left: 16px; -} - -.markdown-body .footnotes li { - position: relative; -} - -.markdown-body .footnotes li:target::before { - position: absolute; - top: -8px; - right: -8px; - bottom: -8px; - left: -24px; - pointer-events: none; - content: ""; - border: 2px solid var(--color-accent-emphasis); - border-radius: 6px; -} - -.markdown-body .footnotes li:target { - color: var(--color-fg-default); -} - -.markdown-body .footnotes .data-footnote-backref g-emoji { - font-family: monospace; -} - -.markdown-body .task-list-item { - list-style-type: none; -} - -.markdown-body .task-list-item label { - font-weight: 400; -} - -.markdown-body .task-list-item.enabled label { - cursor: pointer; -} - -.markdown-body .task-list-item + .task-list-item { - margin-top: 3px; -} - -.markdown-body .task-list-item .handle { - display: none; -} - -.markdown-body .task-list-item-checkbox { - margin: 0 0.2em 0.25em -1.6em; - vertical-align: middle; -} - -.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { - margin: 0 -1.6em 0.25em 0.2em; -} - -.markdown-body ::-webkit-calendar-picker-indicator { - filter: invert(50%); -} diff --git a/src/styles/post.css b/src/styles/post.css index d950820..c1ddbb9 100644 --- a/src/styles/post.css +++ b/src/styles/post.css @@ -3,46 +3,33 @@ @tailwind utilities; @layer base { - ul { - list-style: disc; + /* 1. 기본 상태 (라이트 모드) */ + code[data-theme*=" "], + code[data-theme*=" "] span { + color: var(--shiki-light); + background-color: #f8f9fa; } - ol { - list-style: decimal; - } -} - -@layer utilities { - .copy-button { - position: absolute; - top: 8px; - right: 8px; - padding: 4px 8px; - font-size: 12px; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s ease; /* 부드러운 색상 변화 */ + /* 코드 블록 전체의 배경색을 제어하는 pre 태그 */ + pre[data-theme*=" "] { + background-color: #f8f9fa; + color: var(--shiki-light); } - .copy-button:hover { - background-color: #e5e7eb; /* 마우스 호버 시 색상 변경 */ + /* 2. 다크 모드 상태 (.dark 클래스가 상위 요소에 있을 때) */ + .dark code[data-theme*=" "], + .dark code[data-theme*=" "] span { + color: var(--shiki-dark); + background-color: var(--shiki-dark-bg); } - .check-button { - position: absolute; - top: 8px; - right: 8px; - padding: 4px 8px; - font-size: 12px; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s ease; - background-color: #4caf50; /* 녹색 배경 */ - color: white; /* 텍스트 색은 흰색 */ - border-color: #45a049; /* 테두리 색 */ - font-size: 14px; + .dark pre[data-theme*=" "] { + background-color: var(--shiki-dark-bg); + color: var(--shiki-dark); } +} +@layer utilities { /* WebKit (Chrome, Edge, Safari) */ .toc::-webkit-scrollbar { width: 2px; @@ -70,11 +57,6 @@ padding: 8px 16px; } - .icon { - width: 24px; - height: 24px; - } - @media (max-width: 767px) { .markdown-body { padding: 15px; diff --git a/tailwind.config.ts b/tailwind.config.ts index 6ec2aaa..db20691 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -361,5 +361,6 @@ export default { addUtilities(newUtilities); }), + require("@tailwindcss/typography"), ], } satisfies Config; From 8870991f049179dacbb8057beebbe8586b773195 Mon Sep 17 00:00:00 2001 From: spde3289 Date: Thu, 12 Mar 2026 21:48:15 +0900 Subject: [PATCH 05/11] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 283 +--------------------------------------------- package.json | 5 - 2 files changed, 1 insertion(+), 287 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11146d7..9eaa20c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,15 +11,11 @@ "@next/third-parties": "^15.1.5", "@tailwindcss/postcss": "^4.2.1", "clsx": "^2.1.1", - "framer-motion": "^12.5.0", "gray-matter": "^4.0.3", - "highlight.js": "^11.11.1", "lucide-react": "^0.563.0", "next": "^15.5.12", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-highlight": "^0.15.0", - "react-syntax-highlighter": "^16.1.1", "rehype-pretty-code": "^0.14.3", "rehype-slug": "^6.0.0", "rehype-stringify": "^10.0.1", @@ -38,7 +34,6 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@types/react-highlight": "^0.12.8", "eslint": "^9", "eslint-config-next": "15.1.4", "husky": "^9.1.7", @@ -65,15 +60,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@emnapi/core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", @@ -1937,12 +1923,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/prismjs": { - "version": "1.26.6", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", - "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1964,16 +1944,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/react-highlight": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.8.tgz", - "integrity": "sha512-V7O7zwXUw8WSPd//YUO8sz489J/EeobJljASGhP0rClrvq+1Y1qWEpToGu+Pp7YuChxhAXSgkLkrOYpZX5A62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -3032,16 +3002,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -3840,6 +3800,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4191,19 +4152,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fault": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", - "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4284,41 +4232,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/framer-motion": { - "version": "12.35.2", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.2.tgz", - "integrity": "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.35.2", - "motion-utils": "^12.29.2", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4812,21 +4725,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/highlightjs-vue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", - "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", - "license": "CC0-1.0" - }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -4905,30 +4803,6 @@ "node": ">= 0.4" } }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -5074,16 +4948,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -5168,16 +5032,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -5939,29 +5793,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lowlight": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", - "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", - "license": "MIT", - "dependencies": { - "fault": "^1.0.0", - "highlight.js": "~10.7.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lowlight/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/lucide-react": { "version": "0.563.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", @@ -6845,21 +6676,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/motion-dom": { - "version": "12.35.2", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.2.tgz", - "integrity": "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.29.2" - } - }, - "node_modules/motion-utils": { - "version": "12.29.2", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", - "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7254,31 +7070,6 @@ "node": ">=6" } }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, "node_modules/parse-numeric-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", @@ -7547,15 +7338,6 @@ } } }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7632,24 +7414,6 @@ "react": "^19.2.4" } }, - "node_modules/react-highlight": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.15.0.tgz", - "integrity": "sha512-5uV/b/N4Z421GSVVe05fz+OfTsJtFzx/fJBdafZyw4LS70XjIZwgEx3Lrkfc01W/RzZ2Dtfb0DApoaJFAIKBtA==", - "license": "MIT", - "dependencies": { - "highlight.js": "^10.5.0" - } - }, - "node_modules/react-highlight/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7657,35 +7421,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-syntax-highlighter": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", - "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4", - "highlight.js": "^10.4.1", - "highlightjs-vue": "^1.0.0", - "lowlight": "^1.17.0", - "prismjs": "^1.30.0", - "refractor": "^5.0.0" - }, - "engines": { - "node": ">= 16.20.2" - }, - "peerDependencies": { - "react": ">= 0.14.0" - } - }, - "node_modules/react-syntax-highlighter/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -7709,22 +7444,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/refractor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", - "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/prismjs": "^1.0.0", - "hastscript": "^9.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", diff --git a/package.json b/package.json index 28a93b1..5b53881 100644 --- a/package.json +++ b/package.json @@ -24,18 +24,14 @@ "@next/third-parties": "^15.1.5", "@tailwindcss/postcss": "^4.2.1", "clsx": "^2.1.1", - "framer-motion": "^12.5.0", "gray-matter": "^4.0.3", - "highlight.js": "^11.11.1", "lucide-react": "^0.563.0", "next": "^15.5.12", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-highlight": "^0.15.0", "rehype-pretty-code": "^0.14.3", "rehype-slug": "^6.0.0", "rehype-stringify": "^10.0.1", - "react-syntax-highlighter": "^16.1.1", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-html": "^16.0.1", @@ -51,7 +47,6 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@types/react-highlight": "^0.12.8", "eslint": "^9", "eslint-config-next": "15.1.4", "husky": "^9.1.7", From e09ad1379fdf9d09e256bcc6c0f89ffd88083bf3 Mon Sep 17 00:00:00 2001 From: spde3289 Date: Thu, 12 Mar 2026 22:11:23 +0900 Subject: [PATCH 06/11] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=EB=B8=94?= =?UTF-8?q?=EB=9F=AD,=20prose=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 5 +++++ src/styles/codeblock.css | 21 +++++++++++++++++++++ src/styles/prose.css | 10 ++++++++++ 3 files changed, 36 insertions(+) create mode 100644 src/styles/codeblock.css create mode 100644 src/styles/prose.css diff --git a/src/app/globals.css b/src/app/globals.css index 4f1be11..d40c080 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,9 @@ @import "tailwindcss"; + @import "../styles/typography.css"; +@import "../styles/prose.css"; +@import "../styles/codeblock.css"; + +@plugin "@tailwindcss/typography"; @custom-variant dark (&:where(.dark, .dark *)); diff --git a/src/styles/codeblock.css b/src/styles/codeblock.css new file mode 100644 index 0000000..22ad416 --- /dev/null +++ b/src/styles/codeblock.css @@ -0,0 +1,21 @@ +code[data-theme*=" "], +code[data-theme*=" "] span { + color: var(--shiki-light); + background-color: #f0f4f9; +} + +pre[data-theme*=" "] { + background-color: #f0f4f9; + color: var(--shiki-light); +} + +.dark code[data-theme*=" "], +.dark code[data-theme*=" "] span { + color: var(--shiki-dark); + background-color: var(--shiki-dark-bg); +} + +.dark pre[data-theme*=" "] { + background-color: var(--shiki-dark-bg); + color: var(--shiki-dark); +} diff --git a/src/styles/prose.css b/src/styles/prose.css new file mode 100644 index 0000000..56321ab --- /dev/null +++ b/src/styles/prose.css @@ -0,0 +1,10 @@ +.prose th, +.prose td { + @apply border border-gray-300 dark:border-gray-700; + @apply px-4 py-2 text-center align-middle; +} + +.prose th img, +.prose td img { + @apply mx-auto my-0 h-auto max-w-full; +} From 8adb079d25ddbb9fd37e8295cc3a449caf844d74 Mon Sep 17 00:00:00 2001 From: spde3289 Date: Tue, 17 Mar 2026 18:02:14 +0900 Subject: [PATCH 07/11] =?UTF-8?q?chore:=20html-react-parser=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 180 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 179 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9eaa20c..003bf2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@tailwindcss/postcss": "^4.2.1", "clsx": "^2.1.1", "gray-matter": "^4.0.3", + "html-react-parser": "^5.2.17", "lucide-react": "^0.563.0", "next": "^15.5.12", "react": "^19.0.0", @@ -1927,7 +1928,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -3136,7 +3137,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -3327,6 +3328,73 @@ "node": ">=0.10.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4725,6 +4793,53 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-dom-parser": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.1.8.tgz", + "integrity": "sha512-MCIUng//mF2qTtGHXJWr6OLfHWmg3Pm8ezpfiltF83tizPWY17JxT4dRLE8lykJ5bChJELoY3onQKPbufJHxYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + } + ], + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "htmlparser2": "10.1.0" + } + }, + "node_modules/html-react-parser": { + "version": "5.2.17", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.17.tgz", + "integrity": "sha512-m+K/7Moq1jodAB4VL0RXSOmtwLUYoAsikZhwd+hGQe5Vtw2dbWfpFd60poxojMU0Tsh9w59mN1QLEcoHz0Dx9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/html-react-parser" + } + ], + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "html-dom-parser": "5.1.8", + "react-property": "2.0.2", + "style-to-js": "1.1.21" + }, + "peerDependencies": { + "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", + "react": "0.14 || 15 || 16 || 17 || 18 || 19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -4735,6 +4850,37 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -4788,6 +4934,12 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -7421,6 +7573,12 @@ "dev": true, "license": "MIT" }, + "node_modules/react-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz", + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -8346,6 +8504,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", diff --git a/package.json b/package.json index 5b53881..2e518ea 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@tailwindcss/postcss": "^4.2.1", "clsx": "^2.1.1", "gray-matter": "^4.0.3", + "html-react-parser": "^5.2.17", "lucide-react": "^0.563.0", "next": "^15.5.12", "react": "^19.0.0", From f61cca33c8067a8aff5d051eb694c7cc98b1966d Mon Sep 17 00:00:00 2001 From: spde3289 Date: Tue, 17 Mar 2026 18:02:42 +0900 Subject: [PATCH 08/11] =?UTF-8?q?style:=20404=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/not-found.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 1bfc7f9..84a8a55 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -13,8 +13,8 @@ const NotFound = () => { }; return (

404 ERROR

From bc679c5694cd7a8162a7a881ef1b31c37c375721 Mon Sep 17 00:00:00 2001 From: spde3289 Date: Tue, 17 Mar 2026 18:10:29 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20=EC=98=AC=EB=B0=94=EB=A5=B4?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EA=B2=BD=EB=A1=9C=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=8B=9C=20404=20=EB=9D=BC=EC=9A=B0=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/blog/[category]/[post]/page.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/app/blog/[category]/[post]/page.tsx b/src/app/blog/[category]/[post]/page.tsx index 2989a9f..2031599 100644 --- a/src/app/blog/[category]/[post]/page.tsx +++ b/src/app/blog/[category]/[post]/page.tsx @@ -1,6 +1,7 @@ import { getPostDetail, getPostList, getPostMeta } from "@/lib/postService"; import { Metadata } from "next"; -import PostContainer from "./_components/PostContainer"; +import { notFound } from "next/navigation"; +import PostLayout from "./_components/PostLayout"; interface PageProps { params: Promise<{ category: string; post: string }>; @@ -55,9 +56,18 @@ export const generateStaticParams = () => { const PostPage = async ({ params }: PageProps) => { const { category, post } = await params; - const { post: meta, contentHtml } = getPostDetail(category, post); - return ; + try { + const { post: meta, contentHtml } = getPostDetail(category, post); + + if (!meta || !contentHtml) { + notFound(); + } + + return ; + } catch { + notFound(); + } }; export default PostPage; From 5fed209a1eb42a9e1b1c591872404130a3356be0 Mon Sep 17 00:00:00 2001 From: spde3289 Date: Tue, 17 Mar 2026 18:11:00 +0900 Subject: [PATCH 10/11] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=EB=B8=94?= =?UTF-8?q?=EB=A1=9D=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/codeblock.css | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/styles/codeblock.css b/src/styles/codeblock.css index 22ad416..26ce92b 100644 --- a/src/styles/codeblock.css +++ b/src/styles/codeblock.css @@ -1,21 +1,36 @@ -code[data-theme*=" "], -code[data-theme*=" "] span { - color: var(--shiki-light); - background-color: #f0f4f9; +pre[data-theme] { + background-color: transparent; + color: var(--shiki-light) !important; + padding: 1rem 1.25rem !important; + overflow-x: auto; + margin: 0 !important; +} + +.dark pre[data-theme] { + background-color: transparent; + color: var(--shiki-dark) !important; } -pre[data-theme*=" "] { - background-color: #f0f4f9; +code[data-theme] span { color: var(--shiki-light); } -.dark code[data-theme*=" "], -.dark code[data-theme*=" "] span { +.dark code[data-theme] span { color: var(--shiki-dark); - background-color: var(--shiki-dark-bg); } -.dark pre[data-theme*=" "] { - background-color: var(--shiki-dark-bg); - color: var(--shiki-dark); +pre:not([data-theme]) { + background-color: transparent; + color: #24292e; + overflow-x: auto; +} + +pre:not([data-theme]) code { + color: inherit; + background-color: transparent; +} + +.dark pre:not([data-theme]) { + background-color: transparent; + color: #e1e4e8; } From 8d20280d356e48b28545c20aa95e3dd7b2149508 Mon Sep 17 00:00:00 2001 From: spde3289 Date: Tue, 17 Mar 2026 18:15:14 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[post]/_components/PostContainer.tsx | 46 ----- .../[post]/_components/PostLayout.tsx | 22 +++ .../[post]/_components/TableOfContents.tsx | 157 ++++++++++-------- .../PostSectionMain/ArticleViewPost.tsx | 12 +- src/components/CopyButton.tsx | 101 +++++------ src/components/HighlightCode.tsx | 92 ---------- src/components/PostArticle.tsx | 103 ++++++++++++ src/utils/getLanguageName.ts | 19 +++ src/utils/getTextContent.ts | 15 ++ 9 files changed, 304 insertions(+), 263 deletions(-) delete mode 100644 src/app/blog/[category]/[post]/_components/PostContainer.tsx create mode 100644 src/app/blog/[category]/[post]/_components/PostLayout.tsx delete mode 100644 src/components/HighlightCode.tsx create mode 100644 src/components/PostArticle.tsx create mode 100644 src/utils/getLanguageName.ts create mode 100644 src/utils/getTextContent.ts diff --git a/src/app/blog/[category]/[post]/_components/PostContainer.tsx b/src/app/blog/[category]/[post]/_components/PostContainer.tsx deleted file mode 100644 index 15526f6..0000000 --- a/src/app/blog/[category]/[post]/_components/PostContainer.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import HighlightCode from "@/components/HighlightCode"; -import type { PostMetaData } from "@/types/posts.types"; -import { useEffect, useState } from "react"; -import TableOfContents, { type TocItem } from "./TableOfContents"; - -interface HighlightedCodeProps { - contentHtml: string; - metadata: PostMetaData; -} - -// HTML을 수정하지 않고, 단순히 목차 데이터만 뽑아내는 용도로 단순화 -const extractToc = (htmlString: string): TocItem[] => { - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlString, "text/html"); - const headings = Array.from(doc.querySelectorAll("h2, h3")); - - return headings.map((el) => ({ - id: el.id, // 서버(rehype-slug)가 이미 달아준 id를 그대로 가져옴 - text: el.textContent || "", - level: el.tagName === "H2" ? 2 : 3, - })); -}; - -const PostContainer = ({ metadata, contentHtml }: HighlightedCodeProps) => { - // // 이제 html 상태를 따로 관리할 필요가 없습니다! - const [toc, setToc] = useState([]); - const [html, setHtml] = useState(contentHtml); - useEffect(() => { - // 렌더링 후 목차 배열만 추출해서 상태에 저장 - setHtml(contentHtml); - setToc(extractToc(contentHtml)); - }, [contentHtml]); - - return ( -

- {/* 원본 HTML을 그대로 꽂아주기만 하면 끝 */} - - {/* Scroll Spy가 적용된 목차 컴포넌트 */} - -
- ); -}; - -export default PostContainer; diff --git a/src/app/blog/[category]/[post]/_components/PostLayout.tsx b/src/app/blog/[category]/[post]/_components/PostLayout.tsx new file mode 100644 index 0000000..7cd44a0 --- /dev/null +++ b/src/app/blog/[category]/[post]/_components/PostLayout.tsx @@ -0,0 +1,22 @@ +"use client"; + +import PostArticle from "@/components/PostArticle"; +import type { PostMetaData } from "@/types/posts.types"; +import TableOfContents from "./TableOfContents"; + +interface HighlightedCodeProps { + contentHtml: string; + metadata: PostMetaData; +} + +const PostLayout = ({ metadata, contentHtml }: HighlightedCodeProps) => { + return ( +
+ + {/* 💡 문자열만 툭 던져주면 TableOfContents가 알아서 파싱하고 그립니다. */} + +
+ ); +}; + +export default PostLayout; diff --git a/src/app/blog/[category]/[post]/_components/TableOfContents.tsx b/src/app/blog/[category]/[post]/_components/TableOfContents.tsx index 1a00bcc..c28517d 100644 --- a/src/app/blog/[category]/[post]/_components/TableOfContents.tsx +++ b/src/app/blog/[category]/[post]/_components/TableOfContents.tsx @@ -1,68 +1,89 @@ -"use client"; - -import { useEffect, useState } from "react"; - -export type TocItem = { - id: string; - text: string; - level: 2 | 3; -}; - -interface TableOfContentsProps { - toc: TocItem[]; -} - -const TableOfContents = ({ toc }: TableOfContentsProps) => { - const [activeId, setActiveId] = useState(""); - - useEffect(() => { - // 렌더링된 h2, h3 태그들을 모두 찾습니다. - const elements = document.querySelectorAll("h2, h3"); - - // Intersection Observer 설정 - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - setActiveId(entry.target.id); - } - }); - }, - { rootMargin: "-80px 0px -80% 0px", threshold: 0.1 }, - ); - - elements.forEach((el) => observer.observe(el)); - - return () => observer.disconnect(); - }, [toc]); - - if (toc.length === 0) return null; - - return ( -
- -
- ); -}; - -export default TableOfContents; +"use client"; + +import { useEffect, useState } from "react"; + +export type TocItem = { + id: string; + text: string; + level: 2 | 3; +}; + +interface TableOfContentsProps { + contentHtml: string; +} + +const extractTocSync = (htmlString: string): TocItem[] => { + const regex = /<(h[23])\s+id="([^"]+)"[^>]*>(.*?)<\/\1>/gi; + const headings: TocItem[] = []; + let match; + + while ((match = regex.exec(htmlString)) !== null) { + const textContent = match[3].replace(/<[^>]+>/g, "").trim(); + headings.push({ + level: match[1].toLowerCase() === "h2" ? 2 : 3, + id: match[2], + text: textContent, + }); + } + return headings; +}; + +const TableOfContents = ({ contentHtml }: TableOfContentsProps) => { + const toc = extractTocSync(contentHtml); + const [activeId, setActiveId] = useState(""); + + useEffect(() => { + if (toc.length === 0) return; + + const elements = document.querySelectorAll("h2, h3"); + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + }); + }, + { rootMargin: "-80px 0px -80% 0px", threshold: 0.1 } + ); + + elements.forEach((el) => observer.observe(el)); + + return () => observer.disconnect(); + }, [toc]); + + if (toc.length === 0) return null; + + return ( +
+ +
+ ); +}; + +export default TableOfContents; diff --git a/src/app/blog/_components/PostSection/PostSectionMain/ArticleViewPost.tsx b/src/app/blog/_components/PostSection/PostSectionMain/ArticleViewPost.tsx index 8226e38..fb8c36a 100644 --- a/src/app/blog/_components/PostSection/PostSectionMain/ArticleViewPost.tsx +++ b/src/app/blog/_components/PostSection/PostSectionMain/ArticleViewPost.tsx @@ -1,5 +1,5 @@ -import HighlightCode from "@/components/HighlightCode"; import HighlightText from "@/components/HighlightText"; +import PostArticle from "@/components/PostArticle"; import ArrowHeadSVG from "@/svg/ArrowHeadSVG"; import type { Post } from "@/types/posts.types"; import { useState } from "react"; @@ -41,9 +41,7 @@ const ArticleViewPost = ({ post, searchText }: ArticleViewPostProps) => { border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-800/40" > -
+
{loading ? (