]> git.wh0rd.org Git - tt-rss.git/blob - js/FeedTree.js
feedtree, selectFeed: focus selected node
[tt-rss.git] / js / FeedTree.js
1 require(["dojo/_base/declare", "dijit/tree/ForestStoreModel"], function (declare) {
2
3         return declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
4                 getItemsInCategory: function (id) {
5                         if (!this.store._itemsByIdentity) return undefined;
6
7                         cat = this.store._itemsByIdentity['CAT:' + id];
8
9                         if (cat && cat.items)
10                                 return cat.items;
11                         else
12                                 return undefined;
13
14                 },
15                 getItemById: function (id) {
16                         return this.store._itemsByIdentity[id];
17                 },
18                 getFeedValue: function (feed, is_cat, key) {
19                         if (!this.store._itemsByIdentity) return undefined;
20
21                         if (is_cat)
22                                 treeItem = this.store._itemsByIdentity['CAT:' + feed];
23                         else
24                                 treeItem = this.store._itemsByIdentity['FEED:' + feed];
25
26                         if (treeItem)
27                                 return this.store.getValue(treeItem, key);
28                 },
29                 getFeedName: function (feed, is_cat) {
30                         return this.getFeedValue(feed, is_cat, 'name');
31                 },
32                 getFeedUnread: function (feed, is_cat) {
33                         var unread = parseInt(this.getFeedValue(feed, is_cat, 'unread'));
34                         return (isNaN(unread)) ? 0 : unread;
35                 },
36                 setFeedUnread: function (feed, is_cat, unread) {
37                         return this.setFeedValue(feed, is_cat, 'unread', parseInt(unread));
38                 },
39                 setFeedValue: function (feed, is_cat, key, value) {
40                         if (!value) value = '';
41                         if (!this.store._itemsByIdentity) return undefined;
42
43                         if (is_cat)
44                                 treeItem = this.store._itemsByIdentity['CAT:' + feed];
45                         else
46                                 treeItem = this.store._itemsByIdentity['FEED:' + feed];
47
48                         if (treeItem)
49                                 return this.store.setValue(treeItem, key, value);
50                 },
51                 getNextUnreadFeed: function (feed, is_cat) {
52                         if (!this.store._itemsByIdentity)
53                                 return null;
54
55                         if (is_cat) {
56                                 treeItem = this.store._itemsByIdentity['CAT:' + feed];
57                         } else {
58                                 treeItem = this.store._itemsByIdentity['FEED:' + feed];
59                         }
60
61                         items = this.store._arrayOfAllItems;
62
63                         for (var i = 0; i < items.length; i++) {
64                                 if (items[i] == treeItem) {
65
66                                         for (var j = i + 1; j < items.length; j++) {
67                                                 var unread = this.store.getValue(items[j], 'unread');
68                                                 var id = this.store.getValue(items[j], 'id');
69
70                                                 if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
71                                                         if (!is_cat || !(this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed)) return items[j];
72                                                 }
73                                         }
74
75                                         for (var j = 0; j < i; j++) {
76                                                 var unread = this.store.getValue(items[j], 'unread');
77                                                 var id = this.store.getValue(items[j], 'id');
78
79                                                 if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
80                                                         if (!is_cat || !(this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed)) return items[j];
81                                                 }
82                                         }
83                                 }
84                         }
85
86                         return null;
87                 },
88                 hasCats: function () {
89                         if (this.store && this.store._itemsByIdentity)
90                                 return this.store._itemsByIdentity['CAT:-1'] != undefined;
91                         else
92                                 return false;
93                 },
94
95         });
96 });
97
98 require(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"], function (declare, domConstruct) {
99
100         return declare("fox.FeedTree", dijit.Tree, {
101                 _onKeyPress: function(/* Event */ e) {
102                         return; // Stop dijit.Tree from interpreting keystrokes
103                 },
104                 _createTreeNode: function(args) {
105                         var tnode = new dijit._TreeNode(args);
106
107                         var icon = dojo.doc.createElement('img');
108                         if (args.item.icon && args.item.icon[0]) {
109                                 icon.src = args.item.icon[0];
110                         } else {
111                                 icon.src = 'images/blank_icon.gif';
112                         }
113                         icon.className = 'tinyFeedIcon';
114                         domConstruct.place(icon, tnode.iconNode, 'only');
115
116                         var id = args.item.id[0];
117                         var bare_id = parseInt(id.substr(id.indexOf(':')+1));
118
119                         if (bare_id < _label_base_index) {
120                                 var span = dojo.doc.createElement('span');
121                                 var fg_color = args.item.fg_color[0];
122                                 var bg_color = args.item.bg_color[0];
123
124                                 span.innerHTML = "&alpha;";
125                                 span.className = 'labelColorIndicator';
126                                 span.setStyle({
127                                         color: fg_color,
128                                         backgroundColor: bg_color});
129
130                                 domConstruct.place(span, tnode.iconNode, 'only');
131                         }
132
133                         if (id.match("FEED:")) {
134                                 var menu = new dijit.Menu();
135                                 menu.row_id = bare_id;
136
137                                 menu.addChild(new dijit.MenuItem({
138                                         label: __("Mark as read"),
139                                         onClick: function() {
140                                                 catchupFeed(this.getParent().row_id);
141                                         }}));
142
143                                 if (bare_id > 0) {
144                                         menu.addChild(new dijit.MenuItem({
145                                                 label: __("Edit feed"),
146                                                 onClick: function() {
147                                                         editFeed(this.getParent().row_id, false);
148                                                 }}));
149
150                                         /* menu.addChild(new dijit.MenuItem({
151                                          label: __("Update feed"),
152                                          onClick: function() {
153                                          heduleFeedUpdate(this.getParent().row_id, false);
154                                          }})); */
155                                 }
156
157                                 menu.bindDomNode(tnode.domNode);
158                                 tnode._menu = menu;
159                         }
160
161                         if (id.match("CAT:") && bare_id >= 0) {
162                                 var menu = new dijit.Menu();
163                                 menu.row_id = bare_id;
164
165                                 menu.addChild(new dijit.MenuItem({
166                                         label: __("Mark as read"),
167                                         onClick: function() {
168                                                 catchupFeed(this.getParent().row_id, true);
169                                         }}));
170
171                                 menu.addChild(new dijit.MenuItem({
172                                         label: __("(Un)collapse"),
173                                         onClick: function() {
174                                                 dijit.byId("feedTree").collapseCat(this.getParent().row_id);
175                                         }}));
176
177                                 menu.bindDomNode(tnode.domNode);
178                                 tnode._menu = menu;
179                         }
180
181                         if (id.match("CAT:")) {
182                                 loading = dojo.doc.createElement('img');
183                                 loading.className = 'loadingNode';
184                                 loading.src = 'images/blank_icon.gif';
185                                 domConstruct.place(loading, tnode.labelNode, 'after');
186                                 tnode.loadingNode = loading;
187                         }
188
189                         if (id.match("CAT:") && bare_id == -1) {
190                                 var menu = new dijit.Menu();
191                                 menu.row_id = bare_id;
192
193                                 menu.addChild(new dijit.MenuItem({
194                                         label: __("Mark all feeds as read"),
195                                         onClick: function() {
196                                                 catchupAllFeeds();
197                                         }}));
198
199                                 menu.bindDomNode(tnode.domNode);
200                                 tnode._menu = menu;
201                         }
202
203                         ctr = dojo.doc.createElement('span');
204                         ctr.className = 'counterNode';
205                         ctr.innerHTML = args.item.unread > 0 ? args.item.unread : args.item.auxcounter;
206
207                         //args.item.unread > 0 ? ctr.addClassName("unread") : ctr.removeClassName("unread");
208
209                         args.item.unread > 0 || args.item.auxcounter > 0 ? Element.show(ctr) : Element.hide(ctr);
210
211                         args.item.unread == 0 && args.item.auxcounter > 0 ? ctr.addClassName("aux") : ctr.removeClassName("aux");
212
213                         domConstruct.place(ctr, tnode.rowNode, 'first');
214                         tnode.counterNode = ctr;
215
216                         //tnode.labelNode.innerHTML = args.label;
217                         return tnode;
218                 },
219                 postCreate: function() {
220                         this.connect(this.model, "onChange", "updateCounter");
221                         this.connect(this, "_expandNode", function() {
222                                 this.hideRead(getInitParam("hide_read_feeds"), getInitParam("hide_read_shows_special"));
223                         });
224
225                         this.inherited(arguments);
226                 },
227                 updateCounter: function (item) {
228                         var tree = this;
229
230                         //console.log("updateCounter: " + item.id[0] + " " + item.unread + " " + tree);
231
232                         var node = tree._itemNodesMap[item.id];
233
234                         if (node) {
235                                 node = node[0];
236
237                                 if (node.counterNode) {
238                                         ctr = node.counterNode;
239                                         ctr.innerHTML = item.unread > 0 ? item.unread : item.auxcounter;
240                                         item.unread > 0 || item.auxcounter > 0 ?
241                                                 item.unread > 0 ?
242                                                         Effect.Appear(ctr, {duration : 0.3,
243                                                                 queue: { position: 'end', scope: 'CAPPEAR-' + item.id, limit: 1 }}) :
244                                                         Element.show(ctr) :
245                                                 Element.hide(ctr);
246
247                                         item.unread == 0 && item.auxcounter > 0 ? ctr.addClassName("aux") : ctr.removeClassName("aux");
248
249                                 }
250                         }
251
252                 },
253                 getTooltip: function (item) {
254                         if (item.updated)
255                                 return item.updated;
256                         else
257                                 return "";
258                 },
259                 getIconClass: function (item, opened) {
260                         return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feedIcon";
261                 },
262                 getLabelClass: function (item, opened) {
263                         return (item.unread == 0) ? "dijitTreeLabel" : "dijitTreeLabel Unread";
264                 },
265                 getRowClass: function (item, opened) {
266                         var rc = (!item.error || item.error == '') ? "dijitTreeRow" :
267                                 "dijitTreeRow Error";
268
269                         if (item.unread > 0) rc += " Unread";
270
271                         return rc;
272                 },
273                 getLabel: function(item) {
274                         var name = String(item.name);
275
276                         /* Horrible */
277                         name = name.replace(/&quot;/g, "\"");
278                         name = name.replace(/&amp;/g, "&");
279                         name = name.replace(/&mdash;/g, "-");
280                         name = name.replace(/&lt;/g, "<");
281                         name = name.replace(/&gt;/g, ">");
282
283                         /* var label;
284
285                          if (item.unread > 0) {
286                          label = name + " (" + item.unread + ")";
287                          } else {
288                          label = name;
289                          } */
290
291                         return name;
292                 },
293                 expandParentNodes: function(feed, is_cat, list) {
294                         try {
295                                 for (var i = 0; i < list.length; i++) {
296                                         var id = String(list[i].id);
297                                         var item = this._itemNodesMap[id];
298
299                                         if (item) {
300                                                 item = item[0];
301                                                 this._expandNode(item);
302                                         }
303                                 }
304                         } catch (e) {
305                                 exception_error(e);
306                         }
307                 },
308                 findNodeParentsAndExpandThem: function(feed, is_cat, root, parents) {
309                         // expands all parents of specified feed to properly mark it as active
310                         // my fav thing about frameworks is doing everything myself
311                         try {
312                                 var test_id = is_cat ? 'CAT:' + feed : 'FEED:' + feed;
313
314                                 if (!root) {
315                                         if (!this.model || !this.model.store) return false;
316
317                                         var items = this.model.store._arrayOfTopLevelItems;
318
319                                         for (var i = 0; i < items.length; i++) {
320                                                 if (String(items[i].id) == test_id) {
321                                                         this.expandParentNodes(feed, is_cat, parents);
322                                                 } else {
323                                                         this.findNodeParentsAndExpandThem(feed, is_cat, items[i], []);
324                                                 }
325                                         }
326                                 } else {
327                                         if (root.items) {
328                                                 parents.push(root);
329
330                                                 for (var i = 0; i < root.items.length; i++) {
331                                                         if (String(root.items[i].id) == test_id) {
332                                                                 this.expandParentNodes(feed, is_cat, parents);
333                                                         } else {
334                                                                 this.findNodeParentsAndExpandThem(feed, is_cat, root.items[i], parents.slice(0));
335                                                         }
336                                                 }
337                                         } else {
338                                                 if (String(root.id) == test_id) {
339                                                         this.expandParentNodes(feed, is_cat, parents.slice(0));
340                                                 }
341                                         }
342                                 }
343                         } catch (e) {
344                                 exception_error(e);
345                         }
346                 },
347                 selectFeed: function(feed, is_cat) {
348                         this.findNodeParentsAndExpandThem(feed, is_cat, false, false);
349
350                         if (is_cat)
351                                 treeNode = this._itemNodesMap['CAT:' + feed];
352                         else
353                                 treeNode = this._itemNodesMap['FEED:' + feed];
354
355                         if (treeNode) {
356                                 treeNode = treeNode[0];
357                                 if (!is_cat) this._expandNode(treeNode);
358                                 this.set("selectedNodes", [treeNode]);
359                                 this.focusNode(treeNode);
360                         }
361                 },
362                 setFeedIcon: function(feed, is_cat, src) {
363                         if (is_cat)
364                                 treeNode = this._itemNodesMap['CAT:' + feed];
365                         else
366                                 treeNode = this._itemNodesMap['FEED:' + feed];
367
368                         if (treeNode) {
369                                 treeNode = treeNode[0];
370                                 var icon = dojo.doc.createElement('img');
371                                 icon.src = src;
372                                 icon.className = 'tinyFeedIcon';
373                                 domConstruct.place(icon, treeNode.iconNode, 'only');
374                                 return true;
375                         }
376                         return false;
377                 },
378                 setFeedExpandoIcon: function(feed, is_cat, src) {
379                         if (is_cat)
380                                 treeNode = this._itemNodesMap['CAT:' + feed];
381                         else
382                                 treeNode = this._itemNodesMap['FEED:' + feed];
383
384                         if (treeNode) {
385                                 treeNode = treeNode[0];
386                                 if (treeNode.loadingNode) {
387                                         treeNode.loadingNode.src = src;
388                                         return true;
389                                 } else {
390                                         var icon = dojo.doc.createElement('img');
391                                         icon.src = src;
392                                         icon.className = 'loadingExpando';
393                                         domConstruct.place(icon, treeNode.expandoNode, 'only');
394                                         return true;
395                                 }
396                         }
397
398                         return false;
399                 },
400                 hasCats: function() {
401                         return this.model.hasCats();
402                 },
403                 hideReadCat: function (cat, hide, show_special) {
404                         if (this.hasCats()) {
405                                 var tree = this;
406
407                                 if (cat && cat.items) {
408                                         var cat_unread = tree.hideReadFeeds(cat.items, hide, show_special);
409
410                                         var id = String(cat.id);
411                                         var node = tree._itemNodesMap[id];
412                                         var bare_id = parseInt(id.substr(id.indexOf(":")+1));
413
414                                         if (node) {
415                                                 var check_unread = tree.model.getFeedUnread(bare_id, true);
416
417                                                 if (hide && cat_unread == 0 && check_unread == 0 && (id != "CAT:-1" || !show_special)) {
418                                                         Effect.Fade(node[0].rowNode, {duration : 0.3,
419                                                                 queue: { position: 'end', scope: 'FFADE-' + id, limit: 1 }});
420                                                 } else {
421                                                         Element.show(node[0].rowNode);
422                                                         ++cat_unread;
423                                                 }
424                                         }
425                                 }
426                         }
427                 },
428                 hideRead: function (hide, show_special) {
429                         if (this.hasCats()) {
430
431                                 var tree = this;
432                                 var cats = this.model.store._arrayOfTopLevelItems;
433
434                                 cats.each(function(cat) {
435                                         tree.hideReadCat(cat, hide, show_special);
436                                 });
437
438                         } else {
439                                 this.hideReadFeeds(this.model.store._arrayOfTopLevelItems, hide,
440                                         show_special);
441                         }
442                 },
443                 hideReadFeeds: function (items, hide, show_special) {
444                         var tree = this;
445                         var cat_unread = 0;
446
447                         items.each(function(feed) {
448                                 var id = String(feed.id);
449
450                                 // it's a subcategory
451                                 if (feed.items) {
452                                         tree.hideReadCat(feed, hide, show_special);
453                                 } else {        // it's a feed
454                                         var bare_id = parseInt(feed.bare_id);;
455
456                                         var unread = feed.unread[0];
457                                         var has_error = feed.error[0] != '';
458                                         var node = tree._itemNodesMap[id];
459
460                                         if (node) {
461                                                 if (hide && unread == 0 && !has_error && (bare_id > 0 || bare_id < _label_base_index || !show_special)) {
462                                                         Effect.Fade(node[0].rowNode, {duration : 0.3,
463                                                                 queue: { position: 'end', scope: 'FFADE-' + id, limit: 1 }});
464                                                 } else {
465                                                         Element.show(node[0].rowNode);
466                                                         ++cat_unread;
467                                                 }
468                                         }
469                                 }
470                         });
471
472                         return cat_unread;
473                 },
474                 collapseCat: function(id) {
475                         if (!this.model.hasCats()) return;
476
477                         var tree = this;
478
479                         var node = tree._itemNodesMap['CAT:' + id][0];
480                         var item = tree.model.store._itemsByIdentity['CAT:' + id];
481
482                         if (node && item) {
483                                 if (!node.isExpanded)
484                                         tree._expandNode(node);
485                                 else
486                                         tree._collapseNode(node);
487
488                         }
489                 },
490                 getVisibleUnreadFeeds: function() {
491                         var items = this.model.store._arrayOfAllItems;
492                         var rv = [];
493
494                         for (var i = 0; i < items.length; i++) {
495                                 var id = String(items[i].id);
496                                 var box = this._itemNodesMap[id];
497
498                                 if (box) {
499                                         var row = box[0].rowNode;
500                                         var cat = false;
501
502                                         try {
503                                                 cat = box[0].rowNode.parentNode.parentNode;
504                                         } catch (e) { }
505
506                                         if (row) {
507                                                 if (Element.visible(row) && (!cat || Element.visible(cat))) {
508                                                         var feed_id = String(items[i].bare_id);
509                                                         var is_cat = !id.match('FEED:');
510                                                         var unread = this.model.getFeedUnread(feed_id, is_cat);
511
512                                                         if (unread > 0)
513                                                                 rv.push([feed_id, is_cat]);
514
515                                                 }
516                                         }
517                                 }
518                         }
519
520                         return rv;
521                 },
522                 getNextFeed: function (feed, is_cat) {
523                         if (is_cat) {
524                                 treeItem = this.model.store._itemsByIdentity['CAT:' + feed];
525                         } else {
526                                 treeItem = this.model.store._itemsByIdentity['FEED:' + feed];
527                         }
528
529                         items = this.model.store._arrayOfAllItems;
530                         var item = items[0];
531
532                         for (var i = 0; i < items.length; i++) {
533                                 if (items[i] == treeItem) {
534
535                                         for (var j = i+1; j < items.length; j++) {
536                                                 var id = String(items[j].id);
537                                                 var box = this._itemNodesMap[id];
538
539                                                 if (box) {
540                                                         var row = box[0].rowNode;
541                                                         var cat = box[0].rowNode.parentNode.parentNode;
542
543                                                         if (Element.visible(cat) && Element.visible(row)) {
544                                                                 item = items[j];
545                                                                 break;
546                                                         }
547                                                 }
548                                         }
549                                         break;
550                                 }
551                         }
552
553                         if (item) {
554                                 return [this.model.store.getValue(item, 'bare_id'),
555                                         !this.model.store.getValue(item, 'id').match('FEED:')];
556                         } else {
557                                 return false;
558                         }
559                 },
560                 getPreviousFeed: function (feed, is_cat) {
561                         if (is_cat) {
562                                 treeItem = this.model.store._itemsByIdentity['CAT:' + feed];
563                         } else {
564                                 treeItem = this.model.store._itemsByIdentity['FEED:' + feed];
565                         }
566
567                         items = this.model.store._arrayOfAllItems;
568                         var item = items[0] == treeItem ? items[items.length-1] : items[0];
569
570                         for (var i = 0; i < items.length; i++) {
571                                 if (items[i] == treeItem) {
572
573                                         for (var j = i-1; j > 0; j--) {
574                                                 var id = String(items[j].id);
575                                                 var box = this._itemNodesMap[id];
576
577                                                 if (box) {
578                                                         var row = box[0].rowNode;
579                                                         var cat = box[0].rowNode.parentNode.parentNode;
580
581                                                         if (Element.visible(cat) && Element.visible(row)) {
582                                                                 item = items[j];
583                                                                 break;
584                                                         }
585                                                 }
586
587                                         }
588                                         break;
589                                 }
590                         }
591
592                         if (item) {
593                                 return [this.model.store.getValue(item, 'bare_id'),
594                                         !this.model.store.getValue(item, 'id').match('FEED:')];
595                         } else {
596                                 return false;
597                         }
598
599                 },
600                 getFeedCategory: function(feed) {
601                         try {
602                                 return this.getNodesByItem(this.model.store.
603                                         _itemsByIdentity["FEED:" + feed])[0].
604                                 getParent().item.bare_id[0];
605
606                         } catch (e) {
607                                 return false;
608                         }
609                 },
610         });
611 });
612