Mint.com Customize Default Categories

Hide specified default built-in mint.com categories

当前为 2016-12-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Mint.com Customize Default Categories
  3. // @namespace com.schrauger.mint.js
  4. // @author Stephen Schrauger
  5. // @description Hide specified default built-in mint.com categories
  6. // @homepage https://github.com/schrauger/mint.com-customize-default-categories
  7. // @include https://*.mint.com/*
  8. // @include https://mint.intuit.com/*
  9. // @version 1.4.3
  10. // @grant none
  11. // ==/UserScript==
  12. /*jslint browser: true*/
  13. /*global jQuery*/
  14. (function () {
  15. function after_jquery() {
  16. var number_of_bit_arrays = 3; // use 3 arrays to store all preferences.
  17. var unique_id_length = 4; // the bit array is prefixed with '#!1 ' or '#!2 ' or '#!3 ', which is 4 characters long
  18. var categories_per_string = 8; // with 20 characters per string, and 2 characters per category, we can fit 8 (plus the 4-char unique id)
  19. var characters_per_category = 2; // with 6 flags, this allows for 11 sub categories and 1 major category
  20. var bit_flags_per_char = 6; // using 01XXXXXX ASCII codes, which allows for 6 flags per character
  21. var category_id = 20; // save the custom fields in the 'uncategorized' major category, which has the id of 20
  22. var class_hidden = 'sgs-hide-from-mint'; // just a unique class; if an element has it, it will be hidden.
  23. var class_edit_mode = 'mint_edit_mode';
  24.  
  25. var hs_action_hide = 'hide';
  26. var hs_action_show = 'show';
  27. var hs_action_edit = 'edit';
  28.  
  29. // Need to define all categories and subcategories, along with their ID. Create this list dynamically.
  30. function get_default_category_list() {
  31. var categories = [];
  32.  
  33. // loop through each category and create an array of arrays with their info
  34. jQuery('#popup-cc-L1').find('> li').each(function () {
  35. var category_major = [];
  36. category_major.id = jQuery(this).attr('id').replace(/\D/g, ''); // number-only portion (they all start with 'pop-categories-'{number}
  37. //console.log(category_major.id);
  38. category_major.name = jQuery(this).children('a').text();
  39. category_major.categories_minor = [];
  40. category_major.is_hidden = jQuery(this).hasClass(class_hidden);
  41. /* get the minor/sub categories. only the :first ul, because the second one is
  42. user-defined custom categories, which they can change with native mint.com controls
  43. */
  44. jQuery(this).find('div.popup-cc-L2 ul:first > li').each(function () {
  45. var category_minor = [];
  46. category_minor.id = jQuery(this).attr('id').replace(/\D/g, '');
  47. category_minor.name = jQuery(this).text();
  48. category_minor.is_hidden = jQuery(this).hasClass(class_hidden);
  49. category_major.categories_minor.push(category_minor);
  50. });
  51. categories.push(category_major);
  52. });
  53.  
  54. return categories;
  55. }
  56.  
  57. // Save any categories the user wants hidden.
  58. // This will be done by creating a custom subcategory in the 'uncategorized' category, where the name of this
  59. // subcategory will define which other categories to hide.
  60. // This way, if the UserScript is installed on multiple computers, the user sees their preferences synced.
  61. // Alternatively, a cookie could be used, but preferences would be specific to that device.
  62.  
  63. /**
  64. * @str_bit_array_array Array A printable-ascii encoded bit array (values that can be saved in a custom category)
  65. * @array_of_all_categories Array 8*3 categories with their subcategories and
  66. * ID, name, subcategories and is_hidden members
  67. * for both the category and subcategories
  68. */
  69. function decode_bit_array(str_bit_array_array, array_of_all_categories) {
  70.  
  71. // second, translate any extra characters into their forbidden character
  72. // (double quote is forbidden; use a non-bit-array character to encode)
  73.  
  74. // third, loop through each category and each of its subcategories
  75.  
  76. // use bitwise operators to see if the subcategory minor id (always 1-9) is marked as hidden
  77.  
  78.  
  79. str_bit_array_array.sort();
  80.  
  81. array_of_all_categories.sort(function (a, b) {
  82. return (a.id - b.id); // sort by id, lowest first
  83. });
  84.  
  85.  
  86. var field_count = 0; // 0, 1, or 2; for the 3 unique fields holding the 23 category hidden attributes
  87. var bit_string_category_count = 0; // only 8 categories per string. once this goes past 7, reset and use next string.
  88. var str_bit_array = str_bit_array_array[field_count]; // 3 custom fields with attributes
  89. // remove the first 4 characters (the unique ID plus a space)
  90. str_bit_array = str_bit_array.substring(unique_id_length); // 0-based, meaning start at the fifth character (inclusive)
  91.  
  92. // loop through each major category and its minor categories and mark them as hidden or not
  93. for (var category_major_count = 0, category_major_length = array_of_all_categories.length; category_major_count < category_major_length; category_major_count++) {
  94. var category_major = array_of_all_categories[category_major_count];
  95.  
  96. array_of_all_categories[category_major_count].categories_minor.sort(function (a, b) {
  97. return (a.id - b.id); // sort by id, lowest first
  98. });
  99.  
  100.  
  101. var bit_characters = str_bit_array.substr(bit_string_category_count * characters_per_category, characters_per_category); // grab the 2 characters for this category
  102.  
  103. for (var category_minor_count = 0, category_minor_length = category_major.categories_minor.length; category_minor_count < category_minor_length; category_minor_count++) {
  104.  
  105. var minor_id = category_major.categories_minor[category_minor_count].id.slice(-2); // the last two digits are the minor category; the first two are always the same as the parent major category
  106. // if flag is '1', it is hidden
  107. array_of_all_categories[category_major_count].categories_minor[category_minor_count].is_hidden = is_category_hidden(bit_characters, minor_id);
  108. //console.debug(array_of_all_categories[category_major_count].categories_minor[category_minor_count].name + ' is hidden: '
  109. // + array_of_all_categories[category_major_count].categories_minor[category_minor_count].is_hidden);
  110. }
  111. var last_flag = characters_per_category * bit_flags_per_char; // last flag is used for major category, instead of subcategory #12 (2 * 6);
  112. array_of_all_categories[category_major_count].is_hidden = is_category_hidden(bit_characters, last_flag);
  113. //console.debug(array_of_all_categories[category_major_count].name + ' is hidden: '
  114. // + array_of_all_categories[category_major_count].is_hidden);
  115. bit_string_category_count += 1;
  116. if (bit_string_category_count > (categories_per_string - 1)) {
  117. // each of our custom bit arrays can only hold 8 categories' info. reset the counter and move on to the next bit array
  118. bit_string_category_count = 0;
  119. field_count += 1;
  120.  
  121. str_bit_array = str_bit_array_array[field_count]; // 3 custom fields with attributes
  122. // remove the first 4 characters (the unique ID plus a space)
  123. str_bit_array = str_bit_array.substring(unique_id_length); // 0-based, meaning start at character 5 (inclusive)
  124. }
  125. }
  126. return array_of_all_categories;
  127. }
  128.  
  129. /**
  130. * Returns true if the bit location is set to true.
  131. */
  132. function is_category_hidden(ascii_characters, minor_id) {
  133. var bit_shift_count = ((minor_id - 1) % bit_flags_per_char); // category 1 is stored in last bit flag; cat 2 in the second to last. cat 7 stored in last flag
  134. var bit_to_use = (Math.floor((minor_id - 1) / bit_flags_per_char)); // 1-6 (0-5) in first bit. 7-12 (6-11) stored in second. etc. 7/6 floored is 1.
  135. var bit_character = (ascii_characters.charCodeAt(bit_to_use)); // get binary representation
  136. var is_hidden = ((bit_character >>> bit_shift_count) & 000001); // shift the bits over and mask with '1'. if both are 1, it will return 1 (true) for hidden
  137. //console.log('is_hidden: ' + is_hidden);
  138. return is_hidden;
  139. }
  140.  
  141. function encode_category_hidden(ascii_characters, minor_id, is_hidden) {
  142. var bit_shift_count = ((minor_id - 1) % bit_flags_per_char); // category 1 is stored in last bit flag; cat 2 in the second to last. cat 7 stored in last flag
  143. var bit_to_use = (Math.floor((minor_id - 1) / bit_flags_per_char)); // 1-6 (0-5) in first bit. 7-12 (6-11) stored in second. etc. 7/6 floored is 1.
  144. var mask = ((is_hidden << bit_shift_count)); // move the mask to the proper location
  145.  
  146. var new_char = String.fromCharCode(ascii_characters[bit_to_use].charCodeAt(0) | mask);
  147. ascii_characters = ascii_characters.replaceAt(bit_to_use, new_char); // replace with new character
  148. //console.debug(ascii_characters);
  149. return ascii_characters;
  150. }
  151.  
  152. /**
  153. * Replaces the substituted characters with the 'illegal' characters.
  154. * This way, the script can use bitwise operations in a logical manner.
  155. */
  156. function translate_to_script(string_with_substituted_characters) {
  157. var str_return = string_with_substituted_characters.replace('?', String.fromCharCode(127)); // the delete char (127) is substituted with a question mark when saved at mint
  158. return str_return;
  159. }
  160.  
  161. /**
  162. * Replaces any illegal characters with substituted characters that mint.com allows in text fields.
  163. */
  164. function translate_to_mint(string_with_illegal_characters) {
  165. var str_return = string_with_illegal_characters.replace(String.fromCharCode(127), '?');
  166. return str_return;
  167. }
  168.  
  169. /**
  170. * Extracts the bit arrays from the saved custom category input box and puts all 3 into a string array.
  171. * @returns {Array}
  172. */
  173. function extract_mint_array() {
  174. var str_bit_array_array = [];
  175. for (var array_number = 1; array_number <= number_of_bit_arrays; array_number++){
  176. str_bit_array_array.push(jQuery('#menu-category-' + category_id + ' ul li:contains("#!' + array_number + '"):first').text());
  177. //console.log('processing val is ' + jQuery(this).text());
  178. }
  179. return str_bit_array_array;
  180. }
  181.  
  182. function encode_bit_array(array_of_all_categories) {
  183. // loop through each category, create an ASCII character, and replace illegal characters
  184.  
  185. array_of_all_categories.sort(function (a, b) {
  186. return (a.id - b.id); // sort by id, lowest first
  187. });
  188.  
  189. var field_count = 0; // 0, 1, or 2; for the 3 unique fields holding the 23 category hidden attributes
  190. var bit_string_category_count = 0; // only 8 categories per string. once this goes past 7, reset and use next string.
  191. var str_bit_array_array = [];
  192.  
  193. // loop through each major category and its minor categories and mark them as hidden or not
  194. str_bit_array_array[field_count] = new Array(characters_per_category * categories_per_string + 1).join('@');
  195. for (var category_major_count = 0, category_major_length = array_of_all_categories.length; category_major_count < category_major_length; category_major_count++) {
  196.  
  197. var category_major = array_of_all_categories[category_major_count];
  198.  
  199. array_of_all_categories[category_major_count].categories_minor.sort(function (a, b) {
  200. return (a.id - b.id); // sort by id, lowest first
  201. });
  202.  
  203.  
  204. var bit_characters = new Array(characters_per_category + 1).join('@'); // the @ character is 01000000, so all flags (last 6) start 'off'.
  205. for (var category_minor_count = 0, category_minor_length = category_major.categories_minor.length; category_minor_count < category_minor_length; category_minor_count++) {
  206.  
  207. var minor_id = category_major.categories_minor[category_minor_count].id.slice(-2); // the last two digits are the minor category; the first two are always the same as the parent major category
  208. // if flag is '1', it is hidden
  209.  
  210. bit_characters = encode_category_hidden(bit_characters, minor_id, array_of_all_categories[category_major_count].categories_minor[category_minor_count].is_hidden);
  211. str_bit_array_array[field_count] = str_bit_array_array[field_count].replaceAt(bit_string_category_count * characters_per_category, bit_characters);
  212. }
  213. var last_flag = characters_per_category * bit_flags_per_char; // last flag is used for major category, instead of subcategory #12 (2 * 6);
  214. bit_characters = encode_category_hidden(bit_characters, last_flag, array_of_all_categories[category_major_count].is_hidden);
  215.  
  216. str_bit_array_array[field_count] = str_bit_array_array[field_count].replaceAt(bit_string_category_count * characters_per_category, bit_characters);
  217.  
  218. bit_string_category_count += 1;
  219. if (bit_string_category_count > (categories_per_string - 1)) {
  220. // each of our custom bit arrays can only hold 8 categories' info. reset the counter and move on to the next bit array
  221. bit_string_category_count = 0;
  222. field_count += 1;
  223. str_bit_array_array[field_count] = new Array(characters_per_category * categories_per_string + 1).join('@');
  224. }
  225. }
  226. // now that all 3 bit arrays are creates, tack on the unique id to the string
  227. for (var i = 0; i < number_of_bit_arrays; i++) {
  228. str_bit_array_array[i] = '#!' + (i + 1) + ' ' + str_bit_array_array[i];
  229. str_bit_array_array[i] = translate_to_mint(str_bit_array_array[i]);
  230. //console.log(str_bit_array_array[i]);
  231. }
  232. return str_bit_array_array;
  233. }
  234.  
  235. function clean_up_extra_fields(bit_string) {
  236. var unique_id = bit_string.substr(0, unique_id_length);
  237. jQuery('ul.popup-cc-L2-custom > li > input[value^="' + unique_id + '"]:not(:first)').each(function () {
  238. var input_id = jQuery(this).prev().val();
  239. delete_field(input_id);
  240. });
  241. }
  242.  
  243. /**
  244. * Will update or insert as needed.
  245. * @param bit_string
  246. */
  247. function upsert_field(bit_string) {
  248. var unique_id = bit_string.substr(0, unique_id_length);
  249. var input_id = jQuery('ul.popup-cc-L2-custom > li > input[value^="' + unique_id + '"]:first').prev().val();
  250. if (input_id) {
  251. update_field(bit_string);
  252. } else {
  253. insert_field(bit_string);
  254. }
  255. }
  256.  
  257. function insert_field(bit_string) {
  258. var hidden_token = JSON.parse(jQuery('#javascript-user').val()).token;
  259. var data = {
  260. pcatId: category_id,
  261. catId: 0,
  262. category: bit_string,
  263. task: 'C',
  264. token: hidden_token
  265. };
  266. jQuery.ajax(
  267. {
  268. type: "POST",
  269. url: '/updateCategory.xevent',
  270. data: data
  271. }
  272. );
  273. }
  274.  
  275. function update_field(bit_string) {
  276.  
  277. var hidden_token = JSON.parse(jQuery('#javascript-user').val()).token;
  278. var unique_id = bit_string.substr(0, unique_id_length);
  279.  
  280. var input = jQuery('ul.popup-cc-L2-custom > li > input[value^="' + unique_id + '"]:first');
  281. //console.log('setting input string from ' + input.val() + ' to ' + bit_string);
  282. input.val(bit_string); // set the value on the user's page manually (not needed for ajax, but needed for later processing)
  283. //jQuery('#menu-category-' + category_id + ' ul li:contains("' + unique_id + '")').text(bit_string); //@TODO what is this line doing?
  284. /* input.prop('value',bit_string);
  285. input.attr('value',bit_string);*/
  286. var input_id = input.prev().val();
  287. var data = {
  288. pcatId: category_id,
  289. catId: input_id,
  290. category: bit_string,
  291. task: 'U',
  292. token: hidden_token
  293. };
  294. jQuery.ajax(
  295. {
  296. type: "POST",
  297. url: '/updateCategory.xevent',
  298. data: data
  299. }
  300. );
  301. }
  302.  
  303. function delete_field(input_id) {
  304. var hidden_token = JSON.parse(jQuery('#javascript-user').val()).token;
  305. var data = {
  306. catId: input_id,
  307. task: 'D',
  308. token: hidden_token
  309. };
  310. jQuery.ajax(
  311. {
  312. type: "POST",
  313. url: '/updateCategory.xevent',
  314. data: data
  315. }
  316. );
  317. }
  318.  
  319. /**
  320. * Loop through all the category objects. If any are hidden,
  321. * add the proper CSS to hide the field.
  322. * Also, remove any line-throughs, in case a hidden category has been restored.
  323. * @param default_categories
  324. */
  325. function process_hidden_categories(default_categories) {
  326. for (var major_count = 0; major_count < default_categories.length; major_count++) {
  327. for (var minor_count = 0; minor_count < default_categories[major_count].categories_minor.length; minor_count++) {
  328. if (default_categories[major_count].categories_minor[minor_count].is_hidden) {
  329. jQuery('#menu-category-' + default_categories[major_count].categories_minor[minor_count].id).addClass(class_hidden);
  330. //console.log('hide minor ' + default_categories[major_count].categories_minor[minor_count].id);
  331. jQuery('#pop-categories-' + default_categories[major_count].categories_minor[minor_count].id).addClass(class_hidden);
  332. }
  333. jQuery('#menu-category-' + default_categories[major_count].categories_minor[minor_count].id).css('text-decoration', '');
  334. jQuery('#pop-categories-' + default_categories[major_count].categories_minor[minor_count].id).css('text-decoration', '');
  335. }
  336. if (default_categories[major_count].is_hidden) {
  337. jQuery('#menu-category-' + default_categories[major_count].id).addClass(class_hidden);
  338. jQuery('#pop-categories-' + default_categories[major_count].id).addClass(class_hidden);
  339.  
  340. }
  341. jQuery('#menu-category-' + default_categories[major_count].id).css('text-decoration', '');
  342. jQuery('#pop-categories-' + default_categories[major_count].id).css('text-decoration', '');
  343. }
  344. hide_show_category(hs_action_hide);
  345. }
  346.  
  347. /**
  348. *
  349. * @param action
  350. */
  351. function hide_show_category(action) {
  352. var category = jQuery('.' + class_hidden);
  353. if (action == hs_action_hide) {
  354. // hide the categories completely
  355. category.hide();
  356. category.css('text-decoration', 'line-through');
  357. }
  358. if (action == hs_action_show) {
  359. // remove any visible attributes
  360. category.show();
  361. category.css('text-decoration', '');
  362. }
  363. if (action == hs_action_edit) {
  364. category.show();
  365. category.css('text-decoration', 'line-through');
  366. }
  367. }
  368.  
  369. function add_toggle() {
  370. if (!(jQuery('#sgs-toggle').length)) {
  371. var toggle_style = "position: absolute; right: 30px; top: 35px; cursor: pointer";
  372. var toggle_text = "Edit Hidden Categories";
  373. jQuery('#pop-categories-main').prepend('<div id="sgs-toggle" class="" style="' + toggle_style + '">' + toggle_text + '</div>');
  374. jQuery('#sgs-toggle').click(function () {
  375. edit_categories();
  376. });
  377. jQuery('#pop-categories-submit').click(function(){
  378. jQuery('#sgs-toggle').addClass('editing'); // force the current mode to be editing so the edit_categories call saves
  379. edit_categories(); // if user clicks the "I'm done" button, we should also save the categories.
  380. })
  381. }
  382. }
  383.  
  384. function edit_categories() {
  385. var toggle = jQuery('#sgs-toggle');
  386. toggle.toggleClass('editing');
  387. if (toggle.hasClass('editing')) {
  388. toggle.text('Save Hidden Categories');
  389. mint_edit(true); // make all categories clickable; when clicked, add class and strike out
  390. } else {
  391. mint_edit(false); // remove clickable event and strike css; go back to hiding
  392. mint_save();
  393. toggle.text('Edit Hidden Categories');
  394.  
  395. }
  396.  
  397. }
  398.  
  399. function mint_edit(edit_mode) {
  400. if (edit_mode) {
  401. // get the major and minor categories in the popup editor
  402. var minor_categories = jQuery('div.popup-cc-L2 > ul:first-of-type > li'); // second ul is custom categories, so just get first
  403. var major_categories = jQuery('#popup-cc-L1').find('.isL1');
  404.  
  405.  
  406. // display all previously hidden fields (except our three custom fields holding bit arrays)
  407.  
  408.  
  409. // add checkboxes to the categories
  410. minor_categories.each(function () {
  411. add_checkbox(this);
  412. });
  413. major_categories.each(function () {
  414. add_checkbox(this);
  415. })
  416. jQuery('input.hide_show_checkbox').css({
  417. 'position': 'absolute',
  418. 'right': '-18px'
  419. });
  420.  
  421. // add label for minor checkboxes
  422. jQuery('div.popup-cc-L2 > h3:first-of-type').append('<span class="' + class_edit_mode + ' minor_hide_show_label">Hide</span>');
  423. jQuery('span.minor_hide_show_label').css({
  424. 'position': 'absolute',
  425. 'right': '64px',
  426. 'top': '3px',
  427. 'font-size': '13px',
  428. 'font-weight': 'bold'
  429. });
  430.  
  431.  
  432. // add label for major categories
  433. jQuery('#pop-categories-form fieldset').prepend('<span class="' + class_edit_mode + ' major_hide_show_label">Hide</span>');
  434. jQuery('span.major_hide_show_label').css({
  435. 'position': 'absolute',
  436. 'left': '267px',
  437. 'font-size': '13px',
  438. 'font-weight': 'bold'
  439. });
  440. // add checkbox event. when checked add the 'hidden' class (which is scanned on save)
  441. jQuery('input.hide_show_checkbox').click(function () {
  442. parent_id = jQuery(this).parent().attr('id').replace(/\D/g, '');
  443. if (jQuery(this).is(':checked')) {
  444. jQuery('#menu-category-' + parent_id).addClass(class_hidden);
  445. jQuery('#pop-categories-' + parent_id).addClass(class_hidden);
  446. jQuery(this).parent().css('text-decoration', 'line-through');
  447. } else {
  448. jQuery('#menu-category-' + parent_id).removeClass(class_hidden);
  449. jQuery('#pop-categories-' + parent_id).removeClass(class_hidden);
  450. ;
  451. jQuery(this).parent().css('text-decoration', '');
  452.  
  453. }
  454. });
  455. hide_show_category(hs_action_edit);
  456. } else {
  457. // no longer editing (saving), so remove our labels and checkboxes, and re-hide the desired categories
  458. jQuery('.' + class_edit_mode).remove();
  459. hide_show_category(hs_action_hide);
  460. }
  461. }
  462.  
  463. function add_checkbox(element) {
  464. var checked = '';
  465. if (jQuery(element).hasClass(class_hidden)) {
  466. // hidden categories are checked
  467. checked = 'checked="checked"';
  468. }
  469. jQuery(element).append('<input type="checkbox" class="' + class_edit_mode + ' hide_show_checkbox"' + checked + ' />');
  470. }
  471.  
  472. /**
  473. * hooks to the save or cancel button so that hidden categories will be re-hidden after ajax refresh
  474. */
  475. function add_save_hook() {
  476. if ((jQuery('#pop-categories-submit').length && (!(jQuery('#pop-categories-submit').hasClass('sgs-hook-added'))))) {
  477.  
  478. jQuery('#pop-categories-submit').addClass('sgs-hook-added');
  479. jQuery('#pop-categories-submit, #pop-categories-close').click(function () {
  480. mint_refresh();
  481. });
  482. }
  483. }
  484.  
  485. /**
  486. * Hides our bit array custom categories permanently so the user won't accidentally mess with them.
  487. */
  488. function hide_bit_array() {
  489. jQuery('input[value^="#!"]').parent().hide();
  490. jQuery('li[id^="menu-category-"] a:contains("#!")').parent().hide();
  491. }
  492.  
  493. /**
  494. * Allows immutable strings to have character(s) replaced
  495. * @param index
  496. * @param character
  497. * @returns {string}
  498. */
  499. String.prototype.replaceAt = function (index, character) {
  500. return this.substr(0, index) + character + this.substr(index + character.length);
  501. };
  502.  
  503. /**
  504. * Lets you bind an event and have it run first.
  505. * @param name
  506. * @param fn
  507. */
  508. jQuery.fn.bindFirst = function (name, fn) {
  509. // bind as you normally would
  510. // don't want to miss out on any jQuery magic
  511. this.on(name, fn);
  512.  
  513. // Thanks to a comment by @Martin, adding support for
  514. // namespaced events too.
  515. this.each(function () {
  516. var handlers = jQuery._data(this, 'events')[name.split('.')[0]];
  517. // take out the handler we just inserted from the end
  518. var handler = handlers.pop();
  519. // move it at the beginning
  520. handlers.splice(0, 0, handler);
  521. });
  522. };
  523.  
  524. function mint_refresh() {
  525. add_toggle();
  526. add_save_hook();
  527. // when the popup is opened or closed, re-hide the categories
  528. var str_bit_array_array = extract_mint_array();
  529. var default_categories = get_default_category_list();
  530. default_categories = decode_bit_array(str_bit_array_array, default_categories);
  531. process_hidden_categories(default_categories); // hides the appropriate fields
  532.  
  533. //Hides our three fields, since the user probably shouldn't mess with them directly (and they look weird).
  534. hide_bit_array(); // comment this out in order to see the bit string data
  535. }
  536.  
  537. /**
  538. * Saves the preferences
  539. */
  540. function mint_save() {
  541. //console.debug('saving');
  542. var default_categories = get_default_category_list();
  543. var bit_array_array = encode_bit_array(default_categories);
  544. for (var i = 0; i < bit_array_array.length; i++) {
  545. clean_up_extra_fields(bit_array_array[i]); // delete any extra preference fields my older script created
  546. upsert_field(bit_array_array[i]);
  547. }
  548. }
  549. function add_dropdown_hook() {
  550. //console.log('start dropdown');
  551. //if ((jQuery('#txnEdit-category_input').length) && (!(jQuery('#txtEdit-category_input').hasClass('sgs-hook-added')))) {
  552. jQuery('#txnEdit-category_input').addClass('sgs-hook-added');
  553. jQuery('#txnEdit-category_input, #txnEdit-category_picker').off('click', mint_refresh);
  554. jQuery('#txnEdit-category_input, #txnEdit-category_picker').on('click', mint_refresh);
  555. //}
  556. }
  557. /**
  558. * A bonus feature of this script: Fixes Mint's google search query.
  559. * (If a transaction description contains a space, quote or other URI character,
  560. * mint.com doesn't encode it, which causes the google search link to be invalid.)
  561. */
  562. function google_search_fix(){
  563. jQuery('#txnEdit-toggle').on('click', function(){
  564. // get the text; don't try decoding the partial original search link
  565. plain_search = jQuery('a.desc_link strong var').text();
  566.  
  567. // proper encoding
  568. new_search = encodeURIComponent(plain_search);
  569.  
  570. // encoding changes spaces to %20, but that is deprecated now. urls take '+' instead.
  571. new_search = new_search.replace(/%20/gi, '+');
  572.  
  573. // replace old url with new
  574. jQuery('a.desc_link').attr('href', 'https://www.google.com/#q=' + new_search);
  575. });
  576. }
  577. ////// Start jQuery Addon
  578. if (!(jQuery.fn.arrive)){
  579. /*
  580. * arrive.js
  581. * v2.1.0
  582. * https://github.com/uzairfarooq/arrive
  583. * MIT licensed
  584. *
  585. * Copyright (c) 2014-2015 Uzair Farooq
  586. */
  587. var _arrive_unique_id_=0;
  588. (function(n,u,p){function h(a){return a._shouldBeIgnored===p?-1!=(" "+a.className+" ").indexOf(" ignore-arrive ")?a._shouldBeIgnored=!0:null==a.parentNode?a._shouldBeIgnored=!1:a._shouldBeIgnored=h(a.parentNode):a._shouldBeIgnored}function q(a,c,e){for(var d=0,b;b=a[d];d++)h(b)||(f.matchesSelector(b,c.selector)&&(b._id===p&&(b._id=_arrive_unique_id_++),-1==c.firedElems.indexOf(b._id)&&(c.firedElems.push(b._id),e.push({callback:c.callback,elem:b}))),0<b.childNodes.length&&q(b.childNodes,c,e))}function v(a){for(var c=
  589. 0,e;e=a[c];c++)e.callback.call(e.elem)}function x(a,c){a.forEach(function(a){if(!h(a.target)){var d=a.addedNodes,b=a.target,r=[];null!==d&&0<d.length?q(d,c,r):"attributes"===a.type&&f.matchesSelector(b,c.selector)&&(b._id===p&&(b._id=_arrive_unique_id_++),-1==c.firedElems.indexOf(b._id)&&(c.firedElems.push(b._id),r.push({callback:c.callback,elem:b})));v(r)}})}function y(a,c){a.forEach(function(a){if(!h(a.target)){a=a.removedNodes;var d=[];null!==a&&0<a.length&&q(a,c,d);v(d)}})}function z(a){var c=
  590. {attributes:!1,childList:!0,subtree:!0};a.fireOnAttributesModification&&(c.attributes=!0);return c}function A(a){return{childList:!0,subtree:!0}}function g(a){a.arrive=k.bindEvent;f.addMethod(a,"unbindArrive",k.unbindEvent);f.addMethod(a,"unbindArrive",k.unbindEventWithSelectorOrCallback);f.addMethod(a,"unbindArrive",k.unbindEventWithSelectorAndCallback);a.leave=l.bindEvent;f.addMethod(a,"unbindLeave",l.unbindEvent);f.addMethod(a,"unbindLeave",l.unbindEventWithSelectorOrCallback);f.addMethod(a,"unbindLeave",
  591. l.unbindEventWithSelectorAndCallback)}if(n.MutationObserver&&"undefined"!==typeof HTMLElement){var f=function(){var a=HTMLElement.prototype.matches||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector;return{matchesSelector:function(c,e){return c instanceof HTMLElement&&a.call(c,e)},addMethod:function(a,e,d){var b=a[e];a[e]=function(){if(d.length==arguments.length)return d.apply(this,arguments);if("function"==typeof b)return b.apply(this,
  592. arguments)}}}}(),B=function(){var a=function(){this._eventsBucket=[];this._beforeRemoving=this._beforeAdding=null};a.prototype.addEvent=function(a,e,d,b){a={target:a,selector:e,options:d,callback:b,firedElems:[]};this._beforeAdding&&this._beforeAdding(a);this._eventsBucket.push(a);return a};a.prototype.removeEvent=function(a){for(var e=this._eventsBucket.length-1,d;d=this._eventsBucket[e];e--)a(d)&&(this._beforeRemoving&&this._beforeRemoving(d),this._eventsBucket.splice(e,1))};a.prototype.beforeAdding=
  593. function(a){this._beforeAdding=a};a.prototype.beforeRemoving=function(a){this._beforeRemoving=a};return a}(),w=function(a,c,e){function d(a){"number"!==typeof a.length&&(a=[a]);return a}var b=new B;b.beforeAdding(function(b){var c=b.target,d;if(c===n.document||c===n)c=document.getElementsByTagName("html")[0];d=new MutationObserver(function(a){e.call(this,a,b)});var m=a(b.options);d.observe(c,m);b.observer=d});b.beforeRemoving(function(a){a.observer.disconnect()});this.bindEvent=function(a,e,t){"undefined"===
  594. typeof t&&(t=e,e=c);for(var m=d(this),f=0;f<m.length;f++)b.addEvent(m[f],a,e,t)};this.unbindEvent=function(){var a=d(this);b.removeEvent(function(b){for(var c=0;c<a.length;c++)if(b.target===a[c])return!0;return!1})};this.unbindEventWithSelectorOrCallback=function(a){var c=d(this);b.removeEvent("function"===typeof a?function(b){for(var d=0;d<c.length;d++)if(b.target===c[d]&&b.callback===a)return!0;return!1}:function(b){for(var d=0;d<c.length;d++)if(b.target===c[d]&&b.selector===a)return!0;return!1})};
  595. this.unbindEventWithSelectorAndCallback=function(a,c){var e=d(this);b.removeEvent(function(b){for(var d=0;d<e.length;d++)if(b.target===e[d]&&b.selector===a&&b.callback===c)return!0;return!1})};return this},k=new w(z,{fireOnAttributesModification:!1},x),l=new w(A,{},y);u&&g(u.fn);g(HTMLElement.prototype);g(NodeList.prototype);g(HTMLCollection.prototype);g(HTMLDocument.prototype);g(Window.prototype)}})(this,"undefined"===typeof jQuery?null:jQuery);
  596.  
  597. }
  598. ////// End jQuery Addon
  599. jQuery(document).arrive("#txnEdit-category_input", add_dropdown_hook);
  600. jQuery(document).arrive("#txnEdit-toggle", google_search_fix);
  601.  
  602. }
  603.  
  604.  
  605. /**
  606. * Mint.com loads jquery after page is loaded, and it conflicts with other verions. We can't
  607. * use a sandbox for our script, either, since we must use their version of jquery in order
  608. * to hook into their ajax completion events.
  609. * Therefore, we must manually check for jquery every so often (50 ms) until it finally exists.
  610. * Then, we can call our jquery-requiring function and modify the page.
  611. * @param method
  612. */
  613. function defer(method) {
  614. if (window.jQuery) {
  615. method();
  616. } else {
  617. setTimeout(function () {
  618. defer(method);
  619. }, 50);
  620. }
  621. }
  622. window.addEventListener('load', function () {
  623. defer(after_jquery);
  624. });
  625. }());
  626.