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 >
Text File  |  2007-10-11  |  37KB  |  1,289 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is NewsFox.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Andy Frank <andy@andyfrank.com>.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005-2007
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Andrey Gromyko <andrey@gromyko.name>
  23.  *   Ron Pruitt <wa84it@gmail.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the LGPL or the GPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. ////////////////////////////////////////////////////////////////
  40. // Global
  41. ////////////////////////////////////////////////////////////////
  42.  
  43. // common to many *.js
  44. var gFmodel = new FdModel();
  45. var gIdx = new Indexes();
  46. var gFdGroup = new Array();
  47. var gCollect = null;
  48.  
  49. // globals just in model.js
  50. const FOLDER_OPEN  = "chrome://newsfox/skin/images/folderOpen.png";
  51. const FOLDER_CLOSED  = "chrome://newsfox/skin/images/folderClosed.png";
  52. const FOLDER_SEARCH  = "chrome://newsfox/skin/images/folderSearch.png";
  53. const ICON_ERR = "chrome://newsfox/skin/images/brokenFeed.png";
  54.  
  55. var dragLevel = 3;
  56. var dragGrp = -1;
  57.  
  58. ////////////////////////////////////////////////////////////////
  59. // Model
  60. ////////////////////////////////////////////////////////////////
  61.  
  62. function FdModel()
  63. {
  64.   this.add  = function(feed,isExcluded)
  65.   {
  66.     if (isExcluded) 
  67.       feeds.push(feed);
  68.     else
  69.       feeds.splice(gFmodel.size(),0,feed); 
  70.   }
  71.   this.get  = function(index) { return feeds[index]; }
  72.     this.set  = function(index, feed)
  73.     {
  74.         if(index < 0 || index > feeds.length - 1)
  75.             return;
  76.         feeds[index] = feed;
  77.     }
  78.   this.getIndexByURL  = function(url)
  79.     {
  80.         for(var i=0; i < feeds.length; i++)
  81.             if(url == feeds[i].url)
  82.                 return i;
  83.         return -1;
  84.     }
  85.   this.getFeedByURL  = function(url)
  86.     {
  87.         var nFeed = this.getIndexByURL(url);
  88.         if (nFeed > -1) return feeds[nFeed];
  89.         else return null;
  90.     }
  91.  
  92.   this.size = function() // returns size without excluded feed(s)    
  93.     {
  94.         var size = 0;
  95.         for( var i = 0; i < feeds.length; i++ )
  96.             if( !feeds[i].exclude )
  97.                 size++;
  98.         return size;
  99.     }
  100.  
  101.   this.sizeTotal = function() // returns total size, i.e. with excluded from seeing feed(s)
  102.     {
  103.         return feeds.length;
  104.     }
  105.  
  106.   var feeds = new Array();
  107.  
  108.   /**
  109.    * Return a new unique uid.
  110.    */
  111.   this.makeUniqueUid = function(url)
  112.   {
  113.     var index  = url.indexOf("://");
  114.         var body;
  115.         if( index > -1 )
  116.             if (url.charAt(index+3) == "/")  //  file:///
  117.                 body = "local";
  118.             else
  119.                 body = url.substring(index+3);
  120.         else
  121.             body = url;
  122.     var domain = body;
  123.         if( body.indexOf("/") != -1 )
  124.             domain = body.split("/")[0];
  125.         if( domain.indexOf(":") != -1 )
  126.             domain = domain.split(":")[0]; // there are cases when port number follows domain name
  127.     var count = 1;
  128.     var name = domain;
  129.     for (var i=0; i<feeds.length; i++)
  130.     {
  131.       if (name == feeds[i].uid)
  132.       {
  133.         name = domain + (count++);
  134.         i = -1; // reset loop
  135.       }
  136.     }
  137.     return name;
  138.   }
  139.  
  140.   /**
  141.    * Remove the given feed from the model.
  142.    */
  143.   this.remove = function(feed)
  144.   {
  145.     for (var i=0; i < feeds.length; i++)
  146.       if (feed.uid == feeds[i].uid)
  147.       {
  148.         feeds.splice(i,1);
  149.         return;
  150.       }
  151.   }
  152. }
  153.  
  154. ////////////////////////////////////////////////////////////////
  155. // Group Model
  156. ////////////////////////////////////////////////////////////////
  157.  
  158. function FeedGroup()
  159. {
  160.     const NF_SB = document.getElementById("newsfox-string-bundle");
  161.   this.title = NF_SB.getString('newGroup');
  162.   this.expanded = false;
  163.   this.list = new Array();
  164.     this.search = false;
  165.     this.srchdat = { flagged : 2, unread : 2, text : "", textflags : 6, startTime : -1, endTime: 0 };
  166.   this.getUnread = function()
  167.   {
  168.         if (this.search) return 0;  // TODO, simple on the fly doesn't work well
  169.     var unread = 0;
  170.         for (var i=0; i<this.list.length; i++)
  171.       unread += gFmodel.get(this.list[i]).getUnread(0);
  172.     return unread;
  173.   }
  174. }
  175.  
  176. function Indexes()
  177. {
  178.     this.fdgp = new Array();
  179.     this.feed = new Array();
  180.     this.catg = new Array();
  181.     this.open = new Array();
  182. }
  183.  
  184. ////////////////////////////////////////////////////////////////
  185. // Feed Model
  186. ////////////////////////////////////////////////////////////////
  187.  
  188. function Feed()
  189. {
  190.   this.uid = null;
  191.   this.url = null;
  192.   this.homepage = null;
  193.     this.icon = new Image;
  194.     this.icon.src = ICON_OK;
  195.   this.defaultName = null;
  196.   this.customeName = null;
  197.   this.error = ERROR_OK;
  198.   this.loaded = false;
  199.   this.deleteOld = true;
  200.   this.dontDeleteUnread = true;
  201.     this.daysToKeep = 0;
  202.   this.autoCheck = true;
  203.   var categories = new Array();
  204.     this.exclude = false;
  205.  
  206.   // 0 Use global setting
  207.   // 1 Override to show as text
  208.   // 2 Override to show as webpage
  209.   // 3 Override to show as newspaper
  210.   this.style = 0;
  211.   this.getStyle = function()
  212.   {
  213.     if (this.style == 0)
  214.       return gOptions.globalStyle;
  215.     return this.style;
  216.   }
  217.  
  218.   /**
  219.    * Return display name for feed.
  220.    */
  221.   this.getDisplayName = function()
  222.   {
  223.     return ((this.customName != null) && (this.customName.length > 0)) ? this.customName : this.defaultName;
  224.   }
  225.  
  226.   this.setDefaultName = function(dname) { this.defaultName = dname; }
  227.  
  228.   /**
  229.    * Return the number of unread items for this feed.
  230.    */
  231.   this.getUnread = function(nCategory)
  232.   {
  233.     var unread = 0;
  234.     for (var i=0; i<this.flags.length; i++)
  235.       if ((this.flags[i] & 0x01) == 0) // unread
  236.       {
  237.         if( nCategory == 0 )
  238.           unread++;
  239.         else
  240.                 {
  241.                     var ScatnameS = "\/" + categories[nCategory-1] + "\/";
  242.                     var ScatS = "\/" + articles[i].category + "\/";
  243.                     if (ScatS.indexOf(ScatnameS) > -1)
  244.               unread++;
  245.                 }
  246.       }
  247.     return unread;
  248.   }
  249.  
  250.   this.add = function(article,flag)
  251.   {
  252.         articles.push(article);
  253.         if(article.category != "")
  254.             this.addCategory(article.category);
  255.         this.flags.push(flag);
  256.   }
  257.  
  258.   this.deletedAdd = function(article) { deletedarticles.push(article); }
  259.  
  260.   this.get = function(index) { return articles[index]; }
  261.  
  262.   this.deletedget = function(index) { return deletedarticles[index]; }
  263.  
  264.   this.set = function(index,item) { articles[index] = item; }
  265.  
  266.   this.remove = function(index)
  267.   {
  268.     articles.splice(index,1);
  269.     this.flags.splice(index,1);
  270.   }
  271.  
  272.   this.deletedremove = function(index) { deletedarticles.splice(index,1); }
  273.  
  274.   this.getIndexById = function(id)
  275.   {
  276.         for(var i=0; i < articles.length; i++)
  277.             if(id == articles[i].id)
  278.                 return i;
  279.         return -1;
  280.   }
  281.  
  282.   this.deleteById = function(id)
  283.   {
  284.         if (id == null) return;
  285.     var index = this.getIndexById(id);
  286.     if( index >= 0 && index < articles.length)
  287.         {
  288.         deletedarticles.push(articles[index]);
  289.             articles.splice(index,1);
  290.             this.flags.splice(index,1);
  291.         }
  292.   }
  293.  
  294.   this.size = function() { return articles.length; }
  295.   this.deletedsize = function() { return deletedarticles.length; }
  296.   var articles = new Array();
  297.   var deletedarticles = new Array();
  298.  
  299.   // Flags
  300.   this.flags = new Array();
  301.  
  302.   this.isRead = function(index)
  303.   {
  304.     return ((this.flags[index] & 0x01) != 0);
  305.   }
  306.   this.setRead = function(index, value)
  307.   {
  308.     if (value) this.flags[index] |= 0x01;
  309.     else this.flags[index] &= 0xFE;
  310.   }
  311.  
  312.   this.isFlagged = function(index)
  313.   {
  314.     return ((this.flags[index] & 0x04) != 0);
  315.   }
  316.   this.setFlagged = function(index, value)
  317.   {
  318.     if (value) this.flags[index] |= 0x04;
  319.     else this.flags[index] &= 0xFB;
  320.   }
  321.  
  322.   // AG: added categories
  323.   this.addCategory = function(category)
  324.   {
  325.         var catArray = category.split("\/");
  326.         for (var i=0; i<catArray.length; i++)
  327.         {
  328.             var exists = false;
  329.             for (var j=0; j<categories.length; j++)
  330.                 if (catArray[i] == categories[j]) exists = true;
  331.             if (!exists) categories.push(catArray[i]);
  332.         }
  333.   }
  334.   this.getCategories = function() { return categories; }
  335.   this.sortCategories = function() { categories.sort(); }
  336. }
  337.  
  338. ////////////////////////////////////////////////////////////////
  339. // FeedTree Model
  340. ////////////////////////////////////////////////////////////////
  341.  
  342. function dragIt(evt) {
  343.   var tree = evt.target.parentNode;
  344.   if (tree.id != "newsfox.feedTree") return;
  345.   var row = {}, col = {}, type = {};
  346.   tree.treeBoxObject.getCellAt(evt.clientX, evt.clientY, row, col, type);
  347.   dragLevel = tree.view.getLevel(row.value);
  348.   dragGrp = gIdx.fdgp[row.value]; 
  349.   if (dragLevel <= 1) nsDragAndDrop.startDrag(evt,feedtreeObserver);
  350. }
  351.  
  352. var feedtreeObserver = {
  353.  
  354.   onDragStart: function (evt,transferData,action)
  355.   { 
  356.     var row = {}, col = {}, type = {};
  357.     evt.target.parentNode.treeBoxObject.getCellAt(evt.clientX, evt.clientY, row, col, type); 
  358.     transferData.data=new TransferData(); 
  359.     transferData.data.addDataForFlavour("feedtreerow",gIdx.feed[row.value]); 
  360.   },
  361.  
  362.   getSupportedFlavours : function ()
  363.   { 
  364.         var flavours = new FlavourSet(); 
  365.         flavours.appendFlavour("feedtreerow"); 
  366.         return flavours; 
  367.   }, 
  368.  
  369.   onDragOver: function (evt,flavour,session){}, 
  370.  
  371.   onDrop: function (evt,dropdata,session){} 
  372. };
  373.  
  374. function FeedTreeModel() 
  375. {
  376.   // 3/25/05 - Temp disable flag/trash folder
  377.   // AG: added category. Most subcode is reworked
  378.   // 11 Jan 07 RP: remove flag/trash, add groups, code reworked
  379.  
  380.   this.rowCount = gIdx.fdgp.length;
  381.   this.isTopLevelElement = function(row){ return (this.getLevel(row) == 0); }
  382.   this.getCellText = function(row,col)
  383.   {
  384.     var level = this.getLevel(row);
  385.     var text, unread;
  386.     if (level == 0)
  387.     {
  388.       var curFdGroup = gFdGroup[gIdx.fdgp[row]];
  389.       text = curFdGroup.title;
  390.       unread = curFdGroup.getUnread();
  391.             if (row == 0) newTitle(unread);
  392.     }
  393.     else
  394.     {
  395.       var feed = gFmodel.get(gIdx.feed[row]);
  396.       var nCategory = gIdx.catg[row]; 
  397.       text = "";
  398.       unread = feed.getUnread(nCategory);
  399.       if( nCategory )
  400.       {
  401.         var categories = feed.getCategories();
  402.         text = categories[nCategory-1];
  403.       }
  404.       else
  405.         text = feed.getDisplayName();
  406.     }
  407.     if (unread > 0) text += " (" + unread + ")";
  408.     return text; 
  409.   }
  410.   this.setTree = function(treebox){ this.treebox = treebox; }
  411.   this.isContainer = function(row){ return (this.getLevel(row) < 2); }
  412.   this.isContainerEmpty = function(row)
  413.   {
  414.     var level = this.getLevel(row);
  415.         if (level == 0 && gFdGroup[gIdx.fdgp[row]].search) return true;
  416.     var feed = gFmodel.get(gIdx.feed[row]);
  417.     if (level == 0 && gFdGroup[gIdx.fdgp[row]].list.length) return false;
  418.     if (level == 1 && feed.loaded && feed.getCategories().length) return false;
  419.     return true;
  420.   }
  421.   this.isContainerOpen = function(row){ return gIdx.open[row]; }
  422.   this.hasNextSibling = function(row, index)
  423.   {
  424.     if (index+1 > gIdx.fdgp.length-1) return false;
  425.     var level = this.getLevel(row);
  426.     if (level == 0) return (gIdx.fdgp[gIdx.fdgp.length-1] != gIdx.fdgp[row]);
  427.     else if (level == 1) return (gIdx.fdgp[index+1] == gIdx.fdgp[row]);
  428.     else return ((gIdx.fdgp[index+1] == gIdx.fdgp[row]) && (gIdx.feed[index+1] == gIdx.feed[row]));
  429.   }
  430.   this.getParentIndex = function(row)
  431.   {
  432.     var index = row;
  433.     var level = this.getLevel(row);
  434.     if (level == 0) index = -2;
  435.     else if (level == 1)
  436.       while (gIdx.fdgp[index] == gIdx.fdgp[row]) index--;
  437.     else
  438.       while (gIdx.feed[index] == gIdx.feed[row]) index--;
  439.     return ++index; 
  440.   }
  441.   this.isSeparator = function(row){ return false; }
  442.   this.isEditable = function(row,col){ return false; }
  443.   this.isSorted = function(row){ return false; }
  444.   this.getLevel = function(row)
  445.   {
  446.     var level = 0;
  447.     if (gIdx.catg[row] > 0) level = 2;
  448.     else if (gIdx.feed[row] > -1) level = 1;
  449.     return level; 
  450.   }
  451.   this.getImageSrc = function(row,col) 
  452.   {
  453.     var retval = null;
  454.     var level = this.getLevel(row);
  455.     if (level == 0)
  456.       {
  457.         if (this.isContainerOpen(row)) 
  458.           retval = FOLDER_OPEN;
  459.         else if (gFdGroup[gIdx.fdgp[row]].search)
  460.                     retval = FOLDER_SEARCH;
  461.                 else
  462.           retval = FOLDER_CLOSED;
  463.       }
  464.     else if (level == 1)
  465.       {
  466.         var feed = gFmodel.get(gIdx.feed[row]);
  467.         var nofeederror = (feed.error == ERROR_OK);
  468.         retval = nofeederror ? feed.icon.src : ICON_ERR;
  469.       }
  470.     return retval;
  471.   }
  472.   this.getRowProperties = function(row,props) {}
  473.   this.getCellProperties = function(row,col,props) 
  474.   {
  475.         if (gIdx.catg[row] == 0)
  476.         {
  477.             var aserv = Components.classes["@mozilla.org/atom-service;1"].
  478.             getService(Components.interfaces.nsIAtomService);
  479.             props.AppendElement(aserv.getAtom("faviconcol"));
  480.         }
  481.     var hasUnread = false;
  482.     var nFeed = gIdx.feed[row];
  483.     if (nFeed == -1 && gFdGroup[gIdx.fdgp[row]].getUnread() > 0)
  484.       hasUnread = true;
  485.     if (nFeed > -1 && gFmodel.get(nFeed).getUnread(gIdx.catg[row]) > 0)
  486.       hasUnread = true;
  487.     if (hasUnread)
  488.     {
  489.       var aserv = Components.classes["@mozilla.org/atom-service;1"].
  490.         getService(Components.interfaces.nsIAtomService);
  491.       props.AppendElement(aserv.getAtom("unread"));
  492.     }
  493.   }
  494.   this.getColumnProperties = function(colid,col,props){}
  495.   this.toggleOpenState = function(row)
  496.   {
  497.     if (this.isContainerEmpty(row)) return;
  498.     var feedtree = document.getElementById("newsfox.feedTree");
  499.     var fRow = feedtree.treeBoxObject.getFirstVisibleRow();
  500.     var level = this.getLevel(row);
  501.     if (level == 0)
  502.     {
  503.       var curGrp = gIdx.fdgp[row];
  504.       var grp = gFdGroup[curGrp];
  505.       if (grp.expanded)
  506.       {
  507.         var num = 0;
  508.         while (gIdx.fdgp[row+num] == curGrp) num++;
  509.         num--;
  510.         gIdx.fdgp.splice(row+1,num);
  511.         gIdx.feed.splice(row+1,num);
  512.         gIdx.catg.splice(row+1,num);
  513.         gIdx.open.splice(row+1,num);
  514.             feedtree.treeBoxObject.rowCountChanged(row+1,-num);
  515.       }
  516.       else
  517.       {
  518.         for (var i=1; i <= grp.list.length; i++)
  519.         {
  520.           gIdx.fdgp.splice(row+i,0,curGrp);
  521.           gIdx.feed.splice(row+i,0,grp.list[i-1]);
  522.           gIdx.catg.splice(row+i,0,0);
  523.           gIdx.open.splice(row+i,0,false);
  524.         }
  525.             feedtree.treeBoxObject.rowCountChanged(row+1,grp.list.length);
  526.       }
  527.       grp.expanded = !grp.expanded;
  528.       gIdx.open[row] = !gIdx.open[row];
  529.     }
  530.     else if (level == 1)
  531.     {
  532.       var curFeed = gIdx.feed[row];
  533.       var feed = gFmodel.get(curFeed);
  534.       if (gIdx.open[row])
  535.       {
  536.         var num = 0;
  537.         while (gIdx.feed[row+num] == curFeed) num++;  // can't use categories.length since # categories may change
  538.         num--;
  539.         gIdx.fdgp.splice(row+1,num);
  540.         gIdx.feed.splice(row+1,num);
  541.         gIdx.catg.splice(row+1,num);
  542.         gIdx.open.splice(row+1,num);
  543.             feedtree.treeBoxObject.rowCountChanged(row+1,-num);
  544.       }
  545.       else
  546.       {
  547.         for (var i=1; i <= feed.getCategories().length; i++)
  548.         {
  549.           gIdx.fdgp.splice(row+i,0,gIdx.fdgp[row]);
  550.           gIdx.feed.splice(row+i,0,gIdx.feed[row]);
  551.           gIdx.catg.splice(row+i,0,i);
  552.           gIdx.open.splice(row+i,0,false);
  553.         }
  554.             feedtree.treeBoxObject.rowCountChanged(row+1,feed.getCategories().length);
  555.       }
  556.       gIdx.open[row] = !gIdx.open[row];
  557.     }
  558.     else return;
  559.     saveModels();
  560.     feedtree.treeBoxObject.scrollToRow(fRow);
  561.   }
  562.   this.canDrop = function(row,orientation) 
  563.   {
  564. //    if (orientation == 0) return false;
  565.     switch (this.getLevel(row))
  566.     {
  567.       case 0:
  568.     return (dragLevel == 0) ? true : false;
  569.       case 1:
  570.     return (dragLevel == 1 && dragGrp == gIdx.fdgp[row]) ? true : false;
  571.       case 2:
  572.       default:
  573.     return false;
  574.     }
  575.   }
  576.   this.drop = function(row,orientation)
  577.   {
  578.     var flavourSet = feedtreeObserver.getSupportedFlavours();
  579.     var transferData = nsTransferable.get(flavourSet, nsDragAndDrop.getDragData, true);
  580.     var dropdata = transferData.first.first;
  581.     var oldFeed = dropdata.data;
  582.     var newGrp = gIdx.fdgp[row] + (orientation == 1);
  583.     var newFeed = gIdx.feed[row];
  584.     if (orientation == 1)
  585.     {
  586.       var i = row;
  587.       while (gIdx.feed[i] == newFeed) i++;
  588.       if (gIdx.fdgp[i] == dragGrp) newFeed = gIdx.feed[i]
  589.       else newFeed = -2;
  590.     }
  591.     if (dragLevel == 0) mvGrp(dragGrp,newGrp);
  592.     else   // dragLevel == 1
  593.       mvFeed(dragGrp,oldFeed,newFeed);
  594.     dragLevel = 3;
  595.     dragGrp = -1;
  596.   }
  597. }
  598.  
  599. ////////////////////////////////////////////////////////////////
  600. // Article Model
  601. ////////////////////////////////////////////////////////////////
  602.  
  603. function Article()
  604. {
  605.   this.link  = null;
  606.   this.title = "";
  607.   this.body  = "";
  608.   this.date  = null;
  609.   // AG: added category
  610.   this.category  = "";
  611.     this.enclosures = new Array();
  612.   this.id = null;
  613.     this.toRemove = false;
  614. }
  615.  
  616. function Enclosure()
  617. {
  618.     this.url = "";
  619.     this.type = "";
  620.     this.length = "";
  621. }
  622.  
  623. ////////////////////////////////////////////////////////////////
  624. // ArticleTree Model
  625. ////////////////////////////////////////////////////////////////
  626.  
  627. function ArticleTreeModel() 
  628. {
  629.   var flagIcon = "chrome://newsfox/skin/images/flag.png";
  630.   var readIcon = "chrome://newsfox/skin/images/read.png";
  631.   var unreadIcon = "chrome://newsfox/skin/images/unread.png";
  632.  
  633.     removeHeaderArrows();
  634.  
  635.   this.rowCount = gCollect.size();
  636.   this.getCellText = function(row,col)
  637.   {
  638.     // Try to handle both Firefox 1.0 and 1.1
  639.     var colId = (col.id) ? col.id : col;
  640.     switch (colId)
  641.     {
  642.       case "title": return entityDecode(gCollect.get(row).title);
  643.       case "date": return displayDate(gCollect.get(row).date, gOptions.dateStyle);
  644.       default: return "debug-" + col;
  645.     }
  646.   }
  647.   this.setTree = function(treebox){ this.treebox = treebox; }
  648.   this.isContainer = function(row){ return false; }
  649.   this.isSeparator = function(row){ return false; }
  650.   this.isSorted = function(row){ return false; }
  651.   this.getLevel = function(row){ return 0; }
  652.   this.getImageSrc = function(row,col)
  653.   { 
  654.     var read = gCollect.isRead(row);
  655.     var flag = gCollect.isFlagged(row);
  656.  
  657.     // Try to handle both Firefox 1.0 and 1.1
  658.     var colId = (col.id) ? col.id : col;
  659.     switch (colId)
  660.     {
  661.       case "read": return read ? readIcon : unreadIcon;
  662.       case "flag": return flag ? flagIcon : readIcon;
  663.             case "title": return (gCollect.type == 0 || gCollect.type == 3) ? gCollect.getFeed(row).icon.src : null;
  664.       default: return null;
  665.     }
  666.   }
  667.   this.getRowProperties = function(row,props) {}
  668.   this.getCellProperties = function(row,col,props) 
  669.   {
  670.     // Try to handle both Firefox 1.0 and 1.1
  671.     var colId = (col.id) ? col.id : col;
  672.         if (colId == "title" && (gCollect.type == 0 || gCollect.type == 3))
  673.         {
  674.             var aserv = Components.classes["@mozilla.org/atom-service;1"].
  675.             getService(Components.interfaces.nsIAtomService);
  676.             props.AppendElement(aserv.getAtom("faviconcol"));
  677.         }
  678.     if (!gCollect.isRead(row))
  679.     {
  680.       var aserv = Components.classes["@mozilla.org/atom-service;1"].
  681.         getService(Components.interfaces.nsIAtomService);
  682.       props.AppendElement(aserv.getAtom("unread"));
  683.     }
  684.   }
  685.   this.getColumnProperties = function(colid,col,props) {}
  686.   this.cycleHeader = function(col,elem)
  687.   {
  688.     // Try to handle both Firefox 1.0 and 1.1
  689.     var colId = (col.id) ? col.id : col;
  690.  
  691.         var colObj = document.getElementById(colId);
  692.         var direction = colObj.getAttribute("sortDirection");
  693.         removeHeaderArrows();
  694.         var newDir;
  695.         switch (colId)
  696.         {
  697.             case "flag":
  698.             case "title":
  699.                 newDir = "ascending";
  700.                 if (direction == "ascending") newDir = "descending";
  701.                 break;
  702.             case "read":
  703.             case "date":
  704.                 newDir = "descending";
  705.                 if (direction == "descending") newDir = "ascending";    
  706.         }
  707.     var arttree = document.getElementById("newsfox.articleTree");
  708.         arttree.blur();
  709.         arttree.view.selection.select(-1);
  710.         gCollect.artSort(colId,newDir);
  711.         if (gCollect.type == 1)  // feed, not group or category
  712.         {
  713.             saveFeed(gCollect.feed);     // feed(s) not changed by resorting
  714.             saveFeedModel();                    // group or category
  715.         }
  716.         colObj.setAttribute("sortDirection", newDir);
  717.   }
  718.   this.cycleCell = function(row,col)
  719.   {
  720.     // Try to handle both Firefox 1.0 and 1.1
  721.     var colId = (col.id) ? col.id : col;
  722.  
  723.     if (colId == "read") 
  724.     {
  725.       var read = gCollect.isRead(row);
  726.       gCollect.setRead(row, !read);
  727.       var tree = document.getElementById("newsfox.feedTree");
  728.       tree.treeBoxObject.invalidate();
  729.     }
  730.     else if (colId == "flag")
  731.     {
  732.       var flag = gCollect.isFlagged(row);
  733.       gCollect.setFlagged(row, !flag);
  734.     }
  735.         var arttree = document.getElementById("newsfox.articleTree");
  736.         arttree.treeBoxObject.invalidate();
  737.   }
  738. }
  739.  
  740. function removeHeaderArrows()
  741. {
  742.     var flagObj = document.getElementById("flag");
  743.     var titleObj = document.getElementById("title");
  744.     var readObj = document.getElementById("read");
  745.     var dateObj = document.getElementById("date");
  746.     flagObj.setAttribute("sortDirection", "natural");
  747.     titleObj.setAttribute("sortDirection", "natural");
  748.     readObj.setAttribute("sortDirection", "natural");
  749.     dateObj.setAttribute("sortDirection", "natural");
  750. }
  751.  
  752. ////////////////////////////////////////////////////////////////
  753. // Collections
  754. ////////////////////////////////////////////////////////////////
  755.  
  756. // AG: categoryNo isn't an index. 0 means root (or whole feed)
  757. function NormalCollection(index, categoryNo, isDisplayed)
  758. {
  759.     this.type = 1;  // feed
  760.     if (categoryNo > 0) this.type = 2;
  761.     var feed = gFmodel.get(index);
  762.   this.feed = feed;
  763.     if (isDisplayed)
  764.     {
  765.         var title = feed.getDisplayName();
  766.         var hasHomepage = (feed.homepage != "");
  767.         setFeedbarButtons(this.type,title,hasHomepage);
  768.     }
  769.   var items = new Array();
  770.     var itemFeed = new Array();
  771.     var itemIndex = new Array();
  772.   var art;
  773.   if (categoryNo > 0)
  774.   {
  775.     var categories = feed.getCategories();
  776.     var ScatnameS = "\/" + categories[categoryNo - 1] + "\/";
  777.   }
  778.   for (var i=0; i<feed.size(); i++)
  779.         if (categoryNo < 1)
  780.         {
  781.           items.push(feed.get(i));
  782.             itemFeed.push(index);
  783.             itemIndex.push(i);
  784.         }
  785.         else
  786.         {
  787.           art = feed.get(i);
  788.             var ScatS = "\/" + art.category + "\/";
  789.           if(ScatS.indexOf(ScatnameS) > -1)
  790.             {
  791.             items.push(art);
  792.                 itemFeed.push(index);
  793.                 itemIndex.push(i);
  794.             }
  795.         }
  796.   this.get  = function(index) { return items[index]; }
  797.   this.size = function() { return items.length; }
  798.     this.isRead = function(row) { return feed.isRead(itemIndex[row]); }
  799.     this.setRead = function(row,value) { feed.setRead(itemIndex[row],value); }
  800.     this.isFlagged = function(row) { return feed.isFlagged(itemIndex[row]); }
  801.     this.setFlagged = function(row,value)
  802.         { feed.setFlagged(itemIndex[row],value); }
  803.     this.getFeed = function(index) { return feed; }
  804.     this.artSort = function(by,dir)
  805.         { artSort(by,dir,feed,items,itemFeed,itemIndex,this.type); }
  806. }
  807.  
  808. function GroupCollection(grpindex, isSearch)
  809. {
  810.     const NF_SB = document.getElementById("newsfox-string-bundle");
  811.     var srchitem = new Array();
  812.     this.grpindex = grpindex;
  813.     var grp = gFdGroup[grpindex];
  814.     var grpHeading;
  815.     if (isSearch)
  816.     {
  817.         this.type = 3;  // search
  818.         grpHeading = NF_SB.getString('srchName');
  819.         var srchstr = mkSrchstr(grp.srchdat,srchitem);
  820.     }
  821.     else
  822.     {
  823.         this.type = 0;  // group
  824.         grpHeading = NF_SB.getString('grpName');
  825.     }
  826.   var title = grpHeading + " " + grp.title;
  827.     setFeedbarButtons(this.type,title,false);
  828.   var items = new Array();
  829.     var itemIndex = new Array();
  830.     var itemFeed = new Array();
  831.   var art, feed, nFeed;
  832.   for (var i=0; i<grp.list.length; i++)
  833.     {
  834.         nFeed = grp.list[i];
  835.         feed = gFmodel.get(nFeed);
  836.         for (var j=0; j<feed.size(); j++)
  837.             if (this.type == 0 || hasProp(j, feed, grp.srchdat, srchstr, srchitem))
  838.             {
  839.               items.push(feed.get(j));
  840.                 itemIndex.push(j);
  841.                 itemFeed.push(nFeed);
  842.             }
  843.     }
  844.  
  845.     this.artSort = function(by,dir)
  846.         { artSort(by,dir,feed,items,itemFeed,itemIndex,this.type); }
  847.  
  848.     this.artSort("date","descending");
  849.  
  850.   this.get  = function(index) { return items[index]; }
  851.   this.size = function() { return items.length; }
  852.     this.isRead = function(row) { return gFmodel.get(itemFeed[row]).isRead(itemIndex[row]); }
  853.     this.setRead = function(row,value) { gFmodel.get(itemFeed[row]).setRead(itemIndex[row],value); }
  854.     this.isFlagged = function(row) { return gFmodel.get(itemFeed[row]).isFlagged(itemIndex[row]); }
  855.     this.setFlagged = function(row,value)
  856.         { gFmodel.get(itemFeed[row]).setFlagged(itemIndex[row],value); }
  857.     this.getFeed = function(index) { return gFmodel.get(itemFeed[index]); }
  858.     this.getSrchText = function() { return srchitem; }
  859.     this.getTitle = function() { return title; }
  860. }
  861.  
  862. function EmptyCollection()
  863. {
  864.     var items = new Array();
  865.     this.type = -1;  // empty
  866.     setFeedbarButtons(this.type,"?",false);
  867.   this.size = function() { return items.length; }
  868. }
  869.  
  870. ////////////////////////////////////////////////////////////
  871. /////   Collection utilities
  872. ///////////////////////////////////////////////////////////
  873.  
  874. function hasProp(j, feed, srchdat, srchstr, st)
  875. {
  876.     if ((srchdat.flagged != 2) && (srchdat.flagged == feed.isFlagged(j)))
  877.         return false;
  878.     if ((srchdat.unread != 2) && (srchdat.unread != feed.isRead(j)))
  879.         return false;
  880.     var art = feed.get(j);
  881.  
  882.     var now = new Date().getTime();
  883.     var artTime = art.date.getTime();
  884.     if (artTime > now - srchdat.endTime) return false;
  885.     if (srchdat.startTime > 0 && now - srchdat.startTime > artTime) return false;
  886.  
  887.     var caseSen = ((srchdat.textflags & 0x04) == 0);
  888.     var matchWhat = (srchdat.textflags & 0x03) + 1;
  889.     var arttext = "";
  890.     arttext += ((matchWhat & 0x01) != 0) ? art.body : "";
  891.     arttext += ((matchWhat & 0x02) != 0) ? art.title : "";
  892.     if (!caseSen)
  893.         arttext = arttext.toLowerCase();
  894.     return eval(srchstr);
  895. }
  896.  
  897. function srchText(srchtext, arttext)
  898. {
  899.     return (arttext.indexOf(srchtext) != -1);
  900. }
  901.  
  902. function mkSrchstr(srchdat,st)
  903. {
  904.     var caseSen = ((srchdat.textflags & 0x04) == 0);
  905.     var srchtext = srchdat.text;
  906.     if (srchtext == "") return "true";
  907.     if (!caseSen)
  908.         srchtext = srchtext.toLowerCase();
  909.  
  910.     var srchstr = srchtext;
  911.     var i = 0;
  912. // preprocess quoted stuff
  913.     var postext = 0;
  914.     var done = false;
  915.     var malformed = false;
  916.     var item, repl, newrepl;
  917.     while (!done)
  918.     {
  919.         var q1 = srchtext.indexOf("'",postext);
  920.         var q2 = srchtext.indexOf('"',postext);
  921.         if (q1 == -1 && q2 == -1) done = true;
  922.         else if ((q1 > -1 && q1 < q2) || q2 == -1)
  923.         {
  924.             var q3 = srchtext.indexOf("'",q1+1);
  925.             if (q3 == -1) // unbalanced error
  926.             {
  927.                 malformed = true;
  928.                 srchtext += "'";
  929.                 srchstr += "'";
  930.                 q3 = srchtext.indexOf("'",q1+1);
  931.             }
  932.             item = srchtext.substring(q1+1,q3);
  933.             newrepl = '"st[' + i + ']"';
  934.             st[i++] = item;
  935.             repl = "'" + item + "'";
  936.             srchstr = srchstr.replace(repl,newrepl);
  937.             postext = q3+1;
  938.         }
  939.         else   // -1 < q2 < q1 || q1 == -1
  940.         {
  941.             var q3 = srchtext.indexOf('"',q2+1);
  942.             if (q3 == -1) // unbalanced error
  943.             {
  944.                 malformed = true;
  945.                 srchtext += '"';
  946.                 srchstr += '"';
  947.                 q3 = srchtext.indexOf('"',q2+1);
  948.             }
  949.             item = srchtext.substring(q2+1,q3);
  950.             newrepl = '"st[' + i + ']"';
  951.             st[i++] = item;
  952.             repl = '"' + item + '"';
  953.             srchstr = srchstr.replace(repl,newrepl);
  954.             postext = q3+1;
  955.         }
  956.     }
  957.  
  958. // process string
  959.     postext = 0;
  960.     var pos;
  961.     done = false;
  962.     var inParen = 0;
  963.     var nextIsItem = true;
  964.     while (postext < srchstr.length)
  965.     {
  966.         switch (srchstr.charAt(postext))
  967.         {
  968.             case '"':
  969. //alert("case\"");
  970.                 postext = srchstr.indexOf('"',postext+1)+1;
  971.                 nextIsItem = false;
  972.                 break;
  973.             case "-":
  974. //alert("case-");
  975.                 if (srchstr.charAt(postext+1) != ' ' && srchstr.charAt(postext+1) != '|')
  976.                 {
  977.                     srchstr = srchstr.substring(0,postext) + "!" + srchstr.substring(postext+1);
  978.                     postext++;
  979.                 }
  980.                 else
  981.                 {
  982.                     if (nextIsItem)
  983.                     {
  984.                         malformed = true;
  985.                         srchstr = srchstr.substring(0,postext+1) + srchstr.substring(postext+2);
  986.                     }
  987.                     else
  988.                     {
  989.                         malformed = true;
  990.                         srchstr = srchstr.substring(0,postext) + srchstr.substring(postext+1);
  991.                     }
  992.                 }
  993.                 break;
  994.             case "(":
  995. //alert("case(");
  996.                 inParen++;
  997.                 postext++;
  998.                 break;
  999.             case ")":
  1000. //alert("case)");
  1001.                 if (inParen > 0 && !nextIsItem)
  1002.                 {
  1003.                     inParen--;
  1004.                     postext++;
  1005.                 }
  1006.                 else
  1007.                 {
  1008.                     malformed = true;
  1009.                     srchstr = srchstr.substring(0,postext) + srchstr.substring(postext+1);
  1010.                 }
  1011.                 break;
  1012.             case "|":
  1013. //alert("case|");
  1014.                 if (!nextIsItem)
  1015.                 {
  1016.                     srchstr = srchstr.substring(0,postext) + " || " + srchstr.substring(postext+1);
  1017.                     postext += 4;
  1018.                     nextIsItem = true;
  1019.                 }
  1020.                 else
  1021.                 {
  1022.                     malformed = true;
  1023.                     srchstr = srchstr.substring(0,postext) + srchstr.substring(postext+1);
  1024.                 }
  1025.                 break;
  1026.             case " ":
  1027. //alert("case-space");
  1028.                 pos = postext;
  1029.                 while (srchstr.charAt(pos) == " ") pos++;
  1030.                 switch (srchstr.charAt(pos))
  1031.                 {
  1032.                     case "|":
  1033.                     case ")":
  1034.                         srchstr = srchstr.substring(0,postext) + srchstr.substring(pos);
  1035.                         break;
  1036.                     case "o":
  1037.                     case "O":
  1038.                         if (srchstr.charAt(pos+1).toLowerCase() == "r" && srchstr.charAt(pos+2) == " ")
  1039.                         {
  1040.                             srchstr = srchstr.substring(0,postext) + "|" + srchstr.substring(pos+3);
  1041.                             break;
  1042.                         }
  1043.                     default:
  1044.                         if (pos >= srchstr.length)
  1045.                         {
  1046.                             postext = pos;
  1047.                             break;
  1048.                         }
  1049.                         if (nextIsItem)
  1050.                             srchstr = srchstr.substring(0,postext) + srchstr.substring(pos);
  1051.                         else
  1052.                         {
  1053.                             srchstr = srchstr.substring(0,postext) + " && " + srchstr.substring(pos);
  1054.                             postext += 4;
  1055.                             nextIsItem = true;
  1056.                         }
  1057.                 }
  1058.                 break;
  1059.             default: // search item
  1060. //alert("case");
  1061.                 if (nextIsItem)
  1062.                 {
  1063.                     pos = postext;
  1064.                     while (srchstr.charAt(pos) != '|' && srchstr.charAt(pos) != ' ' && srchstr.charAt(pos) != ')' && pos < srchstr.length) pos++;
  1065.                     if (srchstr.charAt(pos) == ')' && inParen <= 0)
  1066.                         while (srchstr.charAt(pos) != '|' && srchstr.charAt(pos) != ' ' && pos < srchstr.length) pos++;
  1067.                     item = srchstr.substring(postext,pos);
  1068.                     newrepl = '"st[' + i + ']"';
  1069.                     st[i++] = item;
  1070.                     srchstr = srchstr.replace(item,newrepl);
  1071.                     postext = srchstr.indexOf('"',postext+1)+1;
  1072.                     nextIsItem = false;
  1073.                 }
  1074.                 else
  1075.                 {
  1076.                     malformed = true;
  1077.                     srchstr = srchstr.substring(0,postext) + " && " + srchstr.substring(postext+1);
  1078.                     postext += 4;
  1079.                     nextIsItem = true;
  1080.                 }
  1081.         }
  1082.     }
  1083.  
  1084.     if (i == 0) return "true";
  1085.     if (nextIsItem)
  1086.     {
  1087.         malformed = true;
  1088.         var lastAnd = srchstr.lastIndexOf('&&');
  1089.         var lastOr = srchstr.lastIndexOf('||');
  1090.         var lastOp = Math.max(lastAnd, lastOr);
  1091.         while (srchstr.lastIndexOf('(') > lastOp)
  1092.         {
  1093.             srchstr = srchstr.substring(0,srchstr.lastIndexOf('('));
  1094.             inParen--;
  1095.         }
  1096.         var lastParen = srchstr.lastIndexOf('(');
  1097.         srchstr = srchstr.substring(0,lastOp);
  1098.     }
  1099.  
  1100.     while (inParen > 0)
  1101.     {
  1102.         srchstr += ")";
  1103.         inParen--;
  1104.     }
  1105.  
  1106.     if (malformed)
  1107.     {
  1108.         const NF_SB = document.getElementById("newsfox-string-bundle");
  1109.         var msg = NF_SB.getString('malformed');
  1110.         msg += "\n\n" + NF_SB.getString('searchtext') + srchtext + "\n";
  1111.         srchstrG = srchstr.replace(/!/g,"-");
  1112.         srchstrG = srchstrG.replace(/ && /g, " ");
  1113.         srchstrG = srchstrG.replace(/ \|\| /g, " \| ");
  1114.         msg += NF_SB.getString('actualsearch') + srchstrG + "\n\n";
  1115.     }
  1116.     for (var j=0; j<i; j++)
  1117.     {
  1118.         repl = '"st[' + j + ']"';
  1119.         newrepl = "srchText(st[" + j + "], arttext)";
  1120.         srchstr = srchstr.replace(repl,newrepl);
  1121.         if (malformed) msg += "st[" + j + "]= " + st[j] + "\n";
  1122.     }
  1123.     if (malformed) alert(msg);
  1124.  
  1125.     return srchstr;
  1126. }
  1127.  
  1128. function setFeedbarButtons(type,title,hasHomepage)
  1129. {
  1130.   var feedbarTitle = document.getElementById("feedTitle");
  1131.     feedbarTitle.value = title;
  1132.   var feedbarHome = document.getElementById("feedbarHome");
  1133.     feedbarHome.setAttribute("disabled",false);
  1134.     if (!hasHomepage) feedbarHome.setAttribute("disabled",true);
  1135.   var feedbarRefresh = document.getElementById("feedbarRefresh");
  1136.     feedbarRefresh.setAttribute("disabled",false);
  1137.     if (type == 2 || type == -1) feedbarRefresh.setAttribute("disabled",true);
  1138.   var feedbarMarkread = document.getElementById("feedbarMarkread");
  1139.     feedbarMarkread.setAttribute("disabled",false);
  1140.     if (type == -1) feedbarMarkread.setAttribute("disabled",true);
  1141.   var feedbarMarkunread = document.getElementById("feedbarMarkunread");
  1142.     feedbarMarkunread.setAttribute("disabled",false);
  1143.     if (type == -1) feedbarMarkunread.setAttribute("disabled",true);
  1144.   var feedbarDelete = document.getElementById("feedbarDelete");
  1145.     feedbarDelete.setAttribute("disabled",false);
  1146.     if (type == -1) feedbarDelete.setAttribute("disabled",true);
  1147.   var feedbarOptions = document.getElementById("feedbarOptions");
  1148.     feedbarOptions.setAttribute("disabled",false);
  1149.     if (type == 2 || type == -1) feedbarOptions.setAttribute("disabled",true);
  1150. }
  1151.  
  1152. ////////////////////////////////////////////////////////////
  1153. /////   Sorting
  1154. ///////////////////////////////////////////////////////////
  1155.  
  1156. function artSort(by,dir,feed,items,itemFeed,itemIndex,type) 
  1157. {
  1158. // sorter chooser
  1159.     var abc = function(by,dir)
  1160.     {
  1161.         if (dir != "ascending" && dir != "descending") return null;
  1162.         switch (by)
  1163.         {
  1164.             case "flag":
  1165.                 if (dir == "descending") return FlagDown;
  1166.                 else return FlagUp;
  1167.             case "title":
  1168.                 if (dir == "descending") return TitleDown;
  1169.                 else return TitleUp;
  1170.             case "read":
  1171.                 if (dir == "descending") return ReadDown;
  1172.                 else return ReadUp;
  1173.             case "date":
  1174.                 if (dir == "descending") return DateDown;
  1175.                 else return DateUp;
  1176.         }
  1177.     return null;
  1178.     }
  1179.  
  1180. // sorters
  1181.     var FlagUp = function(a,b)
  1182.     {
  1183.         var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x04) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x04));
  1184.         if (tmp > 0) return 1;
  1185.         else if (tmp < 0) return -1;
  1186.         else return (a < b) ? -1 : 1;
  1187.     }
  1188. // when gecko sort stable, can replace FlagUp body with the following line 
  1189. //        { return ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x04) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x04)); };
  1190.     var FlagDown = function(a,b)
  1191.     {
  1192.         var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x04) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x04));
  1193.         if (tmp > 0) return -1;
  1194.         else if (tmp < 0) return 1;
  1195.         else return (a < b) ? -1 : 1;
  1196.     }
  1197. // when stable use next line
  1198. //         { return FlagUp(b,a); };
  1199.  
  1200.     var TitleUp = function(a,b)  // don't worry about ties
  1201.     { return (items[a].title.toLowerCase() < items[b].title.toLowerCase() ? -1 : 1); };
  1202.     var TitleDown = function(a,b) { return TitleUp(b,a); };
  1203.  
  1204.     var ReadUp = function(a,b)
  1205.     {
  1206.         var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x01) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x01));
  1207.         if (tmp > 0) return 1;
  1208.         else if (tmp < 0) return -1;
  1209.         else return (a < b) ? -1 : 1;
  1210.     }
  1211. // when stable use next line
  1212. //        { return ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x01) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x01)); };
  1213.     var ReadDown = function(a,b)
  1214.     {
  1215.         var tmp = ((gFmodel.get(itemFeed[b]).flags[itemIndex[b]] & 0x01) - (gFmodel.get(itemFeed[a]).flags[itemIndex[a]] & 0x01));
  1216.         if (tmp > 0) return -1;
  1217.         else if (tmp < 0) return 1;
  1218.         else return (a < b) ? -1 : 1;
  1219.     }
  1220. // when stable use next line
  1221. //         { return ReadUp(b,a); };
  1222.  
  1223.     var DateUp = function(a,b)  // don't worry about ties
  1224.     { return (items[a].date < items[b].date ? -1 : 1); };
  1225.     var DateDown = function(a,b) { return DateUp(b,a); };
  1226.  
  1227. // movers
  1228.     var tmpLoad = function(open,j,tmpA,tmpIF,tmpI,tmpF)
  1229.     {
  1230.         tmpA[open] = items[j];
  1231.         tmpIF[open] = itemFeed[j];
  1232.         tmpI[open] = itemIndex[j];
  1233.         tmpF[open] = feed.flags[itemIndex[j]];  // only used in feed sort, 
  1234.                                                                                         // so feed exists
  1235.     }
  1236.  
  1237.     var tmpUnload = function(j,open,tmpA,tmpIF,tmpI,tmpF)
  1238.     {
  1239.         items[j] = tmpA[open];
  1240.         if (type == 1)  // feed
  1241.         {
  1242.             feed.set(j,items[j]);
  1243.             feed.flags[j] = tmpF[open];
  1244.         }
  1245.         else  // group or category
  1246.         {
  1247.             itemFeed[j] = tmpIF[open];
  1248.             itemIndex[j] = tmpI[open];
  1249.         }
  1250.     }
  1251.  
  1252. // the sort function
  1253.     var sorter = abc(by,dir);
  1254.     var N = items.length;
  1255.     var dummy = new Array(N);
  1256.     var invdum = new Array(N);
  1257.     var done = new Array(N);
  1258.     var tmpA = new Array();
  1259.     var tmpIF = new Array();
  1260.     var tmpI = new Array();
  1261.     var tmpF = new Array();
  1262.     for (var i=0; i<N; i++)
  1263.     {
  1264.         dummy[i] = i;
  1265.         done[i] = false;
  1266.     }
  1267.     dummy.sort(sorter);
  1268.     for (i=0; i<N; i++)
  1269.         invdum[dummy[i]] = i;
  1270.     var j, open;
  1271.     for (i=0; i<N; i++)
  1272.         if (!done[i])
  1273.         {
  1274.             j = invdum[i];
  1275.             tmpLoad(0,i,tmpA,tmpIF,tmpI,tmpF);
  1276.             open = 1;
  1277.             while (j != i)
  1278.             {
  1279.                 tmpLoad(open,j,tmpA,tmpIF,tmpI,tmpF);
  1280.                 open = 1 - open;
  1281.                 tmpUnload(j,open,tmpA,tmpIF,tmpI,tmpF);
  1282.                 done[dummy[j]] = true;
  1283.                 j = invdum[j];
  1284.             }
  1285.             tmpUnload(j,1-open,tmpA,tmpIF,tmpI,tmpF);
  1286.             done[dummy[j]] = true;
  1287.         }
  1288. }
  1289.