GitHub Utils

GitHub utilities including file icons replacement and auto-fill repo name

// ==UserScript==
// @name         GitHub Utils
// @name:vi      Tiện ích GitHub
// @name:zh-cn   GitHub 实用工具
// @name:zh-tw   GitHub 實用工具
// @name:ru      Утилиты GitHub
// @namespace    http://tampermonkey.net/
// @version      2024.12.30.1
// @description  GitHub utilities including file icons replacement and auto-fill repo name
// @description:vi  Tiện ích GitHub bao gồm thay thế biểu tượng tệp và tự động điền tên repo
// @description:zh-cn  GitHub 实用工具,包括文件图标替换和自动填充仓库名
// @description:zh-tw  GitHub 實用工具,包括文件圖標替換和自動填充倉庫名
// @description:ru  Утилиты GitHub, включая замену иконок файлов и автозаполнение имени репозитория
// @author       Yuusei
// @match        https://github.com/*
// @icon         https://github.githubassets.com/favicon.ico
// @grant        GM_addStyle
// @require      https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @run-at       document-start
// @license      GPL-3.0-only
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @compatible   safari
// ==/UserScript==
(function ($) {
	'use strict';
	// Add custom styles
	GM_addStyle(`
      .material-icon {
          width: 20px;
          height: 20px;
          vertical-align: text-bottom;
          margin-right: 4px;
          transition: transform 0.2s ease;
      }
      .material-icon:hover {
          transform: scale(1.2);
      }
      @media (max-width: 768px) {
          .material-icon {
              width: 16px;
              height: 16px;
              margin-right: 2px;
          }
      }
  `);
	const iconMap = {
		// Development
		'.ts': 'typescript',
		'.tsx': 'react_ts',
		'.js': 'javascript',
		'.jsx': 'react',
		'.py': 'python',
		'.java': 'java',
		'.cpp': 'cpp',
		'.c': 'c',
		'.cs': 'csharp',
		'.go': 'go',
		'.rb': 'ruby',
		'.php': 'php',
		'.rs': 'rust',
		'.swift': 'swift',
		'.kt': 'kotlin',
		'.scala': 'scala',
		'.dart': 'dart',
		'.lua': 'lua',
		'.r': 'r',
		'.sh': 'console',
		'.ps1': 'powershell',
		'.bat': 'console',
		'.cmd': 'console',
		'.wasm': 'assembly',
		'.code-workspace': 'vscode',
		'.sln': 'visualstudio',
		// Web
		'.html': 'html',
		'.htm': 'html',
		'.css': 'css',
		'.scss': 'sass',
		'.sass': 'sass',
		'.less': 'less',
		'.styl': 'stylus',
		'.vue': 'vue',
		'.svelte': 'svelte',
		'.angular': 'angular',
		// Data & Config
		'.json': 'json',
		'.yml': 'yaml',
		'.yaml': 'yaml',
		'.xml': 'xml',
		'.toml': 'settings',
		'.ini': 'settings',
		'.env': 'tune',
		'.conf': 'settings',
		'.sql': 'database',
		'.db': 'database',
		'.sqlite': 'database',
		'.graphql': 'graphql',
		'.proto': 'protobuf',
		// Documentation
		'.md': 'markdown',
		'.mdx': 'markdown',
		'.txt': 'document',
		'.pdf': 'pdf',
		'.doc': 'word',
		'.docx': 'word',
		'.odt': 'document',
		'.rtf': 'document',
		// Images
		'.svg': 'svg',
		'.png': 'image',
		'.jpg': 'image',
		'.jpeg': 'image',
		'.gif': 'image',
		'.ico': 'image',
		'.webp': 'image',
		'.bmp': 'image',
		// Media
		'.mp3': 'audio',
		'.wav': 'audio',
		'.ogg': 'audio',
		'.mp4': 'video',
		'.webm': 'video',
		'.avi': 'video',
		'.mov': 'video',
		// Archives
		'.zip': 'zip',
		'.rar': 'zip',
		'.7z': 'zip',
		'.tar': 'zip',
		'.gz': 'zip',
		'.bz2': 'zip',
		// System
		'.exe': 'exe',
		'.dll': 'dll',
		'.so': 'lib',
		'.dylib': 'lib',
		'.sys': 'windows',
		'.reg': 'windows',
		// Design
		'.fig': 'figma',
		'.sketch': 'sketch',
		'.ai': 'illustrator',
		'.psd': 'photoshop',
		'.xd': 'xd',
		// 3D & Game
		'.unity': 'unity',
		'.blend': 'blender',
		'.fbx': '3d',
		'.obj': '3d',
		'.gltf': '3d',
		'.uasset': 'unreal',
		'.upk': 'unreal',
		// Mobile
		'.apk': 'android',
		'.ipa': 'apple',
		'.xcodeproj': 'xcode',
		'.pbxproj': 'xcode',
		// Container & Cloud
		Dockerfile: 'docker',
		'.dockerignore': 'docker',
		'.tf': 'terraform',
		'.tfvars': 'terraform',
		'.vagrant': 'vagrant',
		'.helm': 'kubernetes',
	};
	const specialFiles = {
		// Package managers
		'package.json': 'nodejs',
		'package-lock.json': 'nodejs',
		'yarn.lock': 'yarn',
		'pnpm-lock.yaml': 'pnpm',
		'bun.lockb': 'bun',
		'composer.json': 'composer',
		'composer.lock': 'composer',
		Gemfile: 'ruby',
		'Gemfile.lock': 'ruby',
		'requirements.txt': 'python',
		'poetry.lock': 'python',
		'Cargo.toml': 'rust',
		'Cargo.lock': 'rust',
		// Config files
		'tsconfig.json': 'typescript',
		'.eslintrc': 'eslint',
		'.prettierrc': 'prettier',
		'.editorconfig': 'editorconfig',
		'webpack.config.js': 'webpack',
		'vite.config.js': 'vite',
		'rollup.config.js': 'rollup',
		'babel.config.js': 'babel',
		'jest.config.js': 'jest',
		'karma.conf.js': 'karma',
		'cypress.config.js': 'cypress',
		'playwright.config.js': 'playwright',
		// Documentation
		'README.md': 'markdown',
		LICENSE: 'certificate',
		'CHANGELOG.md': 'markdown',
		'CONTRIBUTING.md': 'markdown',
		// Git
		'.gitignore': 'git',
		'.gitattributes': 'git',
		'.gitmodules': 'git',
		'.gitmessage': 'git',
		'.gitkeep': 'git',
		// CI/CD
		'.travis.yml': 'travis',
		'.gitlab-ci.yml': 'gitlab',
		Jenkinsfile: 'jenkins',
		'azure-pipelines.yml': 'azure',
		'bitbucket-pipelines.yml': 'bitbucket',
		// Docker
		Dockerfile: 'docker',
		'docker-compose.yml': 'docker',
		'docker-compose.yaml': 'docker',
		'docker-compose.override.yml': 'docker',
		// Framework configs
		'angular.json': 'angular',
		'next.config.js': 'next',
		'nuxt.config.js': 'nuxt',
		'svelte.config.js': 'svelte',
		'capacitor.config.json': 'capacitor',
		'ionic.config.json': 'ionic',
		// Build tools
		Makefile: 'makefile',
		'CMakeLists.txt': 'cmake',
		'build.gradle': 'gradle',
		'pom.xml': 'maven',
		'build.sbt': 'sbt',
		// Environment
		'.env': 'tune',
		'.env.local': 'tune',
		'.env.development': 'tune',
		'.env.production': 'tune',
		'.env.test': 'tune',
		// Version managers
		'.nvmrc': 'nodejs',
		'.node-version': 'nodejs',
		'.ruby-version': 'ruby',
		'.python-version': 'python',
		'.java-version': 'java',
	};
	function replaceIcons() {
		$('.react-directory-row-name-cell-large-screen, .react-directory-row-name-cell-small-screen').each(function () {
			const $filenameElement = $(this).find('.react-directory-filename-cell');
			if ($filenameElement.length) {
				const filename = $filenameElement.text();
				let iconName = specialFiles[filename];
				if (!iconName) {
					const extension = Object.keys(iconMap).find(ext => filename.toLowerCase().endsWith(ext));
					iconName = extension ? iconMap[extension] : null;
				}
				if (iconName) {
					const $oldSvg = $(this).find('svg');
					if ($oldSvg.length) {
						const $newIcon = $('<img>', {
							src: `https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/refs/heads/main/icons/${iconName}.svg`,
							class: 'material-icon',
							alt: iconName,
						});
						$oldSvg.replaceWith($newIcon);
					}
				}
			}
		});
	}
	// Auto fill repo name functionality
	function setupAutoFill() {
		const observer = new MutationObserver(mutations => {
			mutations.forEach(mutation => {
				if (mutation.addedNodes.length) {
					const dialog = document.querySelector('#repo-delete-menu-dialog');
					if (dialog) {
						const repoName = document.querySelector('.text-bold.f3.mt-2')?.textContent.trim();
						if (repoName) {
							const input = document.querySelector('#verification_field');
							if (input) {
								input.value = repoName;
								input.dispatchEvent(
									new Event('input', {
										bubbles: true,
										cancelable: true,
									})
								);
							}

							const deleteBtn = document.querySelector('#repo-delete-proceed-button');
							if (deleteBtn) {
								deleteBtn.disabled = false;
							}
						}
					}
				}
			});
		});

		observer.observe(document.body, {
			childList: true,
			subtree: true,
		});
	}
	// Initial setup
	$(document).ready(() => {
		replaceIcons();
		setupAutoFill();
	});
	// Watch for DOM changes for icon replacement
	const iconObserver = new MutationObserver(replaceIcons);
	iconObserver.observe(document.body, {
		childList: true,
		subtree: true,
	});
})(jQuery);