@@ -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