Filmtipset favorite lists

Makes it possible to highligt movies that are present in pre-selected lists.

2015-02-16 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name       Filmtipset favorite lists
// @namespace  https://github.com/Row/filmtipset-userscripts
// @version    0.6
// @description Makes it possible to highligt movies that are present in pre-selected lists.
// @match      http://nyheter24.se/filmtipset/*
// @copyright  2014+, Row
// ==/UserScript==

/* jQuery from site */
var $ = unsafeWindow.jQuery,
  jQuery = unsafeWindow.jQuery;

/**
 * Unique for array
 */
Array.prototype.unique = function() {
  var u = {},
    a = [];
  for (var i = 0, l = this.length; i < l; ++i) {
    if (u.hasOwnProperty(this[i])) {
      continue;
    }
    a.push(this[i]);
    u[this[i]] = 1;
  }
  return a;
};

function ListHandler()
{
  var STORAGE_KEY = "filmtipsetLists",
    lists,
    dfds = [],
    /**
     * Private
     */
    generateListUrl = function(listId, memberId, pageOffset)
    {
      return "http://nyheter24.se/filmtipset/yourpage.cgi?member=" + memberId
             + "&page=package_view&package=" + listId
             + "&page_nr=" + pageOffset;
    },
    persist = function()
    {
      GM_setValue(STORAGE_KEY, JSON.stringify(lists)); /* Prototype breaks stringify, this is handled on parse */
    },
    collectObjects = function(htmlData, listId)
    {
      lists[listId].objects = lists[listId].objects.concat(parseObjects(htmlData, listId));
    },
    collectListInfo = function(htmlData, listId)
    {
      var pageOffsetsCount = [],
        url, i;
      htmlData.replace(
        /page_nr=(\d+)/gm,
        function(m, n) {
          if (n != 1)
            pageOffsetsCount.push(n);
        }
      );
      pageOffsetsCount = pageOffsetsCount.unique();
      for (i = 0; i < pageOffsetsCount.length; i++) {
        var list = lists[listId];
        url = generateListUrl(listId, list.memberId, pageOffsetsCount[i]);
        dfds.push($.get(url, function(data) {
          collectObjects(data, listId);
        }));
      }
      collectObjects(htmlData, listId, dfds);
      $.when.apply(window, dfds).done(function() {
        window.setTimeout(persist, 100);
        dfds = [];
      });
    },
    parseObjects = function(htmlData, listId)
    {
      var list = [];

      htmlData.replace(
        /'info_(\d+)'/gm,
        function(m, n) {
          list.push(n);
        }
      );
      return list.unique();
    },
    updateLists = function()
    {
      var listId,
        offset = 60 * 60 * 24 * 1000, // Milliseconds
        nextUpdate = new Date().getTime() - offset;
      for (listId in lists) {
        if (!lists.hasOwnProperty(listId))
          continue;

        var list = lists[listId];
        if (nextUpdate > list.lastUpdate) {
          list.objects = [];
          list.lastUpdate = new Date().getTime();
          var url = generateListUrl(listId, list.memberId, 1);
          $.get(url, function(data) {
            collectListInfo(data, listId)
          });
          break; /* update max one list per page load */
        }
      }
    },
    loadLists = function()
    {
      try {
        lists = JSON.parse(GM_getValue(STORAGE_KEY));

        /* Helvetes, Prototype breaks the native stringify, remove this in the future */
        for (var listId in lists) {
          if (!lists.hasOwnProperty(listId))
            continue;

          var list = lists[listId];
          if (typeof list.objects == "string")
            list.objects = JSON.parse(list.objects);

        }
      } catch (err) {
        console.error("Limited support? Malformed JSON? First run?");
        lists = {};
      }
    };

  /**
   * Public
   */
  this.getLists = function()
  {
    for (listId in lists) {
      if (!lists.hasOwnProperty(listId))
        continue;

      lists[listId].url = generateListUrl(listId, lists[listId].memberId, 1);
    }
    return lists;
  };

  this.addList = function(listId, memberId, title, color)
  {
    loadLists();
    lists[listId] = {
      "title": title,
      "lastUpdate": 0,
      "listId": listId,
      "memberId": memberId,
      "color": color,
      "objects": []
    };
    updateLists();
  };

  this.hardRefresh = function(listId)
  {
    lists[listId].lastUpdate = 0;
    updateLists();
  };

  this.removeList = function(listId)
  {
    console.log("del", listId);
    delete lists[listId];
    persist();
  };

  /**
   * Init
   */
  loadLists();
  updateLists();
}

function renderAdmin(list)
{
  var elBtn, elHld, elCol;
  if (!/package_view/.test(document.location.href))
    return;
  elHld = $('<li class="add-new rightlink" />');
  $("#favoriteLists").append(elHld);
  $('<div class="in-list-admin"></div>').prependTo(elHld);
  elCol = $('<input type="text" value="#FF0000" title="Färgkod på listan t.ex: #FF0000, yellow eller blue" />')
    .on("keyup change", function() {
      $(this).siblings('.in-list-admin').css('background', $(this).val());
    })
  elBtn = $("<button>Spara listan till favoriter</button>")
    .on("click", function() {
      var title, memberId, listId, url, matches;
      url = document.location.href;
      title = $('h1').text();
      matches = /\bmember=(\d+).*?\bpackage=(\d+)/.exec(url);
      memberId = matches[1];
      listId = matches[2];
      list.addList(listId, memberId, title, elCol.val());
      $('<span style="color: green; font-weight:bold">Sparad</span>').insertAfter(elBtn).fadeOut(3000);
    });

  elCol.change().appendTo(elHld);
  elBtn.appendTo(elHld);
}

function renderList(list) 
{
  var elDestination = $("td > div.rightlink").last(),
    ul = $('<ul id="favoriteLists" />')
    .insertAfter(elDestination)
    .on("click", ".delete", function() {
      list.removeList($(this).data('listId'));
      $(this).parent().hide();
    })
    .on('click', '.refresh', function() {
      list.hardRefresh($(this).data('listId'));
    });
  lists = list.getLists();
  $('<div class="rightlinkheader">Favoritlistor</div>').insertAfter(elDestination);

  for (var listId in lists) {
    if (!lists.hasOwnProperty(listId))
      continue;
    var l = lists[listId];
    var li = $('<li class="rightlink">').appendTo(ul);
    $('<a>').text(l.title).attr('href', l.url).appendTo(li);
    $('<div class="in-list-admin"></div>').css('background', l.color).prependTo(li);
    $('<button class="refresh">↻</button>').data("listId", listId).appendTo(li);
    $('<button class="delete">X</button>').data("listId", listId).appendTo(li);
  }

}

function renderMarkers(lists) 
{
  var ll = lists.getLists();
  var offset = 0;
  for (listId in ll) {
    if (!ll.hasOwnProperty(listId))
      continue;
    var list = ll[listId]
    for (var i = 0; i < list.objects.length; i++) {
      var elTarget = $("#info_" + list.objects[i]);
      if (elTarget.length) {
        $('<div class="in-list"></div>')
          .css('background', list.color)
          .css('left', offset + 'px')
          .appendTo(elTarget.siblings('.row').first());
      }
    }
    offset += 7;
  }
}

/* Init and render */
GM_addStyle(
  '#favoriteLists {padding: 0 0 0 10px}'
  + '#favoriteLists>li {display: block;position:relative;}'
  + '#favoriteLists .delete {position:absolute;right:0;top:0;}'
  + '#favoriteLists .refresh {position:absolute;right: 24px;top: -1px;}'
  + '#favoriteLists>li button {display: none;}'
  + '#favoriteLists>li:hover button, #favoriteLists>li.add-new button {display: block;margin-top: 2px}'
  + '.in-list, .in-list-admin {z-index: 6;border: 1px solid #000000; border-radius: 4px;width: 8px; height: 8px;position: absolute}'
  + '.in-list {margin-left: 300px;top: 3px;}'
  + '.in-list-admin {margin-left: -12px; top:6px;}'
);

var list = new ListHandler();
renderList(list);
renderMarkers(list);
renderAdmin(list);