// ==UserScript== // @name Jira - Toggle columns // @description Collapse Jira swimlane columns upon click // @namespace jiramod // @license MIT // @version 1.0 // @match https://*.atlassian.net/jira/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @downloadURL https://update.greasyfork.icu/scripts/435961/Jira%20-%20Toggle%20columns.user.js // @updateURL https://update.greasyfork.icu/scripts/435961/Jira%20-%20Toggle%20columns.meta.js // ==/UserScript== /* jshint esversion: 6 */ (function () { "use strict"; const collapsedWidth = 40; const toggled = {}; let board = location.pathname; let mainArea = "#ghx-content-main"; let clickArea = "#ghx-pool"; let header = ".ghx-column-headers .ghx-column"; let column = ".ghx-columns .ghx-column"; let issues = ".ghx-wrap-issue"; let overlay = ".ghx-zone-overlay-table .ghx-zone-overlay-column"; let ticketCount = ".ghx-qty"; let swimlaneHeader = ".ghx-swimlane-header"; let hasSwimlaneHeaders = false; // getComputedStyle(document.querySelector('.ghx-column')).padding let columnPadding = "10px"; let columnPaddingNarrow = columnPadding; let columnPaddingXtraNarrow = "5px"; function rafAsync() { return new Promise((resolve) => { requestAnimationFrame(resolve); // Faster than setTimeout }); } function checkElement(element) { if (document.querySelector(element) === null) { return rafAsync().then(() => checkElement(element)); } else { return Promise.resolve(true); } } checkElement(mainArea).then((element) => { startJiraOverrides(); }); checkElement(clickArea).then((element) => { startClick(); }); function startJiraOverrides() { // `GH` functions overwrite Jira's own logic with slight modifications // Jira: Ignore collapsed columns when detecting narrow widths GH.SwimlaneView.updateIssueLayoutAccording2Size = function (e, firstWidth) { var widths = e .map(function () { return AJS.$(this).width(); }) .get(), uncollapsedWidth = widths.find(function (el) { return el > collapsedWidth; }), i = uncollapsedWidth <= GH.SwimlaneView.NARROW_CARD_WIDTH, l = uncollapsedWidth <= GH.SwimlaneView.XTRA_NARROW_CARD_WIDTH; e.toggleClass("ghx-narrow-card", i), e.toggleClass("ghx-xtra-narrow-card", l); }; // Jira: Show simple ticket count in header if nonzero GH.tpl.rapid.swimlane.renderColumnCount = function (opt_data, opt_ignored) { return ( '
' + (opt_data.column.stats.visible > 0 ? soy.$$escapeHtml(opt_data.column.stats.visible) : "") + "
" ); }; // Jira: Add token icons for each issue, to be rendered on collapsed columns GH.tpl.rapid.swimlane.renderColumnsHeader = function (opt_data, opt_ignored) { var output = '
" + (!opt_data.isHorizontalScrollEnabled ? '
' : "") + "
"; return output; }; } function startClick() { function toggle(index) { console.log("Toggling column", index); if (toggled[index] === undefined) { GM_addStyle(` body.hidden-${index} ${column}:nth-of-type(${index}), body.hidden-${index} ${header}:nth-of-type(${index}), body.hidden-${index} ${overlay}:nth-of-type(${index}) { width: ${collapsedWidth}px !important; } body.hidden-${index} ${column}:nth-of-type(${index}) ${issues} { display: none; } body.hidden-${index} ${header}:nth-of-type(${index}) { overflow: visible !important; } body.hidden-${index} ${header}:nth-of-type(${index}) h2 { overflow: visible !important; transform: rotate(90deg); transform-origin: left; font-weight: normal; margin-left: calc((${collapsedWidth}px / 2) - ${columnPadding}); } body.hidden-${index} ${header}.ghx-narrow-card:nth-of-type(${index}) h2 { margin-left: calc((${collapsedWidth}px / 2) - ${columnPaddingNarrow}); } body.hidden-${index} ${header}.ghx-xtra-narrow-card:nth-of-type(${index}) h2 { margin-left: calc((${collapsedWidth}px / 2) - ${columnPaddingXtraNarrow}); } body.hidden-${index} ${header}:nth-of-type(${index}) h2::after { content: " " attr(data-tokens); white-space: pre; opacity: 0.2; } body.hidden-${index} ${header}:nth-of-type(${index}) ${ticketCount} { display: none; } `); if (hasSwimlaneHeaders) { // Hide column headers until hovered GM_addStyle(` body.hidden-${index} ${header}:nth-of-type(${index}) h2 { font-weight: 600; text-shadow: -1px -1px 0 #fff, -1px 1px 0 #fff, 1px -1px 0 #fff, 1px 1px 0 #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 12px #fff, 0 0 24px #fff, 0 0 24px #fff, 0 0 24px #fff, 0 0 24px #fff, 0 0 24px #fff, 0 0 24px #fff, 0 0 24px #fff, 0 0 24px #fff; z-index: 100; visibility: hidden; } body.hidden-${index} ${header}:nth-of-type(${index}):hover h2 { visibility: visible; } `); } } toggled[index] = !toggled[index]; if (toggled[index]) { document.body.classList.add(`hidden-${index}`); } else { document.body.classList.remove(`hidden-${index}`); } // Refresh in case it has been updated in another window globalToggled = JSON.parse(GM_getValue("globalToggled", "{}")); globalToggled[board] = toggled; GM_setValue("globalToggled", JSON.stringify(globalToggled)); // Jira: Redraw issues as wide or narrow or extra-narrow GH.SwimlaneView.handleResizeEvent(); } // Toggle columns on click document.querySelector(clickArea).addEventListener( "click", (e) => { let target = e.target; if (target.matches(column) || (target = target.closest(header))) { let index = [...target.parentElement.children].indexOf(target) + 1; toggle(index); } }, true ); // Hide collapsed column headers if swimlane headers are present if (document.querySelector(swimlaneHeader)) { hasSwimlaneHeaders = true; } // Collapse previously-toggled columns let globalToggled = JSON.parse(GM_getValue("globalToggled", "{}")); const loaded = globalToggled[board] === undefined ? {} : globalToggled[board]; console.log("Previously-toggled columns:", loaded); Object.entries(loaded).forEach(([index, collapsed]) => { if (collapsed) toggle(index); }); // Style cursor and column headers GM_addStyle(` ${header}, ${column} { cursor: pointer; } ${header} h2 { /* Shortened titles look nicer with some space */ margin-left: 8px; margin-right: 8px; } `); } })();