1
0
mirror of https://tt-rss.org/git/tt-rss.git synced 2024-07-02 12:50:52 +02:00
ttrss/js/FeedTree.js
nanaya 8d8affdc45 Store FeedTree data in localStorage
Patching internal functions of dijit.Tree as they don't provide option on where to store the data.

It stores to cookies by default but the data can get quite big for hundreds of feeds and exceeds cookies size limit.

Not to mention it'll cause the cookie to be sent during any request with nothing handling it server side and just wasting bandwidth.

This patch will also migrate current data in cookie to local storage accordingly.
2020-07-09 01:52:46 +09:00

457 lines
13 KiB
JavaScript
Executable File

/* eslint-disable prefer-rest-params */
/* global __, dojo, dijit, define, App, Feeds, CommonDialogs */
define(["dojo/_base/declare", "dojo/dom-construct", "dojo/_base/array", "dojo/cookie", "dijit/Tree", "dijit/Menu"], function (declare, domConstruct, array, cookie) {
return declare("fox.FeedTree", dijit.Tree, {
// save state in localStorage instead of cookies
// reference: https://stackoverflow.com/a/27968996
_saveExpandedNodes: function(){
if(this.persist && this.cookieName){
var ary = [];
for(var id in this._openedNodes){
ary.push(id);
}
// Was:
// cookie(this.cookieName, ary.join(","), {expires: 365});
localStorage.setItem(this.cookieName, ary.join(","));
}
},
_initState: function(){
// summary:
// Load in which nodes should be opened automatically
this._openedNodes = {};
if(this.persist && this.cookieName){
// Was:
// var oreo = cookie(this.cookieName);
var oreo = localStorage.getItem(this.cookieName);
// migrate old data if nothing in localStorage
if(oreo == null || oreo === '') {
oreo = cookie(this.cookieName);
cookie(this.cookieName, null, { expires: -1 });
}
if(oreo){
array.forEach(oreo.split(','), function(item){
this._openedNodes[item] = true;
}, this);
}
}
},
_onContainerKeydown: function(/* Event */ /* e */) {
return; // Stop dijit.Tree from interpreting keystrokes
},
_onContainerKeypress: function(/* Event */ /* e */) {
return; // Stop dijit.Tree from interpreting keystrokes
},
_createTreeNode: function(args) {
const tnode = new dijit._TreeNode(args);
const iconName = args.item.icon ? String(args.item.icon[0]) : null;
let iconNode;
if (iconName) {
if (iconName.indexOf("/") == -1) {
iconNode = dojo.create("i", { className: "material-icons icon icon-" + iconName, innerHTML: iconName });
} else {
iconNode = dojo.create('img', { className: 'icon' });
if (args.item.icon && args.item.icon[0]) {
iconNode.src = args.item.icon[0];
} else {
iconNode.src = 'images/blank_icon.gif';
}
}
}
if (iconNode)
domConstruct.place(iconNode, tnode.iconNode, 'only');
const id = args.item.id[0];
const bare_id = parseInt(id.substr(id.indexOf(':')+1));
if (bare_id < App.LABEL_BASE_INDEX) {
const label = dojo.create('i', { className: "material-icons icon icon-label", innerHTML: "label" });
//const fg_color = args.item.fg_color[0];
const bg_color = args.item.bg_color[0];
label.setStyle({
color: bg_color,
});
domConstruct.place(label, tnode.iconNode, 'only');
}
if (id.match("FEED:")) {
const menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
label: __("Mark as read"),
onClick: function() {
Feeds.catchupFeed(this.getParent().row_id);
}}));
if (bare_id > 0) {
menu.addChild(new dijit.MenuItem({
label: __("Edit feed"),
onClick: function() {
CommonDialogs.editFeed(this.getParent().row_id, false);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Debug feed"),
onClick: function() {
window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + this.getParent().row_id +
"&csrf_token=" + App.getInitParam("csrf_token"));
}}));
}
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
if (id.match("CAT:") && bare_id >= 0) {
const menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
label: __("Mark as read"),
onClick: function() {
Feeds.catchupFeed(this.getParent().row_id, true);
}}));
menu.addChild(new dijit.MenuItem({
label: __("(Un)collapse"),
onClick: function() {
dijit.byId("feedTree").collapseCat(this.getParent().row_id);
}}));
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
if (id.match("CAT:")) {
tnode.loadingNode = dojo.create('img', { className: 'loadingNode', src: 'images/blank_icon.gif'});
domConstruct.place(tnode.loadingNode, tnode.labelNode, 'after');
}
if (id.match("CAT:") && bare_id == -1) {
const menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
label: __("Mark all feeds as read"),
onClick: function() {
Feeds.catchupAll();
}}));
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
tnode.markedCounterNode = dojo.create('span', { className: 'counterNode marked', innerHTML: args.item.markedcounter });
domConstruct.place(tnode.markedCounterNode, tnode.rowNode, 'first');
tnode.auxCounterNode = dojo.create('span', { className: 'counterNode aux', innerHTML: args.item.auxcounter });
domConstruct.place(tnode.auxCounterNode, tnode.rowNode, 'first');
tnode.unreadCounterNode = dojo.create('span', { className: 'counterNode unread', innerHTML: args.item.unread });
domConstruct.place(tnode.unreadCounterNode, tnode.rowNode, 'first');
return tnode;
},
postCreate: function() {
this.connect(this.model, "onChange", "updateCounter");
this.inherited(arguments);
},
updateCounter: function (item) {
const tree = this;
//console.log("updateCounter: " + item.id[0] + " " + item.unread + " " + tree);
let treeNode = tree._itemNodesMap[item.id];
if (treeNode) {
treeNode = treeNode[0];
treeNode.unreadCounterNode.innerHTML = item.unread;
treeNode.auxCounterNode.innerHTML = item.auxcounter;
treeNode.markedCounterNode.innerHTML = item.markedcounter;
}
},
getTooltip: function (item) {
return [item.updated, item.error].filter((x) => x && x != "").join(" - ");
},
getIconClass: function (item, opened) {
// eslint-disable-next-line no-nested-ternary
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feed-icon";
},
getLabelClass: function (item/* , opened */) {
return (item.unread <= 0) ? "dijitTreeLabel" : "dijitTreeLabel Unread";
},
getRowClass: function (item/*, opened */) {
let rc = "dijitTreeRow";
const is_cat = String(item.id).indexOf('CAT:') != -1;
if (!is_cat && item.error != '') rc += " Error";
if (item.unread > 0) rc += " Unread";
if (item.auxcounter > 0) rc += " Has_Aux";
if (item.markedcounter > 0) rc += " Has_Marked";
if (item.updates_disabled > 0) rc += " UpdatesDisabled";
if (item.bare_id >= App.LABEL_BASE_INDEX && item.bare_id < 0 && !is_cat || item.bare_id == 0 && !is_cat) rc += " Special";
if (item.bare_id == -1 && is_cat) rc += " AlwaysVisible";
if (item.bare_id < App.LABEL_BASE_INDEX) rc += " Label";
return rc;
},
getLabel: function(item) {
let name = String(item.name);
/* Horrible */
name = name.replace(/&quot;/g, "\"");
name = name.replace(/&amp;/g, "&");
name = name.replace(/&mdash;/g, "-");
name = name.replace(/&lt;/g, "<");
name = name.replace(/&gt;/g, ">");
return name;
},
expandParentNodes: function(feed, is_cat, list) {
try {
for (let i = 0; i < list.length; i++) {
const id = String(list[i].id);
let item = this._itemNodesMap[id];
if (item) {
item = item[0];
this._expandNode(item);
}
}
} catch (e) {
App.Error.report(e);
}
},
findNodeParentsAndExpandThem: function(feed, is_cat, root, parents) {
// expands all parents of specified feed to properly mark it as active
// my fav thing about frameworks is doing everything myself
try {
const test_id = is_cat ? 'CAT:' + feed : 'FEED:' + feed;
if (!root) {
if (!this.model || !this.model.store) return false;
const items = this.model.store._arrayOfTopLevelItems;
for (let i = 0; i < items.length; i++) {
if (String(items[i].id) == test_id) {
this.expandParentNodes(feed, is_cat, parents);
} else {
this.findNodeParentsAndExpandThem(feed, is_cat, items[i], []);
}
}
} else if (root.items) {
parents.push(root);
for (let i = 0; i < root.items.length; i++) {
if (String(root.items[i].id) == test_id) {
this.expandParentNodes(feed, is_cat, parents);
} else {
this.findNodeParentsAndExpandThem(feed, is_cat, root.items[i], parents.slice(0));
}
}
} else if (String(root.id) == test_id) {
this.expandParentNodes(feed, is_cat, parents.slice(0));
}
} catch (e) {
App.Error.report(e);
}
},
selectFeed: function(feed, is_cat) {
this.findNodeParentsAndExpandThem(feed, is_cat, false, false);
let treeNode;
if (is_cat)
treeNode = this._itemNodesMap['CAT:' + feed];
else
treeNode = this._itemNodesMap['FEED:' + feed];
if (treeNode) {
treeNode = treeNode[0];
if (!is_cat) this._expandNode(treeNode);
this.set("selectedNodes", [treeNode]);
this.focusNode(treeNode);
// focus headlines to route key events there
setTimeout(() => {
$("headlines-frame").focus();
if (treeNode) {
const node = treeNode.rowNode;
const tree = this.domNode;
if (node && tree) {
// scroll tree to selection if needed
if (node.offsetTop < tree.scrollTop || node.offsetTop > tree.scrollTop + tree.clientHeight) {
$("feedTree").scrollTop = node.offsetTop;
}
}
}
}, 0);
}
},
setFeedIcon: function(feed, is_cat, src) {
let treeNode;
if (is_cat)
treeNode = this._itemNodesMap['CAT:' + feed];
else
treeNode = this._itemNodesMap['FEED:' + feed];
if (treeNode) {
treeNode = treeNode[0];
const icon = dojo.create('img', { src: src, className: 'icon' });
domConstruct.place(icon, treeNode.iconNode, 'only');
return true;
}
return false;
},
setFeedExpandoIcon: function(feed, is_cat, src) {
let treeNode;
if (is_cat)
treeNode = this._itemNodesMap['CAT:' + feed];
else
treeNode = this._itemNodesMap['FEED:' + feed];
if (treeNode) {
treeNode = treeNode[0];
if (treeNode.loadingNode) {
treeNode.loadingNode.src = src;
return true;
} else {
const icon = dojo.create('img', { src: src, className: 'loadingExpando' });
domConstruct.place(icon, treeNode.expandoNode, 'only');
return true;
}
}
return false;
},
hasCats: function() {
return this.model.hasCats();
},
collapseCat: function(id) {
if (!this.model.hasCats()) return;
const tree = this;
const node = tree._itemNodesMap['CAT:' + id][0];
const item = tree.model.store._itemsByIdentity['CAT:' + id];
if (node && item) {
if (!node.isExpanded)
tree._expandNode(node);
else
tree._collapseNode(node);
}
},
getNextFeed: function (feed, is_cat) {
let treeItem;
if (is_cat) {
treeItem = this.model.store._itemsByIdentity['CAT:' + feed];
} else {
treeItem = this.model.store._itemsByIdentity['FEED:' + feed];
}
const items = this.model.store._arrayOfAllItems;
let item = items[0];
for (let i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
for (let j = i+1; j < items.length; j++) {
const id = String(items[j].id);
const box = this._itemNodesMap[id];
if (box) {
const row = box[0].rowNode;
const cat = box[0].rowNode.parentNode.parentNode;
if (Element.visible(cat) && Element.visible(row)) {
item = items[j];
break;
}
}
}
break;
}
}
if (item) {
return [this.model.store.getValue(item, 'bare_id'),
!this.model.store.getValue(item, 'id').match('FEED:')];
} else {
return false;
}
},
getPreviousFeed: function (feed, is_cat) {
let treeItem;
if (is_cat) {
treeItem = this.model.store._itemsByIdentity['CAT:' + feed];
} else {
treeItem = this.model.store._itemsByIdentity['FEED:' + feed];
}
const items = this.model.store._arrayOfAllItems;
let item = items[0] == treeItem ? items[items.length-1] : items[0];
for (let i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
for (let j = i-1; j > 0; j--) {
const id = String(items[j].id);
const box = this._itemNodesMap[id];
if (box) {
const row = box[0].rowNode;
const cat = box[0].rowNode.parentNode.parentNode;
if (Element.visible(cat) && Element.visible(row)) {
item = items[j];
break;
}
}
}
break;
}
}
if (item) {
return [this.model.store.getValue(item, 'bare_id'),
!this.model.store.getValue(item, 'id').match('FEED:')];
} else {
return false;
}
},
getFeedCategory: function(feed) {
try {
return this.getNodesByItem(this.model.store.
_itemsByIdentity["FEED:" + feed])[0].
getParent().item.bare_id[0];
} catch (e) {
return false;
}
},
});
});