Created
December 1, 2025 04:55
-
-
Save kiranwayne/f85cb0e7735e1051d6e74536c5a017f3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name GitHub Enhanced | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.8.0 | |
| // @description Customize content width on GitHub pages with a slider. Dark mode panel & improved input behavior. | |
| // @author kiranwayne | |
| // @match https://github.com/* | |
| // @updateURL https://gist.github.com/kiranwayne/f85cb0e7735e1051d6e74536c5a017f3/raw/github_enhanced.js | |
| // @downloadURL https://gist.github.com/kiranwayne/f85cb0e7735e1051d6e74536c5a017f3/raw/github_enhanced.js | |
| // @match https://gist.github.com/* | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // @grant GM_registerMenuCommand | |
| // @grant GM_unregisterMenuCommand | |
| // @run-at document-end | |
| // ==/UserScript== | |
| (async () => { | |
| 'use strict'; | |
| // --- Configuration & Constants --- | |
| const SCRIPT_NAME = 'GitHub Enhanced'; | |
| const SCRIPT_VERSION = '1.8.0'; | |
| const SCRIPT_AUTHOR = 'kiranwayne'; | |
| const CONFIG_PREFIX = 'githubEnhancedWidth_v1_'; | |
| const MAX_WIDTH_PX_KEY = CONFIG_PREFIX + 'maxWidthPx'; | |
| const USE_DEFAULT_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultWidth'; | |
| const UI_VISIBLE_KEY = CONFIG_PREFIX + 'uiVisible'; | |
| const WIDTH_STYLE_ID = 'gh-enhanced-width-style'; | |
| const LAYOUT_ADJUSTMENTS_STYLE_ID = 'gh-enhanced-layout-adjustments-style'; | |
| const GLOBAL_STYLE_ID = 'gh-enhanced-global-style'; | |
| const PRELOAD_STYLE_ID = 'gh-enhanced-preload-hide-style'; | |
| const SETTINGS_PANEL_ID = 'gh-userscript-settings-panel'; | |
| // CSS selector for elements whose max-width will be controlled by the script | |
| const WIDTH_TARGET_SELECTOR = ` | |
| .application-main .container-xl, | |
| .application-main .container-lg, | |
| .container-xl, | |
| .container-lg, | |
| .feed-content > .Details > .container-lg, | |
| .application-main div[data-target="react-app.reactRoot"] div[class^='prc-PageLayout-Content-'] > div[class^='Box-sc-'], | |
| .application-main div[data-target="react-app.reactRoot"] > div[class^='Box-sc-'] > div[class^='Box-sc-'] > div[class^='Box-sc-'] > div[class^='Box-sc-'], | |
| .application-main div[data-target="react-app.reactRoot"] div[class^='IssueCreatePage-module__createPaneContainer-'], | |
| .application-main div[style^="--sticky-pane-height:"] > div[class^='Box-sc-'] > div[class^='Box-sc-'] > div[class^='Box-sc-'] > div[class^='Box-sc-']:nth-child(2) > div[class^='Box-sc-'], | |
| #js-repo-pjax-container div[style^="--sticky-pane-height:"] > div[class^='Box-sc-']:first-child, | |
| .gist-content .container-lg | |
| `.split(',').map(s => s.trim()).filter(s => s).join(', '); | |
| // Original GitHub constraints maintained | |
| const SCRIPT_DEFAULT_CUSTOM_WIDTH_PX = 1600; | |
| const MIN_WIDTH_PX = 1500; | |
| const MAX_WIDTH_PX = 3300; | |
| const STEP_WIDTH_PX = 20; | |
| let config = { | |
| maxWidthPx: SCRIPT_DEFAULT_CUSTOM_WIDTH_PX, | |
| useDefaultWidth: true, | |
| uiVisible: false, | |
| }; | |
| let settingsPanel = null, widthSlider = null, widthLabel = null, widthInput = null; | |
| let defaultWidthCheckbox = null; | |
| let menuCommandId_ToggleUI = null; | |
| const allStyleRoots = new Set(); | |
| // --- Helper Functions --- | |
| async function loadSettings() { | |
| config.useDefaultWidth = await GM_getValue(USE_DEFAULT_WIDTH_KEY, true); | |
| config.maxWidthPx = await GM_getValue(MAX_WIDTH_PX_KEY, SCRIPT_DEFAULT_CUSTOM_WIDTH_PX); | |
| config.maxWidthPx = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, config.maxWidthPx)); // Clamp | |
| config.uiVisible = await GM_getValue(UI_VISIBLE_KEY, false); | |
| } | |
| async function saveSetting(key, value) { | |
| if (key === MAX_WIDTH_PX_KEY) { | |
| const numValue = parseInt(value, 10); | |
| if (!isNaN(numValue)) { | |
| const clampedValue = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, numValue)); | |
| await GM_setValue(key, clampedValue); | |
| config.maxWidthPx = clampedValue; | |
| } else { return; } | |
| } else { | |
| await GM_setValue(key, value); | |
| if (key === USE_DEFAULT_WIDTH_KEY) config.useDefaultWidth = value; | |
| else if (key === UI_VISIBLE_KEY) config.uiVisible = value; | |
| } | |
| } | |
| // --- Style Generation Functions --- | |
| function getCustomWidthConstraintCss() { | |
| if (config.useDefaultWidth) return ''; | |
| return `${WIDTH_TARGET_SELECTOR} { max-width: ${config.maxWidthPx}px !important; margin-left: auto !important; margin-right: auto !important; }`; | |
| } | |
| function getLayoutAdjustmentsCss() { | |
| if (config.useDefaultWidth) return ''; | |
| return ` | |
| .react-repos-overview-margin { margin-right: 0 !important; } | |
| .application-main .col-11 { width: 100% !important; } | |
| #js-repo-pjax-container .js-issue-row .text-right { max-width: 303px !important; } | |
| `; | |
| } | |
| function getGlobalPanelCss() { | |
| // Dark Mode CSS (Ported from Gemini Script) | |
| return ` | |
| #${SETTINGS_PANEL_ID} { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; | |
| font-size: 13px; | |
| line-height: 1.4; | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| z-index: 99999; | |
| background-color: #1e1f20; /* Dark Grey */ | |
| color: #e3e3e3; | |
| border: 1px solid #444746; | |
| border-radius: 8px; | |
| padding: 14px; | |
| box-shadow: 0 4px 24px rgba(0,0,0,0.5); | |
| min-width: 250px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| #${SETTINGS_PANEL_ID} h4 { | |
| margin: 0 0 8px 0; | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: #e3e3e3; | |
| padding-bottom: 6px; | |
| border-bottom: 1px solid #444746; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| #${SETTINGS_PANEL_ID} .close-btn { | |
| cursor: pointer; | |
| font-size: 20px; | |
| color: #8e918f; | |
| line-height: 1; | |
| font-weight: normal; | |
| } | |
| #${SETTINGS_PANEL_ID} .close-btn:hover { color: #fff; } | |
| #${SETTINGS_PANEL_ID} .meta-info { | |
| margin: 0; | |
| font-size: 11px; | |
| color: #c4c7c5; | |
| opacity: 0.8; | |
| line-height: 1.3; | |
| } | |
| #${SETTINGS_PANEL_ID} .control-group { margin-top: 10px; } | |
| #${SETTINGS_PANEL_ID} label { | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| color: #e3e3e3; | |
| margin-bottom: 8px; | |
| } | |
| #${SETTINGS_PANEL_ID} input[type="checkbox"] { | |
| margin-right: 8px; | |
| accent-color: #8ab4f8; | |
| width: 14px; height: 14px; | |
| } | |
| #${SETTINGS_PANEL_ID} .width-controls-wrapper { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| #${SETTINGS_PANEL_ID} input[type="range"] { | |
| flex-grow: 1; | |
| accent-color: #8ab4f8; | |
| background: #444746; | |
| height: 4px; | |
| border-radius: 2px; | |
| cursor: pointer; | |
| } | |
| #${SETTINGS_PANEL_ID} input[type="number"] { | |
| width: 55px; | |
| padding: 4px 6px; | |
| background-color: #2a2b2d; | |
| color: #e3e3e3; | |
| border: 1px solid #444746; | |
| border-radius: 6px; | |
| text-align: left; | |
| font-family: monospace; | |
| font-size: 12px; | |
| } | |
| #${SETTINGS_PANEL_ID} input[type="number"]:focus { outline: 2px solid #8ab4f8; border-color: transparent; } | |
| #${SETTINGS_PANEL_ID} .width-value-label { | |
| min-width: 45px; | |
| text-align: left; | |
| font-family: monospace; | |
| color: #e3e3e3; | |
| font-size: 12px; | |
| } | |
| #${SETTINGS_PANEL_ID} input:disabled, | |
| #${SETTINGS_PANEL_ID} .width-value-label.disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| `; | |
| } | |
| // --- Style Injection / Update / Removal Functions --- | |
| function injectOrUpdateStyle(root, styleId, cssContent) { | |
| if (!root) return; | |
| let style = root.querySelector(`#${styleId}`); | |
| if (cssContent && cssContent.trim() !== '') { | |
| if (!style) { | |
| style = document.createElement('style'); style.id = styleId; style.textContent = cssContent; | |
| if (root === document.head || (root.nodeType === Node.ELEMENT_NODE && root.shadowRoot === null) || root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) root.appendChild(style); | |
| else if (root.shadowRoot) root.shadowRoot.appendChild(style); | |
| } else if (style.textContent !== cssContent) { | |
| style.textContent = cssContent; | |
| } | |
| } else { | |
| if (style) style.remove(); | |
| } | |
| } | |
| function applyGlobalHeadStyles() { | |
| if (document.head) injectOrUpdateStyle(document.head, GLOBAL_STYLE_ID, getGlobalPanelCss()); | |
| } | |
| function applyCustomWidthConstraintStyleToAllRoots() { | |
| const css = getCustomWidthConstraintCss(); | |
| allStyleRoots.forEach(r => { if(r) injectOrUpdateStyle(r, WIDTH_STYLE_ID, css); }); | |
| } | |
| function applyLayoutAdjustmentsStyleToAllRoots() { | |
| const css = getLayoutAdjustmentsCss(); | |
| allStyleRoots.forEach(r => { if(r) injectOrUpdateStyle(r, LAYOUT_ADJUSTMENTS_STYLE_ID, css); }); | |
| } | |
| // --- UI State Update --- | |
| function updateUIState() { | |
| if (settingsPanel && defaultWidthCheckbox && widthSlider && widthLabel && widthInput) { | |
| defaultWidthCheckbox.checked = config.useDefaultWidth; | |
| const isCustomWidthEnabled = !config.useDefaultWidth; | |
| [widthSlider, widthInput].forEach(el => el.disabled = !isCustomWidthEnabled); | |
| const opacityValue = isCustomWidthEnabled ? 1 : 0.5; | |
| widthSlider.style.opacity = opacityValue; | |
| widthInput.style.opacity = opacityValue; | |
| widthLabel.style.opacity = opacityValue; | |
| widthSlider.value = config.maxWidthPx; | |
| widthInput.value = config.maxWidthPx; | |
| widthLabel.textContent = `${config.maxWidthPx}px`; | |
| } | |
| } | |
| async function closePanel() { | |
| await saveSetting(UI_VISIBLE_KEY, false); | |
| removeSettingsUI(); | |
| updateTampermonkeyMenu(); | |
| } | |
| function removeSettingsUI() { | |
| settingsPanel = document.getElementById(SETTINGS_PANEL_ID); | |
| if (settingsPanel) { | |
| settingsPanel.remove(); | |
| settingsPanel = null; widthSlider = null; widthLabel = null; widthInput = null; defaultWidthCheckbox = null; | |
| } | |
| } | |
| function createSettingsUI() { | |
| if (document.getElementById(SETTINGS_PANEL_ID) || !config.uiVisible) return; | |
| if (!document.body) return; | |
| settingsPanel = document.createElement('div'); settingsPanel.id = SETTINGS_PANEL_ID; | |
| // Header | |
| const header = document.createElement('h4'); | |
| const titleSpan = document.createElement('span'); titleSpan.textContent = SCRIPT_NAME; | |
| const closeBtn = document.createElement('span'); closeBtn.className = 'close-btn'; closeBtn.textContent = '×'; closeBtn.onclick = closePanel; | |
| header.append(titleSpan, closeBtn); | |
| // Meta Info | |
| const versionP = document.createElement('p'); versionP.className = 'meta-info'; versionP.textContent = `Version: ${SCRIPT_VERSION}`; | |
| const authorP = document.createElement('p'); authorP.className = 'meta-info'; authorP.textContent = `Author: ${SCRIPT_AUTHOR}`; | |
| // Controls | |
| const controlContainer = document.createElement('div'); controlContainer.className = 'control-group'; | |
| const checkboxWrapper = document.createElement('div'); | |
| defaultWidthCheckbox = document.createElement('input'); defaultWidthCheckbox.type = 'checkbox'; defaultWidthCheckbox.id = 'gh-userscript-defaultwidth-toggle'; | |
| const defaultWidthLabelElement = document.createElement('label'); defaultWidthLabelElement.htmlFor = 'gh-userscript-defaultwidth-toggle'; | |
| defaultWidthLabelElement.append(defaultWidthCheckbox, document.createTextNode(' Use GitHub Default Width')); | |
| checkboxWrapper.appendChild(defaultWidthLabelElement); | |
| const sliderWrapper = document.createElement('div'); sliderWrapper.className = 'width-controls-wrapper'; | |
| widthLabel = document.createElement('span'); widthLabel.className = 'width-value-label'; | |
| widthSlider = document.createElement('input'); widthSlider.type = 'range'; widthSlider.min = MIN_WIDTH_PX; widthSlider.max = MAX_WIDTH_PX; widthSlider.step = STEP_WIDTH_PX; | |
| widthInput = document.createElement('input'); widthInput.type = 'number'; widthInput.min = MIN_WIDTH_PX; widthInput.max = MAX_WIDTH_PX; widthInput.step = STEP_WIDTH_PX; | |
| sliderWrapper.append(widthLabel, widthSlider, widthInput); | |
| controlContainer.append(checkboxWrapper, sliderWrapper); | |
| settingsPanel.append(header, versionP, authorP, controlContainer); | |
| document.body.appendChild(settingsPanel); | |
| // Events | |
| defaultWidthCheckbox.addEventListener('change', async (e) => { | |
| await saveSetting(USE_DEFAULT_WIDTH_KEY, e.target.checked); | |
| applyCustomWidthConstraintStyleToAllRoots(); | |
| applyLayoutAdjustmentsStyleToAllRoots(); | |
| updateUIState(); | |
| }); | |
| // Slider - Instant Update | |
| widthSlider.addEventListener('input', (e) => { | |
| const nw = parseInt(e.target.value, 10); | |
| config.maxWidthPx = nw; | |
| widthLabel.textContent = `${nw}px`; | |
| widthInput.value = nw; | |
| if (!config.useDefaultWidth) applyCustomWidthConstraintStyleToAllRoots(); | |
| }); | |
| widthSlider.addEventListener('change', async (e) => { | |
| await saveSetting(MAX_WIDTH_PX_KEY, parseInt(e.target.value, 10)); | |
| }); | |
| // Input - Commit on Change/Enter | |
| const commitInputValue = async () => { | |
| let val = parseInt(widthInput.value, 10); | |
| if (isNaN(val)) { widthInput.value = config.maxWidthPx; return; } | |
| // Clamp | |
| val = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, val)); | |
| config.maxWidthPx = val; | |
| widthInput.value = val; | |
| widthSlider.value = val; | |
| widthLabel.textContent = `${val}px`; | |
| await saveSetting(MAX_WIDTH_PX_KEY, val); | |
| if (!config.useDefaultWidth) applyCustomWidthConstraintStyleToAllRoots(); | |
| }; | |
| widthInput.addEventListener('change', commitInputValue); | |
| widthInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') widthInput.blur(); | |
| }); | |
| updateUIState(); | |
| applyGlobalHeadStyles(); | |
| } | |
| function updateTampermonkeyMenu() { | |
| if (menuCommandId_ToggleUI !== null && typeof GM_unregisterMenuCommand === 'function') { | |
| try { GM_unregisterMenuCommand(menuCommandId_ToggleUI); } catch (e) { /* ignore */ } | |
| menuCommandId_ToggleUI = null; | |
| } | |
| if (typeof GM_registerMenuCommand === 'function') { | |
| const labelUI = config.uiVisible ? 'Hide Width Settings Panel' : 'Show Width Settings Panel'; | |
| menuCommandId_ToggleUI = GM_registerMenuCommand(labelUI, async () => { | |
| const newState = !config.uiVisible; | |
| await saveSetting(UI_VISIBLE_KEY, newState); | |
| if (newState) createSettingsUI(); else removeSettingsUI(); | |
| updateTampermonkeyMenu(); | |
| }); | |
| } | |
| } | |
| // --- Shadow DOM Handling --- | |
| function getShadowRoot(element) { try { return element.shadowRoot; } catch (e) { return null; } } | |
| function processElementForShadowDOM(element) { | |
| const shadow = getShadowRoot(element); | |
| if (shadow && shadow.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !allStyleRoots.has(shadow)) { | |
| allStyleRoots.add(shadow); | |
| injectOrUpdateStyle(shadow, WIDTH_STYLE_ID, getCustomWidthConstraintCss()); | |
| injectOrUpdateStyle(shadow, LAYOUT_ADJUSTMENTS_STYLE_ID, getLayoutAdjustmentsCss()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| function scanExistingShadowDOM() { | |
| try { | |
| document.querySelectorAll('*').forEach(el => { | |
| processElementForShadowDOM(el); | |
| if (el.shadowRoot) el.shadowRoot.querySelectorAll('*').forEach(childEl => processElementForShadowDOM(childEl)); | |
| }); | |
| } catch (e) { console.error(`[${SCRIPT_NAME}] Error during initial Shadow DOM scan:`, e); } | |
| } | |
| function startDynamicShadowDOMObserver() { | |
| const observer = new MutationObserver(mutationsList => { | |
| for (const mutation of mutationsList) { | |
| if (mutation.type === 'childList') { | |
| mutation.addedNodes.forEach(node => { | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| processElementForShadowDOM(node); | |
| node.querySelectorAll('*').forEach(el => processElementForShadowDOM(el)); | |
| } | |
| }); | |
| } | |
| } | |
| }); | |
| if(document.body) observer.observe(document.body, { childList: true, subtree: true }); | |
| else document.addEventListener('DOMContentLoaded', () => { | |
| if(document.body) observer.observe(document.body, { childList: true, subtree: true }); | |
| }); | |
| } | |
| // --- Initialization --- | |
| console.log(`[${SCRIPT_NAME}] v${SCRIPT_VERSION} by ${SCRIPT_AUTHOR} starting...`); | |
| // Race Condition Fix: Hide content initially | |
| const preloadStyle = document.createElement('style'); | |
| preloadStyle.id = PRELOAD_STYLE_ID; | |
| preloadStyle.textContent = `.application-main { visibility: hidden !important; }`; | |
| document.head.appendChild(preloadStyle); | |
| if (document.head) allStyleRoots.add(document.head); | |
| else { const rootNode = document.documentElement || document; allStyleRoots.add(rootNode); } | |
| await loadSettings(); | |
| applyGlobalHeadStyles(); | |
| applyCustomWidthConstraintStyleToAllRoots(); | |
| applyLayoutAdjustmentsStyleToAllRoots(); | |
| scanExistingShadowDOM(); | |
| startDynamicShadowDOMObserver(); | |
| if (config.uiVisible) setTimeout(createSettingsUI, 100); | |
| updateTampermonkeyMenu(); | |
| // Reveal content | |
| try { | |
| const styleToRemove = document.getElementById(PRELOAD_STYLE_ID); | |
| if (styleToRemove) setTimeout(() => styleToRemove.remove(), 50); | |
| } catch (e) { | |
| document.body.style.visibility = 'visible'; | |
| } | |
| // Handle GitHub's Turbo Drive navigation | |
| document.addEventListener("turbo:load", async () => { | |
| allStyleRoots.clear(); | |
| if(document.head) allStyleRoots.add(document.head); else allStyleRoots.add(document.documentElement||document); | |
| await loadSettings(); | |
| applyCustomWidthConstraintStyleToAllRoots(); | |
| applyLayoutAdjustmentsStyleToAllRoots(); | |
| scanExistingShadowDOM(); | |
| const existingPanel = document.getElementById(SETTINGS_PANEL_ID); | |
| if(config.uiVisible && !existingPanel) setTimeout(createSettingsUI, 100); | |
| else if(!config.uiVisible && existingPanel) removeSettingsUI(); | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment