Skip to content

Commit e4b4510

Browse files
committed
feat: enhance navigation and error handling in note rendering
1 parent 915b265 commit e4b4510

File tree

1 file changed

+150
-16
lines changed

1 file changed

+150
-16
lines changed

assets/js/app.js

Lines changed: 150 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ document.addEventListener('DOMContentLoaded', () => {
3737
return `${noteUrl.pathname}${noteUrl.search}`;
3838
}
3939

40+
function buildHomeHref() {
41+
return APP_BASE_URL.pathname;
42+
}
43+
4044
function buildNoteAssetUrl(notePath) {
4145
return new URL(normalizeNotePath(notePath), APP_BASE_URL);
4246
}
@@ -50,6 +54,86 @@ document.addEventListener('DOMContentLoaded', () => {
5054
return normalizeNotePath(new URL(targetPath, virtualBase).pathname);
5155
}
5256

57+
function getMarkedLinkArgs(hrefOrToken, title, text, rendererContext) {
58+
if (hrefOrToken && typeof hrefOrToken === 'object') {
59+
const token = hrefOrToken;
60+
const renderedText = token.tokens && rendererContext?.parser
61+
? rendererContext.parser.parseInline(token.tokens)
62+
: (token.text || token.href || '');
63+
64+
return {
65+
href: token.href || '',
66+
title: token.title || '',
67+
text: renderedText,
68+
};
69+
}
70+
71+
return {
72+
href: hrefOrToken || '',
73+
title: title || '',
74+
text: text || hrefOrToken || '',
75+
};
76+
}
77+
78+
function scrollToHeading(targetId) {
79+
if (!targetId) {
80+
return;
81+
}
82+
83+
const targetElement = document.getElementById(targetId);
84+
if (!targetElement) {
85+
return;
86+
}
87+
88+
const yOffset = -80;
89+
const y = targetElement.getBoundingClientRect().top + window.pageYOffset + yOffset;
90+
window.scrollTo({ top: y, behavior: 'smooth' });
91+
}
92+
93+
function navigateToUrl(url, replace = false) {
94+
const method = replace ? 'replaceState' : 'pushState';
95+
window.history[method]({}, '', `${url.pathname}${url.search}${url.hash}`);
96+
handleRoute();
97+
}
98+
99+
function navigateToNote(notePath, options = {}) {
100+
const noteUrl = new URL(APP_BASE_URL);
101+
noteUrl.searchParams.set('note', normalizeNotePath(notePath));
102+
103+
if (options.hash) {
104+
noteUrl.hash = options.hash;
105+
}
106+
107+
navigateToUrl(noteUrl, options.replace);
108+
}
109+
110+
function navigateHome(replace = false) {
111+
navigateToUrl(new URL(APP_BASE_URL), replace);
112+
}
113+
114+
function isModifiedClick(event) {
115+
return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;
116+
}
117+
118+
function getAppUrl(anchor) {
119+
if (!anchor || !anchor.href) {
120+
return null;
121+
}
122+
123+
try {
124+
const url = new URL(anchor.href, window.location.href);
125+
if (url.origin !== window.location.origin) {
126+
return null;
127+
}
128+
if (url.pathname !== APP_BASE_URL.pathname) {
129+
return null;
130+
}
131+
return url;
132+
} catch {
133+
return null;
134+
}
135+
}
136+
53137
// --- Theme Management ---
54138
function initTheme() {
55139
const savedTheme = localStorage.getItem('theme');
@@ -105,13 +189,14 @@ document.addEventListener('DOMContentLoaded', () => {
105189
// Handle relative links in markdown (intercept links to other .md files)
106190
const renderer = new marked.Renderer();
107191
renderer.link = function(href, title, text) {
192+
const linkArgs = getMarkedLinkArgs(href, title, text, this);
108193
// If it's a relative link to an .md file, convert it to our routing system
109-
if (href && !href.startsWith('http') && !href.startsWith('#') && href.endsWith('.md')) {
194+
if (linkArgs.href && !linkArgs.href.startsWith('http') && !linkArgs.href.startsWith('#') && linkArgs.href.endsWith('.md')) {
110195
const currentNote = new URLSearchParams(window.location.search).get('note');
111-
const resolvedPath = resolveNotePath(currentNote, href);
112-
return `<a href="${buildNoteHref(resolvedPath)}" title="${title || ''}">${text}</a>`;
196+
const resolvedPath = resolveNotePath(currentNote, linkArgs.href);
197+
return `<a href="${buildNoteHref(resolvedPath)}" data-note-path="${resolvedPath}" title="${linkArgs.title}">${linkArgs.text}</a>`;
113198
}
114-
return `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
199+
return `<a href="${linkArgs.href}" title="${linkArgs.title}" target="_blank" rel="noopener noreferrer">${linkArgs.text}</a>`;
115200
};
116201
marked.use({ renderer });
117202

@@ -153,10 +238,22 @@ document.addEventListener('DOMContentLoaded', () => {
153238

154239
githubEditLink.href = `${GITHUB_REPO_URL}${normalizedNotePath}`;
155240

241+
let text;
156242
try {
157-
const text = await fetchNoteContent(normalizedNotePath);
243+
text = await fetchNoteContent(normalizedNotePath);
244+
} catch (error) {
245+
console.error('Error fetching markdown:', error);
246+
markdownContent.innerHTML = `
247+
<div style="color: #ef4444; padding: 2rem; text-align: center;">
248+
<h2>无法加载笔记 😢</h2>
249+
<p style="margin-top: 1rem;">找不到文件: <code>${normalizedNotePath}</code></p>
250+
<p style="margin-top: 0.5rem; font-size: 0.9em; color: var(--text-secondary);">请检查路径是否正确,或确认当前站点根目录可访问该 Markdown 文件。</p>
251+
</div>
252+
`;
253+
return;
254+
}
158255

159-
// Parse Markdown to HTML
256+
try {
160257
const html = marked.parse(text);
161258
markdownContent.innerHTML = html;
162259

@@ -166,13 +263,17 @@ document.addEventListener('DOMContentLoaded', () => {
166263

167264
buildTableOfContents();
168265
addCopyButtons();
266+
267+
if (window.location.hash) {
268+
scrollToHeading(window.location.hash.substring(1));
269+
}
169270
} catch (error) {
170-
console.error('Error fetching markdown:', error);
271+
console.error('Error rendering markdown:', error);
171272
markdownContent.innerHTML = `
172273
<div style="color: #ef4444; padding: 2rem; text-align: center;">
173-
<h2>无法加载笔记 😢</h2>
174-
<p style="margin-top: 1rem;">找不到文件: <code>${normalizedNotePath}</code></p>
175-
<p style="margin-top: 0.5rem; font-size: 0.9em; color: var(--text-secondary);">请检查路径是否正确,或确认当前站点根目录可访问该 Markdown 文件。</p>
274+
<h2>笔记渲染失败 😵</h2>
275+
<p style="margin-top: 1rem;">文件已加载,但 Markdown 渲染过程中发生错误。</p>
276+
<p style="margin-top: 0.5rem; font-size: 0.9em; color: var(--text-secondary);">请检查控制台错误信息,或修复当前文档中的链接与语法。</p>
176277
</div>
177278
`;
178279
}
@@ -218,16 +319,48 @@ document.addEventListener('DOMContentLoaded', () => {
218319
if (e.target.tagName === 'A') {
219320
e.preventDefault();
220321
const targetId = e.target.getAttribute('href').substring(1);
221-
const targetElement = document.getElementById(targetId);
222-
if (targetElement) {
223-
const yOffset = -80;
224-
const y = targetElement.getBoundingClientRect().top + window.pageYOffset + yOffset;
225-
window.scrollTo({ top: y, behavior: 'smooth' });
226-
}
322+
window.history.replaceState({}, '', `${window.location.pathname}${window.location.search}#${targetId}`);
323+
scrollToHeading(targetId);
227324
}
228325
});
229326
}
230327

328+
document.addEventListener('click', (event) => {
329+
const anchor = event.target.closest('a');
330+
if (!anchor || isModifiedClick(event) || anchor.target === '_blank' || anchor.hasAttribute('download')) {
331+
return;
332+
}
333+
334+
if (anchor.getAttribute('href')?.startsWith('#')) {
335+
event.preventDefault();
336+
const targetId = anchor.getAttribute('href').substring(1);
337+
window.history.replaceState({}, '', `${window.location.pathname}${window.location.search}#${targetId}`);
338+
scrollToHeading(targetId);
339+
return;
340+
}
341+
342+
const explicitNotePath = anchor.dataset.notePath;
343+
if (explicitNotePath) {
344+
event.preventDefault();
345+
navigateToNote(explicitNotePath);
346+
return;
347+
}
348+
349+
const appUrl = getAppUrl(anchor);
350+
if (!appUrl) {
351+
return;
352+
}
353+
354+
event.preventDefault();
355+
const notePath = appUrl.searchParams.get('note');
356+
if (notePath) {
357+
navigateToNote(notePath, { hash: appUrl.hash });
358+
return;
359+
}
360+
361+
navigateHome();
362+
});
363+
231364
// --- Code Copy ---
232365
function addCopyButtons() {
233366
const preBlocks = markdownContent.querySelectorAll('pre');
@@ -269,6 +402,7 @@ document.addEventListener('DOMContentLoaded', () => {
269402
} else {
270403
noteView.classList.replace('section-active', 'section-hidden');
271404
homeView.classList.replace('section-hidden', 'section-active');
405+
window.scrollTo(0, 0);
272406
renderGrid();
273407
}
274408
}

0 commit comments

Comments
 (0)