|
537 | 537 | // Get icon for file or folder |
538 | 538 | const ico = (name, isFolder) => { |
539 | 539 | if (isFolder) { |
| 540 | + // Check for special folder names |
| 541 | + const folderName = name.toLowerCase(); |
| 542 | + const folderIcon = FOLDER_ICONS[folderName] || FOLDER_ICONS['default']; |
540 | 543 | return { |
541 | 544 | type: 'svg', |
542 | | - url: 'public/arrowRight.svg', |
543 | | - color: '', |
| 545 | + url: ICON_BASE_URL + folderIcon.icon, |
| 546 | + color: folderIcon.color, |
544 | 547 | cls: 'folder-icon' |
545 | 548 | }; |
546 | 549 | } |
547 | | - // File icon by extension |
| 550 | + // File icon |
| 551 | + const fileName = name.toLowerCase(); |
| 552 | + // ======================================== |
| 553 | + // SPRING BOOT / JAVA SPECIAL FILES |
| 554 | + // ======================================== |
| 555 | + if (fileName === 'pom.xml') { |
| 556 | + return { |
| 557 | + type: 'svg', |
| 558 | + url: ICON_BASE_URL + 'maven.svg', |
| 559 | + color: '#c71a36', |
| 560 | + cls: 'maven-icon' |
| 561 | + }; |
| 562 | + } |
| 563 | + if (fileName === 'build.gradle' || fileName === 'build.gradle.kts' || fileName === 'settings.gradle' || fileName === 'settings.gradle.kts') { |
| 564 | + return { |
| 565 | + type: 'svg', |
| 566 | + url: ICON_BASE_URL + 'gradle.svg', |
| 567 | + color: '#02303a', |
| 568 | + cls: 'gradle-icon' |
| 569 | + }; |
| 570 | + } |
| 571 | + if (fileName === 'application.properties' || fileName === 'application.yml' || fileName === 'application.yaml') { |
| 572 | + return { |
| 573 | + type: 'svg', |
| 574 | + url: ICON_BASE_URL + 'spring.svg', |
| 575 | + color: '#6db33f', |
| 576 | + cls: 'spring-icon' |
| 577 | + }; |
| 578 | + } |
| 579 | + if (fileName.startsWith('application-') && (fileName.endsWith('.properties') || fileName.endsWith('.yml') || fileName.endsWith('.yaml'))) { |
| 580 | + return { |
| 581 | + type: 'svg', |
| 582 | + url: ICON_BASE_URL + 'spring.svg', |
| 583 | + color: '#6db33f', |
| 584 | + cls: 'spring-icon' |
| 585 | + }; |
| 586 | + } |
| 587 | + // ======================================== |
| 588 | + // PYTHON SPECIAL FILES |
| 589 | + // ======================================== |
| 590 | + if (fileName === 'requirements.txt' || fileName === 'requirements-dev.txt') { |
| 591 | + return { |
| 592 | + type: 'svg', |
| 593 | + url: ICON_BASE_URL + 'python.svg', |
| 594 | + color: '#3776ab', |
| 595 | + cls: 'python-icon' |
| 596 | + }; |
| 597 | + } |
| 598 | + if (fileName === 'setup.py' || fileName === 'setup.cfg') { |
| 599 | + return { |
| 600 | + type: 'svg', |
| 601 | + url: ICON_BASE_URL + 'python.svg', |
| 602 | + color: '#3776ab', |
| 603 | + cls: 'python-icon' |
| 604 | + }; |
| 605 | + } |
| 606 | + if (fileName === 'pipfile' || fileName === 'pipfile.lock') { |
| 607 | + return { |
| 608 | + type: 'svg', |
| 609 | + url: ICON_BASE_URL + 'python.svg', |
| 610 | + color: '#3776ab', |
| 611 | + cls: 'python-icon' |
| 612 | + }; |
| 613 | + } |
| 614 | + if (fileName === 'pyproject.toml') { |
| 615 | + return { |
| 616 | + type: 'svg', |
| 617 | + url: ICON_BASE_URL + 'python.svg', |
| 618 | + color: '#3776ab', |
| 619 | + cls: 'python-icon' |
| 620 | + }; |
| 621 | + } |
| 622 | + if (fileName === 'manage.py') { // Django |
| 623 | + return { |
| 624 | + type: 'svg', |
| 625 | + url: ICON_BASE_URL + 'django.svg', |
| 626 | + color: '#092e20', |
| 627 | + cls: 'django-icon' |
| 628 | + }; |
| 629 | + } |
| 630 | + // ======================================== |
| 631 | + // JAVASCRIPT/NODE SPECIAL FILES |
| 632 | + // ======================================== |
| 633 | + if (fileName === 'package.json') { |
| 634 | + return { |
| 635 | + type: 'svg', |
| 636 | + url: ICON_BASE_URL + 'nodejs.svg', |
| 637 | + color: '#8cc84b', |
| 638 | + cls: 'nodejs-icon' |
| 639 | + }; |
| 640 | + } |
| 641 | + if (fileName === 'package-lock.json') { |
| 642 | + return { |
| 643 | + type: 'svg', |
| 644 | + url: ICON_BASE_URL + 'npm.svg', |
| 645 | + color: '#cb3837', |
| 646 | + cls: 'npm-icon' |
| 647 | + }; |
| 648 | + } |
| 649 | + if (fileName === 'yarn.lock') { |
| 650 | + return { |
| 651 | + type: 'svg', |
| 652 | + url: ICON_BASE_URL + 'yarn.svg', |
| 653 | + color: '#2c8ebb', |
| 654 | + cls: 'yarn-icon' |
| 655 | + }; |
| 656 | + } |
| 657 | + if (fileName === 'tsconfig.json') { |
| 658 | + return { |
| 659 | + type: 'svg', |
| 660 | + url: ICON_BASE_URL + 'typescript-def.svg', |
| 661 | + color: '#3178c6', |
| 662 | + cls: 'typescript-icon' |
| 663 | + }; |
| 664 | + } |
| 665 | + if (fileName === 'webpack.config.js' || fileName === 'webpack.config.ts') { |
| 666 | + return { |
| 667 | + type: 'svg', |
| 668 | + url: ICON_BASE_URL + 'webpack.svg', |
| 669 | + color: '#8dd6f9', |
| 670 | + cls: 'webpack-icon' |
| 671 | + }; |
| 672 | + } |
| 673 | + if (fileName === 'vite.config.js' || fileName === 'vite.config.ts') { |
| 674 | + return { |
| 675 | + type: 'svg', |
| 676 | + url: ICON_BASE_URL + 'vite.svg', |
| 677 | + color: '#646cff', |
| 678 | + cls: 'vite-icon' |
| 679 | + }; |
| 680 | + } |
| 681 | + if (fileName === 'next.config.js' || fileName === 'next.config.ts') { |
| 682 | + return { |
| 683 | + type: 'svg', |
| 684 | + url: ICON_BASE_URL + 'next.svg', |
| 685 | + color: '#000000', |
| 686 | + cls: 'next-icon' |
| 687 | + }; |
| 688 | + } |
| 689 | + // ======================================== |
| 690 | + // GIT FILES |
| 691 | + // ======================================== |
| 692 | + if (fileName === '.gitignore' || fileName === '.gitattributes' || fileName === '.gitmodules') { |
| 693 | + return { |
| 694 | + type: 'svg', |
| 695 | + url: ICON_BASE_URL + 'git.svg', |
| 696 | + color: '#f34f29', |
| 697 | + cls: 'git-icon' |
| 698 | + }; |
| 699 | + } |
| 700 | + // ======================================== |
| 701 | + // DOCKER FILES |
| 702 | + // ======================================== |
| 703 | + if (fileName === 'dockerfile' || fileName.startsWith('dockerfile.')) { |
| 704 | + return { |
| 705 | + type: 'svg', |
| 706 | + url: ICON_BASE_URL + 'docker.svg', |
| 707 | + color: '#0db7ed', |
| 708 | + cls: 'docker-icon' |
| 709 | + }; |
| 710 | + } |
| 711 | + if (fileName === 'docker-compose.yml' || fileName === 'docker-compose.yaml') { |
| 712 | + return { |
| 713 | + type: 'svg', |
| 714 | + url: ICON_BASE_URL + 'docker.svg', |
| 715 | + color: '#0db7ed', |
| 716 | + cls: 'docker-icon' |
| 717 | + }; |
| 718 | + } |
| 719 | + // ======================================== |
| 720 | + // ENV FILES |
| 721 | + // ======================================== |
| 722 | + if (fileName.startsWith('.env')) { |
| 723 | + return { |
| 724 | + type: 'svg', |
| 725 | + url: ICON_BASE_URL + 'tune.svg', |
| 726 | + color: '#e7c547', |
| 727 | + cls: 'env-icon' |
| 728 | + }; |
| 729 | + } |
| 730 | + // ======================================== |
| 731 | + // README FILES |
| 732 | + // ======================================== |
| 733 | + if (fileName === 'readme.md' || fileName === 'readme' || fileName === 'readme.txt') { |
| 734 | + return { |
| 735 | + type: 'svg', |
| 736 | + url: ICON_BASE_URL + 'readme.svg', |
| 737 | + color: '#4caf50', |
| 738 | + cls: 'readme-icon' |
| 739 | + }; |
| 740 | + } |
| 741 | + // ======================================== |
| 742 | + // LICENSE FILES |
| 743 | + // ======================================== |
| 744 | + if (fileName === 'license' || fileName === 'license.md' || fileName === 'license.txt') { |
| 745 | + return { |
| 746 | + type: 'svg', |
| 747 | + url: ICON_BASE_URL + 'license.svg', |
| 748 | + color: '#cbcb41', |
| 749 | + cls: 'license-icon' |
| 750 | + }; |
| 751 | + } |
| 752 | + // ======================================== |
| 753 | + // GET BY EXTENSION (DEFAULT) |
| 754 | + // ======================================== |
548 | 755 | const ext = name.split('.').pop().toLowerCase(); |
549 | | - let url = ''; |
550 | | - if (ext === 'js') url = 'public/contexticon.svg'; |
551 | | - else if (ext === 'html') url = 'public/contexticon.svg'; |
552 | | - else if (ext === 'css') url = 'public/contexticon.svg'; |
553 | | - else if (ext === 'py') url = 'public/contexticon.svg'; |
554 | | - else if (ext === 'md') url = 'public/contexticon.svg'; |
555 | | - else url = 'public/contexticon.svg'; |
556 | | - // You can add more mappings for other extensions if you add more icons |
| 756 | + const iconData = ICONS[ext] || ICONS['default']; |
| 757 | + |
557 | 758 | return { |
558 | 759 | type: 'svg', |
559 | | - url, |
560 | | - color: '', |
561 | | - cls: '' |
| 760 | + url: ICON_BASE_URL + iconData.icon, |
| 761 | + color: iconData.color, |
| 762 | + cls: '' // No custom classes needed - icons are already colored |
562 | 763 | }; |
563 | 764 | }; |
564 | 765 | const esc = s => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); |
|
691 | 892 | const has = nd.t === 'd' && nd.kids.size > 0; |
692 | 893 | const sz = nd.f ? bytes(nd.f.size) : ''; |
693 | 894 | acc.push({ |
694 | | - html: `<div class="tree-item${nd.ig ? ' ignored' : ''}${has ? ' has-children' : ''}" data-path="${nd.full}"> |
| 895 | + html: `<div class="tree-item ${nd.ig ? 'ignored' : ''}" data-path="${nd.full}"> |
695 | 896 | <div class="tree-item-content" data-level="${lv}"> |
696 | | - <span class="tree-lines">${Array(lv).fill('<span class=\'tree-vline\'></span>').join('')}</span> |
697 | 897 | <button class="expand-btn" style="visibility:${has ? 'visible' : 'hidden'}"> |
698 | | - <img src="public/arrowRight.svg" class="tree-arrow" alt=">" /> |
| 898 | + <span class="material-symbols-outlined">chevron_right</span> |
699 | 899 | </button> |
700 | 900 | <div class="file-icon ${ic.cls}"> |
701 | 901 | <img src="${ic.url}" alt="${nd.n}" class="vscode-icon" onerror="this.style.display='none'" /> |
|
1130 | 1330 | const dl = fmt => { |
1131 | 1331 | if (!S.ctx) { toast('Generate first', 'warning'); return; } |
1132 | 1332 | try { |
1133 | | - if (S.isArray) { |
1134 | | - const blob = new Blob(S.ctx, { type: 'text/plain' }); |
1135 | | - const url = URL.createObjectURL(blob); |
1136 | | - const a = document.createElement('a'); |
1137 | | - a.href = url; |
1138 | | - a.download = `${S.root}-context.${fmt}`; |
1139 | | - a.click(); |
1140 | | - URL.revokeObjectURL(url); // REVOKE URL IMMEDIATELY |
1141 | | - toast(`Downloaded (${bytes(blob.size)})`, 'success'); |
1142 | | - // Explicitly clear S.ctx array after download if it was an array |
1143 | | - S.ctx.length = 0; |
1144 | | - S.ctx = null; |
1145 | | - S.isArray = false; |
1146 | | - return; |
| 1333 | + if (isFolder) { |
| 1334 | + return { |
| 1335 | + type: 'svg', |
| 1336 | + url: 'public/folder.svg', // Use your preferred folder icon SVG |
| 1337 | + color: '', |
| 1338 | + cls: 'folder-icon' |
| 1339 | + }; |
1147 | 1340 | } |
1148 | | - const blob = new Blob([S.ctx], { type: 'text/plain' }); |
1149 | | - const url = URL.createObjectURL(blob); |
1150 | | - const a = document.createElement('a'); |
1151 | | - a.href = url; |
1152 | | - a.download = `${S.root}-context.${fmt}`; |
1153 | | - a.click(); |
1154 | | - URL.revokeObjectURL(url); // REVOKE URL IMMEDIATELY |
1155 | | - toast(`Downloaded (${bytes(blob.size)})`, 'success'); |
1156 | | - // Explicitly clear S.ctx string after download if it was a string |
| 1341 | + // File icon by extension |
| 1342 | + const ext = name.split('.').pop().toLowerCase(); |
| 1343 | + let url = ''; |
| 1344 | + if (ext === 'js') url = 'public/js.svg'; |
| 1345 | + else if (ext === 'html') url = 'public/html.svg'; |
| 1346 | + else if (ext === 'css') url = 'public/css.svg'; |
| 1347 | + else if (ext === 'py') url = 'public/python.svg'; |
| 1348 | + else if (ext === 'md') url = 'public/markdown.svg'; |
| 1349 | + else url = 'public/file.svg'; |
| 1350 | + // You can add more mappings for other extensions if you add more icons |
| 1351 | + return { |
| 1352 | + type: 'svg', |
| 1353 | + url, |
| 1354 | + color: '', |
| 1355 | + cls: '' |
| 1356 | + }; |
1157 | 1357 | S.ctx = null; |
1158 | 1358 | S.isArray = false; |
1159 | 1359 | } catch (e) { |
|
1195 | 1395 | } |
1196 | 1396 | }; |
1197 | 1397 | D.search.oninput = e => { |
1198 | | - const q = e.target.value.trim().toLowerCase(); |
1199 | | - const allItems = Array.from(document.querySelectorAll('.tree-item')); |
1200 | | - // Helper: get full path for a tree-item |
1201 | | - const getPath = item => { |
1202 | | - let path = ''; |
1203 | | - let cur = item; |
1204 | | - while (cur && cur.classList.contains('tree-item')) { |
1205 | | - const name = cur.querySelector('.file-name')?.textContent || ''; |
1206 | | - path = name + (path ? '/' + path : ''); |
1207 | | - cur = cur.parentElement?.closest('.tree-item'); |
1208 | | - } |
1209 | | - return path.toLowerCase(); |
1210 | | - }; |
1211 | | - // First, hide all |
1212 | | - allItems.forEach(item => { item.style.display = 'none'; }); |
1213 | | - // If search is empty, show all |
1214 | | - if (!q) { |
1215 | | - allItems.forEach(item => { item.style.display = ''; }); |
1216 | | - // Collapse all folders |
1217 | | - document.querySelectorAll('.tree-children').forEach(tc => tc.classList.remove('open')); |
1218 | | - document.querySelectorAll('.expand-btn').forEach(btn => btn.classList.remove('expanded')); |
1219 | | - document.querySelectorAll('.expand-btn .material-symbols-outlined').forEach(span => span.textContent = 'chevron_right'); |
1220 | | - return; |
1221 | | - } |
1222 | | - // Find all items that match (by name or path) |
1223 | | - const matches = allItems.filter(item => { |
1224 | | - const name = item.querySelector('.file-name')?.textContent.toLowerCase() || ''; |
1225 | | - const path = getPath(item); |
1226 | | - return name.includes(q) || path.includes(q); |
1227 | | - }); |
1228 | | - // Show all matches and their ancestors |
1229 | | - const showSet = new Set(); |
1230 | | - matches.forEach(item => { |
1231 | | - let cur = item; |
1232 | | - while (cur && cur.classList.contains('tree-item')) { |
1233 | | - showSet.add(cur); |
1234 | | - cur = cur.parentElement?.closest('.tree-item'); |
1235 | | - } |
1236 | | - }); |
1237 | | - allItems.forEach(item => { |
1238 | | - if (showSet.has(item)) { |
1239 | | - item.style.display = ''; |
1240 | | - } else { |
1241 | | - item.style.display = 'none'; |
1242 | | - } |
1243 | | - }); |
1244 | | - // Expand all folders that are ancestors of matches |
1245 | | - document.querySelectorAll('.tree-children').forEach(tc => { |
1246 | | - const parent = tc.parentElement; |
1247 | | - if (parent && showSet.has(parent)) { |
1248 | | - tc.classList.add('open'); |
1249 | | - const btn = parent.querySelector('.expand-btn'); |
1250 | | - if (btn) { |
1251 | | - btn.classList.add('expanded'); |
1252 | | - const span = btn.querySelector('.material-symbols-outlined'); |
1253 | | - if (span) span.textContent = 'expand_more'; |
1254 | | - } |
1255 | | - } else { |
1256 | | - tc.classList.remove('open'); |
1257 | | - const btn = parent?.querySelector('.expand-btn'); |
1258 | | - if (btn) { |
1259 | | - btn.classList.remove('expanded'); |
1260 | | - const span = btn.querySelector('.material-symbols-outlined'); |
1261 | | - if (span) span.textContent = 'chevron_right'; |
1262 | | - } |
1263 | | - } |
| 1398 | + const q = e.target.value.toLowerCase(); |
| 1399 | + document.querySelectorAll('.tree-item').forEach(item => { |
| 1400 | + const n = item.querySelector('.file-name').textContent.toLowerCase(); |
| 1401 | + item.style.display = n.includes(q) ? '' : 'none'; |
1264 | 1402 | }); |
1265 | 1403 | }; |
1266 | 1404 | const togFold = o => { |
|
0 commit comments