| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
function mk(tag, cls, styles) { var d = document.createElement(tag); if (cls) d.className = cls; if (styles) Object.keys(styles).forEach(function (k) { d.style[k] = styles[k]; }); return d; } |
| 4 |
function tx(tag, cls, text, styles) { var d = mk(tag, cls, styles); d.textContent = text; return d; } |
| 5 |
function ap(p) { Array.prototype.slice.call(arguments, 1).forEach(function (c) { if (c) p.appendChild(c); }); return p; } |
| 6 |
|
| 7 |
function typoCssVarsForEl(typo, prefix, el) { |
| 8 |
if (!typo) return; |
| 9 |
if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', monospace"); |
| 10 |
if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight); |
| 11 |
if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform); |
| 12 |
if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style); |
| 13 |
if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration); |
| 14 |
var su = typo.sizeUnit || 'px'; |
| 15 |
if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) el.style.setProperty(prefix + 'font-size-d', typo.sizeDesktop + su); |
| 16 |
if (typo.sizeTablet !== '' && typo.sizeTablet != null) el.style.setProperty(prefix + 'font-size-t', typo.sizeTablet + su); |
| 17 |
if (typo.sizeMobile !== '' && typo.sizeMobile != null) el.style.setProperty(prefix + 'font-size-m', typo.sizeMobile + su); |
| 18 |
var lhu = typo.lineHeightUnit || ''; |
| 19 |
if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) el.style.setProperty(prefix + 'line-height-d', typo.lineHeightDesktop + lhu); |
| 20 |
if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) el.style.setProperty(prefix + 'line-height-t', typo.lineHeightTablet + lhu); |
| 21 |
if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) el.style.setProperty(prefix + 'line-height-m', typo.lineHeightMobile + lhu); |
| 22 |
var lsu = typo.letterSpacingUnit || 'px'; |
| 23 |
if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) el.style.setProperty(prefix + 'letter-spacing-d', typo.letterSpacingDesktop + lsu); |
| 24 |
if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) el.style.setProperty(prefix + 'letter-spacing-t', typo.letterSpacingTablet + lsu); |
| 25 |
if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) el.style.setProperty(prefix + 'letter-spacing-m', typo.letterSpacingMobile + lsu); |
| 26 |
var wsu = typo.wordSpacingUnit || 'px'; |
| 27 |
if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) el.style.setProperty(prefix + 'word-spacing-d', typo.wordSpacingDesktop + wsu); |
| 28 |
if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) el.style.setProperty(prefix + 'word-spacing-t', typo.wordSpacingTablet + wsu); |
| 29 |
if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) el.style.setProperty(prefix + 'word-spacing-m', typo.wordSpacingMobile + wsu); |
| 30 |
} |
| 31 |
|
| 32 |
function getExtension(name) { |
| 33 |
var parts = name.split('.'); |
| 34 |
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : ''; |
| 35 |
} |
| 36 |
|
| 37 |
function getExtColor(ext, a) { |
| 38 |
if (ext === 'js' || ext === 'jsx' || ext === 'mjs') return a.jsColor; |
| 39 |
if (ext === 'ts' || ext === 'tsx') return a.tsColor; |
| 40 |
if (ext === 'css' || ext === 'scss' || ext === 'sass') return a.cssColor; |
| 41 |
if (ext === 'html' || ext === 'htm') return a.htmlColor; |
| 42 |
if (ext === 'json' || ext === 'jsonc') return a.jsonColor; |
| 43 |
if (ext === 'md' || ext === 'mdx') return a.mdColor; |
| 44 |
if (ext === 'py') return a.pyColor; |
| 45 |
if (ext === 'png' || ext === 'jpg' || ext === 'jpeg' || ext === 'svg' || ext === 'gif' || ext === 'ico' || ext === 'webp') return a.imgColor; |
| 46 |
return a.fileColor; |
| 47 |
} |
| 48 |
|
| 49 |
function getFileIcon(item) { |
| 50 |
if (item.type === 'folder') return item.isOpen ? '📂' : '📁'; |
| 51 |
var ext = getExtension(item.name); |
| 52 |
if (ext === 'js' || ext === 'jsx') return '⬡'; |
| 53 |
if (ext === 'ts' || ext === 'tsx') return '⬡'; |
| 54 |
if (ext === 'css' || ext === 'scss') return '#'; |
| 55 |
if (ext === 'html') return '◈'; |
| 56 |
if (ext === 'json') return '{}'; |
| 57 |
if (ext === 'md' || ext === 'mdx') return '✎'; |
| 58 |
if (ext === 'py') return '🐍'; |
| 59 |
if (ext === 'svg' || ext === 'png' || ext === 'jpg' || ext === 'ico') return '🖼'; |
| 60 |
return '◻'; |
| 61 |
} |
| 62 |
|
| 63 |
function buildBlock(appEl) { |
| 64 |
if (appEl.dataset.rendered) return; |
| 65 |
appEl.dataset.rendered = '1'; |
| 66 |
|
| 67 |
var a = Object.assign({ |
| 68 |
treeTitle: 'Project Structure', showTitle: true, showIcons: true, showLines: true, showExtensionColors: true, |
| 69 |
files: [], fontSize: 14, titleFontSize: 18, lineHeight: 180, borderRadius: 10, |
| 70 |
bgColor: '#0f172a', borderColor: '#1e293b', titleColor: '#f8fafc', |
| 71 |
folderColor: '#60a5fa', folderIconColor: '#fbbf24', fileColor: '#cbd5e1', lineColor: '#334155', |
| 72 |
headerBg: '#1e293b', dotRed: '#ef4444', dotYellow: '#f59e0b', dotGreen: '#22c55e', |
| 73 |
jsColor: '#f59e0b', tsColor: '#3b82f6', cssColor: '#a78bfa', htmlColor: '#f97316', |
| 74 |
jsonColor: '#6ee7b7', mdColor: '#94a3b8', pyColor: '#4ade80', imgColor: '#fb7185' |
| 75 |
}, JSON.parse(appEl.dataset.opts || '{}')); |
| 76 |
|
| 77 |
var wrap = mk('div', 'bkbg-ft-wrap'); |
| 78 |
wrap.style.cssText = 'background:' + a.bgColor + ';border-radius:' + a.borderRadius + 'px;border:1px solid ' + a.borderColor + ';overflow:hidden'; |
| 79 |
|
| 80 |
typoCssVarsForEl(a.typoTitle, '--bkbg-ft-tt-', wrap); |
| 81 |
typoCssVarsForEl(a.typoItem, '--bkbg-ft-ti-', wrap); |
| 82 |
|
| 83 |
// Titlebar (macOS-style traffic lights) |
| 84 |
var titlebar = mk('div', 'bkbg-ft-titlebar'); |
| 85 |
titlebar.style.cssText = 'background:' + a.headerBg + ';display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid ' + a.borderColor; |
| 86 |
var dotR = mk('span'); dotR.style.cssText = 'width:12px;height:12px;border-radius:50%;background:' + a.dotRed + ';display:inline-block;flex-shrink:0'; |
| 87 |
var dotY = mk('span'); dotY.style.cssText = 'width:12px;height:12px;border-radius:50%;background:' + a.dotYellow + ';display:inline-block;flex-shrink:0'; |
| 88 |
var dotG = mk('span'); dotG.style.cssText = 'width:12px;height:12px;border-radius:50%;background:' + a.dotGreen + ';display:inline-block;flex-shrink:0'; |
| 89 |
ap(titlebar, dotR, dotY, dotG); |
| 90 |
if (a.showTitle && a.treeTitle) { |
| 91 |
var titleEl = tx('span', 'bkbg-ft-title', a.treeTitle); |
| 92 |
titleEl.style.cssText = 'margin-left:8px;color:' + a.titleColor; |
| 93 |
ap(titlebar, titleEl); |
| 94 |
} |
| 95 |
ap(wrap, titlebar); |
| 96 |
|
| 97 |
// File rows |
| 98 |
var body = mk('div', 'bkbg-ft-body'); |
| 99 |
body.style.padding = '12px 0'; |
| 100 |
|
| 101 |
a.files.forEach(function (item) { |
| 102 |
var ext = getExtension(item.name); |
| 103 |
var isFolder = item.type === 'folder'; |
| 104 |
var nameColor = isFolder ? a.folderColor : (a.showExtensionColors ? getExtColor(ext, a) : a.fileColor); |
| 105 |
var indent = (item.indent || 0) * 20; |
| 106 |
|
| 107 |
var row = mk('div', 'bkbg-ft-row'); |
| 108 |
row.style.cssText = 'display:flex;align-items:center;gap:6px;padding:2px 16px 2px ' + (16 + indent) + 'px;transition:background 0.1s'; |
| 109 |
row.addEventListener('mouseenter', function () { row.style.background = 'rgba(255,255,255,0.04)'; }); |
| 110 |
row.addEventListener('mouseleave', function () { row.style.background = 'transparent'; }); |
| 111 |
|
| 112 |
if (a.showLines && item.indent > 0) { |
| 113 |
var line = mk('span', 'bkbg-ft-indent-line'); |
| 114 |
line.style.cssText = 'display:inline-block;width:1px;height:1.4em;background:' + a.lineColor + ';flex-shrink:0;margin-right:4px'; |
| 115 |
ap(row, line); |
| 116 |
} |
| 117 |
|
| 118 |
if (a.showIcons) { |
| 119 |
var icon = tx('span', 'bkbg-ft-icon', getFileIcon(item)); |
| 120 |
icon.style.cssText = 'color:' + (isFolder ? a.folderIconColor : nameColor) + ';flex-shrink:0;font-style:normal;width:16px;text-align:center;display:inline-block'; |
| 121 |
ap(row, icon); |
| 122 |
} |
| 123 |
|
| 124 |
var name = tx('span', 'bkbg-ft-name', item.name); |
| 125 |
name.style.cssText = 'color:' + nameColor + ';font-weight:' + (isFolder ? 600 : 400) + ';white-space:nowrap;overflow:hidden;text-overflow:ellipsis'; |
| 126 |
ap(row, name); |
| 127 |
ap(body, row); |
| 128 |
}); |
| 129 |
|
| 130 |
ap(wrap, body); |
| 131 |
appEl.innerHTML = ''; |
| 132 |
appEl.appendChild(wrap); |
| 133 |
} |
| 134 |
|
| 135 |
function init() { document.querySelectorAll('.bkbg-file-tree-app').forEach(buildBlock); } |
| 136 |
if (document.readyState !== 'loading') { init(); } else { document.addEventListener('DOMContentLoaded', init); } |
| 137 |
})(); |
| 138 |
|