์์ํ๊ธฐ ์ ์
๋จผ์ ํด๋น ํฌ์คํ ์ ์ปค์คํ ์ ์ ์์ฐ๋๊ป์ ๊ณต์ ํด์ฃผ์ hELLO ํฐ์คํ ๋ฆฌ ์คํจ v4.10.3์ ์ ์ฉํ ํ์ ์งํ๋์๋ค.
hELLO ํฐ์คํ ๋ฆฌ ์คํจ์ ์๊ฐํฉ๋๋ค.
hELLO ๋ 2020๋ 3์ ์ฒซ ๊ณต๊ฐ ์ดํ ์ง๊ธ์ ์ด๋ฅด๊ธฐ๊น์ง ํฐ์คํ ๋ฆฌ์์ ๋ง์ ๋ธ๋ก๊ฑฐ๋ถ๋ค๊ป ์ฌ๋๋ฐ์ ์คํจ์ด ๋์์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ ๊ฐ์ฅ ์ํฅ๋ ฅ ์๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์ธ ๊นํ๋ธ์์ ์ฝ ์ฒ ๊ฐ์ ๋ฌํ๋
pronist.tistory.com
์ฝ๋ ๋ธ๋ญ์ ๊ธฐ๋ณธ์ ์ธ mac ์คํ์ผ ์ปค์คํ ์ ์ ํ๋ ์ฝ๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋์ด๋์ ์๋ ํฌ์คํ ์ ์ฒจ๋ถ๋ ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ค.
[ํฐ์คํ ๋ฆฌ ๋ธ๋ก๊ทธ ํ ๋ง] - 1. ์ฝ๋ ๋ธ๋ก ๋์์ธ์ mac ์ฝ๋ ์คํ์ผ๋ก ๋ฐ๊พธ๊ธฐ
๊ธ์ ์ฝ๊ธฐ ์ ๋ฏธ๋ฆฌ ๋ณด๋ ์์ฑ๋ณธ์ ๋ค์๊ณผ ๊ฐ๋ค. ์ด๋ป๊ฒ ๋ง๋ค์๊ณ ์ ์ฉํ๋์ง ๊ถ๊ธํ์ ๋ถ๋ค์ ์๋๋ก ์คํฌ๋กค! See the Pen tistory code block by MiJeong Kim (@sap03110) on CodePen. ๋ฐ๋จ์ฌ๋ ๋ ๊ณผ ๋ค๋ฆ์์ด
guiyomi.tistory.com
๋ง์ง๋ง์ผ๋ก ์ ํฌ์คํ ์ ์ฝ๋๋ฅผ hELLO ์คํจ์ ์ ์ฉํ๊ธฐ ์ํด์ reifier๋์ ์๋ ํฌ์คํ ์ ์ฐธ๊ณ ํ๋ค.
[ํฐ์คํ ๋ฆฌ] ์ฝ๋ ๋ธ๋ก mac ์คํ์ผ ์ ์ฉ (feat. hELLO)
์ฝ๋๋ธ๋ก mac ๋์์ธ ํ์์ฒ๋ผ ์ผ์ ํ๋ฉด์ ๊ตฌ๊ธ๋ง์ ํด์ ์ฌ๋ฌ ๊ธฐ์ ๋ธ๋ก๊ทธ๋ค์ ํ์ธํ๊ฒ ๋๋๋ฐ, ๊ฑฐ์ ๋๋ถ๋ถ์ ๋ธ๋ก๊ฑฐ ๋ถ๋ค์ด ํฐ์คํ ๋ฆฌ ๊ธฐ๋ณธ ์ฝ๋ ๋ธ๋ก์ ์ฌ์ฉํ๊ธฐ๋ณด๋ค๋ ๋์์ธ์ ์ปค์คํฐ๋ง
reifier.tistory.com
์์ ์ ์ฝ๋ ๋ธ๋ญ
์ธ๊ฐ์ง ํฌ์คํ ์ ์ ํ ๋ด์ฉ์ ์์ฐจ์ ์ผ๋ก ๋ด ๋ธ๋ก๊ทธ ์คํจ์ ์ ์ฉํ์ฌ ์์ ๊ฐ์ ๋ชจ์ต์ ์์ฑํ๋ค. ์คํ์ผ์ ๋์ด๋ ํฌ์คํ ์ ์ฒจ๋ถ๋ CSS ์คํ์ผ์์ ๊ฑด๋ค์ด์ง ์์๋ค.
์์ ๊ฐ์ ์ํ์์ ์ฝ๋ ๋ธ๋ญ์ ์คํ์ผ์ ๋ด ์ทจํฅ์ ๋ง๊ฒ ์์ ํ๊ณ ๊ทธ ๊ณผ์ ์์ ๋ช๊ฐ์ง ์์ ์ฌํญ์ด ์๊ฒจ์ ์ถํ ์คํจ ์ ๋ฐ์ดํธ ๋ฑ์ ์ด์ ๋ก ๋ฐฑ์ ํ๊ธฐ ์ํด ์ด ํฌ์คํ ์ ์์ฑํ๋ค.
์์ ์ฌํญ
1. ์ ๋ฐ์ ์ธ ์คํ์ผ ์์
์ฝ๋๋ธ๋ญ๊ณผ ํค๋์ ๋ฐฐ๊ฒฝ์๊ณผ, ์ฝ๋ ๋ธ๋ญ์ ์๊ธฐ๋ ์คํฌ๋กค์ ์คํ์ผ ๋ฑ์ ์์ ํด์ฃผ์๋ค. ์ฝ๋๋ธ๋ญ์ ํฐํธ๋ก๋ Source Code Pro๋ฅผ ์ฌ์ฉํ๋ค.
@font-face {
font-family: Source Code Pro;
src: url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap');
}
pre {
position: relative;
}
pre::after {
content: attr(data-ke-language);
position: absolute;
bottom: 6px;
right: 12px;
color: #cfd2d1;
font-size: 12px;
}
.hljs {
display: flex !important;
flex-direction: column;
padding: 0 !important;
font-size: 14px;
border-radius: 8px;
box-shadow: 0 12px 24px rgb(0 0 0 / 40%);
color: #cfd2d1;
background-color: #2d3440;
font-family: "Source Code Pro", monospace;
}
.hljs .line {
counter-increment: line-idx;
line-height: 1.8;
white-space: pre;
padding-right: 5px;
min-width: 100% !important;
}
.hljs .line:hover {
background-color: #262830;
}
.hljs .line:hover::before {
color: #cfd2d1;
}
.hljs .line::before {
content: counter(line-idx);
width: 24px;
display: inline-block;
text-align: right;
margin-right: 16px;
font-size: 0.8rem;
color: #747a7a;
}
.hljs .code-header {
display: flex;
align-items: center;
padding: 8px 10px 8px 14px;
background-color: #2d3440;
border-radius: 8px 8px 0 0;
}
.hljs .code-header .btn {
border-radius: 50%;
width: 12px;
height: 12px;
margin: 0 4px;
}
.hljs .code-header .btn.red {
background-color: #f5655b;
}
.hljs .code-header .btn.yellow {
background-color: #f6bd3b;
}
.hljs .code-header .btn.green {
background-color: #43c645;
}
.hljs .code-body {
max-height: 600px;
margin: 20px 8px 25px 8px;
overflow: auto;
}
.hljs .code-body::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.hljs .code-body::-webkit-scrollbar-thumb {
background-color: rgb(60, 64, 75);
}
.hljs .code-body::-webkit-scrollbar-track-piece {
background-color: transparent;
}
.hljs .code-body::-webkit-scrollbar-corner {
display: none;
}
.hljs .copy-btn {
background-color: #ffffff17;
border: none;
cursor: pointer;
color: #fff;
border-radius: 4px;
width: 36px;
height: 28px;
margin-left: auto;
transition: 0.2s background-color;
font-size: 0;
display: flex;
align-items: center;
justify-content: center;
}
.hljs .copy-btn:hover {
background-color: #ffffff30;
}
2. JS ์ฝ๋ ์ ๋ฐ์ดํธ
์ต๊ทผ ๋์ด๋์ ํฌ์คํ ์์ COPY ๋ฒํผ์ด ํ ์คํธ์์ ์์ด์ฝ์ผ๋ก ๋ฐ๋๊ณ ๋ฒ๊ทธ๋ฅผ ์์ ํ๋ฉด์ JS ์ฝ๋๊ฐ ์ ๋ฐ์ดํธ ๋์๋ค. ๋ฐ๋ผ์ reifier๋ ํฌ์คํ ์ ์ ๋ก๋๋ ์ฝ๋์ ๊ฐ์ด ๋์ด๋์ JS ์ฝ๋ ๋ํ codeblock์ด ๋ธ๋ก๊ทธ ํ์ด์ง์ ๋ชจ๋ ๋ฆฌ์์ค๊ฐ ๋ก๋ ๋ ํ์ ์คํ๋ ์ ์๋๋ก ์์ ํด์ฃผ๋ฉด ๋๋ค.
3. ์ฝ๋ ๋ผ์ธ hover ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ธธ์ด ์์
๊ธฐ๋ณธ์ ์ผ๋ก ๋์ด๋์ mac ์คํ์ผ ์ฝ๋ ๋ธ๋ญ์ ๊ฐ ๋ผ์ธ์ ๋ง์ฐ์ค์ค๋ฒ๋ฅผ ํ๋ฉด ํด๋น ๋ผ์ธ์ด ์ด์ง ์ด๋์์ง๋ CSS ์คํ์ผ์ด ์ ์ฉ๋์ด ์๋ค.
ํ์ง๋ง ์ ์ฌ์ง๊ณผ ๊ฐ์ด ๋ง์ฝ ์ฝ๋ ๋ธ๋ญ์ ํฌํจ๋ ์ผ๋ถ ์ฝ๋ ๋ผ์ธ์ด ๋๋ฌด ๊ธธ์ด์ ธ ๊ฐ๋ก ์คํฌ๋กค์ด ์๊ธฐ๋ฉด, ๊ฐ๋ก๋ก ์คํฌ๋กค ํ์ ๋, ๋ง์ฐ์ค์ค๋ฒ ์ ์ด๋์์ง๋ ๋ถ๋ถ์ด ์คํฌ๋กค ๊ธธ์ด๋ณด๋ค ์งง์์ ์๋ ค๋ณด์ธ๋ค. ์ฌ์ํ ๋ฌธ์ ์์ง๋ง ์ด์ง ๋ถํธํจ์ ๋๊ปด์ ์ด ๋ถ๋ถ์ ์์ ํด๋ณด๊ธฐ๋ก ํ๋ค.
์ฝ๋ ๋ธ๋ญ์ ๊ตฌ์ฑํ๋ ๋ชจ๋ ๋ผ์ธ์ ํ ์คํธ ๊ธธ์ด๋ฅผ ๊ตฌํด์, ๊ทธ ์ค์ ๊ฐ์ฅ ๊ธด๊ฒ์ ๋ฐํ์ผ๋ก ๋ชจ๋ ๋ผ์ธ div์์์ width๋ฅผ ๊ฒฐ์ ํด์ฃผ๋ฉด, ๊ฐ๋ก ์คํฌ๋กค ๊ธธ์ด์ ์๊ด์์ด ํญ์ ๋ง์ฐ์ค ์ค๋ฒ ํ์ ๋ ๊ฝ ์ฐจ๊ฒ ๋ฐฐ๊ฒฝ์ด ์ด๋์์ง๋๋ก ํ ์ ์๋ค. ์ฝ๋ ๋ธ๋ญ์ ํฌํจ๋ ๋ผ์ธ ์๊ฐ ๋งค์ฐ ๋ง์์ง๋ค๋ฉด ์ฑ๋ฅ ๋ฉด์์ ์ข์ ๋ฐฉ๋ฒ์ ์๋์ง๋ง ์๋ฌด๋๋ ๋ธ๋ก๊ทธ ํฌ์คํ ์ ๊ทธ๋งํ ๊ธธ์ด์ ์ฝ๋๋ฅผ ํฌ์คํ ํ ์ผ์ ์์ผ๋.. ๊ฐ๋จ ๋ฌด์ํ๊ฒ ํด๊ฒฐํ๋ค!
์ต์ข ์ ์ผ๋ก 2๋ฒ, 3๋ฒ์ ์์ ์ฌํญ์ ๋ฐ์ํ JS ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
const COPY_TEXT_CHANGE_OFFSET = 1000;
const COPY_ERROR_MESSAGE = "์ฝ๋๋ฅผ ๋ณต์ฌํ ์ ์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.";
const COPY_ICON = `
<svg width="14" height="14" fill="currentColor" class="icon" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/>
</svg>`;
const CHECK_ICON = `
<svg width="14" height="14" viewBox="0 0 14 15" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.746.07A.5.5 0 0011.5.003h-6a.5.5 0 00-.5.5v2.5H.5a.5.5 0 00-.5.5v10a.5.5 0 00.5.5h8a.5.5 0 00.5-.5v-2.5h4.5a.5.5 0 00.5-.5v-8a.498.498 0 00-.15-.357L11.857.154a.506.506 0 00-.11-.085zM9 10.003h4v-7h-1.5a.5.5 0 01-.5-.5v-1.5H6v2h.5a.5.5 0 01.357.15L8.85 5.147c.093.09.15.217.15.357v4.5zm-8-6v9h7v-7H6.5a.5.5 0 01-.5-.5v-1.5H1z" fill="currentColor"/>
</svg>`;
const adjustLineWidths = (codeBody) => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const computedStyle = window.getComputedStyle(codeBody);
context.font = `${computedStyle.fontSize} ${computedStyle.fontFamily}`;
const lines = codeBody.querySelectorAll(".line");
let maxWidth = 0;
lines.forEach((line) => {
const textWidth = context.measureText(line.textContent).width;
maxWidth = Math.max(maxWidth, textWidth);
});
maxWidth += 32;
lines.forEach((line) => {
line.style.width = `${maxWidth}px`;
});
};
const copyBlockCode = async (target = null) => {
if (!target) return;
try {
const code = decodeURI(target.dataset.code);
await navigator.clipboard.writeText(code);
target.innerHTML = CHECK_ICON;
setTimeout(() => {
target.innerHTML = COPY_ICON;
}, COPY_TEXT_CHANGE_OFFSET);
} catch (error) {
alert(COPY_ERROR_MESSAGE);
console.error(error);
}
};
const func = () => {
const codeBlocks = document.querySelectorAll("pre > code");
for (const codeBlock of codeBlocks) {
const codes = codeBlock.innerHTML.split(/\n/);
const processedCodes = codes.reduce(
(prevCodes, curCode) => prevCodes + `<div class="line">${curCode}</div>`,
""
);
const copyButton = `<button type="button" class="copy-btn" data-code="${encodeURI(
codeBlock.textContent
)}" onclick="copyBlockCode(this)">${COPY_ICON}</button>`;
const codeBody = `<div class="code-body">${processedCodes}</div>`;
const codeHeader = `
<div class="code-header">
<span class="red btn"></span>
<span class="yellow btn"></span>
<span class="green btn"></span>
${copyButton}
</div>`;
codeBlock.innerHTML = codeHeader + codeBody;
const codeBodyElement = codeBlock.querySelector(".code-body");
adjustLineWidths(codeBodyElement);
}
};
window.addEventListener("load", () => {
func();
});