您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show tags in the transactions listing on Mint.com.
当前为
// ==UserScript== // @name Mint.com tags display // @include https://*.mint.com/* // @include https://mint.intuit.com/* // @description Show tags in the transactions listing on Mint.com. // @namespace com.warkmilson.mint.js // @author Mark Wilson // @version 1.2.0 // @homepage https://github.com/mddub/mint-tags-display // @grant none // @noframes // ==/UserScript== // (function() { var TAG_STYLE = 'font-size: 10px; display: inline-block;'; var SINGLE_TAG_STYLE = 'margin-left: 4px; padding: 0 2px;'; var TAG_COLORS = [ // source: http://colorbrewer2.org/#type=qualitative&scheme=Paired&n=12 // background, foreground ['#a6cee3', 'black'], ['#b2df8a', 'black'], ['#fb9a99', 'black'], ['#fdbf6f', 'black'], ['#cab2d6', 'black'], ['#ffff99', 'black'], ['#1f78b4', 'white'], ['#33a02c', 'white'], ['#e31a1c', 'white'], ['#ff7f00', 'white'], ['#6a3d9a', 'white'], ['#b15928', 'white'] ]; var tagsByFrequency; var transIdToTags = {}; var tagIdToName = {}; function maybeIngestTransactionsList(response) { var json = window.JSON.parse(response); json['set'].forEach(function(item) { if(item['id'] === 'transactions') { item['data'].forEach(function(trans) { transIdToTags[trans['id']] = trans['labels'].map(function(label) { return label['name']; }); trans['labels'].forEach(function(label) { tagIdToName[label['id']] = label['name']; }); }); } }); } function maybeIngestTagsList(response) { var json = window.JSON.parse(response); if(json['bundleResponseSent']) { jQuery.each(json['response'], function(key, val) { if(val['responseType'] === 'MintTransactionService_getTagsByFrequency') { val['response'].forEach(function(tagData) { tagIdToName[tagData['id']] = tagData['name']; }); tagsByFrequency = val['response'].sort(function(a, b) { return b['transactionCount'] - a['transactionCount']; }).map(function(tagData) { return tagData['name']; }); } }); } } function interceptTransactionEdit(data) { var transIds = []; var tagNames = []; data.split('&').forEach(function(pair) { var kv = pair.split('='), key = window.decodeURIComponent(kv[0]), val = window.decodeURIComponent(kv[1]); var tagId = key.match(/tag(\d+)/); if(tagId !== null && val === '2') { tagNames.push(tagIdToName[tagId[1]]); } // value is '1234:0' for a single transaction, '1234:0,2345:0' for multiple if(key === 'txnId') { transIds = val.split(',').map(function(tId) { return tId.split(':')[0]; }); } }); transIds.forEach(function(tId) { transIdToTags[tId] = tagNames; if(jQuery('#transaction-' + tId).length > 0) { updateRow('transaction-' + tId); } }); } // update a transaction row using cached tag data function updateRow(rowId) { var $td = jQuery('#' + rowId).find('td.cat'); var transId = rowId.split('-')[1]; if(transIdToTags[transId] && transIdToTags[transId].length) { if($td.find('.gm-tags').length === 0) { $td.append('<span class="gm-tags" style="' + TAG_STYLE + '"></span>'); } // Alphabetize transIdToTags[transId].sort(function(a, b) { if(a.toLowerCase() < b.toLowerCase()) { return -1; } else if(a.toLowerCase() > b.toLowerCase()) { return 1; } else { return 0; } }); // HTML for each tag, unique color for each tag var tagsHTML = transIdToTags[transId].map(function(tag) { return '<span class="gm-tag" style="' + tagStyleLookup(tag) + '; ' + SINGLE_TAG_STYLE + '">' + tag + '</span>'; }).join(''); $td.find('.gm-tags').html(tagsHTML); } else { $td.find('.gm-tags').remove(); } } (function(open) { XMLHttpRequest.prototype.open = function() { // Firefox and Chrome support this.responseURL, but Safari does not, so we need to store it var requestURL_ = arguments[1]; // instrument all XHR responses to intercept the ones which may contain transaction listing or tag listing this.addEventListener("readystatechange", function() { if(this.readyState === 4 && requestURL_.match('getJsonData.xevent')) { maybeIngestTransactionsList(this.responseText); } else if(this.readyState === 4 && requestURL_.match('bundledServiceController.xevent')) { maybeIngestTagsList(this.responseText); } }, false); // instrument all XHR requests to intercept edits to transactions if(arguments[0].match(/post/i) && arguments[1].match('updateTransaction.xevent')) { var self = this, send = this.send; this.send = function() { interceptTransactionEdit(arguments[0]); send.apply(self, arguments); }; } open.apply(this, arguments); }; })(XMLHttpRequest.prototype.open); function observeDOM(target) { var observer; function handleMutations(mutations) { var rowIdsToUpdate = {}; mutations.forEach(function(mutation) { var $target = jQuery(mutation.target); var $tr = jQuery(mutation.target).parents('tr').first(); if(!$target.hasClass('gm-tags') && $tr.length && $tr.attr('id') && $tr.attr('id').indexOf('transaction-') === 0) { // when the transactions list changes, there will be multiple mutations per row (date column, amount column, etc.) rowIdsToUpdate[$tr.attr('id')] = true; } }); observer.disconnect(); for(var rowId in rowIdsToUpdate) { updateRow(rowId); } observe(); } function observe() { observer = new MutationObserver(handleMutations); observer.observe( target, {subtree: true, childList: true, characterData: true} ); } observe(); } (function waitForTable() { var target = document.querySelector('#transaction-list-body'); if(target === null) { setTimeout(waitForTable, 500); return; } // populate the table with tags after it first loads jQuery(target).find('tr').each(function(_, row) { updateRow(row.id); }); observeDOM(target); })(); function tagStyleLookup(tag) { var index = tagsByFrequency.indexOf(tag); var colors = TAG_COLORS[index % TAG_COLORS.length]; return 'background-color: ' + colors[0] + '; color: ' + colors[1] + ';'; } })();