// ==UserScript== // @name ZenTao // @namespace https://iin.ink // @version 2.1 // @description ZenTao style and function enhancement // @author happy share org // @include /^https:\/\/.*zentao.*$/ // @grant GM_addStyle // @grant GM_setClipboard // ==/UserScript== (function () { 'use strict' GM_addStyle('td.c-name, td.c-title {white-space: normal;}') GM_addStyle('.main-table tbody>tr:nth-child(odd) { background: #fff !important; }') GM_addStyle('.main-table tbody>tr.table-children { background: #90939442 !important; }') GM_addStyle('.main-table .c-actions { width: 300px !important; text-align: left; }') GM_addStyle('.datatable-head-span.datatable-span.fixed-right,.datatable-rows-span.datatable-span.fixed-right { width: 300px !important; text-align: left; }') GM_addStyle('.main-table .c-actions-5 { width: 300px !important; text-align: left; }') GM_addStyle('.chosen-container .chosen-drop { right: 0; }') GM_addStyle('#main .container { max-width: unset !important; }') GM_addStyle('.table-datatable { min-width: unset !important; }') GM_addStyle('.board-item > .title { max-height: unset !important; -webkit-line-clamp: unset !important; -webkit-box-orient: unset !important; font-size: 15px !important; }') GM_addStyle('#kanban .group-title { line-height: 20px !important; font-size: 15px !important; }') GM_addStyle('.histories-custom-filter-btn { margin-right: 8px }') // 根据需要修改分支前缀 const _window = window const urlDomain = location.origin let cachedPrefix = _window.localStorage.getItem('_customFilter_projectPrefix') if (!cachedPrefix) { cachedPrefix = _window.prompt('请补全项目代号,之后可以通过 localStorage _customFilter_projectPrefix 来修改。', 'XXX') _window.localStorage.setItem('_customFilter_projectPrefix', cachedPrefix || 'XXX') } const projectPrefix = cachedPrefix || 'XXX' function enhanceTask (document) { const target = $(document.querySelectorAll('.main-table td.c-actions')) if (target.find('span:contains("copy:")').length > 0) return target.each(function () { const $el = $(this).parent() const taskId = $el.attr('data-id') || $el.find('.cell-id').find('a').text() const $text = $('copy:') $text.appendTo($el.find('.c-actions')) const $copyId = $(document.createElement('a')) $copyId.html(' 分支') $copyId.on('click', function () { GM_setClipboard(`feature/${projectPrefix}-${taskId}`, { type: 'text', mimetype: 'text/plain' }) }) $copyId.appendTo($el.find('.c-actions')) // 复制标题 const $copyTitle = $(document.createElement('a')) $copyTitle.html(' 标题') $copyTitle.on('click', function () { let title = window.location.search.includes('f=bug') ? $($el.children()[3]).attr('title') : $(document).find(`tr[data-id=${taskId}]`).find('.c-name').attr('title') GM_setClipboard(`${projectPrefix}-${taskId} ${title}`, { type: 'text', mimetype: 'text/plain' }) }) $copyTitle.appendTo($el.find('.c-actions')) // 复制链接 const $copyLink = $(document.createElement('a')) $copyLink.html(' 链接') $copyLink.on('click', function () { GM_setClipboard(`${urlDomain}/index.php?m=task&f=view&taskID=${taskId}`, { type: 'text', mimetype: 'text/plain' }) }) $copyLink.appendTo($el.find('.c-actions')) }) } function enhanceKanBan (document) { const target = $(document.querySelectorAll('.board-story')) // 已经添加过了 if (target.find('a:contains("复制分支")').length > 0) return target.each(function () { const $el = $(this) const $ul = $el.find('ul') const storyId = $el.attr('data-id') const $copyIdLi = $(document.createElement('li')) $copyIdLi.html('复制分支') $copyIdLi.on('click', function () { GM_setClipboard(`feature/${projectPrefix}-${storyId}`, { type: 'text', mimetype: 'text/plain' }) }) $copyIdLi.appendTo($ul) const $copyTitle = $(document.createElement('li')) $copyTitle.html('复制标题') $copyTitle.on('click', function () { const title = $el.find('.group-title').attr('title') GM_setClipboard(`${projectPrefix}-${storyId} ${title}`, { type: 'text', mimetype: 'text/plain' }) }) $copyTitle.appendTo($ul) const $copyLink = $(document.createElement('li')) $copyLink.html('复制链接') $copyLink.on('click', function () { const link = `${urlDomain}/index.php?m=story&f=view&storyID=${storyId}` GM_setClipboard(link, { type: 'text', mimetype: 'text/plain' }) }) $copyLink.appendTo($ul) // hover 增强 const $dropdown = $el.find('li.dropdown') $dropdown.on('mouseover', function () { $dropdown.addClass('open') $ul.css('margin-top', '-30px') }).on('mouseleave', function () { $dropdown.removeClass('open') }) }) } function enhanceDialog (mutationsList) { mutationsList.forEach(item => { if (item.addedNodes.length > 0) { const firstChild = $(item.addedNodes[0]) if (firstChild.attr('id') === 'iframe-triggerModal') { // 任务详情弹窗 firstChild.off('load').on('load', function () { const doc = firstChild[0].contentWindow.document enhanceHistoryList(doc) const toolbar = $(doc.querySelector('.main-actions > .btn-toolbar')) // 复制分支 const $copyId = $(document.createElement('a')) $copyId.addClass('btn btn-link showinonlybody') $copyId.html(' 复制分支') const taskId = $(doc.querySelector('.page-title > span.label-id')).text() $copyId.on('click', function () { GM_setClipboard(`feature/${projectPrefix}-${taskId}`, { type: 'text', mimetype: 'text/plain' }) }) $copyId.appendTo(toolbar) // 复制标题 const $copyTitle = $(document.createElement('a')) $copyTitle.addClass('btn btn-link showinonlybody') $copyTitle.html(' 复制标题') $copyTitle.on('click', function () { const title = $(doc.querySelector('.page-title > span.text')).attr('title') GM_setClipboard(`${projectPrefix}-${taskId} ${title}`, { type: 'text', mimetype: 'text/plain' }) }) $copyTitle.appendTo(toolbar) // 复制链接 const $copyLink = $(document.createElement('a')) $copyLink.addClass('btn btn-link showinonlybody') $copyLink.html(' 复制链接') $copyLink.on('click', function () { GM_setClipboard(`${urlDomain}/index.php?m=task&f=view&taskID=${taskId}`, { type: 'text', mimetype: 'text/plain' }) }) $copyLink.appendTo(toolbar) }) } } }) } function hiddenBoardItemWithPrimaryBtn (doc) { const $btn = $(doc).find('.btn.custom-filter-btn.btn-primary') const checkedName = $btn.text().trim() const allBoardList = $(doc.querySelectorAll('.board-item')) allBoardList.each(function () { const $item = $(this) if (checkedName === ALL_TEXT || !checkedName) { $item.css('display', 'block') } else { const name = $($item.find('.task-assignedTo,.bug-assignedTo').children()[1]).text().trim() if (name !== checkedName) { $item.css('display', 'none') } else { $item.css('display', 'block') } } }) // 隐藏空行 $(doc.querySelectorAll('tr[data-id]')).each(function () { const $tr = $(this) let hasTask = false $tr.children().find('.board-item').each(function () { if ($(this).css('display') === 'block') { hasTask = true } }) if (!hasTask && checkedName !== ALL_TEXT && checkedName) { $tr.css('display', 'none') } else { $tr.css('display', 'table-row') } }) } const ALL_TEXT = '全部' function enhanceRoleFilter (doc) { if (doc.querySelectorAll('.custom-filter-btn').length) { hiddenBoardItemWithPrimaryBtn(doc) return } const btnList = [] $(doc.querySelectorAll('.task-assignedTo,.bug-assignedTo')).each(function () { const name = $(this).text().trim() if (!btnList.includes(name)) btnList.push(name) }) btnList.sort() btnList.unshift(ALL_TEXT) btnList.forEach(i => { const $mainMenu = $(doc.querySelector('#mainMenu')) const $btn = $(doc.createElement('a')) $btn.addClass('btn custom-filter-btn') $btn.css('margin-right', '10px') $btn.html(i) $btn.on('click', function () { let checkedName const isChecked = $btn.hasClass('btn-primary') if (!isChecked) { $btn.addClass('btn-primary').siblings().removeClass('btn-primary') checkedName = $btn.text().trim() } else { $btn.removeClass('btn-primary') checkedName = '' } _window.localStorage.setItem('_customerFilter_name', checkedName) hiddenBoardItemWithPrimaryBtn(doc) }) $btn.appendTo($mainMenu) }) const checkedName = _window.localStorage.getItem('_customerFilter_name') if (checkedName) { if (!btnList.includes(checkedName)) { _window.localStorage.setItem('_customerFilter_name', '') return } $(doc).find('.btn.custom-filter-btn').each((index, item) => { const $item = $(item) if ($item.text().trim() === checkedName) { $item.click() } }) } } // 历史记录只展示备注 function enhanceHistoryList (doc) { if (doc.querySelectorAll('.histories-custom-filter-btn').length) return const fn = function (type) { $(doc.querySelectorAll('.histories-list li')).each(function () { const $this = $(this) if (type === 'hide' && $this.text().indexOf('备注') === -1) { $this.hide() } else { $this.show() } }) } const $titleBox = $(doc.querySelector('.histories .detail-title')) const $hideBtn = $(doc.createElement('a')) $hideBtn.addClass('btn btn-link pull-right histories-custom-filter-btn') $hideBtn.html('只看备注') $hideBtn.on('click', function () { if ($hideBtn.html() === '只看备注') { fn('hide') $hideBtn.html('查看全部') } else { fn('show') $hideBtn.html('只看备注') } }) $hideBtn.appendTo($titleBox) } const doc = document enhanceTask(doc) enhanceKanBan(doc) enhanceRoleFilter(doc) enhanceHistoryList(doc) const observer = new MutationObserver((mutationsList) => { enhanceTask(doc) enhanceKanBan(doc) enhanceDialog(mutationsList) enhanceRoleFilter(doc) enhanceHistoryList(doc) }) observer.observe(doc.body, { childList: true, subtree: true }) })()