mirror of
https://tt-rss.org/git/tt-rss.git
synced 2024-07-02 12:50:52 +02:00
8d8affdc45
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.
457 lines
13 KiB
JavaScript
Executable File
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(/"/g, "\"");
|
|
name = name.replace(/&/g, "&");
|
|
name = name.replace(/—/g, "-");
|
|
name = name.replace(/</g, "<");
|
|
name = name.replace(/>/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;
|
|
}
|
|
},
|
|
});
|
|
});
|
|
|