Show all contributions by year in the GitHub profile

Show all contributions by year since the user was created in the GitHub profile page

// ==UserScript==
// @name              Show all contributions by year in the GitHub profile
// @name:zh-CN        在 GitHub profile 页面以年份展示用户所有的贡献
// @namespace         https://github.com/kang8
// @version           0.0.4
// @description       Show all contributions by year since the user was created in the GitHub profile page
// @description:zh-CN 在 GitHub profile 页面以年份展示自用户创建以来所有的 contributions
// @author            kang8
// @match             https://github.com/*
// @grant             none
// @run-at            document-end
// @license           MIT
// @homepage          https://github.com/kang8/.dotfiles/tree/master/tampermonkey-scripts#show-all-contributions-by-year-in-github-profile
// @icon              https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
// ==/UserScript==

;(async function () {
  'use strict'

  // Wait for 1.5 seconds for the DOM to load.
  await new Promise(resolve => setTimeout(resolve, 1500));

  const pathArr = location.pathname.split('/')

  /**
   * match:
   * - https://github.com/kang8
   * - https://github.com/kang8/
   */
  if (pathArr.length === 2 || (pathArr.length === 3 && pathArr[2] === '') && pathArr[1] !== '') {
    const userId = pathArr[1]

    const contributionsLastYear = document.querySelector('div.js-yearly-contributions > div:nth-child(1)')

    const contributionsAllYearsDiv = document.createElement('div')
    contributionsAllYearsDiv.className = 'position-relative border'
    contributionsAllYearsDiv.id = 'contributions-all-years'
    contributionsLastYear.after(contributionsAllYearsDiv)

    const details = document.createElement('details')
    details.className = 'py-3'
    details.innerHTML = '<summary class="px-3">All contributions by year</summary>'
    contributionsAllYearsDiv.append(details)

    const totalYearsCreated = document.querySelectorAll('div.js-profile-timeline-year-list > ul > li').length
    const thisYear = new Date().getFullYear()

    for (let index = 0; index < totalYearsCreated; index++) {
      const currentYear = thisYear - index

      fetch(`/users/${userId}/contributions?from=${currentYear}-1-01&to=${currentYear}-12-31`, {
        method: 'GET',
      })
        .then((response) => response.text())
        .then((data) => {
          const contributionsDiv = document.createRange().createContextualFragment(data)
          const contributionInfo = contributionsDiv.querySelector('div > div:nth-child(1) > h2').textContent
          const svgCalendarGraph = contributionsDiv.querySelector('div > div:nth-child(1) > div > div')

          svgCalendarGraph.querySelector('div > div.float-left').innerHTML = contributionInfo

          if (currentYear !== thisYear) {
            svgCalendarGraph.classList.add('border-top')
          }

          insertAndOrderToDetails(svgCalendarGraph, details, currentYear)
        })
    }
  }
})()

/**
 * @param {Element} graph
 * @param {HTMLDetailsElement} details
 * @param {number} currentYear
 */
function insertAndOrderToDetails(graph, details, currentYear) {
  const contributionsCalendarAllYear = Array.from(details.querySelectorAll('div.js-calendar-graph'))

  if (contributionsCalendarAllYear.length === 0) {
    details.append(graph)
  } else if (contributionsCalendarAllYear.length === 1) {
    const firstCalendarYear = getYearByContributionsCalendar(contributionsCalendarAllYear[0])
    if (currentYear > firstCalendarYear) {
      contributionsCalendarAllYear[0].before(graph)
    } else {
      contributionsCalendarAllYear[0].after(graph)
    }
  } else {
    for (let index = 0; index < contributionsCalendarAllYear.length - 1; index++) {
      const currentYearInCalendar = getYearByContributionsCalendar(contributionsCalendarAllYear[index])
      const nextYearInCalendar = getYearByContributionsCalendar(contributionsCalendarAllYear[index + 1])

      if (currentYear > currentYearInCalendar) {
        contributionsCalendarAllYear[index].before(graph)
        break
      } else if (currentYear > nextYearInCalendar) {
        contributionsCalendarAllYear[index].after(graph)
        break
      } else if (index === contributionsCalendarAllYear.length - 2 && currentYear < nextYearInCalendar) {
        contributionsCalendarAllYear[contributionsCalendarAllYear.length - 1].after(graph)
      }
    }
  }
}

/**
 * @param {number|Element} childNode
 * @returns {number}
 */
function getYearByContributionsCalendar(childNode) {
  return childNode.querySelector('div.float-left').textContent.replace(/\s/g, '').slice(-4)
}