GitLab Extension

Allows to fold any board in GitLab boards, shows estimate and last modified in issue card

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name         GitLab Extension
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Allows to fold any board in GitLab boards, shows estimate and last modified in issue card
// @author       Himalay
// @include		 https://gitlab.*
// ==/UserScript==

// estimate and modified time in card

// Board fold
let foldableGitLabBoardsIntervalCount = 0
const foldableGitLabBoardsInterval = setInterval(() => {
  const boards = [...document.querySelectorAll('.board.is-draggable')]

  if (foldableGitLabBoardsIntervalCount > 100)
    clearInterval(foldableGitLabBoardsInterval)
  if (boards.length) {
    clearInterval(foldableGitLabBoardsInterval)

    document.body.appendChild(
      Object.assign(document.createElement('style'), {
        textContent: `.board.is-collapsed .board-title>span {
                      width: auto;
                      margin-top: 24px;
                      }`,
      }),
    )

    boards.forEach((board) => {
      const boardTitle = board.querySelector('.board-title')
      const toggleIcon = Object.assign(document.createElement('i'), {
        classList: 'fa fa-fw board-title-expandable-toggle fa-caret-down',
        style: 'cursor: pointer',
      })

      toggleIcon.addEventListener('click', (e) => {
        board.classList.toggle('is-collapsed')
        e.target.classList.toggle('fa-caret-down')
        e.target.classList.toggle('fa-caret-right')
      })

      boardTitle.prepend(toggleIcon)
    })
  }

  foldableGitLabBoardsIntervalCount++
}, 100)

var TimeAgo = (function() {
  var self = {}
  // Public Methods
  self.locales = {
    prefix: `It's been`,
    sufix: '',

    seconds: 'less than a minute.',
    minute: 'about a minute.',
    minutes: '%d minutes.',
    hour: 'about an hour.',
    hours: 'about %d hours.',
    day: 'a day.',
    days: '%d days.',
    month: 'about a month.',
    months: '%d months.',
    year: 'about a year.',
    years: '%d years.',
  }

  self.inWords = function(timeAgo) {
    var seconds = Math.floor((new Date() - parseInt(timeAgo)) / 1000),
      separator = this.locales.separator || ' ',
      words = this.locales.prefix + separator,
      interval = 0,
      intervals = {
        year: seconds / 31536000,
        month: seconds / 2592000,
        day: seconds / 86400,
        hour: seconds / 3600,
        minute: seconds / 60,
      }

    var distance = this.locales.seconds

    for (var key in intervals) {
      interval = Math.floor(intervals[key])

      if (interval > 1) {
        distance = this.locales[key + 's']
        break
      } else if (interval === 1) {
        distance = this.locales[key]
        break
      }
    }

    distance = distance.replace(/%d/i, interval)
    words += distance + separator + this.locales.sufix

    return words.trim()
  }

  return self
})()

const shouldFetch = document.querySelector('.board-card,.issue')
const fetchThemAll = async (url) => {
  let nextPage = 1
  let data = []
  while (true) {
    const res = await fetch(url.replace('{{page}}', nextPage), {
      method: 'GET',
      credentials: 'include',
      headers: {
        accept: 'application/json, text/plain, */*',
        'x-requested-with': 'XMLHttpRequest',
      },
      mode: 'cors',
    })
    data.push(...(await res.json()))
    const previousPage = nextPage
    nextPage = res.headers.get('x-next-page')
    console.log({ previousPage, nextPage })
    if (!nextPage || nextPage === previousPage) break
  }
  return data
}

const isLessThanAgo = (hour = 1, date) => date > Date.now() - hour * 3600000
const setLabels = () =>
  [...document.querySelectorAll('.board-card,.issue')].forEach((card) => {
    const { issueId, id } = card.dataset
    const onlyCard = id
    const issue = issues[issueId || id]
    if (issue) {
      const {
        assignee,
        state,
        updated_at,
        time_stats: { time_estimate, total_time_spent },
      } = issue
      const isOpen = state === 'opened'
      const updatedDate = new Date(updated_at)
      const lastUpdate = TimeAgo.inWords(updatedDate.getTime())
      let emoji = isLessThanAgo(4, updatedDate)
        ? '👍'
        : isLessThanAgo(24, updatedDate)
        ? '👎'
        : '🙏'
      emoji = assignee && isOpen ? emoji : ''
      const cardStyle = `
      height: 1.5em;
      width: 1em;
      padding: 1px;
      border-radius: 3px;
      text-align: center;
      font-size: small;
      margin-left: 0.5em;
      background: #5cb85b;
      color: white;
      position: absolute;
      top: 0.5em;
      right: 0.5em;
      ${total_time_spent ? 'text-decoration: line-through;' : ''}
    `
      const sp = time_estimate
        ? `<span style="${cardStyle}">${time_estimate / 60 / 60}</span>`
        : ''
      const assignie = card.querySelector('.board-card-assignee,.controls')
      const pointAndTime = card.querySelector('.point-and-time')
      const content = onlyCard ? sp : emoji + lastUpdate + sp
      if (pointAndTime) {
        pointAndTime.innerHTML = content
      } else {
        let assignieHtml = assignie.innerHTML
        assignieHtml += `<span class="point-and-time" style="margin-left: 0.5em">${content}</span>`
        assignie.innerHTML = assignieHtml
      }
    }
  })

const cachedIssues = localStorage.getItem('issues')
let issues = JSON.parse(cachedIssues || '{}')
setLabels()

if (shouldFetch || !cachedIssues) {
  ;(async function iife() {
    issues = (await fetchThemAll(
      'https://gitlab.innovatetech.io/api/v4/groups/ap/issues?page={{page}}&per_page=100',
    )).reduce((acc, { id, ...issue }) => {
      acc[id] = issue
      return acc
    }, {})
    localStorage.setItem('issues', JSON.stringify(issues))
    setLabels()
  })()
}