Mint.com tags display

Show tags in the transactions listing on Mint.com.

目前為 2015-09-18 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Mint.com tags display
  3. // @include https://*.mint.com/*
  4. // @description Show tags in the transactions listing on Mint.com.
  5. // @namespace com.warkmilson.mint.js
  6. // @author Mark Wilson
  7. // @version 1.0.0
  8. // @homepage https://github.com/mddub/mint-tags-display
  9. // @grant none
  10. // @noframes
  11. // ==/UserScript==
  12. //
  13.  
  14. (function() {
  15. // tweak tag style: (default colors were chosen for consistency with Mint's theme)
  16. var TAG_STYLE = 'background: #0AC775; color: white; font-size: 10px; display: inline-block; margin-left: 4px; padding: 0 2px';
  17.  
  18. var transIdToTags = {};
  19. var tagIdToName = {};
  20.  
  21. function maybeIngestTransactionsList(response) {
  22. var json = window.JSON.parse(response);
  23. json['set'].forEach(function(item) {
  24. if(item['id'] === 'transactions') {
  25. item['data'].forEach(function(trans) {
  26. transIdToTags[trans['id']] = trans['labels'].map(function(label) { return label['name']; }).join(', ');
  27. trans['labels'].forEach(function(label) {
  28. tagIdToName[label['id']] = label['name'];
  29. });
  30. });
  31. }
  32. });
  33. }
  34.  
  35. function maybeIngestTagsList(response) {
  36. var json = window.JSON.parse(response);
  37. if(json['bundleResponseSent']) {
  38. jQuery.each(json['response'], function(key, val) {
  39. if(val['responseType'] === 'MintTransactionService_getTagsByFrequency') {
  40. val['response'].forEach(function(tagData) {
  41. tagIdToName[tagData['id']] = tagData['name'];
  42. });
  43. }
  44. });
  45. }
  46. }
  47.  
  48. function interceptTransactionEdit(data) {
  49. var transIds = [];
  50. var tagNames = [];
  51. data.split('&').forEach(function(pair) {
  52. var kv = pair.split('='), key = window.decodeURIComponent(kv[0]), val = window.decodeURIComponent(kv[1]);
  53.  
  54. var tagId = key.match(/tag(\d+)/);
  55. if(tagId !== null && val === '2') {
  56. tagNames.push(tagIdToName[tagId[1]]);
  57. }
  58.  
  59. // value is '1234:0' for a single transaction, '1234:0,2345:0' for multiple
  60. if(key === 'txnId') {
  61. transIds = val.split(',').map(function(tId) { return tId.split(':')[0]; });
  62. }
  63. });
  64.  
  65. transIds.forEach(function(tId) {
  66. transIdToTags[tId] = tagNames.join(', ') || undefined;
  67. if(jQuery('#transaction-' + tId).length > 0) {
  68. updateRow('transaction-' + tId);
  69. }
  70. });
  71. }
  72.  
  73. // update a transaction row using cached tag data
  74. function updateRow(rowId) {
  75. var $td = jQuery('#' + rowId).find('td.cat');
  76. var transId = rowId.split('-')[1];
  77. if(transIdToTags[transId]) {
  78. if($td.find('.gm-tags').length === 0) {
  79. $td.append('<span class="gm-tags" style="' + TAG_STYLE + '"></span>');
  80. }
  81. $td.find('.gm-tags').text(transIdToTags[transId]);
  82. } else {
  83. $td.find('.gm-tags').remove();
  84. }
  85. }
  86.  
  87. (function(open) {
  88. XMLHttpRequest.prototype.open = function() {
  89. // instrument all XHR responses to intercept the ones which may contain transaction listing or tag listing
  90. this.addEventListener("readystatechange", function() {
  91. if(this.readyState === 4 && this.responseURL.match('getJsonData.xevent')) {
  92. maybeIngestTransactionsList(this.responseText);
  93. } else if(this.readyState === 4 && this.responseURL.match('bundledServiceController.xevent')) {
  94. maybeIngestTagsList(this.responseText);
  95. }
  96. }, false);
  97.  
  98. // instrument all XHR requests to intercept edits to transactions
  99. if(arguments[0].match(/post/i) && arguments[1].match('updateTransaction.xevent')) {
  100. var self = this, send = this.send;
  101. this.send = function() {
  102. interceptTransactionEdit(arguments[0]);
  103. send.apply(self, arguments);
  104. };
  105. }
  106.  
  107. open.apply(this, arguments);
  108. };
  109. })(XMLHttpRequest.prototype.open);
  110.  
  111. function observeDOM(target) {
  112. var observer;
  113.  
  114. function handleMutations(mutations) {
  115. var rowIdsToUpdate = {};
  116. mutations.forEach(function(mutation) {
  117. var $target = jQuery(mutation.target);
  118. var $tr = jQuery(mutation.target).parents('tr').first();
  119. if(!$target.hasClass('gm-tags') && $tr.length && $tr.attr('id') && $tr.attr('id').indexOf('transaction-') === 0) {
  120. // when the transactions list changes, there will be multiple mutations per row (date column, amount column, etc.)
  121. rowIdsToUpdate[$tr.attr('id')] = true;
  122. }
  123. });
  124.  
  125. observer.disconnect();
  126. for(var rowId in rowIdsToUpdate) {
  127. updateRow(rowId);
  128. }
  129. observe();
  130. }
  131.  
  132. function observe() {
  133. observer = new MutationObserver(handleMutations);
  134. observer.observe(
  135. target,
  136. {subtree: true, childList: true, characterData: true}
  137. );
  138. }
  139.  
  140. observe();
  141. }
  142.  
  143. (function waitForTable() {
  144. var target = document.querySelector('#transaction-list-body');
  145. if(target === null) {
  146. setTimeout(waitForTable, 500);
  147. return;
  148. }
  149.  
  150. // populate the table with tags after it first loads
  151. jQuery(target).find('tr').each(function(_, row) {
  152. updateRow(row.id);
  153. });
  154.  
  155. observeDOM(target);
  156. })();
  157.  
  158. })();