HSBC - Better Account History

Better readability, forever transactions retention and a chart display

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           HSBC - Better Account History
// @description    Better readability, forever transactions retention and a chart display
// @namespace      http://github.com/jobwat/
// @author         Joseph Boiteau
// @version        2014.11.15
// @homepage       https://github.com/jobwat/hsbc-account-history-rerender.user.js
// @include        https://*.hsbc.com/*
// @include        https://*.hsbc.com.au/*
// @include        https://*.hsbc.co.uk/*
// @grant          none
// @require        https://code.jquery.com/jquery-1.11.1.min.js
// @require        http://cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js
// @require        http://cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.time.min.js
// ==/UserScript==

// this script is only for the "Account History" page
if(document.getElementsByTagName('title')[0].text.match("Account History")){

  // centering function used in final display
  // Thx Tony L. - http://stackoverflow.com/questions/210717/what-is-the-best-way-to-center-a-div-on-the-screen-using-jquery
  $.fn.center = function () {
    this.css("position","absolute");
    this.css("top", ( $(window).height() - this.height() ) / 2+$(window).scrollTop() + "px");
    this.css("left", ( $(window).width() - this.width() ) / 2+$(window).scrollLeft() + "px");
    return this;
  };

  // set some style (no need to have wide spaces around lines)
  $('<style type="text/css">table.hsbcTableStyle07 tr td{ padding:0px 5px 0px 5px; line-height: 1em; white-space: nowrap; } </style>').prependTo($('head'));

  var graphData = [];   // array containing values for the graph
  var the_table = $("table.hsbcTableStyle07");  // the interesting table object of the page

  // We will first go through all lines of the table
  // Get their content, load our objects and rewrite all, but lighter

  clearTable = function(){
    trs = $('tr', the_table);
    trs.each(function(index, tr){ if(index!==0 && index!=(trs.length-1)){ $(tr).addClass('to_del'); } });
    $('tr.to_del', the_table).remove();
  };

  Line = function() {
    this.timestamp = undefined;
  };
  Line.prototype.populate = function(line_data_array) {
    this.timestamp = line_data_array.timestamp;
    this.details = line_data_array.details;
    this.debit = line_data_array.debit;
    this.credit = line_data_array.credit;
    this.balance = line_data_array.balance;
  };
  Line.prototype.setDate = function(dateString) {
    this.timestamp = new Date(dateString).getTime();
  };
  Line.prototype.hasDate = function() {
    return !(this.timestamp === undefined || isNaN(this.timestamp));
  };
  Line.prototype.getDate = function(){ // reformat the date column
    if(this.hasDate()){
      date = new Date(this.timestamp);
      return '<span style="float:right">'+weekdayArray[date.getDay()]+', '+date.getDate()+' '+monthArray[date.getMonth()].slice(0,3)+' '+date.getFullYear()+'</span>';
    }
    else{
      return undefined;
    }
  };
  Line.prototype.setDetails = function(detailsHTML) { // remove some non-useful data from details column and make it 1 line only
    tmp=detailsHTML.split('<br>');
    this.details = this.cleanStrings(tmp[0] + ' - ' + tmp[2] + ' - ' + tmp[3] + ' ' + tmp[4]);
  };
  Line.prototype.getDetails = function(){
    return this.details;
  };
  Line.prototype.setDebit = function(debit){
    this.debit = this.cleanAmounts(debit);
  };
  Line.prototype.getDebit = function(){
    return this.displayDigits(this.emptyIfZero(this.debit));
  };
  Line.prototype.setCredit = function(credit){
    this.credit = this.cleanAmounts(credit);
  };
  Line.prototype.getCredit = function(){
    return this.displayDigits(this.emptyIfZero(this.credit));
  };
  Line.prototype.setBalance = function(balance){
    this.balance = this.cleanAmounts(balance);
  };
  Line.prototype.getBalance = function(){
    return this.displayDigits(this.balance);
  };
  Line.prototype.cleanAmounts = function(value){
    tmp = parseFloat(this.cleanStrings(value).replace(/[^0-9\.]/g,''));
    if(isNaN(tmp)) return 0;
    else return tmp;
  };
  Line.prototype.cleanStrings = function(value){
    return value.replace(/[\t\n\r   ]+/g,' ');
  };
  Line.prototype.displayDigits = function(value){
   return value.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
  };
  Line.prototype.emptyIfZero = function(value){
    if(value===0) return '&nbsp;';
    else return value+'&nbsp;';
  };

  History = function(){
    this.lines = [];
  };
  History.prototype.add = function(line){
    this.lines.push(line);
  };
  History.prototype.toJSON = function(){
    return JSON.stringify(this.lines);
  };
  History.prototype.merge = function(anotherHistory){
    var merged = $.merge(this.lines, anotherHistory.lines);
    merged.sort(function(a,b){
      if(a.timestamp < b.timestamp) return 1;
      else if(a.timestamp > b.timestamp) return -1;
      else{
        if(a.balance == b.balance + b.credit) return 1;
        if(a.balance == b.balance - b.debit) return 1;
        return -1;
      }
    });

    var ind = 1;
    while(ind < merged.length){
      if(merged[ind-1].timestamp == merged[ind].timestamp && merged[ind-1].details == merged[ind].details){
        merged.splice(ind, 1);
      }
      else
        ind++;
    }

    this.lines = merged;
  };
  History.prototype.testMerge = function(){
    console.log('before merge: ' + this.lines.length + 'lines');
    history.merge(this);
    console.log('after merge: ' + this.lines.length + 'lines');
  };
  History.prototype.displayAll = function(){
    clearTable();
    last_tr = $('tr', the_table).last(); // the greyed line with sorting arrows at the bottom

    $(this.lines).each(function(index, line_data_array){
      var line = new Line();
      line.populate(line_data_array);
      graphData.push([line.timestamp, line.balance]);
      /*jshint multistr: true */
      tr = $('<tr class="hsbcTableRow0'+((index%2===0)?'3':'4')+'">\
        <td class="hsbcTableColumn03" headers="header1">'+line.getDate()+'</td>\
        <td class="" headers="header2">'+line.getDetails()+'</td>\
        <td class="hsbcTableColumn03" headers="header3">'+line.getDebit()+'</td>\
        <td class="hsbcTableColumn03" headers="header4">'+line.getCredit()+'</td>\
        <td class="hsbcTableColumn03" headers="header5">'+line.getBalance()+'</td>\
        <td class="hsbcTableColumn03" headers="header6">&nbsp;</td>\
      </tr>');
      tr.insertBefore(last_tr);
    });
  };

  var previous_history = new History();
  var account_history = new History();

  // recover the account name
  var account_name = $('#LongSelection1 option[value="'+$('#LongSelection1').val()+'"]').text().replace(/[^a-zA-Z]/g, '');
  console.log('account_name: ', account_name);

  // recover existing history
  previous_history_JSON = localStorage.getItem(account_name);
  if (previous_history_JSON){
    previous_history.lines = JSON.parse(previous_history_JSON);
    console.log('previous history from localStorage: ', previous_history.lines.length);
  }
  else{
    previous_history.lines = [];
  }

  // Drop the right panel containing mostly ads
  $('.containerRightTools').remove();

  // loop through table lines
  var prev_line = new Line();
  $.each($("tr", the_table), function(TRind, TRval) { // each tr

    line = new Line();

    var mytd = $(this).children("td").each(function(index, td){ // each td

      if(td.headers=="header1"){ // the date
        line.setDate(td.innerHTML);
        if(line.hasDate()){ td.innerHTML=line.getDate(); }
        else { return false; }
      }
      else if(td.headers=="header2"){ // the details
        line.setDetails(td.innerHTML);
        if(line.getDetails() == prev_line.getDetails()){ line.details = prev_line.getDetails() + ' (2x)'; }
        td.innerHTML = line.getDetails();
      }
      else if(td.headers=="header3"){ // debit
        line.setDebit(td.innerHTML);
        td.innerHTML = line.getDebit();
      }
      else if(td.headers=="header4"){ // credit
        line.setCredit(td.innerHTML);
        td.innerHTML = line.getCredit();
      }
      else if(td.headers=="header5"){ // balance
        line.setBalance(td.innerHTML);
        td.innerHTML = line.getBalance();
      }
      else if(td.headers=="header6"){ // empty last column
        td.innerHTML = account_history.lines.length + 1;
      }
    });

    if(line.getDate()!==undefined && line.getBalance()!==undefined){ // the first and last tr of the array are the sorting arrows
      account_history.add(line);
    }

    prev_line = line;

  });
  console.log('lines parsed: ', account_history.lines.length);

  // merging previous history with actual one and save it locally
  //account_history.merge(previous_history);

  // saving new merged history to localStorage
  localStorage.setItem(account_name, JSON.stringify(account_history.lines));
  console.log('lines saved after merge: ', account_history.lines.length);

  // rewrite the whole thing
  account_history.displayAll();

  // 2e part: draw a chart

  // add a div somewhere in the DOM
  $('<div id="chartwrap" style="border: 1px solid #AAA; background-color:white; position: absolute;"></div>').appendTo("body");
  $('<div id="placeholder" style="width:650px; height:300px;"></div>').appendTo("#chartwrap");

  // draw the chart in its div
  // TODO: do way better ! watch here: http://people.iola.dk/olau/flot/examples/stacking.html
  $.plot("#placeholder", [graphData], { xaxis: { mode: "time" } });

  toggleChartDisplay = function(show_or_hide){
      if($("#chartwrap").is(':hidden')) $("#chartwrap").center();
      $("#chartwrap").toggle(show_or_hide);
  };

  // display a button to toggle chart visibility
  $('<a class="hsbcLinkStyle06">Display chart</a>')
    .prependTo($('td.hsbcTableColumn03')[0])
    .css('cursor', 'pointer')
    .click(toggleChartDisplay);

  // click on the chart hides it
  $('#placeholder').click(toggleChartDisplay);

  // start with Chart hidden
  toggleChartDisplay(false);
}