feat(File Tree): add file-tree shortcode support (#685)

* feat(File Tree): add `file-tree` shortcode support

* feat(file-tree): add ignoreList parameter to file-tree shortcode

* feat(file-tree): enhance file-tree shortcode with ignoreList and folderSlash options

* feat(file-tree): enhance file-tree shortcode to support data mode

* feat(file-tree): enhance file-tree shortcode to support file mode

* feat(file-tree): improve content parsing in file-tree shortcode

* feat(file-tree): improve root path validation and error handling in filesystem mode

* test(file-tree): add unit tests for `file-tree` shortcode

* feat(file-tree): add file-tree code block rendering support

* refactor(file-tree): extract filesystem scanning to get-file-tree partial

Move directory traversal and root path validation to a separate partial function,
consolidating all filesystem-related logic in one place. This simplifies the
shortcode implementation and ensures a unified data format for rendering.

* docs(readme): extend code fence and shortcode support for file tree in documentation

* feat(file-tree): add fullRootName parameter for root path display in file tree

* feat(file-tree): add language parameter to get-file-tree function

* feat(file-tree): add highlightList parameter for file-tree shortcode

* perf(file-tree): add level param for get-file-tree function to descend only level directories deep
This commit is contained in:
Cell
2026-01-14 16:01:22 +08:00
committed by GitHub
parent 0c1d074890
commit fdfd772100
23 changed files with 1030 additions and 5 deletions
+50
View File
@@ -0,0 +1,50 @@
/**
* FileTree class to handle file tree interactions
*/
export default class FileTree {
init(target = document) {
target.querySelectorAll('.file-tree-toggle:not([data-init])').forEach((label) => {
label.addEventListener('click', (e) => {
e.stopPropagation()
const item = label.closest('.file-tree-folder')
const isCollapsed = item.classList.contains('is-collapsed')
item && item.classList.toggle('is-collapsed', !isCollapsed)
const wrapper = label.closest('.file-tree-wrapper')
this.updateLineHeight(wrapper)
})
label.dataset.init = 'true'
})
this.updateLineHeight(target)
}
updateLineHeight(target = document) {
const uls = target.querySelectorAll('.file-tree .file-tree')
uls.forEach((ul) => {
const parentItem = ul.closest('.file-tree-item.is-collapsed')
if (parentItem) {
ul.style.removeProperty('--fi-file-tree-line-height')
return
}
const items = Array.from(ul.children).filter((el) => el.classList?.contains('file-tree-item'))
if (!items.length) {
ul.style.removeProperty('--fi-file-tree-line-height')
return
}
const firstLabel = items[0].querySelector('.file-tree-label')
const lastLabel = items[items.length - 1].querySelector('.file-tree-label')
if (!firstLabel || !lastLabel) return
const firstRect = firstLabel.getBoundingClientRect()
const lastRect = lastLabel.getBoundingClientRect()
const firstCenterY = firstRect.top + firstRect.height / 2
const lastCenterY = lastRect.top + lastRect.height / 2
const offsetY = firstRect.height / 2 + 1/2 + 4
const height = Math.max(0, lastCenterY - firstCenterY + offsetY)
ul.style.setProperty('--fi-file-tree-line-height', `${height}px`)
})
}
}
+5
View File
@@ -1,11 +1,14 @@
// TODO use ESLint to check the code style
// 按需加载主题内部库和功能模块
import Util from './util';
import FileTree from './lib/file-tree.js'
class FixIt {
constructor() {
this.config = window.config;
this.isDark = document.documentElement.dataset.theme === 'dark';
this.util = new Util();
this.fileTree = new FileTree();
this.newScrollTop = this.util.getScrollTop();
this.oldScrollTop = this.newScrollTop;
this.scrollEventSet = new Set();
@@ -1122,6 +1125,7 @@ class FixIt {
initTabEvents(target = document) {
target.addEventListener('tab-container-changed', () => {
this.fileTree.updateLineHeight(target);
window.FixItMermaid?.init?.();
}, false);
}
@@ -1150,6 +1154,7 @@ class FixIt {
this.initMathJax();
this.initJsonViewer();
this.initTabEvents(target);
this.fileTree.init(target);
window.FixItMermaid?.init?.();
window.FixItAPlayer?.init?.();
}