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