diff --git a/.gitignore b/.gitignore index 40b878d..642271f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +coverage/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef861d..deeffe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +# [1.1.0](https://github.com/vernonthedev/github-explorer2/compare/v1.0.0...v1.1.0) (2025-12-29) + + +### Features + +* implement card-based repository grouping, enforce dark theme, and add SPA navigation support ([47b1cf3](https://github.com/vernonthedev/github-explorer2/commit/47b1cf348b0b14175ca4934e5bb0c835b9556d32)) +* improved group card selection logic and enhance active state styling with visual feedback ([ccc08ad](https://github.com/vernonthedev/github-explorer2/commit/ccc08ad940f66237c518152080b4c8c25fa36d8c)) +* revamp UI with a new design system, dark mode, adding Dexie and Lucide icons, fixes [#4](https://github.com/vernonthedev/github-explorer2/issues/4) ([6e5fa86](https://github.com/vernonthedev/github-explorer2/commit/6e5fa86578a73a2516a6fc7250333a16f523b5fa)) + # 1.0.0 (2025-12-28) diff --git a/README.md b/README.md index 1075027..c0d2463 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ ```text - ____ _ _ _ _ _ _____ _ - / ___(_) |_| | | |_ _| |__ | ____|_ ___ __ | | ___ _ __ ___ _ __ - | | _| | __| |_| | | | | '_ \ | _| \ \/ / '_ \| |/ _ \| '__/ _ \ '__| - | |_| | | |_| _ | |_| | |_) | | |___ > <| |_) | | (_) | | | __/ | - \____|_|\__|_| |_|\__,_|_.__/ |_____/_/\_\ .__/|_|\___/|_| \___|_| + ██████╗ ██╗████████╗██╗ ██╗██╗ ██╗██████╗ ███████╗██╗ ██╗██████╗ ██╗ ██████╗ ██████╗ ███████╗██████╗ +██╔════╝ ██║╚══██╔══╝██║ ██║██║ ██║██╔══██╗ ██╔════╝╚██╗██╔╝██╔══██╗██║ ██╔═══██╗██╔══██╗██╔════╝██╔══██╗ +██║ ███╗██║ ██║ ███████║██║ ██║██████╔╝ █████╗ ╚███╔╝ ██████╔╝██║ ██║ ██║██████╔╝█████╗ ██████╔╝ +██║ ██║██║ ██║ ██╔══██║██║ ██║██╔══██╗ ██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║ ██║██╔══██╗██╔══╝ ██╔══██╗ +╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██████╔╝ ███████╗██╔╝ ██╗██║ ███████╗╚██████╔╝██║ ██║███████╗██║ ██║ + ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ``` + # GitHub Explorer -A browser extension that reskins GitHub.com to look like GitLab, adding groups functionality to organize repositories by naming convention. +A browser extension that reskins GitHub.com to add groups functionality to organize repositories by naming convention & card based management. ## Features -- **GitLab Theme**: Complete visual transformation of GitHub's interface - **Repository Groups**: Automatically groups repositories based on naming convention (everything before first hyphen) -- **Modern Design**: Clean, distraction-free interface inspired by GitLab's design language +- **Modern Design**: Clean, distraction-free interface inspired by github's design language - **Preserved Functionality**: All native GitHub features remain intact ## Installation @@ -27,12 +28,14 @@ A browser extension that reskins GitHub.com to look like GitLab, adding groups f ## How It Works ### Repository Grouping + - Repositories are grouped by naming convention - Everything before the first hyphen (`-`) becomes the group name - Example: `project-alpha`, `project-beta` → Group: **project** - Repositories without hyphens are grouped as **General** ### Technical Implementation + - Uses `MutationObserver` to detect GitHub's SPA navigation - Preserves React event listeners by moving DOM nodes instead of recreating - Implements proper cleanup to prevent memory leaks @@ -40,38 +43,20 @@ A browser extension that reskins GitHub.com to look like GitLab, adding groups f ## Design System -The extension implements GitLab's color palette: +The extension implements github's color palette: - **Primary Navy**: `#292961` - **Accent Orange**: `#e24329` - **Clean White**: `#ffffff` - **Border Gray**: `#dbdbdb` -## File Structure - -``` -├── manifest.json # Extension configuration -├── content.js # Core functionality and DOM manipulation -├── styles.css # GitLab theme styling -└── README.md # Simple Docs -``` - -## Customization - -### Modify Grouping Logic -Edit the `extractGroupName()` method in `content.js:221` to change how repositories are grouped. - -### Update Styling -Modify the CSS variables in `styles.css:4` to adjust colors and spacing. - ## Usage Once installed, the extension automatically: 1. Detects when you visit GitHub pages -2. Applies GitLab theming to the interface -3. Groups your repositories automatically -4. Maintains all GitHub functionality +2. Groups your repositories automatically +3. Maintains all GitHub functionality ## Troubleshooting @@ -81,4 +66,4 @@ Once installed, the extension automatically: --- - Made with love by [vernonthedev](https://github.com/vernonthedev) \ No newline at end of file +Made with ❤️ by [vernonthedev](https://github.com/vernonthedev) diff --git a/assets/content.js.backup b/assets/content.js.backup deleted file mode 100644 index 79664df..0000000 --- a/assets/content.js.backup +++ /dev/null @@ -1,860 +0,0 @@ -/** - * GitHub GitLab Dark Theme & Groups Extension - * @author vernonthedev - * @description Transforms GitHub to GitLab's dark theme with intelligent repository grouping and card-based management. - */ - -// Import Dexie for IndexedDB storage -const Dexie = window.Dexie; - -class GitHubGitLabTheme { - constructor() { - this.observer = null; - this.isProcessing = false; - this.debounceTimer = null; - this.groups = new Map(); - this.customGroups = new Set(); - this.groupingEnabled = true; - this.darkMode = true; // Always dark mode - this.db = null; - this.currentActiveGroup = null; - this.processedContainers = new Set(); - this.init(); - } - - async init() { - // Apply dark theme immediately to prevent flash - this.applyDarkTheme(); - - await this.initStorage(); - await this.loadSettings(); - - // Process immediately if DOM is ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => this.run()); - } else { - this.run(); - } - - // Process again after a delay for dynamic content - setTimeout(() => this.run(), 1000); - - // Listen for SPA navigation - this.setupNavigationListener(); - } - -isRepositoryPage() { - const pathname = window.location.pathname; - - // Check for user profile repositories page - if (pathname.match(/^\/[^\/]+$/)) { - const pageHeader = document.querySelector('h1'); - if (pageHeader && pageHeader.textContent.includes('Repositories')) { - return true; - } - } - - // Check for user repositories tab - if (pathname.includes('?tab=repositories')) { - return true; - } - - // Check for organization repositories page - if (pathname.includes('/orgs/') && - (pathname.includes('/repositories') || - document.querySelector('[data-test-selector="org-repositories-list"]'))) { - return true; - } - - // Check for repository list containers - const repoContainers = [ - '#user-repositories-list', - '#org-repositories-list', - '[data-testid="repository-list-container"]', - 'div[data-test-selector="org-repositories-list"]' - ]; - - return repoContainers.some(selector => document.querySelector(selector)); - } - - setupNavigationListener() { - // Listen for URL changes (GitHub is an SPA) - let currentUrl = window.location.href; - new MutationObserver(() => { - if (window.location.href !== currentUrl) { - currentUrl = window.location.href; - console.log('[GitLab Theme] URL changed to:', currentUrl); - setTimeout(() => { - this.processedContainers.clear(); // Clear cache - this.run(); - }, 500); - } - }).observe(document.body, { subtree: true, childList: true }); - } - - applyDarkTheme() { - // Apply dark theme immediately and ensure it stays applied - document.body.classList.add('gitlab-dark-theme'); - - // Also apply to html element - document.documentElement.classList.add('gitlab-dark-theme'); - - // Force dark mode with CSS if needed - if (!document.querySelector('#gitlab-dark-theme-forcer')) { - const style = document.createElement('style'); - style.id = 'gitlab-dark-theme-forcer'; - style.textContent = ` - body, html { background-color: #0d0e11 !important; } - .gitlab-dark-theme { background-color: #0d0e11 !important; } - `; - document.head.appendChild(style); - } - } - - async initStorage() { - try { - // Initialize Dexie database - this.db = new Dexie('GitHubGitLabThemeDB'); - this.db.version(1).stores({ - settings: '++id, key, value', - groups: '++id, name, created' - }); - - console.log('[GitLab Theme] IndexedDB initialized successfully'); - } catch (error) { - console.error('[GitLab Theme] Failed to initialize IndexedDB:', error); - this.initFallbackStorage(); - } - } - - initFallbackStorage() { - // Fallback to localStorage and cookies - console.log('[GitLab Theme] Using fallback storage'); - } - - async saveSetting(key, value) { - try { - if (this.db) { - await this.db.settings.where('key').equals(key).delete(); - await this.db.settings.add({ key, value }); - } - - // Fallback to localStorage - localStorage.setItem(`gitlab_theme_${key}`, JSON.stringify(value)); - - // Fallback to cookie - this.setCookie(`gitlab_theme_${key}`, JSON.stringify(value), 365); - - console.log(`[GitLab Theme] Saved setting: ${key}`); - } catch (error) { - console.error('[GitLab Theme] Failed to save setting:', error); - this.saveSettingFallback(key, value); - } - } - - async loadSetting(key, defaultValue = null) { - try { - if (this.db) { - const setting = await this.db.settings.where('key').equals(key).first(); - if (setting) return setting.value; - } - - // Try localStorage - const localValue = localStorage.getItem(`gitlab_theme_${key}`); - if (localValue) return JSON.parse(localValue); - - // Try cookie - const cookieValue = this.getCookie(`gitlab_theme_${key}`); - if (cookieValue) return JSON.parse(cookieValue); - - return defaultValue; - } catch (error) { - console.error('[GitLab Theme] Failed to load setting:', error); - return defaultValue; - } - } - - saveSettingFallback(key, value) { - try { - localStorage.setItem(`gitlab_theme_${key}`, JSON.stringify(value)); - this.setCookie(`gitlab_theme_${key}`, JSON.stringify(value), 365); - } catch (error) { - console.error('[GitLab Theme] Fallback storage failed:', error); - } - } - - setCookie(name, value, days) { - const expires = new Date(); - expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000)); - document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`; - } - - getCookie(name) { - const nameEQ = name + "="; - const ca = document.cookie.split(';'); - for(let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) === ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); - } - return null; - } - - async loadSettings() { - this.groupingEnabled = await this.loadSetting('groupingEnabled', true); - - const customGroups = await this.loadSetting('customGroups', []); - this.customGroups = new Set(customGroups); - - console.log('[GitLab Theme] Settings loaded:', { groupingEnabled: this.groupingEnabled }); - - // Ensure dark theme is always applied - this.applyDarkTheme(); - } - - async saveCustomGroups() { - await this.saveSetting('customGroups', Array.from(this.customGroups)); - } - -run() { - // Only run on repository-related pages - if (!this.isRepositoryPage()) { - console.log('[GitLab Theme] Not a repository page, skipping processing'); - return; - } - - // Apply dark theme first - this.applyDarkTheme(); - - // Check if we're on an organization page for debugging - const isOrgPage = window.location.pathname.includes('/orgs/') || - window.location.pathname.match(/^\/[^\/]+$/) && - document.querySelector('[data-test-selector="org-header"]'); - - if (isOrgPage) { - console.log('[GitLab Theme] Organization page detected'); - } - - // Setup observer for dynamic content - this.setupMutationObserver(); - - // Add controls and process repositories - this.addGroupControls(); - this.processRepositories(); - } - -setupMutationObserver() { - if (this.observer) this.observer.disconnect(); - - this.observer = new MutationObserver((mutations) => { - if (this.isProcessing) return; - - let shouldProcess = false; - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { - shouldProcess = true; - break; - } - } - - if (shouldProcess) { - if (this.debounceTimer) clearTimeout(this.debounceTimer); - this.debounceTimer = setTimeout(() => { - // Only process if we're still on a repository page - if (this.isRepositoryPage()) { - this.addGroupControls(); - this.processRepositories(); - } - }, 300); - } - }); - - // Only observe specific containers, not the entire body - const targetSelectors = [ - '#user-repositories-list', - '#org-repositories-list', - '[data-testid="repository-list-container"]', - 'div[data-test-selector="org-repositories-list"]', - 'main[role="main"]' - ]; - - targetSelectors.forEach(selector => { - const element = document.querySelector(selector); - if (element) { - this.observer.observe(element, { - childList: true, - subtree: true - }); - } - }); - } - -findRepositoryContainers() { - const selectors = [ - '#user-repositories-list', - '#org-repositories-list', - '[data-testid="repository-list-container"]', - '[data-filterable-for="your-repos-filter"]', - '[data-filterable-for="org-repos-filter"]', - '.js-repo-list', - 'ul[data-test-selector="profile-repository-list"]', - 'div[aria-label="Repositories"]', - // Org page specific selectors - 'div[data-test-selector="org-repositories-list"]', - '[data-test-selector="org-repo-list"]' - ]; - - const containers = []; - selectors.forEach(selector => { - try { - const elements = document.querySelectorAll(selector); - elements.forEach(el => { - if (el && !this.processedContainers.has(el) && this.isValidRepositoryContainer(el)) { - containers.push(el); - this.processedContainers.add(el); - } - }); - } catch (e) { - console.warn(`[GitLab Theme] Invalid selector: ${selector}`); - } - }); - - console.log(`[GitLab Theme] Found ${containers.length} repository containers`); - return containers; - } - - isValidRepositoryContainer(container) { - // Check if container has repository items - const repoItems = this.findRepositoryItems(container); - return repoItems.length > 0; - } - - findRepositoryItems(container) { - const itemSelectors = [ - '[itemprop="owns"]', - '[data-testid="repository-list-item"]', - '.repo-list-item', - '.public', - '.private', - '.source', - '.fork', - '.archived', - 'li[itemprop="owns"]', - 'div[data-testid="repository-item"]', - // Org page specific selectors - 'li[data-test-selector="repository-list-item"]', - 'div[data-test-selector="repository-list-item"]', - '.Box-row', // GitHub uses Box-row for repo items on org pages - 'li[itemprop="codeRepository"]', - 'div[itemprop="codeRepository"]' - ]; - - let items = []; - itemSelectors.forEach(selector => { - try { - const found = container.querySelectorAll(selector); - found.forEach(item => { - const nameElement = item.querySelector('h3 a, a[itemprop="name codeRepository"], .wb-break-all a, [data-testid="repository-name"], [itemprop="name"], .Link--primary'); - if (nameElement && !items.includes(item)) { - items.push(item); - } - }); - } catch (e) { - console.warn(`[GitLab Theme] Invalid item selector: ${selector}`); - } - }); - - return items; - } - -processRepositories() { - if (this.isProcessing) return; - this.isProcessing = true; - - try { - // Always apply dark theme - this.applyDarkTheme(); - - const containers = this.findRepositoryContainers(); - - containers.forEach(container => { - const items = this.findRepositoryItems(container); - - if (items.length > 0) { - // Only process if container hasn't been processed already - if (!container.dataset.gitlabProcessed) { - if (this.groupingEnabled) { - this.createGroupCards(container, items); - } else { - this.displayAllRepos(container, items); - } - container.dataset.gitlabProcessed = 'true'; - } - } - }); - - } catch (e) { - console.error('[GitLab Theme] Error processing repositories:', e); - } finally { - this.isProcessing = false; - } - } - -createGroupCards(container, items) { - const groups = this.extractGroups(items); - - if (groups.size <= 1) { - this.displayAllRepos(container, items); - return; - } - - // Store original content and clear container safely - const originalContent = container.innerHTML; - const originalClasses = container.className; - - try { - container.innerHTML = ''; - container.classList.add('gitlab-grouped-repositories'); - - const fragment = document.createDocumentFragment(); - - // Create group cards section - const groupCardsSection = this.createGroupCardsSection(groups, container); - fragment.appendChild(groupCardsSection); - - // Create hidden repo containers for each group - const repoContainersSection = this.createRepoContainers(groups, container); - fragment.appendChild(repoContainersSection); - - container.appendChild(fragment); - - // Show the first group by default - const firstGroup = Array.from(groups.keys())[0]; - console.log(`[GitLab Theme] Auto-showing first group: ${firstGroup}`); - - // Wait a bit for DOM to settle, then simulate first group click - setTimeout(() => { - console.log(`[GitLab Theme] Attempting to show first group...`); - const firstCard = container.querySelector(`.gitlab-group-card[data-group-id="${firstGroup}"]`); - if (firstCard) { - console.log(`[GitLab Theme] Found first group card, simulating click`); - firstCard.click(); - } else { - console.error(`[GitLab Theme] Could not find first group card: ${firstGroup}`); - console.log(`[GitLab Theme] Available cards:`, container.querySelectorAll('.gitlab-group-card')); - } - }, 200); - } catch (error) { - console.error('[GitLab Theme] Error creating group cards, reverting to original content:', error); - container.innerHTML = originalContent; - container.className = originalClasses; - this.displayAllRepos(container, items); - } - } - - extractGroups(items) { - const groups = new Map(); - - items.forEach(item => { - const repoName = this.getRepositoryName(item); - const groupName = this.getGroupName(repoName); - - if (!groups.has(groupName)) { - groups.set(groupName, []); - } - groups.get(groupName).push(item); - }); - - return groups; - } - - createGroupCardsSection(groups, container) { - const section = document.createElement('div'); - section.className = 'gitlab-cards-section'; - - const containerDiv = document.createElement('div'); - containerDiv.className = 'gitlab-group-cards-container'; - - // Add "All Repositories" card - const allReposCard = this.createGroupCard('All Repositories', Array.from(groups.values()).flat(), 'all'); - containerDiv.appendChild(allReposCard); - - // Add group cards - Array.from(groups.entries()).forEach(([name, items]) => { - const card = this.createGroupCard(name, items, name); - containerDiv.appendChild(card); - }); - - section.appendChild(containerDiv); - return section; - } - - createGroupCard(name, items, groupId) { - const card = document.createElement('div'); - card.className = 'gitlab-group-card'; - card.dataset.groupId = groupId; - - const cardHeader = document.createElement('div'); - cardHeader.className = 'gitlab-card-header'; - - const icon = document.createElement('div'); - icon.className = 'gitlab-card-icon'; - icon.innerHTML = this.getGroupIcon(name); - - const titleCount = document.createElement('div'); - titleCount.className = 'gitlab-card-title-count'; - - const title = document.createElement('h4'); - title.className = 'gitlab-card-title'; - title.textContent = name; - - const count = document.createElement('span'); - count.className = 'gitlab-card-count'; - count.textContent = `${items.length}`; - - titleCount.appendChild(title); - titleCount.appendChild(count); - - cardHeader.appendChild(icon); - cardHeader.appendChild(titleCount); - - card.appendChild(cardHeader); - - // Make card clickable - card.style.cursor = 'pointer'; - card.addEventListener('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - console.log(`[GitLab Theme] Clicked group: ${groupId}`); - console.log(`[GitLab Theme] Clicked card:`, card); - - // Find the grouped repositories container - const groupedContainer = card.closest('.gitlab-grouped-repositories') || - card.parentElement.closest('.gitlab-grouped-repositories') || - document.querySelector('.gitlab-grouped-repositories'); - - console.log(`[GitLab Theme] Found grouped container:`, groupedContainer); - - if (groupedContainer) { - this.showGroupRepos(groupId, groupedContainer); - } else { - console.error(`[GitLab Theme] Could not find grouped repositories container`); - // Try to find any container with gitlab-grouped-repositories class - const allGroupedContainers = document.querySelectorAll('.gitlab-grouped-repositories'); - console.log(`[GitLab Theme] Available grouped containers:`, allGroupedContainers); - if (allGroupedContainers.length > 0) { - this.showGroupRepos(groupId, allGroupedContainers[0]); - } - } - }); - - return card; - } - - getGroupIcon(groupName) { - const iconMap = { - 'All Repositories': '', - 'General': '', - 'Web': '', - 'Api': '', - 'Mobile': '', - 'Backend': '', - 'Frontend': '', - 'Default': '' - }; - - return iconMap[name] || iconMap['Default']; - } - - createRepoContainers(groups, container) { - const section = document.createElement('div'); - section.className = 'gitlab-repos-section'; - - // Create container for "All Repositories" - const allReposContainer = document.createElement(container.tagName); - allReposContainer.className = container.className; - allReposContainer.classList.add('gitlab-repo-container'); - allReposContainer.dataset.groupId = 'all'; - allReposContainer.style.display = 'none'; - - Array.from(groups.values()).flat().forEach(item => { - allReposContainer.appendChild(item.cloneNode(true)); - }); - - section.appendChild(allReposContainer); - - // Create containers for each group - Array.from(groups.entries()).forEach(([name, items]) => { - const groupContainer = document.createElement(container.tagName); - groupContainer.className = container.className; - groupContainer.classList.add('gitlab-repo-container'); - groupContainer.dataset.groupId = name; - groupContainer.style.display = 'none'; - - items.forEach(item => { - groupContainer.appendChild(item); - }); - - section.appendChild(groupContainer); - }); - - return section; - } - - showGroupRepos(groupId, container) { - console.log(`[GitLab Theme] Showing repos for group: ${groupId}`); - console.log(`[GitLab Theme] Container:`, container); - - // Find the repos section within the grouped container - const reposSection = container.querySelector('.gitlab-repos-section'); - console.log(`[GitLab Theme] Repos section:`, reposSection); - - if (!reposSection) { - console.error(`[GitLab Theme] Repos section not found!`); - return; - } - - // Hide all repo containers - const allContainers = reposSection.querySelectorAll('.gitlab-repo-container'); - console.log(`[GitLab Theme] Found ${allContainers.length} repo containers:`, allContainers); - - allContainers.forEach((cont, index) => { - cont.style.display = 'none'; - console.log(`[GitLab Theme] Hiding container ${index} (data-group-id: ${cont.dataset.groupId})`); - }); - - // Show selected container - const selectedContainer = reposSection.querySelector(`.gitlab-repo-container[data-group-id="${groupId}"]`); - if (selectedContainer) { - selectedContainer.style.display = 'block'; - console.log(`[GitLab Theme] Found and showing container for ${groupId} with ${selectedContainer.children.length} items`); - console.log(`[GitLab Theme] Container children:`, selectedContainer.children); - - // Ensure repository items are visible - Array.from(selectedContainer.children).forEach((child, index) => { - child.style.display = ''; - console.log(`[GitLab Theme] Making repo ${index} visible:`, child); - }); - } else { - console.error(`[GitLab Theme] Container not found for group: ${groupId}`); - console.log(`[GitLab Theme] Available group IDs in repos section:`, - Array.from(reposSection.querySelectorAll('.gitlab-repo-container')).map(c => c.dataset.groupId)); - } - - // Update active card styling - const allCards = container.querySelectorAll('.gitlab-group-card'); - console.log(`[GitLab Theme] Found ${allCards.length} group cards`); - - allCards.forEach(card => { - card.classList.remove('active'); - }); - - const activeCard = container.querySelector(`.gitlab-group-card[data-group-id="${groupId}"]`); - if (activeCard) { - activeCard.classList.add('active'); - console.log(`[GitLab Theme] Active card set for ${groupId}`); - } else { - console.error(`[GitLab Theme] Active card not found for group: ${groupId}`); - } - - this.currentActiveGroup = groupId; - - // Scroll to the repos section - reposSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - -displayAllRepos(container, items) { - // Only modify if we haven't already processed this container - if (container.dataset.gitlabProcessed === 'true') { - return; - } - - const fragment = document.createDocumentFragment(); - - items.forEach(item => { - fragment.appendChild(item); - }); - - // Preserve non-repo items - const nonRepoItems = Array.from(container.children).filter(child => - !items.includes(child) && - !child.classList.contains('gitlab-cards-section') && - !child.classList.contains('gitlab-repos-section') - ); - - nonRepoItems.forEach(item => fragment.appendChild(item)); - - container.innerHTML = ''; - container.appendChild(fragment); - } - - getRepositoryName(item) { - const nameSelectors = [ - 'h3 a', - 'a[itemprop="name codeRepository"]', - '.wb-break-all a', - '[data-testid="repository-name"]', - '[itemprop="name"]', - '.Link--primary', - 'a[href*="/"][title]' - ]; - - for (const selector of nameSelectors) { - const element = item.querySelector(selector); - if (element && element.textContent.trim()) { - return element.textContent.trim(); - } - } - return ''; - } - - getGroupName(repoName) { - if (!repoName) return 'General'; - - for (const customGroup of this.customGroups) { - if (repoName.toLowerCase().startsWith(customGroup.toLowerCase())) { - return customGroup; - } - } - - const separators = ['-', '_', '/', '.', ' ']; - for (const sep of separators) { - const index = repoName.indexOf(sep); - if (index > 0) { - const prefix = repoName.substring(0, index).toLowerCase(); - if (prefix.length >= 2) { - return prefix.charAt(0).toUpperCase() + prefix.slice(1); - } - } - } - - return 'General'; - } - - addGroupControls() { - const existingControls = document.querySelector('.gitlab-group-controls'); - if (existingControls) return; - - const repoSections = [ - '#user-repositories-list', - '#org-repositories-list', - '[data-testid="repository-list-container"]', - 'div[data-test-selector="org-repositories-list"]', - '.org-repos', - '#org-repositories' - ]; - - for (const selector of repoSections) { - const container = document.querySelector(selector); - if (container && !container.previousElementSibling?.classList.contains('gitlab-group-controls')) { - const controls = this.createGroupControls(); - container.parentNode.insertBefore(controls, container); - break; - } - } - } - - createGroupControls() { - const controls = document.createElement('div'); - controls.className = 'gitlab-group-controls'; - - const toggleGrouping = document.createElement('button'); - toggleGrouping.className = 'gitlab-control-btn'; - toggleGrouping.innerHTML = ' Disable'; - toggleGrouping.onclick = async () => { - this.groupingEnabled = !this.groupingEnabled; - await this.saveSetting('groupingEnabled', this.groupingEnabled); - toggleGrouping.innerHTML = this.groupingEnabled ? - ' Disable' : - ' Enable'; - this.processedContainers.clear(); - this.processRepositories(); - }; - - const manageGroups = document.createElement('button'); - manageGroups.className = 'gitlab-control-btn gitlab-manage-btn'; - manageGroups.innerHTML = ' Manage'; - manageGroups.onclick = () => this.showGroupManager(); - - controls.appendChild(toggleGrouping); - controls.appendChild(manageGroups); - - return controls; - } - - showGroupManager() { - const existing = document.querySelector('.gitlab-group-manager'); - if (existing) { - existing.remove(); - return; - } - - const manager = document.createElement('div'); - manager.className = 'gitlab-group-manager'; - manager.innerHTML = ` -
No custom groups yet
' : ''} -- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| src | -
-
- |
- 8.82% | -12/136 | -5.26% | -3/57 | -0% | -0/22 | -9.3% | -12/129 | -
| src/core/repository | -
-
- |
- 1.61% | -4/247 | -0% | -0/58 | -0% | -0/54 | -1.63% | -4/244 | -
| src/core/theme | -
-
- |
- 5.26% | -1/19 | -0% | -0/6 | -0% | -0/5 | -5.26% | -1/19 | -
| src/storage | -
-
- |
- 1.75% | -1/57 | -0% | -0/17 | -0% | -0/9 | -2% | -1/50 | -
| src/ui/components | -
-
- |
- 2.85% | -2/70 | -0% | -0/5 | -0% | -0/15 | -2.85% | -2/70 | -
| src/ui/managers | -
-
- |
- 1.96% | -1/51 | -0% | -0/14 | -0% | -0/20 | -2.08% | -1/48 | -
| src/utils | -
-
- |
- 5.71% | -2/35 | -0% | -0/24 | -0% | -0/11 | -5.88% | -2/34 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| src | -
-
- |
- 8.82% | -12/136 | -5.26% | -3/57 | -0% | -0/22 | -9.3% | -12/129 | -
| src/core/repository | -
-
- |
- 1.61% | -4/247 | -0% | -0/58 | -0% | -0/54 | -1.63% | -4/244 | -
| src/core/theme | -
-
- |
- 5.26% | -1/19 | -0% | -0/6 | -0% | -0/5 | -5.26% | -1/19 | -
| src/storage | -
-
- |
- 1.75% | -1/57 | -0% | -0/17 | -0% | -0/9 | -2% | -1/50 | -
| src/ui/components | -
-
- |
- 2.85% | -2/70 | -0% | -0/5 | -0% | -0/15 | -2.85% | -2/70 | -
| src/ui/managers | -
-
- |
- 1.96% | -1/51 | -0% | -0/14 | -0% | -0/20 | -2.08% | -1/48 | -
| src/utils | -
-
- |
- 5.71% | -2/35 | -0% | -0/24 | -0% | -0/11 | -5.88% | -2/34 | -