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