GitHub Repo Size Display

Display repository size on GitHub repo pages

От 16.08.2024. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==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)
})()