Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
406 changes: 5 additions & 401 deletions .eslint_todo.ts

Large diffs are not rendered by default.

326 changes: 9 additions & 317 deletions app/javascript/application.ts
Original file line number Diff line number Diff line change
@@ -1,329 +1,21 @@
// @ts-nocheck
import "@hotwired/turbo-rails";
import "@rails/activestorage";
import "jquery";
// @ts-expect-error — Bootstrap 3 has no type declarations
import "bootstrap";
import "mousetrap";
import "jquery-visible";
import _ from "underscore";
import Backbone from "backbone";

import "./controllers/index";

Turbo.session.drive = false;

/* global jQuery, Mousetrap */
var $ = jQuery;

window.$ = $;

Backbone.$ = $;

_.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() });
},

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');
declare global {
interface JQuery {
modal: (action: string) => JQuery;
}
});

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

template: "#story-template",

events: {
"click .story-preview" : "storyClicked"
},

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.el.addEventListener('keep-unread-toggle:toggled', (e) => {
var detail = e.detail;
this.model.set({keep_unread: detail.keepUnread, is_read: detail.isRead}, {silent: true});
this.model.trigger('change:is_read');
});
},

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');
}
Object.assign(this.el.dataset, {
controller: "star-toggle keep-unread-toggle",
keepUnreadToggleIdValue: String(jsonModel.id),
keepUnreadToggleIsReadValue: String(jsonModel.is_read),
keepUnreadToggleKeepUnreadValue: String(jsonModel.keep_unread),
starToggleIdValue: String(jsonModel.id),
starToggleStarredValue: String(jsonModel.is_starred),
});
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);
},

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);
}
},

});

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();
}
});

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();
},
Turbo.session.drive = false;

toggleCurrentKeepUnread: function() {
this.stories.toggleCurrentKeepUnread();
document.addEventListener("keydown", (event: KeyboardEvent) => {
if (event.key === "?") {
jQuery("#shortcuts").modal("toggle");
}
});

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

window.StoryList = StoryList;
window.AppView = AppView;

export { Story, StoryView, StoryList, AppView };
6 changes: 6 additions & 0 deletions app/javascript/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ application.register("mark-all-as-read", MarkAllAsReadController);

import StarToggleController from "./star_toggle_controller";
application.register("star-toggle", StarToggleController);

import StoryController from "./story_controller";
application.register("story", StoryController);

import StoryListController from "./story_list_controller";
application.register("story-list", StoryListController);
Loading