// ==UserScript==
// @name Use Trello AS Playing Game
// @name:zh-CN 将Trello改造为玩游戏做任务
//
// @description You can set the level of the difficulty for every job, and when you complete it, you can get the proper reward.
// @description:zh-CN 可以在Trello上设定任务难度的级别,并且在完成任务后,发放对应级别的奖励值。
//
// @namespace http://tampermonkey.net/
// @version 0.2
// @match https://trello.com/b/*
// @match https://trello.com/c/*
// @author oraant
// @grant none
// ==/UserScript==
// 更新:
// 修复了Trello更新后,无法正常使用的问题。
// 增加了自动触发功能,无需在看板页面等待,现在可以在卡片页面等待了。
window.onload = function(){ // 必须这么搞,否则选择器获取不到东西。decument.ready也不行!
// ------------------------------------------------------------------------------------------------------------------
// 前置模块,存放通用的变量
// ------------------------------------------------------------------------------------------------------------------
var CustomFields = document.getElementsByClassName("custom-field-detail-item");
var RandomButtons = document.getElementsByClassName("random-button");
var WindowWrapper = document.getElementsByClassName("window-wrapper")[0];
var CheckLists = document.getElementsByClassName("editable non-empty checklist-title");
var CheckItems = document.getElementsByClassName("checklist-item");
var CardTitle = document.getElementsByClassName('window-title');
var LandTitle = document.getElementsByClassName('js-board-editing-target');
// ------------------------------------------------------------------------------------------------------------------
// common模块,存放通用的方法函数
// ------------------------------------------------------------------------------------------------------------------
// ----------------- 从自定义字段中存取配置 -----------------
function GetDataDom(field){ // 获取指定的自定义字段的dom
var dom; var i;
switch (field){
case 'Total': i = 0; break;
case 'Copy': i = 1; break;
case 'Object': i = 2; break;
case 'Domain': i = 3; break;
case 'Monster': i = 4; break;
case 'Fstwin': i = 5; break;
}
dom = CustomFields[i].childNodes[1]
return dom
}
function SetDataDom(field, value){ // 设置指定的自定义字段的内容
var dataDom = GetDataDom(field);
if (dataDom.value != value){
dataDom.value = value;
dataDom.focus({preventScroll: true});
dataDom.blur();
}
}
function GetDirectlyData(field){ // 获取指定的自定义字段的内容,若为空则解析为空字符串
var dataDom = GetDataDom(field)
var value = dataDom.value?dataDom.value:''
return value
}
function SetDirectlyData(field, data){ // 覆写指定的自定义字段的内容
var dataDom = GetDataDom(field)
var value = JSON.stringify(data);
SetDataDom(field, value);
}
function GetInternalData(field){ // 获取指定的自定义字段的内容,若为空则解析为空json
var dataDom = GetDataDom(field)
var value = dataDom.value?dataDom.value:'{}'
return JSON.parse(value);
}
function SetInternalData(field, data){ // 覆写指定的自定义字段的内容
var dataDom = GetDataDom(field)
var value = JSON.stringify(data);
SetDataDom(field, value);
}
// ----------------- 获取某一DOM的内容中,是否存在符合格式的标记,格式为:ID#LV,比如3#S -----------------
function GetSigns(dom){
// 判断标题格式是否正确
var titles = dom.innerText.split(' ');
if (titles.length < 2){return []}
// 若标题格式正确,则判断标记格式是否正确
var signs = titles[0].split('#');
if (signs.length != 2){return []}
// 若标记格式正确,则返回标志列表
return signs;
}
// 获取徽章、最小值、最大值
function GetConfigure(cls, level){
// bedge, min, max configuration
var LandConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
var AreaConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
var CopyConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
var DomainConfiguration = [["⭐", 1, 10], ["🌟", 10, 100], ["🌙", 100, 1000], ["🌝", 1000, 10000], ["🌞", 10000, 100000], ["🌠", 0, 0]];
var MonsterConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
var ObjectConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
var FstwinConfiguration = [["🌀", 3, 30], ["🌌", 30, 300], ["💧", 300, 3000], ["🔥", 3000, 30000], ["⚡", 30000, 300000], ["💀", 0, 0]];
var config; var suffix;
switch(cls){
case 'Land': config = LandConfiguration; break;
case 'Area': config = AreaConfiguration; break;
case 'Copy': config = CopyConfiguration; break;
case 'Domain': config = DomainConfiguration; break;
case 'Monster': config = MonsterConfiguration; break;
case 'Object': config = ObjectConfiguration; break;
case 'Fstwin': config = FstwinConfiguration; break;
}
switch(level){
case 'S': suffix = 4; break;
case 'A': suffix = 3; break;
case 'B': suffix = 2; break;
case 'C': suffix = 1; break;
case 'D': suffix = 0; break;
default: suffix = 5;
}
return config[suffix];
}
// 获取一个随机数
function GetRandomNum(min, max){
return parseInt(Math.random()*(max-min+1)+min,10);
}
// 判断按钮的内容是否正确,若正确时还一直插入,会引起栈溢出
function SetInnerHTML(dom, inner){
// console.log("正确内容为:" + inner + " 现在内容为:" + dom.innerHTML);
if (dom.innerHTML != inner){
dom.innerHTML = inner;
}
}
// 向某一dom中插入新的dom,index为插在第几个后面,0代表最前面。数太大则插在最后面
function InsertCustomDOM(fdom, ndom, index){ // 旧的文本处理方式
if (index == 0){
fdom.innerHTML = ndom + fdom.innerHTML;
}else if (index>fdom.children.length){
fdom.innerHTML += ndom
}else{
fdom.children[index-1].innerHTML += ndom;
}
}
// 将字符串转为dom
function parseDom(arg) {
var objE = document.createElement("div");
objE.innerHTML = arg;
return objE.children[0];
};
// 像Dom中的最后添加一个新的Dom,并且为新Dom指定一个点击监听器
function CustomTopButton(head, button, listener){
if(head.children.length == 1){
var ButtonDom = parseDom(button);
ButtonDom.addEventListener("click", listener);
head.appendChild(ButtonDom);
};
}
// ------------------------------------------------------------------------------------------------------------------
// 自定义计算总分
// ------------------------------------------------------------------------------------------------------------------
var TotalButton = '<a class="random-button card-label button" style="display:inline;margin-left:5px;">🏆🏆🏆</a>'
function TotalButtonListener(event){
// 获取所有的内部数据
var copy_data = GetDirectlyData('Copy');
var object_data = GetDirectlyData('Object');
var domain_data = GetInternalData('Domain');
var monster_data = GetInternalData('Monster');
var fstwin_data = GetInternalData('Fstwin');
// 把所有存储的数据加起来
var number = 0;
if (copy_data != ''){number += parseInt(copy_data)};
if (object_data != ''){number += parseInt(object_data)};
if (domain_data != {}){
for (var key1 in domain_data){
number += domain_data[key1]
}
}
if (monster_data != {}){
for (var key2 in monster_data){
number += monster_data[key2]
}
}
if (fstwin_data != {}){
for (var key3 in fstwin_data){
number += fstwin_data[key3][1]
}
}
// 把数据在内部数据中显示出来
SetDirectlyData('Total', number);
}
function CustomTotalButton(){
CustomTopButton(CustomFields[0].children[0], TotalButton, TotalButtonListener);
}
// ------------------------------------------------------------------------------------------------------------------
// 自定义副本积分
// ------------------------------------------------------------------------------------------------------------------
var CopyButton = '<a class="random-button card-label button" style="display:inline;margin-left:5px;">🎲🎲🎲</a>'
function CopyButtonListener(event){
// 获取标志
var signs = GetSigns(CardTitle[0].children[0]);
if (!signs.length){return}
var id = signs[0]; var level = signs[1];
// 获取数据和配置
var copy_data = GetDirectlyData('Copy');
var copy_config = GetConfigure('Copy', level) // 获取配置信息 // todo: 获取级别、ID之类的
var copy_bedge = copy_config[0]; var copy_min = copy_config[1]; var copy_max = copy_config[2];
var copy_number = GetRandomNum(copy_min, copy_max);
SetDirectlyData('Copy', copy_number);
}
function CustomCopyButton(){
CustomTopButton(CustomFields[1].children[0], CopyButton, CopyButtonListener);
}
// ------------------------------------------------------------------------------------------------------------------
// 自定义副本奖励
// ------------------------------------------------------------------------------------------------------------------
var ObjectButton = '<a class="random-button card-label button" style="display:inline;margin-left:5px;">🎁🎁🎁</a>'
function ObjectButtonListener(event){
// 获取标志
var signs = GetSigns(CardTitle[0].children[0]);
if (!signs.length){return}
var id = signs[0]; var level = signs[1];
// 获取数据和配置
var object_data = GetDirectlyData('Object'); // todo:换成object
var object_config = GetConfigure('Object', level) // 获取配置信息
var object_bedge = object_config[0]; var object_min = object_config[1]; var object_max = object_config[2];
var object_number = GetRandomNum(object_min, object_max);
SetDirectlyData('Object', object_number);
}
function CustomObjectButton(){
CustomTopButton(CustomFields[2].children[0], ObjectButton, ObjectButtonListener);
}
// ------------------------------------------------------------------------------------------------------------------
// 自定义清单
// ------------------------------------------------------------------------------------------------------------------
var CheckListButton = '<a class="random-button button subtle hide-on-edit" style="margin:0 0 0 6px;color:#fff;background-color:#f17143;font-weight:bold;">???</a>'
function CustomCheckListsListener(event){
var target = event.currentTarget;
var title = target.parentNode.previousSibling; // 获取标题、ID、级别
var signs = GetSigns(title);
if (!signs.length){return}
var id = signs[0]; var level = signs[1];
var domain_data = GetInternalData('Domain'); // 获取内部数据
var domain_config = GetConfigure('Domain', level) // 获取配置信息
if (typeof(domain_data[id]) != "undefined"){return} // 若已有内部数据则禁止再次生成
var domain_bedge = domain_config[0]; var domain_min = domain_config[1]; var domain_max = domain_config[2];
var domain_number = GetRandomNum(domain_min, domain_max);
domain_data[id] = domain_number; // 更新内部数据
SetInternalData('Domain', domain_data)
SetInnerHTML(target, domain_number);
}
function CustomCheckLists(){
if(CheckLists.length == 0){return};
for (var i=0; i<CheckLists.length; i++){
// 获取关键DOM、获取检查项的名称DOM、自定义DOM
var title = CheckLists[i].children[0];
var option = CheckLists[i].children[1];
// 获取标记中的信息
var signs = GetSigns(title);
if (!signs.length){continue}
var id = signs[0]; var level = signs[1];
// 添加自定义按钮
if(option.children.length == 3){
var CheckListButtonDom = parseDom(CheckListButton);
CheckListButtonDom.addEventListener("click", CustomCheckListsListener);
option.appendChild(CheckListButtonDom)
}
// 实时调整其按钮显示的内容
else if(option.children.length == 4){
var buttons = option.children; // 获取添加的自定义按钮
var domain_data = GetInternalData('Domain'); // 获取内部数据
var bedge = GetConfigure('Domain', level)[0] // 获取配置信息中的图标
// 计算应正确显示的内容
var inner = ""; // 按钮要显示的内容
if (typeof(domain_data[id]) == "undefined"){ // 打开卡片时,若数据中没有相关的数据,则显示礼包按钮
inner = bedge;
}else{ // 若已经有相关数据了,则显示相关数据
inner = domain_data[id];
}
// 判断按钮的内容是否正确,若正确时还一直插入,会引起栈溢出
SetInnerHTML(buttons[0], '显');
SetInnerHTML(buttons[1], '隐');
SetInnerHTML(buttons[2], '删');
SetInnerHTML(buttons[3], inner);
}
}
}
// ------------------------------------------------------------------------------------------------------------------
// 自定义检查项
// ------------------------------------------------------------------------------------------------------------------
var CheckItemTag = '<span class="oraant-custom card-label" style="float:left; max-height:20px;padding:0px 2px;margin:8px 5px;background-color:#e3e7e9;overflow:initial;color:#17394d;">?</span>'
var CheckItemCoin = '<span class="oraant-custom card-label card-label-green" style="float:right;max-height:20px;padding:0px 5px;margin:8px 2px;text-overflow:initial; overflow:initial;font-weight:bold;">?</span>'
var CheckItemFstwin = '<span class="oraant-custom card-label card-label-sky" style="float:right;max-height:20px;padding:0px 5px;margin:8px 2px;text-overflow:initial; overflow:initial;">?</span>'
// 自定义检查项后的信息
function CustomCheckItems(){
if(CheckItems.length == 0){return};
for (var i=0; i<CheckItems.length; i++){
// 获取详情DOM、获取检查项的名称DOM、自定义DOM
var detail = CheckItems[i].children[1].children[0];
var cbtag, cbtext, cbcoin, cbfstw;
var signs, id, level;
// 计算应正确显示的内容
// 添加自定义按钮
if(detail.children.length == 2){
// 单独验证格式是否合适,因为插入的原因,两次cbtext的位置是不一样的
cbtext = detail.children[0];
signs = GetSigns(cbtext);
if (!signs.length){continue}
InsertCustomDOM(detail, CheckItemTag, 0)
InsertCustomDOM(detail, CheckItemCoin, 10)
InsertCustomDOM(detail, CheckItemFstwin, 20)
}
// 实时调整其按钮显示的内容(之前那些DOM里Onclick的功能,也要做到这里面来。因为这个不是个按钮,不需要去按。)
else if(detail.children.length == 5){
// 获取各组件的dom
cbtag = detail.children[0];
cbtext = detail.children[1];
cbcoin = detail.children[3];
cbfstw = detail.children[4];
// 单独验证格式是否合适,因为插入的原因,两次cbtext的位置是不一样的
signs = GetSigns(cbtext);
if (!signs.length){continue}
id = signs[0]; level = signs[1];
// console.log("->内容全面,判断是否要进行修正");
var state = CheckItems[i].getAttribute("class"); // 获取类属性
var monster_data = GetInternalData('Monster'); // 获取内部数据
var fstwin_data = GetInternalData('Fstwin'); // 获取内部数据
var cbcoin_config = GetConfigure('Monster', level) // 获取配置信息
var cbfstw_config = GetConfigure('Fstwin', level) // 获取配置信息
var cbcoin_bedge = cbcoin_config[0]; var cbcoin_min = cbcoin_config[1]; var cbcoin_max = cbcoin_config[2];
var cbfstw_bedge = cbfstw_config[0]; var cbfstw_min = cbfstw_config[1]; var cbfstw_max = cbfstw_config[2];
var cbcoin_inner = ""; // 金币标签要显示的内容
var cbfstw_inner = ""; // 首胜标签要显示的内容
SetInnerHTML(cbtag, cbfstw_bedge); // 不管是否勾选,都在前面显示图标
if (state == "checklist-item"){ // 若未勾选,则根据难度,显示图标
cbcoin_inner = cbcoin_bedge;
cbfstw_inner = '';
}else if(state.search("checklist-item-state-complete") != -1){ // 若已勾选 // todo:这里应该能去掉,和下面的一起if
// 计算首胜标签应正确显示的内容
// ----------------------------------------
// 判断今天的有没有记录过(必须得在coin前边,需要根据coin的状态,判断是否是已经有数据的)
// 若已勾选但没有今日数据,且这个检查项也没有存过数据(否则昨天加了11号的后,今天还会加11号的首胜),则在数据栏中添加数据
var options = {year: 'numeric', month: 'numeric', day: 'numeric' };
var date = new Date().toLocaleDateString('ch-zh', options);
if (typeof(fstwin_data[date]) == "undefined" && typeof(monster_data[id]) == "undefined"){
var cbfstw_number = GetRandomNum(cbfstw_min, cbfstw_max); // 每日首胜奖励+3倍
fstwin_data[date] = [id, cbfstw_number];
SetInternalData('Fstwin', fstwin_data)
}
// 根据以前的数据,将首胜信息展示出来
for (var k in fstwin_data){
if(fstwin_data[k][0] == id){ // 若已经有今日数据了,且是这个ID,则显示相关数据
cbfstw_inner = fstwin_data[k][1];
}
}
// 计算金币标签应正确显示的内容
// ----------------------------------------
if (typeof(monster_data[id]) == "undefined"){ // 已勾选但未曾保存数据,则插入数据
var cbcoin_number = GetRandomNum(cbcoin_min, cbcoin_max);
monster_data[id] = cbcoin_number;
SetInternalData('Monster', monster_data)
cbcoin_inner = cbcoin_number
}else{ // 若已经有相关数据了,则显示相关数据
cbcoin_inner = monster_data[id];
}
}else{console.log('很奇怪,检查项的类属性和预期的不同:'+state)}
SetInnerHTML(cbfstw, cbfstw_inner);
SetInnerHTML(cbcoin, cbcoin_inner);
if(cbfstw_inner == ""){
cbfstw.style.display = "none";
}else{
cbfstw.style.display = "initial";
}
}
}
}
// ------------------------------------------------------------------------------------------------------------------
// 程序入口
// ------------------------------------------------------------------------------------------------------------------
var callback = function (records){
// 检查看板的标题是否符合格式
if(LandTitle.length == 0){return};
var title_signs = GetSigns(LandTitle[0]);
if (!title_signs.length){return}
// 校验是否有自定义域
if(CustomFields.length == 0){return};
// 获取判断卡片标题是否符合要求
var card_signs = GetSigns(CardTitle[0].children[0]);
if (!card_signs.length){return}
CustomTotalButton()
CustomCopyButton()
CustomObjectButton()
CustomCheckLists()
CustomCheckItems()
console.log('看看能不能输出日志')
};
var mo = new MutationObserver(callback);
mo.observe(WindowWrapper, {'childList': true, 'subtree': true}); // 设置一个监听器,页面由变化就触发。
callback(); // 如果直接打开一个页面的话,默认监听器不会被触发。这时手动触发一次就很有必要了。
};