// ==UserScript== // @name GitHub file list beautifier // @description Adds colors to files by type, displays small images in place of file-type icons in a repository source tree // @license MIT License // @version 3.0.7 // @match https://github.com/* // @grant none // @run-at document-end // @author wOxxOm // @namespace wOxxOm.scripts // @icon https://octodex.github.com/images/murakamicat.png // @downloadURL none // ==/UserScript== 'use strict'; let savedConfig = {}; try { savedConfig = JSON.parse(localStorage.FileListBeautifier) || {}; } catch (e) {} const config = Object.assign({}, ...Object.entries({ iconSize: 24, colorSeed1: 2, colorSeed2: 1299721, colorSeed3: 179426453, }).map(([k, v]) => ({[k]: +savedConfig[k] || v}))); const styleQueue = []; const {sheet} = document.head.appendChild(Object.assign(document.createElement('style'), { textContent: ` .wOxxOm-image-icon:not(#foo) { max-width: ${config.iconSize}px; max-height: ${config.iconSize}px; width: auto; height: auto; margin: auto; position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .wOxxOm-image-td:not(#foo) { position: relative; padding: 0; min-width: ${config.iconSize + 4}px; line-height: inherit; } a[file-type=":folder"]:not(#foo) { font-weight: bold; } ` })); const filetypes = {}; const tables = document.getElementsByTagName('table'); const ME = Symbol(GM_info.script.name); const ob = new MutationObserver(() => { ob.disconnect(); requestAnimationFrame(start); }); const {pushState} = unsafeWindow.History.prototype; unsafeWindow.History.prototype.pushState = function() { const res = pushState.apply(this, arguments); start(); return res; }; let lumaBias, lumaFix, lumaAmp; addEventListener('popstate', start); if (document.body) start(); else document.addEventListener('DOMContentLoaded', start, {once: true}); function start() { beautify(); ob.observe(document, {subtree: true, childList: true}); } // postpone observing until the parser can breathe after the initial burst of activity during page load function beautify() { const table = tables[0]; if (!table || !table.classList.contains('files')) return; let didSomeWork = false; for (const a of table.getElementsByClassName('js-navigation-open')) { if (!a.hasAttribute('href') || ME in a) continue; a[ME] = true; const tr = a.closest('tr'); if (tr && tr.querySelector('.octicon-file-directory')) { a.setAttribute('file-type', ':folder'); continue; } didSomeWork = true; const ext = a.href.match(/\.(\w+)$|$/)[1] || ':empty'; a.setAttribute('file-type', ext); if (!filetypes[ext]) addFileTypeStyle(ext); if (/^(png|jpe?g|bmp|gif|cur|ico)$/.test(ext)) { const m = a.href.match(/github\.com\/(.+?\/)blob\/(.*)$/); if (!m) continue; const iconCell = a.closest('.js-navigation-item').querySelector('.icon'); const icon = iconCell.querySelector('.octicon-file, .octicon-file-text'); if (!icon || icon.style.display === 'none') continue; icon.style.display = 'none'; icon.insertAdjacentElement('afterend', Object.assign( document.createElement('img'), { className: 'wOxxOm-image-icon', src: `https://raw.githubusercontent.com/${m[1]}${m[2]}`, })); iconCell.classList.add('wOxxOm-image-td'); } } } function addFileTypeStyle(type) { filetypes[type] = true; if (!styleQueue.length) requestAnimationFrame(commitStyleQueue); styleQueue.push(type); } function commitStyleQueue() { if (!lumaAmp) initLumaScale(); for (const type of styleQueue) { const hash = calcSimpleHash(type); const H = hash % 360; const Hq = H / 60; const S = hash * config.colorSeed2 % 50 + 50 | 0; const redFix = (Hq < 1 ? 1 - Hq : Hq > 4 ? (Hq - 4) / 2 : 0); const blueFix = (Hq < 3 || Hq > 5 ? 0 : Hq < 4 ? Hq - 3 : 5 - Hq) * 3; const L = hash * config.colorSeed3 % lumaAmp + lumaBias + (redFix + blueFix) * lumaFix * S / 100 | 0; sheet.insertRule(`a[file-type="${type}"]:not(#foo) { color: hsl(${H},${S}%,${L}%) !important }`); } styleQueue.length = 0; } function calcSimpleHash(text) { let hash = 0; for (let i = 0, len = text.length; i < len; i++) hash = ((hash << 5) - hash) + text.charCodeAt(i); return Math.abs(hash * config.colorSeed1 | 0); } function initLumaScale() { const [, r, g, b] = getComputedStyle(document.body).backgroundColor.split(/[^\d.]+/).map(parseFloat); const isDark = (r * .2126 + g * .7152 + b * .0722) < 128; [lumaBias, lumaAmp, lumaFix] = isDark ? [30, 50, 12] : [25, 15, 0]; }