stringer-rss/stringer

View on GitHub
app/assets/javascripts/application.js

Summary

Maintainability
C
7 hrs
Test Coverage
//= require jquery-min.js
//= require bootstrap-min.js
//= require bootstrap.file-input.js
//= require mousetrap-min.js
//= require jquery-visible-min.js
//= require underscore-min.js
//= require backbone-min.js

_.templateSettings = {
  interpolate: /\{\{\=(.+?)\}\}/g,
  evaluate: /\{\{(.+?)\}\}/g
};

function CSRFToken() {
  const tokenTag = document.getElementsByName('csrf-token')[0];

  return (tokenTag && tokenTag.content) || '';
}

function requestHeaders() {
  return { 'X-CSRF-Token': CSRFToken() };
}

var Story = Backbone.Model.extend({
  defaults: function() {
    return {
      "open" : false,
      "selected" : false
    }
  },

  toggle: function() {
    if (this.get("open")) {
      this.close();
    } else {
      this.open();
    }
  },

  shouldSave: function() {
    return this.changedAttributes() && this.get("id") > 0;
  },

  open: function() {
    if (!this.get("keep_unread")) this.set("is_read", true);
    if (this.shouldSave()) this.save(null, { headers: requestHeaders() });

    if(this.collection){
      this.collection.closeOthers(this);
      this.collection.unselectAll();
      this.collection.setSelection(this);
    }

    this.set("open", true);
    this.set("selected", true);
  },

  toggleKeepUnread: function() {
    if (this.get("keep_unread")) {
      this.set("keep_unread", false);
      this.set("is_read", true);
    } else {
      this.set("keep_unread", true);
      this.set("is_read", false);
    }

    if (this.shouldSave()) this.save(null, { headers: requestHeaders() });
  },

  toggleStarred: function() {
    if (this.get("is_starred")) {
      this.set("is_starred", false);
    } else {
      this.set("is_starred", true);
    }

    if (this.shouldSave()) this.save(null, { headers: requestHeaders() });
  },

  close: function() {
    this.set("open", false);
  },

  select: function() {
    if(this.collection) this.collection.unselectAll();
    this.set("selected", true);
  },

  unselect: function() {
    this.set("selected", false);
  },

  openInTab: function() {
    window.open(this.get("permalink"), '_blank');
  }
});

var StoryView = Backbone.View.extend({
  tagName: "li",
  className: "story",

  template: "#story-template",

  events: {
    "click .story-preview" : "storyClicked",
    "click .story-keep-unread" : "toggleKeepUnread",
    "click .story-starred" : "toggleStarred"
  },

  initialize: function() {
    this.template = _.template($(this.template).html());
    this.listenTo(this.model, 'add', this.render);
    this.listenTo(this.model, 'change:selected', this.itemSelected);
    this.listenTo(this.model, 'change:open', this.itemOpened);
    this.listenTo(this.model, 'change:is_read', this.itemRead);
    this.listenTo(this.model, 'change:keep_unread', this.itemKeepUnread);
    this.listenTo(this.model, 'change:is_starred', this.itemStarred);
  },

  render: function() {
    var jsonModel = this.model.toJSON();
    this.$el.html(this.template(jsonModel));
    if (jsonModel.is_read) {
      this.$el.addClass('read');
    }
    if (jsonModel.keep_unread) {
      this.$el.addClass('keepUnread');
    }
    return this;
  },

  itemRead: function() {
    this.$el.toggleClass("read", this.model.get("is_read"));
  },

  itemOpened: function() {
    if (this.model.get("open")) {
      this.$el.addClass("open");
      $(".story-lead", this.$el).fadeOut(1000);
      window.scrollTo(0, this.$el.offset().top);
    } else {
      this.$el.removeClass("open");
      $(".story-lead", this.$el).show();
    }
  },

  itemSelected: function() {
    this.$el.toggleClass("cursor", this.model.get("selected"));
    if (!this.$el.visible()) window.scrollTo(0, this.$el.offset().top);
  },

  itemKeepUnread: function() {
    var icon = this.model.get("keep_unread") ? "icon-check" : "icon-check-empty";
    this.$(".story-keep-unread > i").attr("class", icon);
    this.$el.toggleClass("keepUnread", this.model.get("keep_unread"));
  },

  itemStarred: function() {
    var icon = this.model.get("is_starred") ? "icon-star" : "icon-star-empty";
    this.$(".story-starred > i").attr("class", icon);
  },

  storyClicked: function(e) {
    if (e.metaKey || e.ctrlKey || e.which == 2) {
      var backgroundTab = window.open(this.model.get("permalink"));
      if (backgroundTab) backgroundTab.blur();
      window.focus();
      if (!this.model.get("keep_unread")) this.model.set("is_read", true);
      if (this.model.shouldSave()) this.model.save(null, { headers: requestHeaders() });
    } else {
      this.model.toggle();
      window.scrollTo(0, this.$el.offset().top);
    }
  },

  toggleKeepUnread: function() {
    this.model.toggleKeepUnread();
  },

  toggleStarred: function(e) {
    e.stopPropagation();
    this.model.toggleStarred();
  }
});

var StoryList = Backbone.Collection.extend({
  model: Story,
  url: "/stories",

  initialize: function() {
    this.cursorPosition = -1;
  },

  max_position: function() {
    return this.length - 1;
  },

  unreadCount: function() {
    return this.where({is_read: false}).length;
  },

  closeOthers: function(modelToSkip) {
    this.each(function(model) {
      if (model.id != modelToSkip.id) {
        model.close();
      }
    });
  },

  selected: function() {
    return this.where({selected: true});
  },

  unselectAll: function() {
    _.invoke(this.selected(), "unselect");
  },

  selectedStoryId: function() {
    var selectedStory = this.at(this.cursorPosition);
    return selectedStory ? selectedStory.id : -1;
  },

  setSelection: function(model) {
    this.cursorPosition = this.indexOf(model);
  },

  moveCursorDown: function() {
    if (this.cursorPosition < this.max_position()) {
      this.cursorPosition++;
    } else {
      this.cursorPosition = 0;
    }

    this.at(this.cursorPosition).select();
  },

  moveCursorUp: function() {
    if (this.cursorPosition > 0) {
      this.cursorPosition--;
    } else {
      this.cursorPosition = this.max_position();
    }

    this.at(this.cursorPosition).select();
  },

  openCurrentSelection: function() {
    this.at(this.cursorPosition).open();
  },

  toggleCurrent: function() {
    if (this.cursorPosition < 0) this.cursorPosition = 0;
    this.at(this.cursorPosition).toggle();
  },

  viewCurrentInTab: function() {
    if (this.cursorPosition < 0) this.cursorPosition = 0;
    this.at(this.cursorPosition).openInTab();
  },

  toggleCurrentKeepUnread: function() {
    if (this.cursorPosition < 0) this.cursorPosition = 0;
    this.at(this.cursorPosition).toggleKeepUnread();
  },

  toggleCurrentStarred: function() {
    if (this.cursorPosition < 0) this.cursorPosition = 0;
    this.at(this.cursorPosition).toggleStarred();
  }
});

var AppView = Backbone.View.extend({
  el: "#stories",

  initialize: function(collection) {
    this.stories = collection;
    this.el = $(this.el);

    this.listenTo(this.stories, 'add', this.addOne);
    this.listenTo(this.stories, 'reset', this.addAll);
    this.listenTo(this.stories, 'all', this.render);
  },

  loadData: function(data) {
    this.stories.reset(data);
  },

  render: function() {
    var unreadCount = this.stories.unreadCount();

    if (unreadCount === 0) {
      document.title = window.i18n.titleName;
    } else {
      document.title = "(" + unreadCount + ") " + window.i18n.titleName;
    }
  },

  addOne: function(story) {
    var view = new StoryView({model: story});
    this.$("#story-list").append(view.render().el);
  },

  addAll: function() {
    this.stories.each(this.addOne, this);
  },

  moveCursorDown: function() {
    this.stories.moveCursorDown();
  },

  moveCursorUp: function() {
    this.stories.moveCursorUp();
  },

  openCurrentSelection: function() {
    this.stories.openCurrentSelection();
  },

  toggleCurrent: function() {
    this.stories.toggleCurrent();
  },

  viewCurrentInTab: function() {
    this.stories.viewCurrentInTab();
  },

  toggleCurrentKeepUnread: function() {
    this.stories.toggleCurrentKeepUnread();
  },

  toggleCurrentStarred: function() {
    this.stories.toggleCurrentStarred();
  }
});

$(document).ready(function() {
  Mousetrap.bind("?", function() {
    $("#shortcuts").modal('toggle');
  });
});