Skip to content

Commit 1174b58

Browse files
authored
Merge pull request #71 from krsy0411/krsy0411-ui
[UI] TableOfContents 작업
2 parents 862e344 + ebe0b1d commit 1174b58

4 files changed

Lines changed: 115 additions & 34 deletions

File tree

index.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@
130130
</div>
131131
</div>
132132
<hr class="m-2 text-gray-light-200 dark:text-gray-dark-300" />
133-
<div id="nav__content" class="flex flex-col text-black">
133+
<div
134+
id="nav__content"
135+
class="flex flex-col text-black text-sm md:text-base font-light"
136+
>
134137
<ul>
135138
<li
136139
class="rounded px-2 hover:text-blue-500 hover:dark:text-blue-500"
@@ -708,9 +711,10 @@
708711

709712
<!-- aside: 오른쪽 부분 사이드 -->
710713
<aside
711-
class="sticky top-12 h-full min-w-48 overflow-y-auto py-4 px-2 lg:block"
714+
id="aside-toc"
715+
class="sticky top-12 h-full min-w-80 overflow-y-auto py-4 px-2 lg:block"
712716
>
713-
<div id="TableOfContents" class="text-black text-[12px]"></div>
717+
<div id="toc" class="text-black text-[16px]"></div>
714718
</aside>
715719
</main>
716720

src/scripts/load_md.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,30 @@
1-
import { marked } from "marked";
1+
import { marked } from 'marked';
22

33
async function loadMarkdown(page: string) {
4-
try {
5-
console.log(`📥 Fetching Markdown: /docs/${page}.md`); // 디버깅 로그 추가
4+
try {
5+
const response = await fetch(`/docs/${page}.md?cache=${Date.now()}`);
6+
if (!response.ok) throw new Error(`❌ 페이지를 찾을 수 없습니다: ${page}`);
67

7-
const response = await fetch(`/docs/${page}.md?cache=${Date.now()}`);
8-
if (!response.ok) throw new Error(`❌ 페이지를 찾을 수 없습니다: ${page}`);
8+
const mdText = await response.text();
9+
if (mdText.trim().startsWith('<!DOCTYPE html>') || mdText.includes('<html'))
10+
throw new Error(
11+
`❌ 요청된 경로가 Markdown이 아닌 HTML을 반환합니다: ${page}`
12+
);
913

10-
const mdText = await response.text();
11-
if (mdText.trim().startsWith("<!DOCTYPE html>") || mdText.includes("<html"))
12-
throw new Error(`❌ 요청된 경로가 Markdown이 아닌 HTML을 반환합니다: ${page}`);
13-
14-
const htmlContent = marked.parse(mdText);
15-
document.getElementById("content")!.innerHTML = await htmlContent;
16-
17-
console.log("✅ Markdown 로드 완료!");
18-
} catch (error) {
19-
console.error(error);
20-
document.getElementById("content")!.innerHTML = `
21-
<div class="not-found">
22-
<h2>페이지를 찾을 수 없습니다.</h2>
23-
<p>요청하신 문서를 찾을 수 없습니다. 경로를 확인해 주세요.</p>
14+
const htmlContent = marked.parse(mdText);
15+
document.getElementById('content')!.innerHTML = await htmlContent;
16+
} catch (error) {
17+
document.getElementById('content')!.innerHTML = `
18+
<div id="not-found" class="w-full">
19+
<p>페이지를 찾을 수 없습니다.</p>
2420
<a href="#/home" class="back-home">홈으로 돌아가기</a>
2521
</div>
2622
`;
27-
}
23+
}
2824
}
2925

30-
export function initializeMarkdownLoader() {
31-
function updateMarkdown() {
32-
let page = location.hash ? location.hash.substring(2) : "home";
33-
34-
console.log(`🔄 해시 기반 페이지 변경 감지: ${page}`);
35-
loadMarkdown(page);
36-
}
26+
export async function initializeMarkdownLoader() {
27+
let page = location.hash ? location.hash.substring(2) : 'home';
3728

38-
window.addEventListener("hashchange", updateMarkdown);
39-
updateMarkdown(); // 초기 실행
29+
await loadMarkdown(page);
4030
}

src/scripts/main.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ import '../styles/style.css';
44
import './load_md';
55
import { initializeMarkdownLoader } from './load_md';
66
import { initializeNavFn } from './nav';
7+
import { initializeTableContents } from './table-contents';
78

8-
initializeMarkdownLoader();
9-
initializeNavFn();
9+
document.addEventListener('DOMContentLoaded', async () => {
10+
try {
11+
await initializeMarkdownLoader();
12+
initializeNavFn();
13+
initializeTableContents();
14+
} catch (error) {
15+
console.error('❌ main.ts: DOMContentLoaded : Markdown 로드 실패!', error);
16+
// 실제 다른 작업이 필요
17+
}
18+
});
19+
20+
window.addEventListener('hashchange', async () => {
21+
try {
22+
await initializeMarkdownLoader();
23+
initializeTableContents();
24+
window.scrollTo(0, 0); // 페이지 이동 시 최상단으로 스크롤 이동
25+
} catch (error) {
26+
console.error('❌ main.ts: hashchange : Markdown 로드 실패!', error);
27+
// 실제 다른 작업이 필요
28+
}
29+
});

src/scripts/table-contents.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const createObserver = (headingMap: Record<string, HTMLLIElement>) => {
2+
return new IntersectionObserver((entries) => {
3+
const visibleEntry = entries
4+
.filter((entry) => entry.isIntersecting)
5+
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
6+
7+
if (visibleEntry) {
8+
const id = (visibleEntry.target as HTMLElement).id;
9+
10+
Object.entries(headingMap).forEach(([headingId, li]) => {
11+
if (headingId === id) {
12+
li.classList.add('border-l-2', 'border-blue-500');
13+
} else {
14+
li.classList.remove('border-l-2', 'border-blue-500');
15+
}
16+
});
17+
}
18+
});
19+
};
20+
21+
export const initializeTableContents = () => {
22+
const content = document.getElementById('content') as HTMLElement;
23+
const toc = document.getElementById('toc') as HTMLElement;
24+
25+
toc.innerHTML = '';
26+
if (!content.querySelector('h1')) return;
27+
28+
const tocTitle = document.createElement('p');
29+
const tocList = document.createElement('ul');
30+
const headings = content.querySelectorAll('h2, h3');
31+
32+
tocTitle.classList.add('text-black', 'font-light', 'text-2xl', 'pb-5');
33+
tocTitle.textContent = 'Table of contents';
34+
35+
const headingMap: Record<string, HTMLLIElement> = {};
36+
37+
headings.forEach((heading, index) => {
38+
const listItem = document.createElement('li');
39+
listItem.classList.add(
40+
'max-w-64',
41+
'font-extralight',
42+
'hover:bg-gray-300',
43+
'hover:font-light',
44+
'cursor-pointer',
45+
'truncate'
46+
);
47+
const link = document.createElement('a');
48+
link.classList.add('flex', 'justify-start', 'items-stretch', 'p-1');
49+
link.textContent = heading.textContent || '';
50+
51+
listItem.appendChild(link);
52+
tocList.appendChild(listItem);
53+
54+
if (heading.tagName === 'H3') {
55+
listItem.classList.add('pl-3');
56+
}
57+
58+
heading.id = `${index}`;
59+
headingMap[heading.id] = listItem;
60+
});
61+
62+
toc.appendChild(tocTitle);
63+
toc.appendChild(tocList);
64+
65+
const observer = createObserver(headingMap);
66+
headings.forEach((heading) => observer.observe(heading));
67+
};

0 commit comments

Comments
 (0)