Skip to content

Commit 30e07cd

Browse files
committed
use Stimulus for story views
1 parent 7629d34 commit 30e07cd

File tree

17 files changed

+414
-1308
lines changed

17 files changed

+414
-1308
lines changed

.eslint_todo.ts

Lines changed: 5 additions & 401 deletions
Large diffs are not rendered by default.

app/javascript/application.ts

Lines changed: 9 additions & 317 deletions
Original file line numberDiff line numberDiff line change
@@ -1,329 +1,21 @@
1-
// @ts-nocheck
21
import "@hotwired/turbo-rails";
32
import "@rails/activestorage";
43
import "jquery";
4+
// @ts-expect-error — Bootstrap 3 has no type declarations
55
import "bootstrap";
6-
import "mousetrap";
7-
import "jquery-visible";
8-
import _ from "underscore";
9-
import Backbone from "backbone";
106

117
import "./controllers/index";
128

13-
Turbo.session.drive = false;
14-
15-
/* global jQuery, Mousetrap */
16-
var $ = jQuery;
17-
18-
window.$ = $;
19-
20-
Backbone.$ = $;
21-
22-
_.templateSettings = {
23-
interpolate: /\{\{=(.+?)\}\}/g,
24-
evaluate: /\{\{(.+?)\}\}/g
25-
};
26-
27-
function CSRFToken() {
28-
const tokenTag = document.getElementsByName('csrf-token')[0];
29-
30-
return (tokenTag && tokenTag.content) || '';
31-
}
32-
33-
function requestHeaders() {
34-
return { 'X-CSRF-Token': CSRFToken() };
35-
}
36-
37-
var Story = Backbone.Model.extend({
38-
defaults: function() {
39-
return {
40-
"open" : false,
41-
"selected" : false
42-
}
43-
},
44-
45-
toggle: function() {
46-
if (this.get("open")) {
47-
this.close();
48-
} else {
49-
this.open();
50-
}
51-
},
52-
53-
shouldSave: function() {
54-
return this.changedAttributes() && this.get("id") > 0;
55-
},
56-
57-
open: function() {
58-
if (!this.get("keep_unread")) this.set("is_read", true);
59-
if (this.shouldSave()) this.save(null, { headers: requestHeaders() });
60-
61-
if(this.collection){
62-
this.collection.closeOthers(this);
63-
this.collection.unselectAll();
64-
this.collection.setSelection(this);
65-
}
66-
67-
this.set("open", true);
68-
this.set("selected", true);
69-
},
70-
71-
toggleKeepUnread: function() {
72-
if (this.get("keep_unread")) {
73-
this.set("keep_unread", false);
74-
this.set("is_read", true);
75-
} else {
76-
this.set("keep_unread", true);
77-
this.set("is_read", false);
78-
}
79-
80-
if (this.shouldSave()) this.save(null, { headers: requestHeaders() });
81-
},
82-
83-
close: function() {
84-
this.set("open", false);
85-
},
86-
87-
select: function() {
88-
if(this.collection) this.collection.unselectAll();
89-
this.set("selected", true);
90-
},
91-
92-
unselect: function() {
93-
this.set("selected", false);
94-
},
95-
96-
openInTab: function() {
97-
window.open(this.get("permalink"), '_blank');
9+
declare global {
10+
interface JQuery {
11+
modal: (action: string) => JQuery;
9812
}
99-
});
100-
101-
var StoryView = Backbone.View.extend({
102-
tagName: "li",
103-
className: "story",
104-
105-
template: "#story-template",
106-
107-
events: {
108-
"click .story-preview" : "storyClicked"
109-
},
110-
111-
initialize: function() {
112-
this.template = _.template($(this.template).html());
113-
this.listenTo(this.model, 'add', this.render);
114-
this.listenTo(this.model, 'change:selected', this.itemSelected);
115-
this.listenTo(this.model, 'change:open', this.itemOpened);
116-
this.listenTo(this.model, 'change:is_read', this.itemRead);
117-
this.el.addEventListener('keep-unread-toggle:toggled', (e) => {
118-
var detail = e.detail;
119-
this.model.set({keep_unread: detail.keepUnread, is_read: detail.isRead}, {silent: true});
120-
this.model.trigger('change:is_read');
121-
});
122-
},
123-
124-
render: function() {
125-
var jsonModel = this.model.toJSON();
126-
this.$el.html(this.template(jsonModel));
127-
if (jsonModel.is_read) {
128-
this.$el.addClass('read');
129-
}
130-
if (jsonModel.keep_unread) {
131-
this.$el.addClass('keepUnread');
132-
}
133-
Object.assign(this.el.dataset, {
134-
controller: "star-toggle keep-unread-toggle",
135-
keepUnreadToggleIdValue: String(jsonModel.id),
136-
keepUnreadToggleIsReadValue: String(jsonModel.is_read),
137-
keepUnreadToggleKeepUnreadValue: String(jsonModel.keep_unread),
138-
starToggleIdValue: String(jsonModel.id),
139-
starToggleStarredValue: String(jsonModel.is_starred),
140-
});
141-
return this;
142-
},
143-
144-
itemRead: function() {
145-
this.$el.toggleClass("read", this.model.get("is_read"));
146-
},
147-
148-
itemOpened: function() {
149-
if (this.model.get("open")) {
150-
this.$el.addClass("open");
151-
$(".story-lead", this.$el).fadeOut(1000);
152-
window.scrollTo(0, this.$el.offset().top);
153-
} else {
154-
this.$el.removeClass("open");
155-
$(".story-lead", this.$el).show();
156-
}
157-
},
158-
159-
itemSelected: function() {
160-
this.$el.toggleClass("cursor", this.model.get("selected"));
161-
if (!this.$el.visible()) window.scrollTo(0, this.$el.offset().top);
162-
},
163-
164-
storyClicked: function(e) {
165-
if (e.metaKey || e.ctrlKey || e.which == 2) {
166-
var backgroundTab = window.open(this.model.get("permalink"));
167-
if (backgroundTab) backgroundTab.blur();
168-
window.focus();
169-
if (!this.model.get("keep_unread")) this.model.set("is_read", true);
170-
if (this.model.shouldSave()) this.model.save(null, { headers: requestHeaders() });
171-
} else {
172-
this.model.toggle();
173-
window.scrollTo(0, this.$el.offset().top);
174-
}
175-
},
176-
177-
});
178-
179-
var StoryList = Backbone.Collection.extend({
180-
model: Story,
181-
url: "/stories",
182-
183-
initialize: function() {
184-
this.cursorPosition = -1;
185-
},
186-
187-
max_position: function() {
188-
return this.length - 1;
189-
},
190-
191-
unreadCount: function() {
192-
return this.where({is_read: false}).length;
193-
},
194-
195-
closeOthers: function(modelToSkip) {
196-
this.each(function(model) {
197-
if (model.id != modelToSkip.id) {
198-
model.close();
199-
}
200-
});
201-
},
202-
203-
selected: function() {
204-
return this.where({selected: true});
205-
},
206-
207-
unselectAll: function() {
208-
_.invoke(this.selected(), "unselect");
209-
},
210-
211-
selectedStoryId: function() {
212-
var selectedStory = this.at(this.cursorPosition);
213-
return selectedStory ? selectedStory.id : -1;
214-
},
215-
216-
setSelection: function(model) {
217-
this.cursorPosition = this.indexOf(model);
218-
},
219-
220-
moveCursorDown: function() {
221-
if (this.cursorPosition < this.max_position()) {
222-
this.cursorPosition++;
223-
} else {
224-
this.cursorPosition = 0;
225-
}
226-
227-
this.at(this.cursorPosition).select();
228-
},
229-
230-
moveCursorUp: function() {
231-
if (this.cursorPosition > 0) {
232-
this.cursorPosition--;
233-
} else {
234-
this.cursorPosition = this.max_position();
235-
}
236-
237-
this.at(this.cursorPosition).select();
238-
},
239-
240-
openCurrentSelection: function() {
241-
this.at(this.cursorPosition).open();
242-
},
243-
244-
toggleCurrent: function() {
245-
if (this.cursorPosition < 0) this.cursorPosition = 0;
246-
this.at(this.cursorPosition).toggle();
247-
},
248-
249-
viewCurrentInTab: function() {
250-
if (this.cursorPosition < 0) this.cursorPosition = 0;
251-
this.at(this.cursorPosition).openInTab();
252-
},
253-
254-
toggleCurrentKeepUnread: function() {
255-
if (this.cursorPosition < 0) this.cursorPosition = 0;
256-
this.at(this.cursorPosition).toggleKeepUnread();
257-
}
258-
});
259-
260-
var AppView = Backbone.View.extend({
261-
el: "#stories",
262-
263-
initialize: function(collection) {
264-
this.stories = collection;
265-
this.el = $(this.el);
266-
267-
this.listenTo(this.stories, 'add', this.addOne);
268-
this.listenTo(this.stories, 'reset', this.addAll);
269-
this.listenTo(this.stories, 'all', this.render);
270-
},
271-
272-
loadData: function(data) {
273-
this.stories.reset(data);
274-
},
275-
276-
render: function() {
277-
var unreadCount = this.stories.unreadCount();
278-
279-
if (unreadCount === 0) {
280-
document.title = window.i18n.titleName;
281-
} else {
282-
document.title = "(" + unreadCount + ") " + window.i18n.titleName;
283-
}
284-
},
285-
286-
addOne: function(story) {
287-
var view = new StoryView({model: story});
288-
this.$("#story-list").append(view.render().el);
289-
},
290-
291-
addAll: function() {
292-
this.stories.each(this.addOne, this);
293-
},
294-
295-
moveCursorDown: function() {
296-
this.stories.moveCursorDown();
297-
},
298-
299-
moveCursorUp: function() {
300-
this.stories.moveCursorUp();
301-
},
302-
303-
openCurrentSelection: function() {
304-
this.stories.openCurrentSelection();
305-
},
306-
307-
toggleCurrent: function() {
308-
this.stories.toggleCurrent();
309-
},
13+
}
31014

311-
viewCurrentInTab: function() {
312-
this.stories.viewCurrentInTab();
313-
},
15+
Turbo.session.drive = false;
31416

315-
toggleCurrentKeepUnread: function() {
316-
this.stories.toggleCurrentKeepUnread();
17+
document.addEventListener("keydown", (event: KeyboardEvent) => {
18+
if (event.key === "?") {
19+
jQuery("#shortcuts").modal("toggle");
31720
}
31821
});
319-
320-
$(document).ready(function() {
321-
Mousetrap.bind("?", function() {
322-
$("#shortcuts").modal('toggle');
323-
});
324-
});
325-
326-
window.StoryList = StoryList;
327-
window.AppView = AppView;
328-
329-
export { Story, StoryView, StoryList, AppView };

app/javascript/controllers/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ application.register("mark-all-as-read", MarkAllAsReadController);
2020

2121
import StarToggleController from "./star_toggle_controller";
2222
application.register("star-toggle", StarToggleController);
23+
24+
import StoryController from "./story_controller";
25+
application.register("story", StoryController);
26+
27+
import StoryListController from "./story_list_controller";
28+
application.register("story-list", StoryListController);

0 commit comments

Comments
 (0)