GitLab tree selector

Select current file name in the MR tree

  1. // ==UserScript==
  2. // @name GitLab tree selector
  3. // @namespace http://tampermonkey.net/
  4. // @version 2025-03-05
  5. // @description Select current file name in the MR tree
  6. // @author nw
  7. // @match https://gitlab.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=gitlab.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const style = document.createElement('style');
  17. style.innerHTML = `
  18. .gl-truncate-component {
  19. font-weight: initial;
  20. }
  21. .diff-file-row.is-active .gl-truncate-component {
  22. font-weight: bold;
  23. }
  24. `;
  25. document.body.appendChild(style);
  26.  
  27. const observer = new MutationObserver((mutations, obs) => {
  28. const treeList = document.querySelector('.mr-tree-list');
  29.  
  30. if (!treeList) {
  31. return;
  32. }
  33.  
  34. window.addEventListener('scroll', function(event) {
  35. const treeFiles = {};
  36.  
  37. document.querySelectorAll('.mr-tree-list .file-row:not(.folder)').forEach(treeFile => {
  38. const fileName = treeFile.querySelector('.gl-truncate-component').textContent.replace(/[\u200B-\u200F\u202A-\u202E\u2060-\u206F]/g, '').trim();
  39.  
  40. treeFile.style.removeProperty("background-color");
  41. //treeFile.classList.remove("is-active");
  42.  
  43. treeFiles[fileName] = treeFile;
  44. });
  45.  
  46. const frontFileHeaders = document.querySelectorAll('.diff-files-holder .vue-recycle-scroller__item-view .file-title');
  47. const paginationHeaderHeight = document.querySelector('.top-bar-fixed').getBoundingClientRect().height;
  48. const titleHeaderHeight = document.querySelector('.issue-sticky-header').getBoundingClientRect().height;
  49. const offsetTop = paginationHeaderHeight + titleHeaderHeight;
  50.  
  51. const filesBelowCenter = [];
  52.  
  53. frontFileHeaders.forEach(frontFileTitleHeader => {
  54. const frontFileTitleHeaderTop = frontFileTitleHeader.getBoundingClientRect().top;
  55. const viewportCenter = ((window.innerHeight - offsetTop) / 2) + offsetTop;
  56.  
  57. if (frontFileTitleHeaderTop > 0 && frontFileTitleHeaderTop < viewportCenter) {
  58. filesBelowCenter.push(frontFileTitleHeader);
  59. }
  60. });
  61.  
  62. if (filesBelowCenter.length === 0) {
  63. return;
  64. }
  65.  
  66. const currentFile = filesBelowCenter[filesBelowCenter.length - 1];
  67. const filePaths = currentFile.querySelectorAll('.file-header-content > a strong');
  68. const filePath = filePaths[filePaths.length - 1].title;
  69. const filePathArray = filePath.split('/');
  70. const fileName = filePathArray[filePathArray.length - 1];
  71.  
  72. if (treeFiles[fileName] === undefined) {
  73. return;
  74. }
  75.  
  76. treeFiles[fileName].style.setProperty("background-color", "var(--gray-50, #ececef)");
  77. });
  78.  
  79. obs.disconnect();
  80. });
  81.  
  82. observer.observe(document.body, { childList: true, subtree: true });
  83. })();