Last active
September 8, 2024 21:13
-
-
Save inad9300/90be33471763812da3769e06d0a07b1c to your computer and use it in GitHub Desktop.
GitLab board enhancements via "User JavaScript and CSS" Chrome extension (https://gitlab.com/*/boards)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Icons (source: https://css.gg) | |
.gg-notes { | |
box-sizing: border-box; | |
position: relative; | |
display: block; | |
width: 20px; | |
height: 22px; | |
border: 2px solid; | |
border-radius: 3px; | |
} | |
.gg-notes::after, | |
.gg-notes::before { | |
content: ''; | |
display: block; | |
box-sizing: border-box; | |
position: absolute; | |
border-radius: 3px; | |
height: 2px; | |
background: currentColor; | |
left: 2px; | |
} | |
.gg-notes::before { | |
box-shadow: 0 4px 0, 0 8px 0; | |
width: 12px; | |
top: 2px; | |
} | |
.gg-notes::after { | |
width: 6px; | |
top: 14px; | |
} | |
.gg-list-tree { | |
box-sizing: border-box; | |
position: relative; | |
display: block; | |
width: 22px; | |
height: 22px; | |
background: | |
linear-gradient(to left, currentcolor 8px, transparent 0) no-repeat left top/8px 8px, | |
linear-gradient(to left, currentcolor 8px, transparent 0) no-repeat center 3px/8px 2px, | |
linear-gradient(to left, currentcolor 8px, transparent 0) no-repeat 10px 17px/6px 2px, | |
linear-gradient(to left, currentcolor 8px, transparent 0) no-repeat 10px 3px/2px 16px; | |
} | |
.gg-list-tree::after, | |
.gg-list-tree::before { | |
content: ''; | |
display: block; | |
box-sizing: border-box; | |
position: absolute; | |
width: 8px; | |
height: 8px; | |
border: 2px solid; | |
right: 0; | |
} | |
.gg-list-tree::after { | |
bottom: 0; | |
} | |
.gg-comment { | |
box-sizing: border-box; | |
position: relative; | |
display: block; | |
width: 20px; | |
height: 16px; | |
border: 2px solid; | |
border-bottom: 0; | |
box-shadow: | |
-6px 8px 0 -6px, | |
6px 8px 0 -6px; | |
} | |
.gg-comment::after, | |
.gg-comment::before { | |
content: ""; | |
display: block; | |
box-sizing: border-box; | |
position: absolute; | |
width: 8px; | |
} | |
.gg-comment::before { | |
border: 2px solid; | |
border-top-color: transparent; | |
border-bottom-left-radius: 20px; | |
right: 4px; | |
bottom: -6px; | |
height: 6px; | |
} | |
.gg-comment::after { | |
height: 2px; | |
background: currentColor; | |
box-shadow: 0 4px 0 0; | |
left: 4px; | |
top: 4px; | |
} | |
// Custom styles | |
.board-card-number-container { | |
align-items: center !important; | |
} | |
.issue-card-badge { | |
display: flex; | |
align-items: center; | |
margin-right: 2px; | |
font-size: smaller; | |
color: var(--gl-text-secondary, #737278); | |
} | |
.issue-card-icon { | |
transform: scale(0.6364); | |
color: var(--gl-text-secondary, #737278); | |
} | |
.issue-side-block { | |
white-space: pre-wrap; | |
font-size: smaller; | |
font-style: italic; | |
color: gray; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
const h = tag => document.createElement(tag) | |
const $ = selector => document.querySelector(selector) | |
const $$ = selector => Array.from(document.querySelectorAll(selector)) | |
const waitFor = selector => new Promise(resolve => { | |
const timerId = setInterval(() => { | |
const node = $(selector) | |
if (node) { | |
clearInterval(timerId) | |
resolve(node) | |
} | |
}, 100) | |
}) | |
const graphql = query => { | |
return fetch('/api/graphql', { | |
method: 'POST', | |
headers: { | |
Accept: 'application/json', | |
'Content-Type': 'application/json', | |
'X-CSRF-Token': $('meta[name=csrf-token]').content | |
}, | |
body: JSON.stringify({ query }) | |
}) | |
.then(res => res.json()) | |
} | |
const descriptionBadge = desc => { | |
const root = h('span') | |
root.className = 'issue-card-badge issue-card-icon gg-notes' | |
root.style.order = '1' | |
root.title = desc | |
return root | |
} | |
const descriptionBlock = desc => { | |
const root = h('div') | |
root.className = 'issue-side-block' | |
root.textContent = desc | |
return root | |
} | |
const commentsBadge = count => { | |
const icon = h('span') | |
icon.className = 'issue-card-icon gg-comment' | |
const root = h('span') | |
root.className = 'issue-card-badge' | |
root.style.order = '2' | |
root.style.marginTop = '-3px' | |
root.append(icon, count) | |
return root | |
} | |
const issuesToMarkdown = issues => | |
issues | |
.map(iss => `- [${iss.state === 'OPEN' ? ' ' : 'x'}] ${iss.name}`) | |
.join('\n') | |
const childIssuesBadge = issues => { | |
const doneIssues = issues.filter(iss => iss.state !== 'OPEN') | |
const count = `${doneIssues.length}/${issues.length}` | |
const icon = h('span') | |
icon.className = 'issue-card-icon gg-list-tree' | |
const root = h('span') | |
root.className = 'issue-card-badge' | |
root.style.order = '3' | |
root.append(icon, count) | |
root.title = issuesToMarkdown(issues) | |
return root | |
} | |
const childIssuesBlock = issues => { | |
const root = h('div') | |
root.className = 'issue-side-block' | |
root.textContent = issuesToMarkdown(issues) | |
return root | |
} | |
let interval = 100 | |
let times = 0 | |
const enhanceCards = () => { | |
for (const card of $$('.board-card:not([data-visited])')) { | |
card.dataset.visited = '' | |
const metadataContainer = card.querySelector('.board-card-number-container') | |
const issue = { id: card.dataset.itemId } | |
graphql(`query { issue(id: "${issue.id}") { description userNotesCount } }`) | |
.then(({ data }) => { | |
Object.assign(issue, data.issue) | |
if (issue.description) | |
metadataContainer.append(descriptionBadge(issue.description)) | |
if (issue.userNotesCount) | |
metadataContainer.append(commentsBadge(issue.userNotesCount)) | |
}) | |
graphql(`query { | |
workItem(id: "${issue.id.replace('/Issue/', '/WorkItem/')}") { | |
widgets { | |
... on WorkItemWidgetHierarchy { | |
type | |
children { | |
nodes { name state } | |
} | |
} | |
} | |
} | |
}`) | |
.then(({ data }) => { | |
issue.children = data | |
.workItem | |
.widgets | |
.find(w => w.type === 'HIERARCHY') | |
.children | |
.nodes | |
if (issue.children.length > 0) | |
metadataContainer.append(childIssuesBadge(issue.children)) | |
}) | |
card.addEventListener('click', () => { | |
waitFor('.boards-sidebar').then(sidebar => { | |
sidebar.querySelector('.description-block')?.remove() | |
sidebar.querySelector('.child-issues-block')?.remove() | |
const sidebarTitle = sidebar.querySelector('[data-testid=sidebar-title]') | |
if (issue.children.length > 0) | |
sidebarTitle.after(childIssuesBlock(issue.children)) | |
if (issue.description) | |
sidebarTitle.after(descriptionBlock(issue.description)) | |
}) | |
}) | |
} | |
if (times++ > 10) interval = 1000 | |
setTimeout(enhanceCards, interval) | |
} | |
enhanceCards() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment