11( function ( ) {
2- // Functions
3- // =========================================================================
4- /**
5- * Adds event listeners to change active stylesheet and restore previously
6- * activated stylesheet on reload.
7- *
8- * @example
9- *
10- * This link:
11- * <a href="#" data-link-title="Foo">Foo</a>
12- * Will active this existing link:
13- * <link rel="stylesheet alternate" title="Foo" href="..." >
14- *
15- * @example
16- *
17- * This link:
18- * <a href="#" data-link-href="path/to/file.css">Bar</a>
19- * Will activate this existing link:
20- * <link rel="stylesheet alternate" title="[someID]" href="path/to/file.css" >
21- * Or generate this active link:
22- * <link rel="stylesheet" title="Bar" href="path/to/file.css" >
23- */
242 function initStyleSwitcher ( ) {
253 var isInitialzed = false ;
264 var sessionStorageKey = "activeStylesheetHref" ;
5836 '"])'
5937 ) ;
6038
61- // Remove "alternate" keyword
6239 activeElm . setAttribute (
6340 "rel" ,
6441 ( activeElm . rel || "" ) . replace ( / \s * a l t e r n a t e / g, "" ) . trim ( )
6542 ) ;
6643
67- // Force enable stylesheet (required for some browsers)
6844 activeElm . disabled = true ;
6945 activeElm . disabled = false ;
7046
71- // Store active style sheet
7247 sessionStorage . setItem ( sessionStorageKey , activeHref ) ;
7348
74- // Disable other elms
7549 for ( var i = 0 ; i < inactiveElms . length ; i ++ ) {
7650 var elm = inactiveElms [ i ] ;
7751
8862 }
8963 }
9064
91- // CSS custom property ponyfil
9265 if ( ( window . $docsify || { } ) . themeable ) {
9366 window . $docsify . themeable . util . cssVars ( ) ;
9467 }
9568 }
9669
97- // Event listeners
9870 if ( ! isInitialzed ) {
9971 isInitialzed = true ;
10072
101- // Restore active stylesheet
10273 document . addEventListener ( "DOMContentLoaded" , function ( ) {
10374 var activeHref = sessionStorage . getItem ( sessionStorageKey ) ;
10475
10778 }
10879 } ) ;
10980
110- // Update active stylesheet
11181 document . addEventListener ( "click" , function ( evt ) {
11282 var dataHref = evt . target . getAttribute ( "data-link-href" ) ;
11383 var dataTitle = evt . target . getAttribute ( "data-link-title" ) ;
@@ -139,7 +109,6 @@ async function loadAllMd() {
139109 const response = await fetch ( treeUrl ) ;
140110 const { tree } = await response . json ( ) ;
141111
142- // 2. 过滤 en/products/**/.md
143112 const mdPaths = tree
144113 ?. filter (
145114 ( i ) =>
@@ -149,13 +118,11 @@ async function loadAllMd() {
149118 )
150119 ?. map ( ( i ) => i . path ) ;
151120
152- // 3. 构建目录树
153121 const directoryTree = { } ;
154122 mdPaths . forEach ( ( path ) => {
155123 const parts = path . replace ( / ^ e n \/ p r o d u c t s \/ / , "" ) . split ( "/" ) ;
156124 let current = directoryTree ;
157125
158- // 构建目录结构
159126 for ( let i = 0 ; i < parts . length - 1 ; i ++ ) {
160127 const part = parts [ i ] ;
161128 if ( ! current [ part ] ) {
@@ -164,7 +131,6 @@ async function loadAllMd() {
164131 current = current [ part ] . subdirs ;
165132 }
166133
167- // 添加文件
168134 const fileName = parts [ parts . length - 1 ] ;
169135 const parentDir = parts
170136 . slice ( 0 , - 1 )
@@ -176,22 +142,19 @@ async function loadAllMd() {
176142 }
177143 } ) ;
178144
179- // 4. 渲染导航
180145 const navElement = document . getElementById ( "product-folders" ) ;
181146 navElement . innerHTML = "<h3>产品分类</h3><ul></ul>" ;
182147 const ulElement = navElement . querySelector ( "ul" ) ;
183148
184- // 检查目录是否包含子文件夹
185149 function hasSubFolders ( node ) {
186150 return Object . values ( node . subdirs ) . some ( ( item ) => item . type === "folder" ) ;
187151 }
188152
189- // 递归渲染目录
190153 function renderDirectory ( dir , parentElement , level = 0 ) {
191154 Object . entries ( dir ) . forEach ( ( [ name , data ] ) => {
192155 const li = document . createElement ( "li" ) ;
193156 li . className = "category-item" ;
194- li . style . paddingLeft = `${ level * 2 } em` ; // 每层增加2个字符宽度的缩进
157+ li . style . paddingLeft = `${ level * 2 } em` ;
195158
196159 if ( data . type === "folder" ) {
197160 // 这是一个目录
@@ -213,16 +176,13 @@ async function loadAllMd() {
213176 const subfolderWrapper = li . querySelector ( ".subfolder-wrapper" ) ;
214177 const subfolder = li . querySelector ( ".subfolder" ) ;
215178
216- // 点击文件夹名称时加载该文件夹下的所有 md 文件
217179 folderName . addEventListener ( "click" , ( ) => {
218- // 移除所有选中状态
219180 document . querySelectorAll ( ".category-item" ) . forEach ( ( item ) => {
220181 item . classList . remove ( "active" ) ;
221182 } ) ;
222- // 添加当前选中状态
183+
223184 li . classList . add ( "active" ) ;
224185
225- // 收集当前文件夹及其子文件夹下的所有 md 文件
226186 const allFiles = [ ] ;
227187 function collectFiles ( node ) {
228188 if ( node . type === "file" ) {
@@ -234,7 +194,6 @@ async function loadAllMd() {
234194 collectFiles ( data ) ;
235195 showAllMd ( allFiles ) ;
236196
237- // 如果有子文件夹,处理展开/折叠
238197 if ( hasSubdirs ) {
239198 const isExpanded =
240199 subfolderWrapper . classList . contains ( "expanded" ) ;
@@ -247,7 +206,6 @@ async function loadAllMd() {
247206 renderDirectory ( data . subdirs , subfolder , level + 1 ) ;
248207 }
249208 } else {
250- // 这是一个文件,不显示在导航中
251209 return ;
252210 }
253211
@@ -257,7 +215,6 @@ async function loadAllMd() {
257215
258216 renderDirectory ( directoryTree , ulElement ) ;
259217
260- // 自动展开并选中第一个目录
261218 const firstFolder = ulElement . querySelector ( ".folder-item" ) ;
262219 if ( firstFolder ) {
263220 const folderName = firstFolder . querySelector ( ".folder-name" ) ;
@@ -287,12 +244,13 @@ async function showAllMd(paths) {
287244 const md = await fetch ( rawUrl ) . then ( ( r ) => r . text ( ) ) ;
288245 const name = p . split ( "/" ) . pop ( ) ;
289246
290- // 提取元数据
291247 const metadata = extractMetadata ( md ) ;
292248
293249 // 渲染内容
294250 html += `
295- <div class="md-document">
251+ <div class="md-card" ${
252+ metadata . link ? `data-link="${ metadata . link } "` : ""
253+ } >
296254 <div class="md-header">
297255 <h2>${ metadata . title || name } </h2>
298256 ${
@@ -312,17 +270,91 @@ async function showAllMd(paths) {
312270 : ""
313271 }
314272 </div>
315- <hr/>
316273 ` ;
317274 }
318275 mdContent . innerHTML = html ;
276+
277+
278+ const cards = document . getElementsByClassName ( "md-card" ) ;
279+ Array . from ( cards ) . forEach ( ( card ) => {
280+ const link = card . getAttribute ( "data-link" ) ;
281+ if ( link ) {
282+ card . style . cursor = "pointer" ;
283+ card . addEventListener ( "click" , ( e ) => {
284+
285+ if ( e . target . tagName === "A" ) {
286+ return ;
287+ }
288+ window . open ( link , "_blank" ) ;
289+ } ) ;
290+ }
291+ } ) ;
292+
293+ initMasonry ( ) ;
319294 } catch ( error ) {
320295 console . error ( "加载文件内容失败:" , error ) ;
321296 mdContent . innerHTML = "<p>加载失败,请重试</p>" ;
322297 }
323298}
324299
325- // 提取元数据函数
300+ function initMasonry ( ) {
301+ const container = document . getElementById ( "md-content" ) ;
302+ const cards = container . getElementsByClassName ( "md-card" ) ;
303+ const cardWidth = 400 ;
304+ const gap = 20 ;
305+
306+ function calculateLayout ( ) {
307+ const containerWidth = container . clientWidth ;
308+ const columnCount = Math . floor ( ( containerWidth + gap ) / ( cardWidth + gap ) ) ;
309+ return columnCount ;
310+ }
311+
312+ function layout ( ) {
313+ const columnCount = calculateLayout ( ) ;
314+ const columns = Array ( columnCount )
315+ . fill ( )
316+ . map ( ( ) => [ ] ) ;
317+ const columnHeights = Array ( columnCount ) . fill ( 0 ) ;
318+
319+ container . style . position = "relative" ;
320+ container . style . height = "auto" ;
321+
322+ Array . from ( cards ) . forEach ( ( card ) => {
323+ const minHeightIndex = columnHeights . indexOf ( Math . min ( ...columnHeights ) ) ;
324+ columns [ minHeightIndex ] . push ( card ) ;
325+ columnHeights [ minHeightIndex ] += card . offsetHeight + gap ;
326+ } ) ;
327+
328+ let currentX = 0 ;
329+ columns . forEach ( ( column , columnIndex ) => {
330+ let currentY = 0 ;
331+ column . forEach ( ( card ) => {
332+ card . style . position = "absolute" ;
333+ card . style . width = `${ cardWidth } px` ;
334+ card . style . left = `${ currentX } px` ;
335+ card . style . top = `${ currentY } px` ;
336+ currentY += card . offsetHeight + gap ;
337+ } ) ;
338+ currentX += cardWidth + gap ;
339+ } ) ;
340+
341+ container . style . height = `${ Math . max ( ...columnHeights ) } px` ;
342+ }
343+
344+ let resizeTimer ;
345+ window . addEventListener ( "resize" , ( ) => {
346+ clearTimeout ( resizeTimer ) ;
347+ resizeTimer = setTimeout ( layout , 100 ) ;
348+ } ) ;
349+
350+ const images = container . getElementsByTagName ( "img" ) ;
351+ Array . from ( images ) . forEach ( ( img ) => {
352+ img . addEventListener ( "load" , layout ) ;
353+ } ) ;
354+
355+ layout ( ) ;
356+ }
357+
326358function extractMetadata ( md ) {
327359 const metadata = {
328360 title : null ,
@@ -331,7 +363,6 @@ function extractMetadata(md) {
331363 link : null ,
332364 } ;
333365
334- // 尝试匹配 YAML front matter
335366 const yamlMatch = md . match ( / ^ - - - \s * \n ( [ \s \S ] * ?) \n - - - / ) ;
336367 if ( yamlMatch ) {
337368 const yamlContent = yamlMatch [ 1 ] ;
@@ -372,7 +403,6 @@ function extractMetadata(md) {
372403 return metadata ;
373404}
374405
375- // 页面加载完成后执行
376406document . addEventListener ( "DOMContentLoaded" , ( ) => {
377407 loadAllMd ( ) ;
378408} ) ;
0 commit comments