Beautify the Teambition.
// ==UserScript== // @name Teambition // @name:zh-CN Teambition // @description Beautify the Teambition. // @description:zh-CN 美化Teambition。 // @namespace https://github.com/HaleShaw // @version 1.0.3 // @author HaleShaw // @copyright 2022+, HaleShaw (https://github.com/HaleShaw) // @license AGPL-3.0-or-later // @homepage https://github.com/HaleShaw/TM-Teambition // @supportURL https://github.com/HaleShaw/TM-Teambition/issues // @contributionURL https://www.jianwudao.com/ // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAA21BMVEUAAAAyr/8bofcSm/dOuv4OnPUWnvZKufwRm/Ugo/cLmPRFtvtVvv0wq/krqfgGlfRPvP1As/sboPcLmPVVvv01rvolpvcFlfRKuv0Rm/ZTvv46sfsGlvROuv1EtfsMl/RUvv4/tf0xrPo7sfs2rvtLuv1AtPwQm/b///9Gt/3j9P9RvP4mpvkdovcaoPf6/f8LmPXD5/1QvP10x/xbvftWu/twxPpHtPksqfkrqfnm9f7T7f664/2S0/16y/2r3fxlwPrp9v7d8f6g2v6z4PyZ1vyCzPw9sPpQtvl1UhomAAAAInRSTlMACJQ/Ghr+/Pz+/vz39/f37Ozs7NfX19eurpWVlZKSkj8/xzBnxQAAAMZJREFUGNMtz0cWgkAQBNAeQMCcc86gAwoCEgWz9z+RPfP4iw61K0BEnPTr9f5EJMAV5u3SGZXa8wL/h+o+pw4xIYIsm6ZhmDhkVSAgtgzX8zLnd00sx2iJIBRpfH9EVuqF2osWBehRP9ZQmKXRhdIe1ALf/bwt7XBdff0gqEF1xVxOGDBV6G4YDBR+dGG8Zm6ng82PMSybO4SBy3ZzCWRW1nXdSZ5HXOUZ1pEGyjanDCRA0rRhH5HdmErAkcWoU6l0RgtW/w98aR8lvTImwwAAAABJRU5ErkJggg== // @match https://www.teambition.com/project/* // @match https://www.teambition.com/organization/* // @match https://apps.teambition.com/work-time-client/* // @compatible Chrome // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // ==/UserScript== // ==OpenUserJS== // @author HaleShaw // @collaborator HaleShaw // ==/OpenUserJS== (function () { 'use strict'; let data; const mainStyle = ` /* 任务面板 */ /* 看板模式下,将任务卡片的任务ID左移,鼠标悬浮时使“…”按钮不覆盖任务ID */ .task-card .task-content-set .task-card-footer>* { margin-right: 25px !important; } /* 隐藏工作流顶部右侧的“更多”按钮 */ div.kanban-single-lane-body-wrapper>div.kanban-single-list>div.kanban-single-list-contents>div>div>div:last-child { display: none !important; } /* -------------------------------------------------------------------------- */ /* 工作流配置页面 */ /* 将整个页面周围边距清除 */ .taskflow-config-contents { bottom: 0px !important; left: 0px !important; right: 0px !important; top: 60px !important; } /* 减小行高 */ .taskflow-config-table-block-item { height: 50px !important; } /* 减小列宽 */ .taskflow-config-table-status-column { width: 110px !important; } .status-label-with-menu { padding: 0 !important; width: 110px !important; } /* -------------------------------------------------------------------------- */ /* 迭代面板 */ .sprint-panel-component-title-wrapper { padding: 4px 16px !important; } .sprint-panel-component-sprint-content>.sprint-name { padding: 3px 0 !important; } .sprint-panel-item .sprint-control-option { height: 28px !important; } .sprint-panel-item .sprint-subtitle { margin-bottom: 0px !important; } `; const loadAllStyle = ` .loadAll { background-color: #f2fbff!important; border-color: transparent!important; color: #1b9aee!important; font-size: 14px; height: 28px; line-height: 26px; min-width: 52px; padding: 0 7px; margin-right: 15px; border-radius: 4px; border-width: 1px; box-shadow: none; transition: background-color .3s ease,color .3s ease,border-color .3s ease; } .loadAll:hover{ color: #ffffff !important; background-color: #1b9aee !important; border-color: #1b9aee !important; } `; const worktimeStyle = ` /* 头部标题 */ #root header h4 { display: none !important; } /* 头部日期选择器 */ ._18ivZlWaPJH2YVLnpZrb0N>header .LZNYW1Yb6acagP8pDYBcB .vkkNP00hyMhbgKHQS-CCT { margin-right: 8px !important; } ._18ivZlWaPJH2YVLnpZrb0N>header .LZNYW1Yb6acagP8pDYBcB ._2gIxRD0Aw3Wz4V58lFor-k { margin-left: 8px !important; } #root > div > div > header > div:first-child > h4 { padding: 0 5px !important; } #root > div > div > header > div:last-child { flex-grow: 0 !important; flex-basis: 240px !important; } /* 表头第一列 */ ._1a3bcSp1CtO4ejX9ugVzzO._2Q3J8umLBh0SslZCOuVtuw { width: 180px !important; } /* 表体第一列 */ ._1a3bcSp1CtO4ejX9ugVzzO { padding: 0 0px 0 28px !important; } .rt-table > .rt-tbody > .rt-tr-group > .rt-tr > .rt-td:first-child, .rt-table > .rt-thead > .rt-tr > .rt-th:first-child, .rt-table > .rt-tfoot > .rt-tr > .rt-td:first-child { flex: 180 0 auto !important; width: 180px !important; max-width: 180px !important; } .rt-table > .rt-tbody > .rt-tr-group > .rt-tr > .rt-td:not(:first-child), .rt-table > .rt-thead > .rt-tr > .rt-th:not(:first-child), .rt-table > .rt-tfoot > .rt-tr > .rt-td:not(:first-child) { flex: 124 0 auto !important; width: 124px !important; max-width: 124px !important; } /* 隐藏悬浮边框 */ ._1SmlEXYUElETMoqOAF1ym0:hover { border: none !important; } ._3AzO2C7wToS7pqSbOxyEXm { border: none !important; } /* 直接展示实际和计划工时 */ ._3sRT72VQQvRBhWYMmH0hD2 { display: none !important; } .JqBbgp5bIj-_fWcuSdn8X { display: block !important; } /* footer总计工时 */ .Z8fZCPB9x3aQBvMMYEzWI { padding: 0px 0px !important; } button.cusBtn { outline: none; padding: 2px 10px; border: none; border-radius: 4px; color: #595959; line-height: 26px; background-color: #e5e5e5; font-size: 14px; cursor: pointer; margin-left: 5px; } button.cusBtn:hover { color: #1b9aee; } button.cusBtn.selected { background-color: #888888; color: #ffffff; } select.cusSelect { font-size: 14px; border: 1px solid #e5e5e5; border-radius: 4px; color: #262626; transition: border 218ms; padding: 0px 8px; margin-left: 10px; height: 30px; } /* 计划时间过大 */ span.time.plan.greater { color: #ff2222; } /* 计划时间过小 */ span.time.plan.less { color: #f29900; } `; const settingHTML = ` <div id="settingContent"> <div id="settingTitle"> <span class="icon"></span><span class="title">Teambition - 工时应用设置</span> </div> <ol id="settingBody"> <li> <span class="setting title">工时应用URL</span> <div class="setting comment"> <a>a.在左侧边栏上,点击“更多”按钮 -> “全部应用” -> “工时”</a><span class="errMsg url"></span> <a>b.页面加载完成后,按F12进入开发者模式,找到第一个iframe的地址,即是工时应用的地址</a> <a class="code">document.querySelectorAll('iframe')[0].src</a> <button type="button" id="getUrl">一键获取</button> </div> <input type="text" class="setting text url" placeholder="https://appshell.teambition.com/api/v1/organization/..."></input> </li> <li> <span class="setting title">分组过滤按钮</span> <div class="setting comment"> <a>配置每个按钮过滤部分人员。请使用标准JSON格式,使用英文双引号。</a><span class="errMsg filter"></span> </div> <textarea type="text" class="setting text filter" placeholder="{ 'button name':['username','username'], 'button name':['username','username'] }"></textarea> </li> <li> <span class="setting title">移除人员</span> <div class="setting comment"> <a>可将不必要的人员从表格中移除。请使用标准JSON格式,使用英文双引号。</a><span class="errMsg remove"></span> </div> <input type="text" class="setting text remove" placeholder="['username','username']"></input> </li> </ol> <div id="settingFooter"> <button type="button" id="settingCancel" class="setting button">取消</button> <button type="button" id="settingSave" class="setting button">保存</button> </div> </div> `; const settingStyle = ` #settingPanel { display: none; justify-content: center; align-items: center; position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 200000000; overflow: auto; font-family: arial, sans-serif; min-height: 100%; font-size: 16px; transition: 0.5s; opacity: 1; user-select: none; -moz-user-select: none; padding-bottom: 80px; box-sizing: border-box; } #settingContent { display: flex; flex-wrap: wrap; padding: 20px; background-color: #f7f7f7; border-radius: 4px; position: absolute; width: 50%; transition: 0.5s; } #settingTitle { width: 100%; margin: 15px 0; } #settingTitle > span.icon { width: 20px; height: 20px; display: inline-block; background-image: url("https://tcs-ga.teambition.net/thumbnail/312ca08e98b657c56f74c8f5eae0b66dd8d2/w/20/h/20"); background-position: center center; background-repeat: no-repeat; background-size: 100%; border-radius: 4px; } #settingTitle > span.title { font-size: 18px; vertical-align: top; font-weight: 500; } #settingBody { padding: 20px 20px 20px 40px; background-color: #fff; border: 1px solid #fff; border-radius: 5px; width: 100%; list-style: decimal; } span.setting.title { font-size: 1.125rem; font-weight: 500; line-height: 2rem; display: block; } span.setting.title:not(:first-child) { margin-top: 8px; } .setting.comment > a { color: #666; display: inline-block; line-height: 24px; text-decoration: none; user-select: text; } .setting.comment > a.code { background: #fafafa; line-height: 150%; padding-right: 10px; padding-left: 10px; border-left: 2px solid #6ce26c; display: block !important; } .setting.text { width: 100%; margin-top: 5px; line-height: 24px; } .setting.text.url { width: calc(100% - 90px) !important; } textarea.setting.text { min-height: 126px; max-height: 200px; } #getUrl { position: absolute; right: 40px; padding: 4px 8px; margin-top: 5px; cursor: pointer; outline-style: none; border-radius: 3px; color: #fff; border: 1px solid #1b9aee; background-color: #1b9aee; } #settingFooter { text-align: center; margin: auto; margin-top: 15px; border-radius: 4px; } .setting.button { padding: 5px 10px; cursor: pointer; outline-style: none; border-radius: 3px; } #settingCancel { border: 1px solid #e7e7e7; background-color: #e7e7e7; } #settingCancel:hover { border: 1px solid #979797; background-color: #979797; } #settingSave { margin-left: 20px; color: #fff; border: 1px solid #1b9aee; background-color: #1b9aee; } #settingSave:hover, #getUrl:hover { border: 1px solid #0171c2; background-color: #0171c2; } span.errMsg { color: red; margin-left: 10px; } `; let members = []; const planTimeMaximum = 10; const planTimeMinimum = 7; // The number of times the worktime page was loaded. const loadCount = 5; main(); function main() { GM_addStyle(mainStyle); logInfo(GM_info.script.name, GM_info.script.version); data = GM_getValue('worktime'); GM_registerMenuCommand("设置", () => { new Setting(); }); if (window.location.href.startsWith('https://apps.teambition.com/work-time-client')) { beautifyWorktime(); } else { setTimeout(() => { hideAddingStatus(); listenDom(); loadAll(); addSideButton(); }, 4000); } } function beautifyWorktime() { GM_addStyle(worktimeStyle); let timeout = 0; let table; let interval = setInterval(() => { timeout += 1; table = document.querySelector('.ReactTable > .rt-table'); // Stop the circulator when the element is found or after 10 seconds(100*100 ms). if (timeout == 100 || table) { clearInterval(interval); loadAllWorktime(); choosePlanTime(); addFilterButton(); } }, 100); } /** * Listen the parent DOM. While the children is changing, and the top board is TaskBoard then hide the panel. */ function listenDom() { let parentObj = document.querySelector('div#teambition-web-content>div.project-app-view>div.project-app-inner'); if (!parentObj) { return; } let innerListener = new MutationObserver(() => { setTimeout(() => { hideAddingStatus() }, 4000); }); let listener = new MutationObserver((mutationRecords) => { top: for (let i = 0; i < mutationRecords.length; i++) { let addedNodes = mutationRecords[i].addedNodes; let removedNodes = mutationRecords[i].removedNodes; for (let j = 0; j < addedNodes.length; j++) { if (addedNodes[j].className == 'board-view') { // When you enter the task panel, start the task listener. setTimeout(() => { let innerTaskBoard = document.querySelector('div.board-view > div.board-flex-view > div.board-right-view'); if (!innerTaskBoard) { return; } hideAddingStatus(); innerListener.observe(innerTaskBoard, { childList: true }); }, 4000); break top; } } for (let j = 0; j < removedNodes.length; j++) { if (removedNodes[j].className == 'board-view') { // When you leave the task panel, stop the task listener. innerListener.disconnect(); break top; } } } }); listener.observe(parentObj, { childList: true }); listenTaskBoard(); } /** * While the content of the task board changing then hide the adding button. */ function listenTaskBoard() { let taskListener = new MutationObserver(() => { setTimeout(() => { hideAddingStatus() }, 3000); }); let taskBoard = document.querySelector('div.board-view > div.board-flex-view > div.board-right-view'); if (taskBoard) { hideAddingStatus(); taskListener.observe(taskBoard, { childList: true }); } } function hideAddingStatus() { let status = document.querySelector('div.kanban-single-lane-body-wrapper>div.kanban-single-list:last-child'); if (!status) { return; } let statusText = status.querySelector('div>div>button>span:last-child'); if (!statusText) { return; } if ('添加状态' == statusText.textContent) { status.style.display = 'none'; } } /** * Add the button of loading all the content. */ function loadAll() { GM_addStyle(loadAllStyle); let parent = $('nav.project-navigation.sub-nav.sub-navigator > div.row-flex.nav-container > div.nav-footer'); if (!parent || parent.length == 0) { return; } let loadAllButton = $('<button type="button" class="loadAll" title="表格视图下,加载所有数据">LoadAll</button>'); loadAllButton.click(function (e) { let test = getLoadButton(); load(test); }); loadAllButton.insertBefore(parent.children().eq(0)); } /** * Load content. * @param {Object} element The button of loading. */ function load(element) { if (element) { element.click(); setTimeout(() => { let test = getLoadButton(); load(test); // load(getLoadButton()); }, 1000); } } /** * Get the button of loading. * @returns The button of loading. */ function getLoadButton() { let button = null; $('div').each((index, element) => { if (element.textContent.startsWith('加载更多')) { button = element; return; } }) return button; } /** * Add the side button which used for opening the worktime page. */ function addSideButton() { let data = GM_getValue('worktime'); if (!data || !data?.url || data.url.trim() == '') { return; } let parent = $('#nav_bar-apps > div.nav_bar_detail-apps > ul').last(); let button = $(` <li class="nav_bar_detail-app"> <span class="next-badge nav_bar_detail-badge"> <span class="nav_bar_detail-app-icon" style="display: inline-block"> <span style=" width: 100%; height: 100%; display: inline-block; background-image: url('https://tcs-ga.teambition.net/thumbnail/312ca08e98b657c56f74c8f5eae0b66dd8d2/w/20/h/20'); background-position: center center; background-repeat: no-repeat; background-size: 100%; border-radius: 4px; " ></span> </span> <span></span> </span> <span class="nav_bar_detail-app-text">工时</span> </li> `); button.click(() => { window.open(data?.url, "_blank"); }); if (parent.length < 1) { return; } parent.append(button); } /** * Load all of the worktime list. */ function loadAllWorktime() { let table = document.querySelector('.ReactTable > .rt-table'); for (var i = 0; i < loadCount; i++) { (function (t) { setTimeout(function () { table.scroll(0, 10000); }, 1000 * t); })(i) } setTimeout(() => { table.scroll(0, 0); }, loadCount * 1000); setTimeout(() => { simplifyHeader(); removeWorktime(); cleanWord(); AppendMemberNumber(); addMemberSelect(); markAbnormalTime(); }, (loadCount + 1) * 1000); } /** * Simplify header. */ function simplifyHeader() { let spanList = document.querySelectorAll('._38MgU8RZnSct2M6Qs3ZR2i'); for (let i = 0; i < spanList.length; i++) { const text = spanList[i].textContent; spanList[i].textContent = text.replace('工时', ""); } } /** * Add a button for clean the unuseful word. */ function cleanWord() { let spans = document.querySelectorAll('._3RNDj8L2GanC2QT53PysSS > span'); for (let i = 0; i < spans.length; i++) { spans[i].innerHTML = spans[i].innerHTML.replace('合计 : ', ''); } let total = document.querySelector('.rt-tfoot > .rt-tr > .rt-td > div > div'); if (total) { total.innerHTML = total.innerHTML.replace('总计 : ', ''); } let values = document.querySelectorAll('.rt-td span.JqBbgp5bIj-_fWcuSdn8X'); for (let i = 0; i < values.length; i++) { const element = values[i]; let text = values[i].innerText; if (text.endsWith(' 小时')) { values[i].innerText = text.replace(' 小时', ''); } } document.querySelectorAll('span._3sRT72VQQvRBhWYMmH0hD2').forEach((item) => { item.remove(); }); } /** * Append the number of members at the 'All' button. */ function AppendMemberNumber() { let button = document.querySelector('.team.cusBtn.all'); let trList = document.querySelectorAll('.rt-table > .rt-tbody > .rt-tr-group'); if (!button || !trList) { return; } button.textContent += `(${trList.length})`; // Collect member list. for (let i = 0; i < trList.length; i++) { const nameElement = trList[i].querySelector('.rt-tr > .rt-td:first-child > div > div > div'); const username = nameElement.textContent; members.push(username); } members.sort((a, b) => { return a.localeCompare(b) }); } /** * The Plan Time is selected by default. */ function choosePlanTime() { let times = document.querySelectorAll('#root header h4 + div > div'); if (times.length == 2) { times[1].click(); } } /** * Add the buttons for filtering worktime list. */ function addFilterButton() { let parent = document.querySelector('#root header > div'); if (!parent) { return; } let teamMember = data?.filter; if (!teamMember) { return; } const teams = Object.keys(teamMember); for (let i = 0; i < teams.length; i++) { let button = document.createElement('button'); button.className = 'team cusBtn'; button.textContent = `${teams[i]}(${teamMember[teams[i]].length})`; button.title = teamMember[teams[i]].join(','); button.onclick = function () { changeButtonStyle(button.textContent); let trList = document.querySelectorAll('.rt-table > .rt-tbody > .rt-tr-group'); for (let j = 0; j < trList.length; j++) { const nameElement = trList[j].querySelector('.rt-tr > .rt-td:first-child > div > div > div'); const username = nameElement.textContent; if (teamMember[teams[i]].indexOf(username) != -1) { trList[j].style.display = 'flex'; } else { trList[j].style.display = 'none'; } } changeSelectStatus(); }; parent.append(button); } parent.append(createAllButton()); } /** * Create the button for displaying all the worktime list. */ function createAllButton() { let button = document.createElement('button'); button.className = 'team cusBtn all'; button.textContent = 'All'; button.onclick = function () { changeButtonStyle(button.textContent); let trList = document.querySelectorAll('.rt-table > .rt-tbody > .rt-tr-group'); for (let j = 0; j < trList.length; j++) { trList[j].style.display = 'flex'; } changeSelectStatus(); }; return button; } /** * Change the style of the button. * @param {String} button The name of the button. */ function changeButtonStyle(button) { let buttons = document.querySelectorAll('button.team.cusBtn'); for (let i = 0; i < buttons.length; i++) { if (buttons[i].textContent == button) { addClass(buttons[i], 'selected'); } else { removeClass(buttons[i], 'selected'); } } } /** * Set the first element be selected. */ function changeSelectStatus() { let select = document.querySelector('select.cusSelect'); if (select) { select.selectedIndex = 0; } } /** * Remove the worktime item in the table. */ function removeWorktime() { let removeMembers = data?.remove; if (!removeMembers) { return; } let trList = document.querySelectorAll('.rt-table > .rt-tbody > .rt-tr-group'); for (let i = 0; i < trList.length; i++) { const nameElement = trList[i].querySelector('.rt-tr > .rt-td:first-child > div > div > div'); const username = nameElement.textContent; if (removeMembers.indexOf(username) != -1) { trList[i].remove(); } } } /** * Add the select element for searching member. */ function addMemberSelect() { let parent = document.querySelector('#root header > div'); if (!parent || members.length == 0) { return; } let select = document.createElement('select'); select.className = 'cusSelect'; let hiddenOption = document.createElement('option'); hiddenOption.style.display = 'none'; select.append(hiddenOption); for (let i = 0; i < members.length; i++) { let option = document.createElement('option'); option.value = members[i]; option.textContent = members[i]; select.append(option); } select.onchange = function () { let selectedMember = select.options[select.selectedIndex].value; let trList = document.querySelectorAll('.rt-table > .rt-tbody > .rt-tr-group'); for (let j = 0; j < trList.length; j++) { const nameElement = trList[j].querySelector('.rt-tr > .rt-td:first-child > div > div > div'); const username = nameElement.textContent; if (username == selectedMember) { trList[j].style.display = 'flex'; } else { trList[j].style.display = 'none'; } } // Remove the selected style for all the fileter button. let buttons = document.querySelectorAll('button.team.cusBtn'); for (let i = 0; i < buttons.length; i++) { removeClass(buttons[i], 'selected'); } } parent.append(select); } /** * Color the abnormal times. */ function markAbnormalTime() { let spanList = document.querySelectorAll('.rt-tbody .rt-tr .rt-td:not(:first-child) span.JqBbgp5bIj-_fWcuSdn8X'); for (let i = 0; i < spanList.length; i++) { let timeArr = spanList[i].textContent.split('/'); const actualTime = new Number(timeArr[0].trim()).toFixed(0); const planTime = new Number(timeArr[1].trim()).toFixed(0); let innerHTML; if (planTime >= planTimeMaximum) { innerHTML = `<span>${actualTime}</span><span> / </span><span class="time plan greater">${planTime}</span>`; } else if (planTime <= planTimeMinimum) { innerHTML = `<span>${actualTime}</span><span> / </span><span class="time plan less">${planTime}</span>`; } else { innerHTML = `<span>${actualTime}</span><span> / </span><span class="time plan">${planTime}</span>`; } spanList[i].innerHTML = innerHTML; } } class Setting { constructor() { this.init() } init() { let self = this; setTimeout(() => { if (!document.getElementById('settingPanel')) { self.addElement(); self.addListener(); GM_addStyle(settingStyle); } self.loadConfiguration(); self.show(); }, 300); } addElement() { let settingPanel = document.createElement('div'); settingPanel.setAttribute('id', 'settingPanel'); settingPanel.innerHTML = settingHTML; document.body.append(settingPanel); } addListener() { let self = this; let panel = document.getElementById('settingPanel'); document.getElementById('getUrl').onclick = function () { let iframes = document.querySelectorAll('iframe'); if (iframes.length > 0) { const url = iframes[0].src; if (url.startsWith('https://appshell.teambition.com/api/v1/organization/')) { document.querySelector('input.setting.text.url').value = url; document.querySelector('span.errMsg.url').textContent = ''; } else { document.querySelector('span.errMsg.url').textContent = '未获取到,请先打开工时应用。'; } } else { document.querySelector('span.errMsg.url').textContent = '未检测到iframe,请先打开工时应用。'; } } document.getElementById('settingCancel').onclick = function () { panel.style.display = 'none'; } document.getElementById('settingSave').onclick = function () { if (self.saveConfiguration()) { panel.style.display = 'none'; location.reload(); } } document.onkeyup = function (e) { var theEvent = e || window.event; var code = theEvent.keyCode || theEvent.which || theEvent.charCode; // Escape if (code == 27 && panel) { panel.style.display = 'none'; } }; } loadConfiguration() { let data = GM_getValue('worktime'); if (!data) { return; } const urlValue = data?.url; if (urlValue && urlValue.trim() != '') { document.querySelector('.setting.text.url').value = urlValue; } const filterValue = data?.filter; if (filterValue) { document.querySelector('.setting.text.filter').value = JSON.stringify(filterValue); } const removeValue = data?.remove; if (removeValue) { document.querySelector('.setting.text.remove').value = JSON.stringify(removeValue); } document.querySelector('span.errMsg.url').textContent = ''; document.querySelector('span.errMsg.filter').textContent = ''; document.querySelector('span.errMsg.remove').textContent = ''; } saveConfiguration() { let urlEle = document.querySelector('.setting.text.url'); let filterEle = document.querySelector('.setting.text.filter'); let filterValue; try { filterValue = JSON.parse(filterEle.value); } catch (error) { console.error(error); document.querySelector('span.errMsg.filter').textContent = 'JSON格式不正确,请修改后重试!'; return false; } document.querySelector('span.errMsg.filter').textContent = ''; let removeEle = document.querySelector('.setting.text.remove'); let removeValue; try { removeValue = JSON.parse(removeEle.value); } catch (error) { console.error(error); document.querySelector('span.errMsg.remove').textContent = '数组格式不正确,请修改后重试!'; return false; } document.querySelector('span.errMsg.remove').textContent = ''; const data = { 'url': urlEle.value, 'filter': filterValue, 'remove': removeValue } GM_setValue('worktime', data); return true; } show() { document.getElementById('settingPanel').style.display = 'flex'; } } /** * Add class name for the element. * @param {Object} element The DOM elment object. * @param {String} value the class name of the element. */ function addClass(element, value) { if (!element.className) { element.className = value; } else if (element.className.indexOf(value) == -1) { let newClassName = element.className; newClassName += " "; newClassName += value; element.className = newClassName; } } /** * Remove class name for the element. * @param {Object} element The DOM elment object. * @param {String} value the class name of the element. */ function removeClass(element, value) { if (element.className) { let newClassName = element.className; newClassName = newClassName.replace(value, ''); newClassName = newClassName.trim(); element.className = newClassName; } } /** * Log the title and version at the front of the console. * @param {String} title title. * @param {String} version script version. */ function logInfo(title, version) { console.clear(); const titleStyle = 'color:white;background-color:#606060'; const versionStyle = 'color:white;background-color:#1475b2'; const logTitle = ' ' + title + ' '; const logVersion = ' ' + version + ' '; console.log('%c' + logTitle + '%c' + logVersion, titleStyle, versionStyle); } })();