home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2008 September
/
PCWorld_2008-09_cd.bin
/
komunikace
/
kmeleon
/
K-Meleon1.1.3en-US.exe
/
chrome
/
newsfox.jar
/
content
/
newsfox
/
model.js
< prev
next >
Wrap
Text File
|
2007-10-11
|
37KB
|
1,289 lines
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is NewsFox.
*
* The Initial Developer of the Original Code is
* Andy Frank <andy@andyfrank.com>.
* Portions created by the Initial Developer are Copyright (C) 2005-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Andrey Gromyko <andrey@gromyko.name>
* Ron Pruitt <wa84it@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the LGPL or the GPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
////////////////////////////////////////////////////////////////
// Global
////////////////////////////////////////////////////////////////
// common to many *.js
var gFmodel = new FdModel();
var gIdx = new Indexes();
var gFdGroup = new Array();
var gCollect = null;
// globals just in model.js
const FOLDER_OPEN = "chrome://newsfox/skin/images/folderOpen.png";
const FOLDER_CLOSED = "chrome://newsfox/skin/images/folderClosed.png";
const FOLDER_SEARCH = "chrome://newsfox/skin/images/folderSearch.png";
const ICON_ERR = "chrome://newsfox/skin/images/brokenFeed.png";
var dragLevel = 3;
var dragGrp = -1;
////////////////////////////////////////////////////////////////
// Model
////////////////////////////////////////////////////////////////
function FdModel()
{
this.add = function(feed,isExcluded)
{
if (isExcluded)
feeds.push(feed);
else
feeds.splice(gFmodel.size(),0,feed);
}
this.get = function(index) { return feeds[index]; }
this.set = function(index, feed)
{
if(index < 0 || index > feeds.length - 1)
return;
feeds[index] = feed;
}
this.getIndexByURL = function(url)
{
for(var i=0; i < feeds.length; i++)
if(url == feeds[i].url)
return i;
return -1;
}
this.getFeedByURL = function(url)
{
var nFeed = this.getIndexByURL(url);
if (nFeed > -1) return feeds[nFeed];
else return null;
}
this.size = function() // returns size without excluded feed(s)
{
var size = 0;
for( var i = 0; i < feeds.length; i++ )
if( !feeds[i].exclude )
size++;
return size;
}
this.sizeTotal = function() // returns total size, i.e. with excluded from seeing feed(s)
{
return feeds.length;
}
var feeds = new Array();
/**
* Return a new unique uid.
*/
this.makeUniqueUid = function(url)
{
var index = url.indexOf("://");
var body;
if( index > -1 )
if (url.charAt(index+3) == "/") // file:///
body = "local";
else
body = url.substring(index+3);
else
body = url;
var domain = body;
if( body.indexOf("/") != -1 )
domain = body.split("/")[0];
if( domain.indexOf(":") != -1 )
domain = domain.split(":")[0]; // there are cases when port number follows domain name
var count = 1;
var name = domain;
for (var i=0; i<feeds.length; i++)
{
if (name == feeds[i].uid)
{
name = domain + (count++);
i = -1; // reset loop
}
}
return name;
}
/**
* Remove the given feed from the model.
*/
this.remove = function(feed)
{
for (var i=0; i < feeds.length; i++)
if (feed.uid == feeds[i].uid)
{
feeds.splice(i,1);
return;
}
}
}
////////////////////////////////////////////////////////////////
// Group Model
////////////////////////////////////////////////////////////////
function FeedGroup()
{
const NF_SB = document.getElementById("newsfox-string-bundle");
this.title = NF_SB.getString('newGroup');
this.expanded = false;
this.list = new Array();
this.search = false;
this.srchdat = { flagged : 2, unread : 2, text : "", textflags : 6, startTime : -1, endTime: 0 };
this.getUnread = function()
{
if (this.search) return 0; // TODO, simple on the fly doesn't work well
var unread = 0;
for (var i=0; i<this.list.length; i++)
unread += gFmodel.get(this.list[i]).getUnread(0);
return unread;
}
}
function Indexes()
{
this.fdgp = new Array();
this.feed = new Array();
this.catg = new Array();
this.open = new Array();
}
////////////////////////////////////////////////////////////////
// Feed Model
////////////////////////////////////////////////////////////////
function Feed()
{
this.uid = null;
this.url = null;
this.homepage = null;
this.icon = new Image;
this.icon.src = ICON_OK;
this.defaultName = null;
this.customeName = null;
this.error = ERROR_OK;
this.loaded = false;
this.deleteOld = true;
this.dontDeleteUnread = true;
this.daysToKeep = 0;
this.autoCheck = true;
var categories = new Array();
this.exclude = false;
// 0 Use global setting
// 1 Override to show as text
// 2 Override to show as webpage
// 3 Override to show as newspaper
this.style = 0;
this.getStyle = function()
{
if (this.style == 0)
return gOptions.globalStyle;
return this.style;
}
/**
* Return display name for feed.
*/
this.getDisplayName = function()
{
return ((this.customName != null) && (this.customName.length > 0)) ? this.customName : this.defaultName;
}
this.setDefaultName = function(dname) { this.defaultName = dname; }
/**
* Return the number of unread items for this feed.
*/
this.getUnread = function(nCategory)
{
var unread = 0;
for (var i=0; i<this.flags.length; i++)
if ((this.flags[i] & 0x01) == 0) // unread
{
if( nCategory == 0 )
unread++;
else
{
var ScatnameS = "\/" + categories[nCategory-1] + "\/";
var ScatS = "\/" + articles[i].category + "\/";
if (ScatS.indexOf(ScatnameS) > -1)
unread++;
}
}
return unread;
}
this.add = function(article,flag)
{
articles.push(article);
if(article.category != "")
this.addCategory(article.category);
this.flags.push(flag);
}
this.deletedAdd = function(article) { deletedarticles.push(article); }
this.get = function(index) { return articles[index]; }
this.deletedget = function(index) { return deletedarticles[index]; }
this.set = function(index,item) { articles[index] = item; }
this.remove = function(index)
{
articles.splice(index,1);
this.flags.splice(index,1);
}
this.deletedremove = function(index) { deletedarticles.splice(index,1); }
this.getIndexById = function(id)
{
for(var i=0; i < articles.length; i++)
if(id == articles[i].id)
return i;
return -1;
}
this.deleteById = function(id)
{
if (id == null) return;
var index = this.getIndexById(id);
if( index >= 0 && index < articles.length)
{
deletedarticles.push(articles[index]);
articles.splice(index,1);
this.flags.splice(index,1);
}
}
this.size = function() { return articles.length; }
this.deletedsize = function() { return deletedarticles.length; }
var articles = new Array();
var deletedarticles = new Array();
// Flags
this.flags = new Array();
this.isRead = function(index)
{
return ((this.flags[index] & 0x01) != 0);
}
this.setRead = function(index, value)
{
if (value) this.flags[index] |= 0x01;
else this.flags[index] &= 0xFE;
}
this.isFlagged = function(index)
{
return ((this.flags[index] & 0x04) != 0);
}
this.setFlagged = function(index, value)
{
if (value) this.flags[index] |= 0x04;
else this.flags[index] &= 0xFB;
}
// AG: added categories
this.addCategory = function(category)
{
var catArray = category.split("\/");
for (var i=0; i<catArray.length; i++)
{
var exists = false;
for (var j=0; j<categories.length; j++)
if (catArray[i] == categories[j]) exists = true;
if (!exists) categories.push(catArray[i]);
}
}
this.getCategories = function() { return categories; }
this.sortCategories = function() { categories.sort(); }
}
////////////////////////////////////////////////////////////////
// FeedTree Model
////////////////////////////////////////////////////////////////
function dragIt(evt) {
var tree = evt.target.parentNode;
if (tree.id != "newsfox.feedTree") return;
var row = {}, col = {}, type = {};
tree.treeBoxObject.getCellAt(evt.clientX, evt.clientY, row, col, type);
dragLevel = tree.view.getLevel(row.value);
dragGrp = gIdx.fdgp[row.value];
if (dragLevel <= 1) nsDragAndDrop.startDrag(evt,feedtreeObserver);
}
var feedtreeObserver = {
onDragStart: function (evt,transferData,action)
{
var row = {}, col = {}, type = {};
evt.target.parentNode.treeBoxObject.getCellAt(evt.clientX, evt.clientY, row, col, type);
transferData.data=new TransferData();
transferData.data.addDataForFlavour("feedtreerow",gIdx.feed[row.value]);
},
getSupportedFlavours : function ()
{
var flavours = new FlavourSet();
flavours.appendFlavour("feedtreerow");
return flavours;
},
onDragOver: function (evt,flavour,session){},
onDrop: function (evt,dropdata,session){}
};
function FeedTreeModel()
{
// 3/25/05 - Temp disable flag/trash folder
// AG: added category. Most subcode is reworked
// 11 Jan 07 RP: remove flag/trash, add groups, code reworked
this.rowCount = gIdx.fdgp.length;
this.isTopLevelElement = function(row){ return (this.getLevel(row) == 0); }
this.getCellText = function(row,col)
{
var level = this.getLevel(row);
var text, unread;
if (level == 0)
{
var curFdGroup = gFdGroup[gIdx.fdgp[row]];
text = curFdGroup.title;
unread = curFdGroup.getUnread();
if (row == 0) newTitle(unread);
}
else
{
var feed = gFmodel.get(gIdx.feed[row]);
var nCategory = gIdx.catg[row];
text = "";
unread = feed.getUnread(nCategory);
if( nCategory )
{
var categories = feed.getCategories();
text = categories[nCategory-1];
}
else
text = feed.getDisplayName();
}
if (unread > 0) text += " (" + unread + ")";
return text;
}
this.setTree = function(treebox){ this.treebox = treebox; }
this.isContainer = function(row){ return (this.getLevel(row) < 2); }
this.isContainerEmpty = function(row)
{
var level = this.getLevel(row);
if (level == 0 && gFdGroup[gIdx.fdgp[row]].search) return true;
var feed = gFmodel.get(gIdx.feed[row]);
if (level == 0 && gFdGroup[gIdx.fdgp[row]].list.length) return false;
if (level == 1 && feed.loaded && feed.getCategories().length) return false;
return true;
}
this.isContainerOpen = function(row){ return gIdx.open[row]; }
this.hasNextSibling = function(row, index)
{
if (index+1 > gIdx.fdgp.length-1) return false;
var level = this.getLevel(row);
if (level == 0) return (gIdx.fdgp[gIdx.fdgp.length-1] != gIdx.fdgp[row]);
else if (level == 1) return (gIdx.fdgp[index+1] == gIdx.fdgp[row]);
else return ((gIdx.fdgp[index+1] == gIdx.fdgp[row]) && (gIdx.feed[index+1] == gIdx.feed[row]));
}
this.getParentIndex = function(row)
{
var index = row;
var level = this.getLevel(row);
if (level == 0) index = -2;
else if (level == 1)
while (gIdx.fdgp[index] == gIdx.fdgp[row]) index--;
else
while (gIdx.feed[index] == gIdx.feed[row]) index--;
return ++index;
}
this.isSeparator = function(row){ return false; }
this.isEditable = function(row,col){ return false; }
this.isSorted = function(row){ return false; }
this.getLevel = function(row)
{
var level = 0;
if (gIdx.catg[row] > 0) level = 2;
else if (gIdx.feed[row] > -1) level = 1;
return level;
}
this.getImageSrc = function(row,col)
{
var retval = null;
var level = this.getLevel(row);
if (level == 0)
{
if (this.isContainerOpen(row))
retval = FOLDER_OPEN;
else if (gFdGroup[gIdx.fdgp[row]].search)
retval = FOLDER_SEARCH;
else
retval = FOLDER_CLOSED;
}
else if (level == 1)
{
var feed = gFmodel.get(gIdx.feed[row]);
var nofeederror = (feed.error == ERROR_OK);
retval = nofeederror ? feed.icon.src : ICON_ERR;
}
return retval;
}
this.getRowProperties = function(row,props) {}
this.getCellProperties = function(row,col,props)
{
if (gIdx.catg[row] == 0)
{
var aserv = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
props.AppendElement(aserv.getAtom("faviconcol"));
}
var hasUnread = false;
var nFeed = gIdx.feed[row];
if (nFeed == -1 && gFdGroup[gIdx.fdgp[row]].getUnread() > 0)
hasUnread = true;
if (nFeed > -1 && gFmodel.get(nFeed).getUnread(gIdx.catg[row]) > 0)
hasUnread = true;
if (hasUnread)
{
var aserv = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
props.AppendElement(aserv.getAtom("unread"));
}
}
this.getColumnProperties = function(colid,col,props){}
this.toggleOpenState = function(row)
{
if (this.isContainerEmpty(row)) return;
var feedtree = document.getElementById("newsfox.feedTree");
var fRow = feedtree.treeBoxObject.getFirstVisibleRow();
var level = this.getLevel(row);
if (level == 0)
{
var curGrp = gIdx.fdgp[row];
var grp = gFdGroup[curGrp];
if (grp.expanded)
{
var num = 0;
while (gIdx.fdgp[row+num] == curGrp) num++;
num--;
gIdx.fdgp.splice(row+1,num);
gIdx.feed.splice(row+1,num);
gIdx.catg.splice(row+1,num);
gIdx.open.splice(row+1,num);
feedtree.treeBoxObject.rowCountChanged(row+1,-num);
}
else
{
for (var i=1; i <= grp.list.length; i++)
{
gIdx.fdgp.splice(row+i,0,curGrp);
gIdx.feed.splice(row+i,0,grp.list[i-1]);
gIdx.catg.splice(row+i,0,0);
gIdx.open.splice(row+i,0,false);
}
feedtree.treeBoxObject.rowCountChanged(row+1,grp.list.length);
}
grp.expanded = !grp.expanded;
gIdx.open[row] = !gIdx.open[row];
}
else if (level == 1)
{
var curFeed = gIdx.feed[row];
var feed = gFmodel.get(curFeed);
if (gIdx.open[row])
{
var num = 0;
while (gIdx.feed[row+num] == curFeed) num++; // can't use categories.length since # categories may change
num--;
gIdx.fdgp.splice(row+1,num);
gIdx.feed.splice(row+1,num);
gIdx.catg.splice(row+1,num);
gIdx.open.splice(row+1,num);
feedtree.treeBoxObject.rowCountChanged(row+1,-num);
}
else
{
for (var i=1; i <= feed.getCategories().length; i++)
{
gIdx.fdgp.splice(row+i,0,gIdx.fdgp[row]);
gIdx.feed.splice(row+i,0,gIdx.feed[row]);
gIdx.catg.splice(row+i,0,i);
gIdx.open.splice(row+i,0,false);
}
feedtree.treeBoxObject.rowCountChanged(row+1,feed.getCategories().length);
}
gIdx.open[row] = !gIdx.open[row];
}
else return;
saveModels();
feedtree.treeBoxObject.scrollToRow(fRow);
}
this.canDrop = function(row,orientation)
{
// if (orientation == 0) return false;
switch (this.getLevel(row))
{
case 0:
return (dragLevel == 0) ? true : false;
case 1:
return (dragLevel == 1 && dragGrp == gIdx.fdgp[row]) ? true : false;
case 2:
default:
return false;
}
}
this.drop = function(row,orientation)
{
var flavourSet = feedtreeObserver.getSupportedFlavours();
var transferData = nsTransferable.get(flavourSet, nsDragAndDrop.getDragData, true);
var dropdata = transferData.first.first;
var oldFeed = dropdata.data;
var newGrp = gIdx.fdgp[row] + (orientation == 1);
var newFeed = gIdx.feed[row];
if (orientation == 1)
{
var i = row;
while (gIdx.feed[i] == newFeed) i++;
if (gIdx.fdgp[i] == dragGrp) newFeed = gIdx.feed[i]
else newFeed = -2;
}
if (dragLevel == 0) mvGrp(dragGrp,newGrp);
else // dragLevel == 1
mvFeed(dragGrp,oldFeed,newFeed);
dragLevel = 3;
dragGrp = -1;
}
}
////////////////////////////////////////////////////////////////
// Article Model
////////////////////////////////////////////////////////////////
function Article()
{
this.link = null;
this.title = "";
this.body = "";
this.date = null;
// AG: added category
this.category = "";
this.enclosures = new Array();
this.id = null;
this.toRemove = false;
}
function Enclosure()
{
this.url = "";
this.type = "";
this.length = "";
}
////////////////////////////////////////////////////////////////
// ArticleTree Model
////////////////////////////////////////////////////////////////
function ArticleTreeModel()
{
var flagIcon = "chrome://newsfox/skin/images/flag.png";
var readIcon = "chrome://newsfox/skin/images/read.png";
var unreadIcon = "chrome://newsfox/skin/images/unread.png";
removeHeaderArrows();
this.rowCount = gCollect.size();
this.getCellText = function(row,col)
{
// Try to handle both Firefox 1.0 and 1.1
var colId = (col.id) ? col.id : col;
switch (colId)
{
case "title": return entityDecode(gCollect.get(row).title);
case "date": return displayDate(gCollect.get(row).date, gOptions.dateStyle);
default: return "debug-" + col;
}
}
this.setTree = function(treebox){ this.treebox = treebox; }
this.isContainer = function(row){ return false; }
this.isSeparator = function(row){ return false; }
this.isSorted = function(row){ return false; }
this.getLevel = function(row){ return 0; }
this.getImageSrc = function(row,col)
{
var read = gCollect.isRead(row);
var flag = gCollect.isFlagged(row);
// Try to handle both Firefox 1.0 and 1.1
var colId = (col.id) ? col.id : col;
switch (colId)
{
case "read": return read ? readIcon : unreadIcon;
case "flag": return flag ? flagIcon : readIcon;
case "title": return (gCollect.type == 0 || gCollect.type == 3) ? gCollect.getFeed(row).icon.src : null;
default: return null;
}
}
this.getRowProperties = function(row,props) {}
this.getCellProperties = function(row,col,props)
{
// Try to handle both Firefox 1.0 and 1.1
var colId = (col.id) ? col.id : col;
if (colId == "title" && (gCollect.type == 0 || gCollect.type == 3))
{
var aserv = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
props.AppendElement(aserv.getAtom("faviconcol"));
}
if (!gCollect.isRead(row))
{
var aserv = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
props.AppendElement(aserv.getAtom("unread"));
}
}
this.getColumnProperties = function(colid,col,props) {}
this.cycleHeader = function(col,elem)
{
// Try to handle both Firefox 1.0 and 1.1
var colId = (col.id) ? col.id : col;
var colObj = document.getElementById(colId);
var direction = colObj.getAttribute("sortDirection");
removeHeaderArrows();
var newDir;
switch (colId)
{
case "flag":
case "title":
newDir = "ascending";
if (direction == "ascending") newDir = "descending";
break;
case "read":
case "date":
newDir = "descending";
if (direction == "descending") newDir = "ascending";
}
var arttree = document.getElementById("newsfox.articleTree");
arttree.blur();
arttree.view.selection.select(-1);
gCollect.artSort(colId,newDir);
if (gCollect.type == 1) // feed, not group or category
{
saveFeed(gCollect.feed); // feed(s) not changed by resorting
saveFeedModel(); // group or category
}
colObj.setAttribute("sortDirection", newDir);
}
this.cycleCell = function(row,col)
{
// Try to handle both Firefox 1.0 and 1.1
var colId = (col.id) ? col.id : col;
if (colId == "read")
{
var read = gCollect.isRead(row);
gCollect.setRead(row, !read);
var tree = document.getElementById("newsfox.feedTree");
tree.treeBoxObject.invalidate();
}
else if (colId == "flag")
{
var flag = gCollect.isFlagged(row);
gCollect.setFlagged(row, !flag);
}
var arttree = document.getElementById("newsfox.articleTree");
arttree.treeBoxObject.invalidate();
}
}
function removeHeaderArrows()
{
var flagObj = document.getElementById("flag");
var titleObj = document.getElementById("title");
var readObj = document.getElementById("read");
var dateObj = document.getElementById("date");
flagObj.setAttribute("sortDirection", "natural");
titleObj.setAttribute("sortDirection", "natural");
readObj.setAttribute("sortDirection", "natural");
dateObj.setAttribute("sortDirection", "natural");
}
////////////////////////////////////////////////////////////////
// Collections
////////////////////////////////////////////////////////////////
// AG: categoryNo isn't an index. 0 means root (or whole feed)
function NormalCollection(index, categoryNo, isDisplayed)
{
this.type = 1; // feed
if (categoryNo > 0) this.type = 2;
var feed = gFmodel.get(index);
this.feed = feed;
if (isDisplayed)
{
var title = feed.getDisplayName();
var hasHomepage = (feed.homepage != "");
setFeedbarButtons(this.type,title,hasHomepage);
}
var items = new Array();
var itemFeed = new Array();
var itemIndex = new Array();
var art;
if (categoryNo > 0)
{
var categories = feed.getCategories();
var ScatnameS = "\/" + categories[categoryNo - 1] + "\/";
}
for (var i=0; i<feed.size(); i++)
if (categoryNo < 1)
{
items.push(feed.get(i));
itemFeed.push(index);
itemIndex.push(i);
}
else
{
art = feed.get(i);
var ScatS = "\/" + art.category + "\/";
if(ScatS.indexOf(ScatnameS) > -1)
{
items.push(art);
itemFeed.push(index);
itemIndex.push(i);
}
}
this.get = function(index) { return items[index]; }
this.size = function() { return items.length; }
this.isRead = function(row) { return feed.isRead(itemIndex[row]); }
this.setRead = function(row,value) { feed.setRead(itemIndex[row],value); }
this.isFlagged = function(row) { return feed.isFlagged(itemIndex[row]); }
this.setFlagged = function(row,value)
{ feed.setFlagged(itemIndex[row],value); }
this.getFeed = function(index) { return feed; }
this.artSort = function(by,dir)
{ artSort(by,dir,feed,items,itemFeed,itemIndex,this.type); }
}
function GroupCollection(grpindex, isSearch)
{
const NF_SB = document.getElementById("newsfox-string-bundle");
var srchitem = new Array();
this.grpindex = grpindex;
var grp = gFdGroup[grpindex];
var grpHeading;
if (isSearch)
{
this.type = 3; // search
grpHeading = NF_SB.getString('srchName');
var srchstr = mkSrchstr(grp.srchdat,srchitem);
}
else
{
this.type = 0; // group
grpHeading = NF_SB.getString('grpName');
}
var title = grpHeading + " " + grp.title;
setFeedbarButtons(this.type,title,false);
var items = new Array();
var itemIndex = new Array();
var itemFeed = new Array();
var art, feed, nFeed;
for (var i=0; i<grp.list.length; i++)
{
nFeed = grp.list[i];
feed = gFmodel.get(nFeed);
for (var j=0; j<feed.size(); j++)
if (this.type == 0 || hasProp(j, feed, grp.srchdat, srchstr, srchitem))
{
items.push(feed.get(j));
itemIndex.push(j);
itemFeed.push(nFeed);
}
}
this.artSort = function(by,dir)
{ artSort(by,dir,feed,items,itemFeed,itemIndex,this.type); }
this.artSort("date","descending");
this.get = function(index) { return items[index]; }
this.size = function() { return items.length; }
this.isRead = function(row) { return gFmodel.get(itemFeed[row]).isRead(itemIndex[row]); }
this.setRead = function(row,value) { gFmodel.get(itemFeed[row]).setRead(itemIndex[row],value); }
this.isFlagged = function(row) { return gFmodel.get(itemFeed[row]).isFlagged(itemIndex[row]); }
this.setFlagged = function(row,value)
{ gFmodel.get(itemFeed[row]).setFlagged(itemIndex[row],value); }
this.getFeed = function(index) { return gFmodel.get(itemFeed[index]); }
this.getSrchText = function() { return srchitem; }
this.getTitle = function() { return title; }
}
function EmptyCollection()
{
var items = new Array();
this.type = -1; // empty
setFeedbarButtons(this.type,"?",false);
this.size = function() { return items.length; }
}
////////////////////////////////////////////////////////////
///// Collection utilities
///////////////////////////////////////////////////////////
function hasProp(j, feed, srchdat, srchstr, st)
{
if ((srchdat.flagged != 2) && (srchdat.flagged == feed.isFlagged(j)))
return false;
if ((srchdat.unread != 2) && (srchdat.unread != feed.isRead(j)))
return false;
var art = feed.get(j);
var now = new Date().getTime();
var artTime = art.date.getTime();
if (artTime > now - srchdat.endTime) return false;
if (srchdat.startTime > 0 && now - srchdat.startTime > artTime) return false;
var caseSen = ((srchdat.textflags & 0x04) == 0);
var matchWhat = (srchdat.textflags & 0x03) + 1;
var arttext = "";
arttext += ((matchWhat & 0x01) != 0) ? art.body : "";
arttext += ((matchWhat & 0x02) != 0) ? art.title : "";
if (!caseSen)
arttext = arttext.toLowerCase();
return eval(srchstr);
}
function srchText(srchtext, arttext)
{
return (arttext.indexOf(srchtext) != -1);
}
function mkSrchstr(srchdat,st)
{
var caseSen = ((srchdat.textflags & 0x04) == 0);
var srchtext = srchdat.text;
if (srchtext == "") return "true";
if (!caseSen)
srchtext = srchtext.toLowerCase();
var srchstr = srchtext;
var i = 0;
// preprocess quoted stuff
var postext = 0;
var done = false;
var malformed = false;
var item, repl, newrepl;
while (!done)
{
var q1 = srchtext.indexOf("'",postext);
var q2 = srchtext.indexOf('"',postext);
if (q1 == -1 && q2 == -1) done = true;
else if ((q1 > -1 && q1 < q2) || q2 == -1)
{
var q3 = srchtext.indexOf("'",q1+1);
if (q3 == -1) // unbalanced error
{
malformed = true;
srchtext += "'";
srchstr += "'";
q3 = srchtext.indexOf("'",q1+1);
}
item = srchtext.substring(q1+1,q3);
newrepl = '"st[' + i + ']"';
st[i++] = item;
repl = "'" + item + "'";
srchstr = srchstr.replace(repl,newrepl);
postext = q3+1;
}
else // -1 < q2 < q1 || q1 == -1
{
var q3 = srchtext.indexOf('"',q2+1);
if (q3 == -1) // unbalanced error
{
malformed = true;
srchtext += '"';
srchstr += '"';
q3 = srchtext.indexOf('"',q2+1);
}
item = srchtext.substring(q2+1,q3);
newrepl = '"st[' + i + ']"';
st[i++] = item;
repl = '"' + item + '"';
srchstr = srchstr.replace(repl,newrepl);
postext = q3+1;
}
}
// process string
postext = 0;
var pos;
done = false;
var inParen = 0;
var nextIsItem = true;
while (postext < srchstr.length)
{
switch (srchstr.charAt(postext))
{
case '"':
//alert("case\"");
postext = srchstr.indexOf('"',postext+1)+1;
nextIsItem = false;
break;
case "-":
//alert("case-");
if (srchstr.charAt(postext+1) != ' ' && srchstr.charAt(postext+1) != '|')
{
srchstr = srchstr.substring(0,postext) + "!" + srchstr.substring(postext+1);
postext++;
}
else
{
if (nextIsItem)
{
malformed = true;
srchstr = srchstr.substring(0,postext+1) + srchstr.substring(postext+2);
}
else
{
malformed = true;
srchstr = srchstr.substring(0,postext) + srchstr.substring(postext+1);
}
}
break;
case "(":
//alert("case(");
inParen++;
postext++;
break;
case ")":
//alert("case)");
if (inParen > 0 && !nextIsItem)
{
inParen--;
postext++;
}
else
{
malformed = true;
srchstr = srchstr.substring(0,postext) + srchstr.substring(postext+1);
}
break;
case "|":
//alert("case|");
if (!nextIsItem)
{
srchstr = srchstr.substring(0,postext) + " || " + srchstr.substring(postext+1);
postext += 4;
nextIsItem = true;
}
else
{
malformed = true;
srchstr = srchstr.substring(0,postext) + srchstr.substring(postext+1);
}
break;
case " ":
//alert("case-space");
pos = postext;
while (srchstr.charAt(pos) == " ") pos++;
switch (srchstr.charAt(pos))
{
case "|":
case ")":
srchstr = srchstr.substring(0,postext) + srchstr.substring(pos);
break;
case "o":
case "O":
if (srchstr.charAt(pos+1).toLowerCase() == "r" && srchstr.charAt(pos+2) == " ")
{
srchstr = srchstr.substring(0,postext) + "|" + srchstr.substring(pos+3);
break;
}
default:
if (pos >= srchstr.length)
{
postext = pos;
break;
}
if (nextIsItem)
srchstr = srchstr.substring(0,postext) + srchstr.substring(pos);
else
{
srchstr = srchstr.substring(0,postext) + " && " + srchstr.substring(pos);
postext += 4;
nextIsItem = true;
}
}
break;
default: // search item
//alert("case");
if (nextIsItem)
{
pos = postext;
while (srchstr.charAt(pos) != '|' && srchstr.charAt(pos) != ' ' && srchstr.charAt(pos) != ')' && pos < srchstr.length) pos++;
if (srchstr.charAt(pos) == ')' && inParen <= 0)
while (srchstr.charAt(pos) != '|' && srchstr.charAt(pos) != ' ' && pos < srchstr.length) pos++;
item = srchstr.substring(postext,pos);
newrepl = '"st[' + i + ']"';
st[i++] = item;
srchstr = srchstr.replace(item,newrepl);
postext = srchstr.indexOf('"',postext+1)+1;
nextIsItem = false;
}
else
{
malformed = true;
srchstr = srchstr.substring(0,postext) + " && " + srchstr.substring(postext+1);
postext += 4;
nextIsItem = true;
}
}
}
if (i == 0) return "true";
if (nextIsItem)
{
malformed = true;
var lastAnd = srchstr.lastIndexOf('&&');
var lastOr = srchstr.lastIndexOf('||');
var lastOp = Math.max(lastAnd, lastOr);
while (srchstr.lastIndexOf('(') > lastOp)
{
srchstr = srchstr.substring(0,srchstr.lastIndexOf('('));
inParen--;
}
var lastParen = srchstr.lastIndexOf('(');
srchstr = srchstr.substring(0,lastOp);
}
while (inParen > 0)
{
srchstr += ")";
inParen--;
}
if (malformed)
{
const NF_SB = document.getElementById("newsfox-string-bundle");
var msg = NF_SB.getString('malformed');
msg += "\n\n" + NF_SB.getString('searchtext') + srchtext + "\n";
srchstrG = srchstr.replace(/!/g,"-");
srchstrG = srchstrG.replace(/ && /g, " ");
srchstrG = srchstrG.replace(/ \|\| /g, " \| ");
msg += NF_SB.getString('actualsearch') + srchstrG + "\n\n";
}
for (var j=0; j<i; j++)
{
repl = '"st[' + j + ']"';
newrepl = "srchText(st[" + j + "], arttext)";
srchstr = srchstr.replace(repl,newrepl);
if (malformed) msg += "st[" + j + "]= " + st[j] + "\n";
}
if (malformed) alert(msg);
return srchstr;
}
function setFeedbarButtons(type,title,hasHomepage)
{
var feedbarTitle = document.getElementById("feedTitle");
feedbarTitle.value = title;
var feedbarHome = document.getElementById("feedbarHome");
feedbarHome.setAttribute("disabled",false);
if (!hasHomepage) feedbarHome.setAttribute("disabled",true);
var feedbarRefresh = document.getElementById("feedbarRefresh");
feedbarRefresh.setAttribute("disabled",false);
if (type == 2 || type == -1) feedbarRefresh.setAttribute("disabled",true);
var feedbarMarkread = document.getElementById("feedbarMarkread");
feedbarMarkread.setAttribute("disabled",false);
if (type == -1) feedbarMarkread.setAttribute("disabled",true);
var feedbarMarkunread = document.getElementById("feedbarMarkunread");
feedbarMarkunread.setAttribute("disabled",false);
if (type == -1) feedbarMarkunread.setAttribute("disabled",true);
var feedbarDelete = document.getElementById("feedbarDelete");
feedbarDelete.setAttribute("disabled",false);
if (type == -1) feedbarDelete.setAttribute("disabled",true);
var feedbarOptions = document.getElementById("feedbarOptions");
feedbarOptions.setAttribute("disabled",false);
if (type == 2 || type == -1) feedbarOptions.setAttribute("disabled",true);
}
////////////////////////////////////////////////////////////
///// Sorting
///////////////////////////////////////////////////////////
function artSort(by,dir,feed,items,itemFeed,itemIndex,type)
{
// sorter chooser
var abc = function(by,dir)
{
if (dir != "ascending" && dir != "descending") return null;
switch (by)
{
case "flag":
if (dir == "descending") return FlagDown;
else return FlagUp;
case "title":
if (dir == "descending") return TitleDown;
else return TitleUp;
case "read":
if (dir == "descending") return ReadDown;
else return ReadUp;
case "date":
if (dir == "descending") return DateDown;
else return DateUp;
}
return null;
}
// sorters
var FlagUp = function(a,b)
{
var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x04) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x04));
if (tmp > 0) return 1;
else if (tmp < 0) return -1;
else return (a < b) ? -1 : 1;
}
// when gecko sort stable, can replace FlagUp body with the following line
// { return ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x04) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x04)); };
var FlagDown = function(a,b)
{
var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x04) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x04));
if (tmp > 0) return -1;
else if (tmp < 0) return 1;
else return (a < b) ? -1 : 1;
}
// when stable use next line
// { return FlagUp(b,a); };
var TitleUp = function(a,b) // don't worry about ties
{ return (items[a].title.toLowerCase() < items[b].title.toLowerCase() ? -1 : 1); };
var TitleDown = function(a,b) { return TitleUp(b,a); };
var ReadUp = function(a,b)
{
var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x01) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x01));
if (tmp > 0) return 1;
else if (tmp < 0) return -1;
else return (a < b) ? -1 : 1;
}
// when stable use next line
// { return ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x01) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x01)); };
var ReadDown = function(a,b)
{
var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x01) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x01));
if (tmp > 0) return -1;
else if (tmp < 0) return 1;
else return (a < b) ? -1 : 1;
}
// when stable use next line
// { return ReadUp(b,a); };
var DateUp = function(a,b) // don't worry about ties
{ return (items[a].date < items[b].date ? -1 : 1); };
var DateDown = function(a,b) { return DateUp(b,a); };
// movers
var tmpLoad = function(open,j,tmpA,tmpIF,tmpI,tmpF)
{
tmpA[open] = items[j];
tmpIF[open] = itemFeed[j];
tmpI[open] = itemIndex[j];
tmpF[open] = feed.flags[itemIndex[j]]; // only used in feed sort,
// so feed exists
}
var tmpUnload = function(j,open,tmpA,tmpIF,tmpI,tmpF)
{
items[j] = tmpA[open];
if (type == 1) // feed
{
feed.set(j,items[j]);
feed.flags[j] = tmpF[open];
}
else // group or category
{
itemFeed[j] = tmpIF[open];
itemIndex[j] = tmpI[open];
}
}
// the sort function
var sorter = abc(by,dir);
var N = items.length;
var dummy = new Array(N);
var invdum = new Array(N);
var done = new Array(N);
var tmpA = new Array();
var tmpIF = new Array();
var tmpI = new Array();
var tmpF = new Array();
for (var i=0; i<N; i++)
{
dummy[i] = i;
done[i] = false;
}
dummy.sort(sorter);
for (i=0; i<N; i++)
invdum[dummy[i]] = i;
var j, open;
for (i=0; i<N; i++)
if (!done[i])
{
j = invdum[i];
tmpLoad(0,i,tmpA,tmpIF,tmpI,tmpF);
open = 1;
while (j != i)
{
tmpLoad(open,j,tmpA,tmpIF,tmpI,tmpF);
open = 1 - open;
tmpUnload(j,open,tmpA,tmpIF,tmpI,tmpF);
done[dummy[j]] = true;
j = invdum[j];
}
tmpUnload(j,1-open,tmpA,tmpIF,tmpI,tmpF);
done[dummy[j]] = true;
}
}