GitHub Repo Size Display

Display repository size on GitHub repo pages

Per 16-08-2024. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         GitHub Repo Size Display
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Display repository size on GitHub repo pages
// @license      MIT
// @author       Lainbo
// @match        https://github.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSIxMSIgaGVpZ2h0PSIxMyIgeD0iMS41IiB5PSIuNSIgcng9IjEiLz48cGF0aCBkPSJtNC41IDEwLjVsMS43NS0yLjc1TTQuMDkgN0EyLjkzIDIuOTMgMCAwIDEgNCA1LjE2YTMgMyAwIDEgMSA0LjY3IDMuMjdNNy41IDExSDEwIi8+PC9nPjwvc3ZnPg==
// ==/UserScript==

(function () {
  'use strict'

  let observer
  let repoTitleComponent

  GM_addStyle(`
    .modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 9999;
    }
    .modal-content {
        background-color: #f6f8fa;
        padding: 24px;
        border-radius: 6px;
        width: 340px;
        box-shadow: 0 8px 24px rgba(140,149,159,0.2);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    }
    .modal-title {
        font-size: 20px;
        font-weight: 600;
        margin-bottom: 16px;
        color: #24292f;
    }
    .modal-description {
        font-size: 14px;
        color: #57606a;
        margin-bottom: 16px;
        line-height: 1.5;
    }
    .modal-content input {
        width: 100%;
        margin-bottom: 16px;
        padding: 5px 12px;
        font-size: 14px;
        line-height: 20px;
        color: #24292f;
        vertical-align: middle;
        background-color: #ffffff;
        background-repeat: no-repeat;
        background-position: right 8px center;
        border: 1px solid #d0d7de;
        border-radius: 6px;
        box-shadow: inset 0 1px 0 rgba(208,215,222,0.2);
    }
    .modal-content button {
        color: #ffffff;
        background-color: #2da44e;
        padding: 5px 16px;
        font-size: 14px;
        font-weight: 500;
        line-height: 20px;
        white-space: nowrap;
        vertical-align: middle;
        cursor: pointer;
        border: 1px solid;
        border-radius: 6px;
        appearance: none;
        user-select: none;
        margin-right: 8px;
    }
    .modal-content button.cancel {
        color: #24292f;
        background-color: #f6f8fa;
        border-color: rgba(27,31,36,0.15);
    }
    .modal-content button:hover {
        background-color: #2c974b;
    }
    .modal-content button.cancel:hover {
        background-color: #f3f4f6;
    }
    .repo-size-label.error {
        background-color: #ffdce0;
        color: #cf222e;
    }
`)

  function formatSize(sizeInKB) {
    if (sizeInKB >= 1024 * 1024) {
      return `${(sizeInKB / (1024 * 1024)).toFixed(2)} GB`
    }
    else if (sizeInKB >= 1024) {
      return `${(sizeInKB / 1024).toFixed(2)} MB`
    }
    else {
      return `${sizeInKB} KB`
    }
  }

  function updateRepoSize(content, isError = false) {
    if (!repoTitleComponent) {
      repoTitleComponent = document.querySelector('#repo-title-component')
      if (!repoTitleComponent) { return }
    }

    let sizeElement = repoTitleComponent.querySelector('.repo-size-label')
    if (!sizeElement) {
      sizeElement = document.createElement('span')
      sizeElement.className = 'Label Label--secondary v-align-middle ml-1 repo-size-label'
      repoTitleComponent.appendChild(sizeElement)
    }

    sizeElement.textContent = content
    sizeElement.classList.toggle('error', isError)
  }

  function fetchRepoSize() {
    const repoRegex = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/
    const match = location.href.match(repoRegex)

    if (!match) { return }

    const [, owner, repo] = match
    const apiUrl = `https://api.github.com/repos/${owner}/${repo}`
    const token = GM_getValue('github_token', '')

    if (!token) {
      updateRepoSize('Token not set', true)
      return
    }

    fetch(apiUrl, {
      headers: { Authorization: `token ${token}` },
    })
      .then((response) => {
        if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) }
        return response.json()
      })
      .then((data) => {
        if (data.size !== undefined) {
          updateRepoSize(formatSize(data.size))
        }
        else {
          throw new Error('Size information not available')
        }
      })
      .catch((error) => {
        console.error('Error fetching repo size:', error)
        updateRepoSize('Size fetch failed', true)
      })
  }

  function createModal() {
    const tokenCreationUrl = new URL('https://github.com/settings/tokens/new')
    tokenCreationUrl.searchParams.append('description', 'GitHub Repo Size Display')
    tokenCreationUrl.searchParams.append('scopes', 'repo')

    const modalHTML = `
        <div class="modal-overlay">
            <div class="modal-content">
                <h2 class="modal-title">Set GitHub Token</h2>
                <p class="modal-description">
                    Enter your GitHub personal access token with "repo" scope.
                    <a href="${tokenCreationUrl.toString()}" target="_blank" rel="noopener noreferrer">
                        Click here to create a new token
                    </a>
                </p>
                <input type="text" id="github-token-input" placeholder="Enter your GitHub personal access token">
                <button id="save-token">Save</button>
                <button id="cancel-token" class="cancel">Cancel</button>
            </div>
        </div>
    `

    const modalContainer = document.createElement('div')
    modalContainer.innerHTML = modalHTML
    document.body.appendChild(modalContainer)

    const input = document.getElementById('github-token-input')
    input.value = GM_getValue('github_token', '')

    document.getElementById('save-token').addEventListener('click', () => {
      const token = input.value.trim()
      if (token) {
        GM_setValue('github_token', token)
        modalContainer.remove()
        location.reload()
      }
    })

    document.getElementById('cancel-token').addEventListener('click', () => modalContainer.remove())
  }

  function observePageChanges() {
    if (observer) {
      observer.disconnect()
    }

    const targetNode = document.body
    const config = { childList: true, subtree: true }

    observer = new MutationObserver((mutationsList, observer) => {
      for (const mutation of mutationsList) {
        if (mutation.type === 'childList') {
          repoTitleComponent = document.querySelector('#repo-title-component')
          if (repoTitleComponent && !repoTitleComponent.querySelector('.repo-size-label')) {
            fetchRepoSize()
            observer.disconnect()
            break
          }
        }
      }
    })

    observer.observe(targetNode, config)
  }

  function init() {
    fetchRepoSize()
    observePageChanges()
  }

  // Register the menu command to set the GitHub token
  GM_registerMenuCommand('Set GitHub Token', createModal)

  // Execute on page load
  window.addEventListener('load', init)

  // Listen for GitHub's pjax:end event to handle updates during internal navigation
  document.addEventListener('pjax:end', init)
})()