Skip to content

Commit

Permalink
Feat: #63 마크다운 코드 블록 하이라이팅 기능 추가 (#86)
Browse files Browse the repository at this point in the history
* Config: #63 ESLint 설정 추가 (no-cond-assign: off)

* Config: #63 @types/react-syntax-highlighter 추가

* Feat: #63 코드 블록 하이라이팅 기능 추가

* Fix: #63 인라인 코드와 코드 블록 분기 처리 수정

* Feat: #63 markdown 개행 처리를 위한 기능 추가
  • Loading branch information
Seok93 authored Aug 26, 2024
1 parent 1db78dc commit b3ed819
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 15 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
'no-param-reassign': 'warn',
'no-return-assign': 'warn',
'no-unused-vars': 'warn',
'no-cond-assign': 'off',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'react/require-default-props': 'off',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@types/react": "^18.3.2",
"@types/react-big-calendar": "^1.8.9",
"@types/react-dom": "^18.3.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
Expand Down
56 changes: 41 additions & 15 deletions src/components/common/CustomMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import React, { useMemo } from 'react';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import { LightAsync as SyntaxHighlighter } from 'react-syntax-highlighter';
import { hybrid } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import { languageMap } from '@constants/language';
import type { Components } from 'react-markdown';

type CustomMarkdownProps = {
Expand All @@ -14,29 +17,29 @@ const component: Partial<Components> = {
return (
<>
<h1 className="text-3xl">{children}</h1>
<hr className="my-3" />
<hr className="my-5" />
</>
);
},
h2(props) {
const { children } = props;
return <h2 className="mb-3 text-2xl">{children}</h2>;
return <h2 className="mb-5 text-2xl">{children}</h2>;
},
h3(props) {
const { children } = props;
return <h3 className="mb-3 text-xl">{children}</h3>;
return <h3 className="mb-5 text-xl">{children}</h3>;
},
h4(props) {
const { children } = props;
return <h3 className="mb-3 text-lg">{children}</h3>;
return <h3 className="mb-5 text-lg">{children}</h3>;
},
h5(props) {
const { children } = props;
return <h3 className="mb-3 text-base">{children}</h3>;
return <h3 className="mb-5 text-base">{children}</h3>;
},
h6(props) {
const { children } = props;
return <h3 className="mb-3 text-sm">{children}</h3>;
return <h3 className="mb-5 text-sm">{children}</h3>;
},
hr() {
return <hr className="my-5" />;
Expand All @@ -51,15 +54,15 @@ const component: Partial<Components> = {
},
img(props) {
const { src, alt } = props;
return <img src={src} alt={alt} className="m-auto" />;
return <img src={src} alt={alt} className="m-auto my-5" />;
},
blockquote(props) {
const { children } = props;
return <blockquote className="border-l-[3px] border-[#20C997] bg-[#F8F9FA] p-4">{children}</blockquote>;
return <blockquote className="my-5 border-l-[3px] border-[#20C997] bg-[#F8F9FA] p-8">{children}</blockquote>;
},
table(props) {
const { children } = props;
return <table className="border-collapse overflow-hidden rounded-md shadow-md">{children}</table>;
return <table className="my-5 border-collapse overflow-hidden rounded-md shadow-md">{children}</table>;
},
th(props) {
const { children, style } = props;
Expand Down Expand Up @@ -118,17 +121,40 @@ const component: Partial<Components> = {
return <section className={className}>{children}</section>;
},
code(props) {
const { children, className } = props;
return <code className={`${className} rounded-sm border-none bg-[#E9ECEF] px-2`}>{children}</code>;
const { children, className, node } = props;
const language = className?.split('-')[1] || '';
const mappedLanguage = languageMap[language] || 'plaintext';

if (!language && node?.position?.start.line === node?.position?.end.line) {
return <code className={`${className} rounded-sm border-none bg-[#E9ECEF] px-2`}>{children}</code>;
}
return (
<SyntaxHighlighter style={hybrid} language={mappedLanguage} className="my-5">
{children as string}
</SyntaxHighlighter>
);
},
};

function getChangedMarkdownForLineBreak(markdown: string) {
return markdown
.split('\n')
.map((sentence) => (sentence === '' ? '\n<br />\n' : sentence))
.join('\n');
}

export default function CustomMarkdown({ markdown }: CustomMarkdownProps) {
const changedMarkdown = useMemo(() => getChangedMarkdownForLineBreak(markdown), [markdown]);

return (
<section className="rounded-md border border-input p-10 text-sm">
<Markdown components={component} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{markdown}
</Markdown>
{markdown.trim().length === 0 ? (
<div className="text-xs text-gray-400/90">입력된 내용이 없습니다.</div>
) : (
<Markdown components={component} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{changedMarkdown}
</Markdown>
)}
</section>
);
}
203 changes: 203 additions & 0 deletions src/constants/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
export const languageMap: { [key: string]: string } = {
oneC: '1c',
'1c': '1c',
abnf: 'abnf',
accesslog: 'accesslog',
actionscript: 'actionscript',
ada: 'ada',
angelscript: 'angelscript',
apache: 'apache',
applescript: 'applescript',
arcade: 'arcade',
arduino: 'arduino',
armasm: 'armasm',
asciidoc: 'asciidoc',
aspectj: 'aspectj',
autohotkey: 'autohotkey',
autoit: 'autoit',
avrasm: 'avrasm',
awk: 'awk',
axapta: 'axapta',
bash: 'bash',
basic: 'basic',
bnf: 'bnf',
brainfuck: 'brainfuck',
cLike: 'c-like',
'c-like': 'c-like',
c: 'c',
cal: 'cal',
capnproto: 'capnproto',
ceylon: 'ceylon',
clean: 'clean',
clojureRepl: 'clojure-repl',
'clojure-repl': 'clojure-repl',
clojure: 'clojure',
cmake: 'cmake',
coffeescript: 'coffeescript',
coq: 'coq',
cos: 'cos',
cpp: 'cpp',
crmsh: 'crmsh',
crystal: 'crystal',
csharp: 'csharp',
csp: 'csp',
css: 'css',
d: 'd',
dart: 'dart',
delphi: 'delphi',
diff: 'diff',
django: 'django',
dns: 'dns',
dockerfile: 'dockerfile',
dos: 'dos',
dsconfig: 'dsconfig',
dts: 'dts',
dust: 'dust',
ebnf: 'ebnf',
elixir: 'elixir',
elm: 'elm',
erb: 'erb',
erlangRepl: 'erlang-repl',
'erlang-repl': 'erlang-repl',
erlang: 'erlang',
excel: 'excel',
fix: 'fix',
flix: 'flix',
fortran: 'fortran',
fsharp: 'fsharp',
gams: 'gams',
gauss: 'gauss',
gcode: 'gcode',
gherkin: 'gherkin',
glsl: 'glsl',
gml: 'gml',
go: 'go',
golo: 'golo',
gradle: 'gradle',
groovy: 'groovy',
haml: 'haml',
handlebars: 'handlebars',
haskell: 'haskell',
haxe: 'haxe',
hsp: 'hsp',
htmlbars: 'htmlbars',
http: 'http',
hy: 'hy',
inform7: 'inform7',
ini: 'ini',
irpf90: 'irpf90',
isbl: 'isbl',
java: 'java',
js: 'javascript',
javascript: 'javascript',
jbossCli: 'jboss-cli',
'jboss-cli': 'jboss-cli',
json: 'json',
juliaRepl: 'julia-repl',
'julia-repl': 'julia-repl',
julia: 'julia',
kotlin: 'kotlin',
lasso: 'lasso',
latex: 'latex',
ldif: 'ldif',
leaf: 'leaf',
less: 'less',
lisp: 'lisp',
livecodeserver: 'livecodeserver',
livescript: 'livescript',
llvm: 'llvm',
lsl: 'lsl',
lua: 'lua',
makefile: 'makefile',
markdown: 'markdown',
mathematica: 'mathematica',
matlab: 'matlab',
maxima: 'maxima',
mel: 'mel',
mercury: 'mercury',
mipsasm: 'mipsasm',
mizar: 'mizar',
mojolicious: 'mojolicious',
monkey: 'monkey',
moonscript: 'moonscript',
n1ql: 'n1ql',
nginx: 'nginx',
nim: 'nim',
nix: 'nix',
nodeRepl: 'node-repl',
'node-repl': 'node-repl',
nsis: 'nsis',
objectivec: 'objectivec',
ocaml: 'ocaml',
openscad: 'openscad',
oxygene: 'oxygene',
parser3: 'parser3',
perl: 'perl',
pf: 'pf',
pgsql: 'pgsql',
phpTemplate: 'php-template',
'php-template': 'php-template',
php: 'php',
plaintext: 'plaintext',
pony: 'pony',
powershell: 'powershell',
processing: 'processing',
profile: 'profile',
prolog: 'prolog',
properties: 'properties',
protobuf: 'protobuf',
puppet: 'puppet',
purebasic: 'purebasic',
pythonRepl: 'python-repl',
'python-repl': 'python-repl',
python: 'python',
q: 'q',
qml: 'qml',
r: 'r',
reasonml: 'reasonml',
rib: 'rib',
roboconf: 'roboconf',
routeros: 'routeros',
rsl: 'rsl',
ruby: 'ruby',
ruleslanguage: 'ruleslanguage',
rust: 'rust',
sas: 'sas',
scala: 'scala',
scheme: 'scheme',
scilab: 'scilab',
scss: 'scss',
shell: 'shell',
smali: 'smali',
smalltalk: 'smalltalk',
sml: 'sml',
sqf: 'sqf',
sql: 'sql',
stan: 'stan',
stata: 'stata',
step21: 'step21',
stylus: 'stylus',
subunit: 'subunit',
swift: 'swift',
taggerscript: 'taggerscript',
tap: 'tap',
tcl: 'tcl',
thrift: 'thrift',
tp: 'tp',
twig: 'twig',
typescript: 'typescript',
vala: 'vala',
vbnet: 'vbnet',
vbscriptHtml: 'vbscript-html',
'vbscript-html': 'vbscript-html',
vbscript: 'vbscript',
verilog: 'verilog',
vhdl: 'vhdl',
vim: 'vim',
x86asm: 'x86asm',
xl: 'xl',
xml: 'xml',
xquery: 'xquery',
yaml: 'yaml',
zephir: 'zephir',
};
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,13 @@
dependencies:
"@types/react" "*"

"@types/react-syntax-highlighter@^15.5.13":
version "15.5.13"
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz#c5baf62a3219b3bf28d39cfea55d0a49a263d1f2"
integrity sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==
dependencies:
"@types/react" "*"

"@types/react@*", "@types/react@>=16.9.11", "@types/react@^18.3.2":
version "18.3.2"
resolved "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz"
Expand Down

0 comments on commit b3ed819

Please sign in to comment.