Skip to content

Commit 334aa2f

Browse files
committed
Mobile UI update
1 parent 051bd49 commit 334aa2f

14 files changed

Lines changed: 1008 additions & 135 deletions

File tree

.github/workflows/deploy-pages.yml

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ jobs:
5151
pip install --upgrade pip
5252
pip install -r requirements.txt
5353
54+
- name: Setup Rust
55+
uses: dtolnay/rust-toolchain@stable
56+
57+
- name: Test docs validator tooling
58+
run: cargo test -q --manifest-path tools/docs-validator-rs/Cargo.toml
59+
5460
- name: Inject AI configuration from secrets
5561
env:
5662
AI_API_BASE_URL: ${{ secrets.AI_API_BASE_URL }}
@@ -119,39 +125,7 @@ jobs:
119125
120126
- name: Copy extra static content to site (post-build)
121127
run: |
122-
mkdir -p site/extra
123-
124-
# algorithm simulator (optional - may not exist in all branches)
125-
if [ -d "algorithm/simulator/javascript" ]; then
126-
cp -rL algorithm/simulator/javascript site/extra/algorithm-simulator
127-
else
128-
echo "⚠️ algorithm/simulator/javascript not found, skipping"
129-
fi
130-
131-
rsync -av --exclude='*.class' --exclude='*.pyc' --exclude='__pycache__' scripts/ site/extra/scripts/ 2>/dev/null || true
132-
133-
rsync -av --exclude='.git' --exclude='*.class' docker/ site/extra/docker/ 2>/dev/null || true
134-
135-
rsync -av configs/ site/extra/configs/ 2>/dev/null || true
136-
137-
rsync -av project-docs/ site/extra/project-docs/ 2>/dev/null || true
138-
139-
rsync -av --exclude='.git' legacy/ site/extra/legacy/ 2>/dev/null || true
140-
141-
rsync -av prompt/ site/extra/prompts-raw/ 2>/dev/null || true
142-
143-
rsync -av --no-links mcp/ site/extra/mcp/ 2>/dev/null || true
144-
145-
rsync -av memo/ site/extra/memo/ 2>/dev/null || true
146-
147-
mkdir -p site/extra/algorithm-code
148-
rsync -av --exclude='*.class' --exclude='*.pyc' --exclude='__pycache__' --exclude='.ruff_cache' algorithm/data-structures/ site/extra/algorithm-code/data-structures/ 2>/dev/null || true
149-
rsync -av --exclude='*.class' algorithm/code/ site/extra/algorithm-code/code/ 2>/dev/null || true
150-
rsync -av algorithm/C-lang/ site/extra/algorithm-code/C-lang/ 2>/dev/null || true
151-
rsync -av --exclude='*.pyc' --exclude='__pycache__' algorithm/simulator/python/ site/extra/algorithm-code/simulator-python/ 2>/dev/null || true
152-
rsync -av --exclude='*.class' algorithm/simulator/java/ site/extra/algorithm-code/simulator-java/ 2>/dev/null || true
153-
cp algorithm/README.md site/extra/algorithm-code/ 2>/dev/null || true
154-
cp algorithm/REVIEW_REPORT.md site/extra/algorithm-code/ 2>/dev/null || true
128+
bash scripts/site/sync-extra-assets.sh site/extra
155129
156130
- name: Create CNAME file
157131
run: echo 'docs.nodove.com' > site/CNAME

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ release/
104104
debug/
105105

106106
# Docs
107-
site/
107+
/site/
108108
docs/_build/
109109

110110
# ====================

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ open http://localhost:8000
2020
# Build static site
2121
docker compose -f docker-compose.docs.yml --profile build up docs-build
2222

23+
# Sync extra raw assets into site/extra
24+
bash scripts/site/sync-extra-assets.sh
25+
2326
# Serve with nginx
2427
docker compose -f docker-compose.docs.yml --profile production up nginx
2528
```

docs/extra/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@
110110
| Resource | Description | Type |
111111
|----------|-------------|------|
112112
| [Algorithm Simulator](/extra/algorithm-simulator/index.html){ target="_blank" } | 인터랙티브 알고리즘 시각화 | :material-web: Web App |
113-
| [Python Simulators](/extra/algorithm-code/simulator-python/){ target="_blank" } | CLI 기반 시뮬레이터 | :material-language-python: Python |
114-
| [Java Simulators](/extra/algorithm-code/simulator-java/){ target="_blank" } | CLI 기반 시뮬레이터 | :material-language-java: Java |
113+
| [Simulator Sources](/extra/algorithm-code/simulator/){ target="_blank" } | Python/JavaScript/Java 시뮬레이터 소스 | :material-source-repository: Source |
114+
| [Architect Code](/extra/algorithm-code/code/){ target="_blank" } | 알고리즘 풀이 아카이브 | :material-code-braces: Code |
115115
| [BST Implementation](/extra/algorithm-code/data-structures/tree/binary-search-tree/){ target="_blank" } | 4개 언어 BST 구현 | :material-code-braces: Multi-lang |
116116
| [Deploy Script](/extra/scripts/deployment/deploy.sh){ target="_blank" } | 배포 스크립트 | :material-bash: Shell |
117117
| [Docker Images](/extra/docker/images/){ target="_blank" } | 개발 환경 Docker 이미지 | :material-docker: Docker |

docs/javascripts/ai-config.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ window.AI_CONFIG = {
4747
placeholder: '메시지를 입력하세요...',
4848
systemPrompt: `${openNotebookEnabled ? `${openNotebookPromptPrefix}
4949
50-
` : ''}당신은 Documentation Hub의 AI 어시스턴트입니다.
51-
이 문서 사이트는 인프라, 개발, 보안, Docker 설정 등 기술 문서를 제공합니다.
52-
사용자가 문서 내용에 대해 질문하면 친절하고 정확하게 답변해주세요.
53-
답변은 한국어로 작성하고, 관련 문서 링크가 있다면 안내해주세요.
54-
마크다운 형식으로 답변하세요.`
50+
` : ''}당신은 Documentation Hub(docs.nodove.com)의 AI 어시스턴트입니다.
51+
이 사이트는 인프라, 개발, 보안, Docker, Linux 등 기술 문서를 제공합니다.
52+
53+
중요 지침:
54+
1. 반드시 문서에 기반하여 답변하세요. 확실하지 않으면 추측하지 마세요.
55+
2. 문서에서 찾을 수 없는 내용은 "해당 내용은 현재 문서에서 찾을 수 없습니다"라고 솔직하게 답하세요.
56+
3. 답변 시 관련 문서 경로나 링크를 함께 제공하세요 (예: /infrastructure/proxmox/cluster/).
57+
4. 기술적 사실은 반드시 문서 내용에 근거해야 하며, 만들어내지 마세요.
58+
5. 답변은 한국어로 작성하고, 마크다운 형식을 사용하세요.
59+
6. 코드 예시는 실제 문서의 내용만 사용하세요.`
5560
}
5661
};
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
2+
(function () {
3+
'use strict';
4+
5+
function initReadingProgress() {
6+
const bar = document.createElement('div');
7+
bar.id = 'reading-progress';
8+
bar.setAttribute('role', 'progressbar');
9+
bar.setAttribute('aria-label', '읽기 진행률');
10+
bar.setAttribute('aria-valuemin', '0');
11+
bar.setAttribute('aria-valuemax', '100');
12+
document.body.prepend(bar);
13+
14+
function updateProgress() {
15+
const doc = document.documentElement;
16+
const scrollTop = doc.scrollTop || document.body.scrollTop;
17+
const scrollHeight = doc.scrollHeight - doc.clientHeight;
18+
const progress = scrollHeight > 0 ? Math.round((scrollTop / scrollHeight) * 100) : 0;
19+
bar.style.width = progress + '%';
20+
bar.setAttribute('aria-valuenow', progress);
21+
}
22+
23+
window.addEventListener('scroll', updateProgress, { passive: true });
24+
updateProgress();
25+
}
26+
27+
function initSkipToContent() {
28+
if (document.getElementById('skip-to-content')) return;
29+
const link = document.createElement('a');
30+
link.id = 'skip-to-content';
31+
link.href = '#main-content';
32+
link.className = 'skip-to-content';
33+
link.textContent = '본문으로 건너뛰기';
34+
35+
const main = document.querySelector('.md-main__inner, .md-content, main');
36+
if (main && !main.id) {
37+
main.id = 'main-content';
38+
}
39+
40+
document.body.prepend(link);
41+
}
42+
43+
function initExternalLinkIndicators() {
44+
document.querySelectorAll('.md-typeset a[href]').forEach(function (a) {
45+
const href = a.getAttribute('href') || '';
46+
const isExternal =
47+
href.startsWith('http') &&
48+
!href.includes(window.location.hostname) &&
49+
!href.includes('docs.nodove.com');
50+
51+
if (isExternal) {
52+
if (!a.getAttribute('target')) {
53+
a.setAttribute('target', '_blank');
54+
a.setAttribute('rel', 'noopener noreferrer');
55+
}
56+
if (!a.querySelector('.external-icon') && !a.closest('.md-button')) {
57+
const icon = document.createElement('span');
58+
icon.className = 'external-icon';
59+
icon.setAttribute('aria-label', '(외부 링크)');
60+
icon.innerHTML =
61+
'<svg xmlns="http://www.w3.org/2000/svg" width="0.75em" height="0.75em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';
62+
icon.style.cssText =
63+
'display:inline-block;margin-left:0.2em;vertical-align:middle;opacity:0.6;';
64+
a.appendChild(icon);
65+
}
66+
}
67+
});
68+
}
69+
70+
function initTableOfContentsHighlight() {
71+
const tocLinks = document.querySelectorAll('.md-nav--secondary .md-nav__link');
72+
if (!tocLinks.length) return;
73+
74+
const headings = Array.from(
75+
document.querySelectorAll('.md-content h2[id], .md-content h3[id]')
76+
);
77+
78+
if (!headings.length) return;
79+
80+
const observer = new IntersectionObserver(
81+
function (entries) {
82+
entries.forEach(function (entry) {
83+
if (entry.isIntersecting) {
84+
const id = entry.target.id;
85+
tocLinks.forEach(function (link) {
86+
const href = link.getAttribute('href');
87+
if (href === '#' + id) {
88+
link.classList.add('md-nav__link--active-scroll');
89+
} else {
90+
link.classList.remove('md-nav__link--active-scroll');
91+
}
92+
});
93+
}
94+
});
95+
},
96+
{
97+
rootMargin: '-10% 0px -80% 0px',
98+
threshold: 0,
99+
}
100+
);
101+
102+
headings.forEach(function (h) {
103+
observer.observe(h);
104+
});
105+
}
106+
107+
function initKeyboardNav() {
108+
document.addEventListener('keydown', function (e) {
109+
if (e.key === '/' && !e.ctrlKey && !e.metaKey) {
110+
const activeEl = document.activeElement;
111+
const isInput =
112+
activeEl &&
113+
(activeEl.tagName === 'INPUT' ||
114+
activeEl.tagName === 'TEXTAREA' ||
115+
activeEl.isContentEditable);
116+
if (!isInput) {
117+
const searchInput = document.querySelector('.md-search__input');
118+
if (searchInput) {
119+
e.preventDefault();
120+
searchInput.focus();
121+
searchInput.select();
122+
}
123+
}
124+
}
125+
});
126+
}
127+
128+
function initSmoothTabs() {
129+
const tabLinks = document.querySelectorAll('.md-tabs__link');
130+
tabLinks.forEach(function (link) {
131+
if (!link.getAttribute('tabindex')) {
132+
link.setAttribute('tabindex', '0');
133+
}
134+
if (!link.getAttribute('aria-label')) {
135+
const text = link.textContent.trim();
136+
if (text) link.setAttribute('aria-label', text + ' 섹션');
137+
}
138+
});
139+
}
140+
141+
function init() {
142+
initReadingProgress();
143+
initSkipToContent();
144+
initKeyboardNav();
145+
initSmoothTabs();
146+
147+
document$.subscribe(function () {
148+
initExternalLinkIndicators();
149+
initTableOfContentsHighlight();
150+
});
151+
}
152+
153+
if (typeof document$ !== 'undefined') {
154+
document$.subscribe(init);
155+
} else if (document.readyState === 'loading') {
156+
document.addEventListener('DOMContentLoaded', init);
157+
} else {
158+
init();
159+
}
160+
})();

docs/projects/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ mindmap
5252
| Project | Stack | Status |
5353
|---------|-------|--------|
5454
| [CBT System](cbt-system.md) | Spring Boot, React, PostgreSQL | Active |
55-
| [Emotion Diary](emotion-diary.md) | Flutter, Firebase | Active |
55+
| [Emotion Diary](emotion-diary.md) | React, TypeScript, Spring Boot, MySQL | Active |
5656

5757
---
5858

0 commit comments

Comments
 (0)