!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 //@line 38 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/mail/extensions/newsblog/content/feed-subscriptions.js"
2
3 const MSG_FOLDER_FLAG_TRASH = 0x0100;
4 const IPS = Components.interfaces.nsIPromptService;
5 const nsIDragService = Components.interfaces.nsIDragService;
6 const kRowIndexUndefined = -1;
7
8 var gFeedSubscriptionsWindow = {
9 mFeedContainers : [],
10 mTree : null,
11 mBundle : null,
12 mRSSServer : null,
13
init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
14 init: function ()
15 {
16 // extract the server argument
17 if (window.arguments[0].server)
18 this.mRSSServer = window.arguments[0].server;
19
20 var docshell = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
21 .getInterface(Components.interfaces.nsIWebNavigation)
22 .QueryInterface(Components.interfaces.nsIDocShell);
23 docshell.allowAuth = true;
24
25 this.mTree = document.getElementById("rssSubscriptionsList");
26 this.mBundle = document.getElementById("bundle_newsblog");
27
28 this.loadSubscriptions();
29 this.mTree.treeBoxObject.view = this.mView;
30
31 if (window.arguments[0].folder)
32 this.selectFolder(window.arguments[0].folder);
33 },
34
uninit
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
35 uninit: function ()
36 {
37 var dismissDialog = true;
38
39 // if we are in the middle of subscribing to a feed, inform the user that
40 // dismissing the dialog right now will abort the feed subscription.
41 // cheat and look at the disabled state of the add button to determine if we are in the middle of a new subscription
42 if (document.getElementById('addFeed').getAttribute('disabled'))
43 {
44 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(IPS);
45 var newsBlogBundle = document.getElementById("bundle_newsblog");
46 dismissDialog = !(promptService.confirmEx(window, newsBlogBundle.getString('subscribe-cancelSubscriptionTitle'),
47 newsBlogBundle.getString('subscribe-cancelSubscription'),
48 IPS.STD_YES_NO_BUTTONS,
49 null, null, null, null, { }));
50 }
51 return dismissDialog;
52 },
53
54 mView:
55 {
56 mRowCount : 0,
57
get_rowCount
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
58 get rowCount()
59 {
60 return this.mRowCount;
61 },
62
getItemAtIndex
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
63 getItemAtIndex: function (aIndex)
64 {
65 if (aIndex < 0 ||
66 aIndex >= gFeedSubscriptionsWindow.mFeedContainers.length)
67 return null;
68 return gFeedSubscriptionsWindow.mFeedContainers[aIndex];
69 },
70
removeItemAtIndex
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
71 removeItemAtIndex: function (aIndex, aCount)
72 {
73 var itemToRemove = this.getItemAtIndex(aIndex);
74 if (!itemToRemove)
75 return;
76
77 var parentIndex = this.getParentIndex(aIndex);
78 if (parentIndex != kRowIndexUndefined)
79 {
80 var parent = this.getItemAtIndex(parentIndex);
81 if (parent)
82 {
83 for (var index = 0; index < parent.children.length; index++)
84 if (parent.children[index] == itemToRemove)
85 {
86 parent.children.splice(index, 1);
87 break;
88 }
89 }
90 }
91
92 // now remove it from our view
93 gFeedSubscriptionsWindow.mFeedContainers.splice(aIndex, 1);
94
95 // now invalidate the correct tree rows
96 var tbo = gFeedSubscriptionsWindow.mTree.treeBoxObject;
97
98 this.mRowCount--;
99 tbo.rowCountChanged(aIndex, -1);
100
101 // now update the selection position
102 if (aIndex < gFeedSubscriptionsWindow.mFeedContainers.length)
103 this.selection.select(aIndex);
104 else
105 this.selection.clearSelection();
106
107 // now refocus the tree
108 gFeedSubscriptionsWindow.mTree.focus();
109 },
110
getCellText
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
111 getCellText: function (aIndex, aColumn)
112 {
113 var item = this.getItemAtIndex(aIndex);
114 return (item && aColumn.id == "folderNameCol") ? item.name : "";
115 },
116
117 _selection: null,
get_selection
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
118 get selection () { return this._selection; },
set_selection
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
119 set selection (val) { this._selection = val; return val; },
getRowProperties
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
120 getRowProperties: function (aIndex, aProperties) {},
getCellProperties
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
121 getCellProperties: function (aIndex, aColumn, aProperties) {},
getColumnProperties
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
122 getColumnProperties: function (aColumn, aProperties) {},
123
isContainer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
124 isContainer: function (aIndex)
125 {
126 var item = this.getItemAtIndex(aIndex);
127 return item ? item.container : false;
128 },
129
isContainerOpen
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
130 isContainerOpen: function (aIndex)
131 {
132 var item = this.getItemAtIndex(aIndex);
133 return item ? item.open : false;
134 },
135
isContainerEmpty
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
136 isContainerEmpty: function (aIndex)
137 {
138 var item = this.getItemAtIndex(aIndex);
139 if (!item)
140 return false;
141 return item.children.length == 0;
142 },
143
isSeparator
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
144 isSeparator: function (aIndex) { return false; },
isSorted
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
145 isSorted: function (aIndex) { return false; },
146
canDrop
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
147 canDrop: function (aIndex, aOrientation)
148 {
149 var dropResult = this.extractDragData();
150 return (aOrientation == Components.interfaces.nsITreeView.DROP_ON) &&
151 dropResult.canDrop && (dropResult.url || (dropResult.index != kRowIndexUndefined));
152 },
153
154 mDropUrl: "",
155 mDropFolderUrl: "",
drop
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
156 drop: function (aIndex, aOrientation)
157 {
158 var results = this.extractDragData();
159 if (!results.canDrop)
160 return;
161
162 if (results.url)
163 {
164 var folderItem = this.getItemAtIndex(aIndex);
165 // don't freeze the app that initiaed the drop just because we are in a loop waiting for the user
166 // to dimisss the add feed dialog....
167 this.mDropUrl = results.url;
168 this.mDropFolderUrl = folderItem.url;
169 setTimeout(processDrop, 0);
170 }
171 else if (results.index != kRowIndexUndefined)
172 gFeedSubscriptionsWindow.moveFeed(results.index, aIndex);
173 },
174
175 // helper function for drag and drop
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
176 extractDragData: function()
177 {
178 var canDrop = false;
179 var urlToDrop;
180 var sourceIndex = kRowIndexUndefined;
181 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].getService().QueryInterface(nsIDragService);
182 var dragSession = dragService.getCurrentSession();
183
184 var transfer = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
185 transfer.addDataFlavor("text/x-moz-url");
186 transfer.addDataFlavor("text/x-moz-feed-index");
187
188 dragSession.getData (transfer, 0);
189 var dataObj = new Object();
190 var flavor = new Object();
191 var len = new Object();
192
193 try {
194 transfer.getAnyTransferData(flavor, dataObj, len);
195 } catch (ex) { return { canDrop: false, url: "" }; }
196
197 if (dataObj.value)
198 {
199 dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
200 sourceUri = dataObj.data.substring(0, len.value); // pull the URL out of the data object
201
202 if (flavor.value == 'text/x-moz-url')
203 {
204 var uri = Components.classes["@mozilla.org/network/standard-url;1"].createInstance(Components.interfaces.nsIURI);
205 uri.spec = sourceUri.split("\n")[0];
206
207 if (uri.schemeIs("http") || uri.schemeIs("https"))
208 {
209 urlToDrop = uri.spec;
210 canDrop = true;
211 }
212 }
213 else if (flavor.value == 'text/x-moz-feed-index')
214 {
215 sourceIndex = parseInt(sourceUri);
216 canDrop = true;
217 }
218 } // if dataObj.value
219
220 return { canDrop: canDrop, url: urlToDrop, index: sourceIndex };
221 },
222
getParentIndex
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
223 getParentIndex: function (aIndex)
224 {
225 var item = this.getItemAtIndex(aIndex);
226
227 if (item)
228 {
229 for (var index = aIndex; index >= 0; index--)
230 if (gFeedSubscriptionsWindow.mFeedContainers[index].level < item.level)
231 return index;
232 }
233
234 return kRowIndexUndefined;
235 },
hasNextSibling
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
236 hasNextSibling: function (aParentIndex, aIndex)
237 {
238 var item = this.getItemAtIndex(aIndex);
239 if (item)
240 {
241 // if the next node in the view has the same level as us, then we must have a next sibling...
242 if (aIndex + 1 < gFeedSubscriptionsWindow.mFeedContainers.length )
243 return this.getItemAtIndex(aIndex + 1).level == item.level;
244 }
245
246 return false;
247 },
hasPreviousSibling
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
248 hasPreviousSibling: function (aIndex)
249 {
250 var item = this.getItemAtIndex(aIndex);
251 if (item && aIndex)
252 return this.getItemAtIndex(aIndex - 1).level == item.level;
253 else
254 return false;
255 },
getLevel
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
256 getLevel: function (aIndex)
257 {
258 var item = this.getItemAtIndex(aIndex);
259 if (!item)
260 return 0;
261 return item.level;
262 },
getImageSrc
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
263 getImageSrc: function (aIndex, aColumn) {},
getProgressMode
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
264 getProgressMode: function (aIndex, aColumn) {},
getCellValue
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
265 getCellValue: function (aIndex, aColumn) {},
setTree
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
266 setTree: function (aTree) {},
toggleOpenState
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
267 toggleOpenState: function (aIndex)
268 {
269 var item = this.getItemAtIndex(aIndex);
270 if (!item) return;
271
272 // save off the current selection item
273 var seln = this.selection;
274 var currentSelectionIndex = seln.currentIndex;
275
276 var multiplier = item.open ? -1 : 1;
277 var delta = multiplier * item.children.length;
278 this.mRowCount += delta;
279
280 if (multiplier < 0)
281 gFeedSubscriptionsWindow.mFeedContainers.splice(aIndex + 1, item.children.length);
282 else
283 for (var i = 0; i < item.children.length; i++)
284 gFeedSubscriptionsWindow.mFeedContainers.splice(aIndex + 1 + i, 0, item.children[i]);
285
286 // add or remove the children from our view
287 item.open = !item.open;
288 gFeedSubscriptionsWindow.mTree.treeBoxObject.rowCountChanged(aIndex, delta);
289
290 // now restore selection
291 seln.select(currentSelectionIndex);
292
293 },
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
294 cycleHeader: function (aColumn) {},
selectionChanged
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
295 selectionChanged: function () {},
cycleCell
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
296 cycleCell: function (aIndex, aColumn) {},
isEditable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
297 isEditable: function (aIndex, aColumn)
298 {
299 return false;
300 },
isSelectable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
301 isSelectable: function (aIndex, aColumn)
302 {
303 return false;
304 },
setCellValue
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
305 setCellValue: function (aIndex, aColumn, aValue) {},
setCellText
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
306 setCellText: function (aIndex, aColumn, aValue) {},
performAction
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
307 performAction: function (aAction) {},
performActionOnRow
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
308 performActionOnRow: function (aAction, aIndex) {},
performActionOnCell
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
309 performActionOnCell: function (aAction, aindex, aColumn) {}
310 },
311
makeFolderObject
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
312 makeFolderObject: function (aFolder, aCurrentLevel)
313 {
314 var folderObject = { children : [],
315 name : aFolder.prettiestName,
316 level : aCurrentLevel,
317 url : aFolder.QueryInterface(Components.interfaces.nsIRDFResource).Value,
318 open : false,
319 container: true };
320
321 // if a feed has any sub folders, we should add them to the list of children
322 var folderEnumerator = aFolder.subFolders;
323
324 while (folderEnumerator.hasMoreElements())
325 {
326 var folder =
327 folderEnumerator.getNext().QueryInterface(Components.interfaces.nsIMsgFolder);
328 folderObject.children.push(this.makeFolderObject(folder, aCurrentLevel + 1));
329 }
330
331 var feeds = this.getFeedsInFolder(aFolder);
332
333 for (feed in feeds)
334 {
335 // Special case, if a folder only has a single feed associated with it, then just use the feed
336 // in the view and don't show the folder at all.
337 // if (feedUrlArray.length <= 2 && !aFolder.hasSubFolders) // Note: split always adds an empty element to the array...
338 // this.mFeedContainers[aCurrentLength] = this.makeFeedObject(feed, aCurrentLevel);
339 // else // now add any feed urls for the folder
340 folderObject.children.push(this.makeFeedObject(feeds[feed], aCurrentLevel + 1));
341 }
342
343 return folderObject;
344 },
345
getFeedsInFolder
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
346 getFeedsInFolder: function (aFolder)
347 {
348 var feeds = new Array();
349 try
350 {
351 var msgdb = aFolder.QueryInterface(Components.interfaces.nsIMsgFolder).getMsgDatabase(null);
352 var folderInfo = msgdb.dBFolderInfo;
353 var feedurls = folderInfo.getCharProperty("feedUrl");
354 var feedUrlArray = feedurls.split("|");
355 for (url in feedUrlArray)
356 {
357 if (!feedUrlArray[url])
358 continue;
359 var feedResource = rdf.GetResource(feedUrlArray[url]);
360 var feed = new Feed(feedResource, this.mRSSServer);
361 feeds.push(feed);
362 }
363 }
364 catch(ex) {}
365 return feeds;
366 },
367
makeFeedObject
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
368 makeFeedObject: function (aFeed, aLevel)
369 {
370 // look inside the data source for the feed properties
371 var feed = { children : [],
372 name : aFeed.title,
373 url : aFeed.url,
374 level : aLevel,
375 open : false,
376 container : false };
377 return feed;
378 },
379
loadSubscriptions
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
380 loadSubscriptions: function ()
381 {
382 // put together an array of folders
383 var numFolders = 0;
384 this.mFeedContainers = [];
385
386 var folderEnumerator = this.mRSSServer.rootFolder.subFolders;
387
388 while (folderEnumerator.hasMoreElements())
389 {
390 var folder = folderEnumerator.getNext()
391 .QueryInterface(Components.interfaces.nsIMsgFolder);
392 if (!folder.getFlag(MSG_FOLDER_FLAG_TRASH))
393 {
394 this.mFeedContainers.push(this.makeFolderObject(folder, 0));
395 numFolders++;
396 }
397 }
398 this.mView.mRowCount = numFolders;
399
400 gFeedSubscriptionsWindow.mTree.focus();
401 },
402
selectFolder
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
403 selectFolder: function(aFolder)
404 {
405 if (aFolder.isServer)
406 return;
407
408 var folderURI = aFolder.QueryInterface(Components.interfaces.nsIRDFResource)
409 .Value;
410
containsFolder
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
411 function containsFolder(aItem)
412 {
413 var items = aItem ? aItem.children : this.mFeedContainers;
414 for (var i = 0; i < items.length; i++) {
415 if (items[i].url == folderURI ||
416 item.container && containsFolder(items[i]))
417 return true;
418 }
419 return false;
420 }
421
422 for (var index = 0; index < this.mView.rowCount; index++)
423 {
424 var item = this.mView.getItemAtIndex(index);
425 if (item.url == folderURI || containsFolder(item))
426 {
427 if (!item.open)
428 this.mView.toggleOpenState(index);
429 if (item.url == folderURI) {
430 // we found the actual folder - not an ancestor
431 this.mTree.view.selection.select(index);
432 this.mTree.boxObject.ensureRowIsVisible(index);
433 break;
434 }
435 }
436 }
437 },
438
selectFeed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
439 selectFeed: function(aFeed)
440 {
441 this.selectFolder(aFeed.folder);
442
443 var seln = this.mTree.view.selection;
444 var item = this.mView.getItemAtIndex(seln.currentIndex);
445 if (item) {
446 for (var i = 0; i < item.children.length; i++) {
447 if (item.children[i].url == aFeed.url) {
448 var index = seln.currentIndex + i + 1;
449 this.mTree.view.selection.select(index);
450 this.mTree.boxObject.ensureRowIsVisible(index);
451 break;
452 }
453 }
454 }
455 },
456
updateFeedData
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
457 updateFeedData: function (aItem)
458 {
459 var ids = ['nameLabel', 'nameValue', 'locationLabel', 'locationValue'];
460 if (aItem && !aItem.container)
461 {
462 // set the feed location and title info
463 document.getElementById('nameValue').value = aItem.name;
464 document.getElementById('locationValue').value = aItem.url;
465 }
466 else
467 {
468 var noneSelected = this.mBundle.getString("subscribe-noFeedSelected");
469 document.getElementById('nameValue').value = noneSelected;
470 document.getElementById('locationValue').value = "";
471 }
472
473 for (var i = 0; i < ids.length; ++i)
474 document.getElementById(ids[i]).disabled = !aItem || aItem.container;
475 },
476
onKeyPress
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
477 onKeyPress: function(aEvent)
478 {
479 if (aEvent.keyCode == aEvent.DOM_VK_ENTER || aEvent.keyCode == aEvent.DOM_VK_RETURN)
480 {
481 var seln = this.mTree.view.selection;
482 item = this.mView.getItemAtIndex(seln.currentIndex);
483 if (item && !item.container)
484 this.editFeed();
485 }
486 },
487
onSelect
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
488 onSelect: function ()
489 {
490 var properties, item;
491 var seln = this.mTree.view.selection;
492 item = this.mView.getItemAtIndex(seln.currentIndex);
493
494 this.updateFeedData(item);
495
496 document.getElementById("removeFeed").disabled = !item || item.container;
497 document.getElementById("editFeed").disabled = !item || item.container;
498 },
499
removeFeed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
500 removeFeed: function ()
501 {
502 var seln = this.mView.selection;
503 if (seln.count != 1) return;
504
505 var itemToRemove = this.mView.getItemAtIndex(seln.currentIndex);
506
507 if (!itemToRemove)
508 return;
509
510 // ask the user if he really wants to unsubscribe from the feed
511 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(IPS);
512 var abortRemoval = promptService.confirmEx(window, this.mBundle.getString('subsribe-confirmFeedDeletionTitle'),
513 this.mBundle.getFormattedString('subsribe-confirmFeedDeletion', [itemToRemove.name], 1),
514 IPS.STD_YES_NO_BUTTONS, null, null, null, null, { });
515 if (abortRemoval)
516 return;
517
518 var resource = rdf.GetResource(itemToRemove.url);
519 var feed = new Feed(resource, this.mRSSServer);
520 var ds = getSubscriptionsDS(this.mRSSServer);
521
522 if (feed && ds)
523 {
524 // remove the feed from the subscriptions ds
525 var feeds = getSubscriptionsList(this.mRSSServer);
526 var index = feeds.IndexOf(resource);
527 if (index != kRowIndexUndefined)
528 feeds.RemoveElementAt(index, false);
529
530 // remove the feed property string from the folder data base
531 var currentFolder = ds.GetTarget(resource, FZ_DESTFOLDER, true);
532 if (currentFolder)
533 {
534 var currentFolderURI = currentFolder.QueryInterface(Components.interfaces.nsIRDFResource).Value;
535 currentFolder = rdf.GetResource(currentFolderURI).QueryInterface(Components.interfaces.nsIMsgFolder);
536
537 var feedUrl = ds.GetTarget(resource, DC_IDENTIFIER, true);
538 ds.Unassert(resource, DC_IDENTIFIER, feedUrl, true);
539
540 feedUrl = feedUrl ? feedUrl.QueryInterface(Components.interfaces.nsIRDFLiteral).Value : "";
541
542 updateFolderFeedUrl(currentFolder, feedUrl, true); // remove the old url
543 }
544
545 // Remove all assertions about the feed from the subscriptions database.
546 removeAssertions(ds, resource);
547 ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush(); // flush any changes
548
549 // Remove all assertions about items in the feed from the items database.
550 var itemds = getItemsDS(this.mRSSServer);
551 feed.invalidateItems();
552 feed.removeInvalidItems();
553 itemds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush(); // flush any changes
554 }
555
556 // Now that we have removed the feed from the datasource, it is time to update our
557 // view layer. Start by removing the child from its parent folder object
558 this.mView.removeItemAtIndex(seln.currentIndex);
559
560 // If we don't have any more subscriptions pointing into
561 // this folder, then I think we should offer to delete it...
562 // Cheat and look at the feed url property to see if anyone else is still using the feed...
563 // you could also accomplish this by looking at some properties in the data source...
564
565 // var msgdb = currentFolder.QueryInterface(Components.interfaces.nsIMsgFolder).getMsgDatabase(null);
566 // var folderInfo = msgdb.dBFolderInfo;
567 // var oldFeedUrl = folderInfo.getCharProperty("feedUrl");
568
569 // if (!oldFeedUrl) // no more feeds pointing to the folder?
570 // {
571 // try {
572 // var openerResource = this.mRSSServer.rootMsgFolder.QueryInterface(Components.interfaces.nsIRDFResource);
573 // var folderResource = currentFolder.QueryInterface(Components.interfaces.nsIRDFResource);
574 // window.opener.messenger.DeleteFolders(window.opener.GetFolderDatasource(), openerResource, folderResource);
575 // } catch (e) { }
576 // }
577 },
578
579 // aRootFolderURI --> optional argument. The folder to initially create the new feed under.
addFeed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
580 addFeed: function(aFeedLocation, aRootFolderURI)
581 {
582 var userAddedFeed = false;
583 var defaultQuickMode = this.mRSSServer.getBoolAttribute('quickMode');
584 var feedProperties = { feedName: "", feedLocation: aFeedLocation,
585 serverURI: this.mRSSServer.serverURI,
586 serverPrettyName: this.mRSSServer.prettyName,
587 quickMode: this.mRSSServer.getBoolAttribute('quickMode'),
588 newFeed: true,
589 result: userAddedFeed};
590
591 // unless another folder is specified, default to currently selected
592 if (aRootFolderURI) {
593 feedProperties.folderURI = aRootFolderURI;
594 } else {
595 var index = this.mTree.view.selection.currentIndex;
596 var item = this.mView.getItemAtIndex(index);
597 if (item) {
598 if (!item.container)
599 item = this.mView.getItemAtIndex(this.mView.getParentIndex(index));
600 feedProperties.folderURI = item.url;
601 }
602 }
603
604 feedProperties = openFeedEditor(feedProperties);
605
606 // if the user hit cancel, exit without doing anything
607 if (!feedProperties.result)
608 return;
609
610 if (!feedProperties.feedLocation)
611 return;
612
613 // before we go any further, make sure the user is not already subscribed to this feed.
614 if (feedAlreadyExists(feedProperties.feedLocation, this.mRSSServer))
615 {
616 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(IPS);
617 promptService.alert(window, null, this.mBundle.getString("subscribe-feedAlreadySubscribed"));
618 return;
619 }
620
621 var feed = this.storeFeed(feedProperties);
622 if(!feed)
623 return;
624
625 // Now validate and start downloadng the feed....
626 updateStatusItem('statusText', document.getElementById("bundle_newsblog").getString('subscribe-validating'));
627 updateStatusItem('progressMeter', 0);
628 document.getElementById('addFeed').setAttribute('disabled', 'true');
629 feed.download(true, this.mFeedDownloadCallback);
630 },
631
632 // helper routine used by addFeed and importOPMLFile
storeFeed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
633 storeFeed: function(feedProperties)
634 {
635 var itemResource = rdf.GetResource(feedProperties.feedLocation);
636 var feed = new Feed(itemResource, this.mRSSServer);
637
638 // if the user specified a specific folder to add the feed too, then set it here
639 if (feedProperties.folderURI)
640 {
641 var folderResource = rdf.GetResource(feedProperties.folderURI);
642 if (folderResource)
643 {
644 var folder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
645 if (folder && !folder.isServer)
646 feed.folder = folder;
647 }
648 }
649
650 feed.quickMode = feedProperties.quickMode;
651 return feed;
652 },
653
editFeed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
654 editFeed: function()
655 {
656 var seln = this.mView.selection;
657 if (seln.count != 1)
658 return;
659
660 var itemToEdit = this.mView.getItemAtIndex(seln.currentIndex);
661 if (!itemToEdit || itemToEdit.container)
662 return;
663
664 var resource = rdf.GetResource(itemToEdit.url);
665 var feed = new Feed(resource, this.mRSSServer);
666
667 var ds = getSubscriptionsDS(this.mRSSServer);
668 var currentFolder = ds.GetTarget(resource, FZ_DESTFOLDER, true);
669 var currentFolderURI = currentFolder.QueryInterface(Components.interfaces.nsIRDFResource).Value;
670
671 var userModifiedFeed = false;
672 var feedProperties = { feedLocation: itemToEdit.url, serverURI: this.mRSSServer.serverURI,
673 serverPrettyName: this.mRSSServer.prettyName, folderURI: currentFolderURI,
674 quickMode: feed.quickMode, newFeed: false, result: userModifiedFeed};
675
676 feedProperties = openFeedEditor(feedProperties);
677 if (!feedProperties.result) // did the user cancel?
678 return;
679
680 // check to see if the quickMode value changed
681 if (feed.quickMode != feedProperties.quickMode)
682 feed.quickMode = feedProperties.quickMode;
683
684 // did the user change the folder URI for storing the feed?
685 if (feedProperties.folderURI && feedProperties.folderURI != currentFolderURI)
686 {
687 // we need to find the index of the new parent folder...
688 var newParentIndex = kRowIndexUndefined;
689 for (index = 0; index < this.mView.rowCount; index++)
690 {
691 var item = this.mView.getItemAtIndex(index);
692 if (item && item.container && item.url == feedProperties.folderURI)
693 {
694 newParentIndex = index;
695 break;
696 }
697 }
698
699 if (newParentIndex != kRowIndexUndefined)
700 this.moveFeed(seln.currentIndex, newParentIndex)
701 }
702
703 ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush(); // flush any changes
704 },
705
706 // moves the feed located at aOldFeedIndex to a child of aNewParentIndex
moveFeed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
707 moveFeed: function(aOldFeedIndex, aNewParentIndex)
708 {
709 // if the new parent is the same as the current parent, then do nothing
710 if (this.mView.getParentIndex(aOldFeedIndex) == aNewParentIndex)
711 return;
712
713 var currentItem = this.mView.getItemAtIndex(aOldFeedIndex);
714 var currentParentItem = this.mView.getItemAtIndex(this.mView.getParentIndex(aOldFeedIndex));
715 var currentParentResource = rdf.GetResource(currentParentItem.url);
716
717 var newParentItem = this.mView.getItemAtIndex(aNewParentIndex);
718 var newParentResource = rdf.GetResource(newParentItem.url);
719
720 var ds = getSubscriptionsDS(this.mRSSServer);
721 var resource = rdf.GetResource(currentItem.url);
722 var currentFolder = currentParentResource.QueryInterface(Components.interfaces.nsIMsgFolder);
723
724 // unassert the older URI, add an assertion for the new parent URI...
725 ds.Change(resource, FZ_DESTFOLDER, currentParentResource, newParentResource);
726
727 // we need to update the feed url attributes on the databases for each folder
728 updateFolderFeedUrl(currentParentResource.QueryInterface(Components.interfaces.nsIMsgFolder),
729 currentItem.url, true); // remove our feed url property from the current folder
730 updateFolderFeedUrl(newParentResource.QueryInterface(Components.interfaces.nsIMsgFolder),
731 currentItem.url, false); // add our feed url property to the new folder
732
733
734 // Finally, update our view layer
735 this.mView.removeItemAtIndex(aOldFeedIndex, 1);
736 if (aNewParentIndex > aOldFeedIndex)
737 aNewParentIndex--;
738
739 currentItem.level = newParentItem.level + 1;
740 newParentItem.children.push(currentItem);
741 var indexOfNewItem = aNewParentIndex + newParentItem.children.length;
742
743 if (!newParentItem.open) // force open the container
744 this.mView.toggleOpenState(aNewParentIndex);
745 else
746 {
747 this.mFeedContainers.splice(indexOfNewItem, 0, currentItem);
748 this.mView.mRowCount++;
749 this.mTree.treeBoxObject.rowCountChanged(indexOfNewItem, 1);
750 }
751
752 gFeedSubscriptionsWindow.mTree.view.selection.select(indexOfNewItem)
753 },
754
beginDrag
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
755 beginDrag: function (aEvent)
756 {
757 // get the selected feed article (if there is one)
758 var seln = this.mView.selection;
759 if (seln.count != 1)
760 return;
761
762 // only initiate a drag if the item is a feed (i.e. ignore folders/containers)
763 var item = this.mView.getItemAtIndex(seln.currentIndex);
764 if (!item || item.container)
765 return;
766
767 var transfer = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
768 var transArray = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
769 var dragData = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
770
771 transfer.addDataFlavor("text/x-moz-feed-index"); // i made this flavor type up
772 dragData.data = seln.currentIndex.toString();
773
774 transfer.setTransferData ( "text/x-moz-feed-index", dragData, seln.currentIndex.toString() * 2 ); // doublebyte byte data
775 transArray.AppendElement(transfer.QueryInterface(Components.interfaces.nsISupports));
776
777 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].getService().QueryInterface(nsIDragService);
778 dragService.invokeDragSession ( aEvent.target, transArray, null, nsIDragService.DRAGDROP_ACTION_MOVE);
779 },
780
781 mFeedDownloadCallback:
782 {
downloaded
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
783 downloaded: function(feed, aErrorCode)
784 {
785 // feed is null if our attempt to parse the feed failed
786 if (aErrorCode == kNewsBlogSuccess)
787 {
788 updateStatusItem('progressMeter', 100);
789
790 // if we get here...we should always have a folder by now...either
791 // in feed.folder or FeedItems created the folder for us....
792 updateFolderFeedUrl(feed.folder, feed.url, false);
793
794 // add feed just adds the feed we have validated and downloaded to our datasource
795 // it also flushes the subscription datasource
796 addFeed(feed.url, feed.name, feed.folder);
797
798 // now add the feed to our view
799 refreshSubscriptionView();
800
801 gFeedSubscriptionsWindow.selectFeed(feed);
802 }
803 else if (aErrorCode == kNewsBlogInvalidFeed) // the feed was bad...
804 window.alert(gFeedSubscriptionsWindow.mBundle.getFormattedString('newsblog-invalidFeed', [feed.url]));
805 else if (aErrorCode == kNewsBlogRequestFailure)
806 window.alert(gFeedSubscriptionsWindow.mBundle.getFormattedString('newsblog-networkError', [feed.url]));
807
808 // re-enable the add button now that we are done subscribing
809 document.getElementById('addFeed').removeAttribute('disabled');
810
811 // our operation is done...clear out the status text and progressmeter
812 setTimeout(clearStatusInfo, 1000);
813 },
814
815 // this gets called after the RSS parser finishes storing a feed item to disk
816 // aCurrentFeedItems is an integer corresponding to how many feed items have been downloaded so far
817 // aMaxFeedItems is an integer corresponding to the total number of feed items to download
onFeedItemStored
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
818 onFeedItemStored: function (feed, aCurrentFeedItems, aMaxFeedItems)
819 {
820 updateStatusItem('statusText', gFeedSubscriptionsWindow.mBundle.getFormattedString("subscribe-fetchingFeedItems",
821 [aCurrentFeedItems, aMaxFeedItems]));
822 this.onProgress(feed, aCurrentFeedItems, aMaxFeedItems);
823 },
824
onProgress
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
825 onProgress: function(feed, aProgress, aProgressMax)
826 {
827 updateStatusItem('progressMeter', (aProgress * 100) / aProgressMax);
828 }
829 },
830
exportOPML
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
831 exportOPML: function()
832 {
833 if (this.mRSSServer.rootFolder.hasSubFolders)
834 {
835 var opmlDoc = document.implementation.createDocument("","opml",null);
836 var opmlRoot = opmlDoc.documentElement;
837 opmlRoot.setAttribute("version","1.0");
838
839 this.generatePPSpace(opmlRoot," ");
840
841 // Make the <head> element
842 var head = opmlDoc.createElement("head");
843 this.generatePPSpace(head, " ");
844 var title = opmlDoc.createElement("title");
845 title.appendChild(opmlDoc.createTextNode(this.mBundle.getString("subscribe-OPMLExportFileTitle")));
846 head.appendChild(title);
847 this.generatePPSpace(head, " ");
848 var dt = opmlDoc.createElement("dateCreated");
849 dt.appendChild(opmlDoc.createTextNode((new Date()).toGMTString()));
850 head.appendChild(dt);
851 this.generatePPSpace(head, " ");
852 opmlRoot.appendChild(head);
853
854 this.generatePPSpace(opmlRoot, " ");
855
856 //add <outline>s to the <body>
857 var body = opmlDoc.createElement("body");
858 this.generateOutline(this.mRSSServer.rootFolder, body, 4);
859 this.generatePPSpace(body, " ");
860 opmlRoot.appendChild(body);
861
862 this.generatePPSpace(opmlRoot, "");
863
864 var serial=new XMLSerializer();
865 var rv = pickSaveAs(this.mBundle.getString("subscribe-OPMLExportTitle"),'$all',
866 this.mBundle.getString("subscribe-OPMLExportFileName"));
867 if(rv.reason == PICK_CANCEL)
868 return;
869 else if(rv)
870 {
871 //debug("opml:\n"+serial.serializeToString(opmlDoc)+"\n");
872 var file = new LocalFile(rv.file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE);
873 serial.serializeToStream(opmlDoc,file.outputStream,'utf-8');
874 file.close();
875 }
876 }
877 },
878
generatePPSpace
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
879 generatePPSpace: function(aNode, indentString)
880 {
881 aNode.appendChild(aNode.ownerDocument.createTextNode("\n"));
882 aNode.appendChild(aNode.ownerDocument.createTextNode(indentString));
883 },
884
generateOutline
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
885 generateOutline: function(baseFolder, parent, indentLevel)
886 {
887 var folderEnumerator = baseFolder.subFolders;
888
889 // pretty printing
890 var indentString = "";
891 for(i = 0; i < indentLevel; i++)
892 indentString = indentString + " ";
893
894 while (folderEnumerator.hasMoreElements())
895 {
896 var folder = folderEnumerator.getNext()
897 .QueryInterface(Components.interfaces.nsIMsgFolder);
898 if (!folder.getFlag(MSG_FOLDER_FLAG_TRASH))
899 {
900 var outline;
901 if (folder.hasSubFolders)
902 {
903 // Make a mostly empty outline element
904 outline = parent.ownerDocument.createElement("outline");
905 outline.setAttribute("text",folder.prettiestName);
906 this.generateOutline(folder, outline, indentLevel+2); // recurse
907 this.generatePPSpace(parent, indentString);
908 this.generatePPSpace(outline, indentString);
909 parent.appendChild(outline);
910 }
911 else
912 {
913 // Add outline elements with xmlUrls
914 var feeds = this.getFeedsInFolder(folder);
915 for (feed in feeds)
916 {
917 outline = this.opmlFeedToOutline(feeds[feed],parent.ownerDocument);
918 this.generatePPSpace(parent, indentString);
919 parent.appendChild(outline);
920 }
921 }
922 }
923 }
924 },
925
opmlFeedToOutline
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
926 opmlFeedToOutline: function(aFeed,aDoc)
927 {
928 var outRv = aDoc.createElement("outline");
929 outRv.setAttribute("title",aFeed.title);
930 outRv.setAttribute("text",aFeed.title);
931 outRv.setAttribute("type","rss");
932 outRv.setAttribute("version","RSS");
933 outRv.setAttribute("xmlUrl",aFeed.url);
934 outRv.setAttribute("htmlUrl",aFeed.link);
935 return outRv;
936 },
937
importOPML
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
938 importOPML: function()
939 {
940 var rv = pickOpen(this.mBundle.getString("subscribe-OPMLImportTitle"), '$xml $opml $all');
941 if(rv.reason == PICK_CANCEL)
942 return;
943
944 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(IPS);
945 var stream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
946 var opmlDom = null;
947
948 // read in file as raw bytes, so Expat can do the decoding for us
949 try{
950 stream.init(rv.file, MODE_RDONLY, PERM_IROTH, 0);
951 var parser = new DOMParser();
952 opmlDom = parser.parseFromStream(stream, null, stream.available(), 'application/xml');
953 }catch(e){
954 promptService.alert(window, null, this.mBundle.getString("subscribe-errorOpeningFile"));
955 return;
956 }finally{
957 stream.close();
958 }
959
960 // return if the user didn't give us an OPML file
961 if(!opmlDom || !(opmlDom.documentElement.tagName == "opml"))
962 {
963 promptService.alert(window, null, this.mBundle.getFormattedString("subscribe-errorInvalidOPMLFile", [rv.file.leafName]));
964 return;
965 }
966
967 var outlines = opmlDom.getElementsByTagName("body")[0].getElementsByTagName("outline");
968 var feedsAdded = false;
969
970 for (var index = 0; index < outlines.length; index++)
971 {
972 var outline = outlines[index];
973
974 // XXX only dealing with flat OPML files for now.
975 // We still need to add support for grouped files.
976 if(outline.hasAttribute("xmlUrl") || outline.hasAttribute("url"))
977 {
978 var userAddedFeed = false;
979 var newFeedUrl = outline.getAttribute("xmlUrl") || outline.getAttribute("url")
980 var defaultQuickMode = this.mRSSServer.getBoolAttribute('quickMode');
981 var feedProperties = { feedName: this.findOutlineTitle(outline),
982 feedLocation: newFeedUrl,
983 serverURI: this.mRSSServer.serverURI,
984 serverPrettyName: this.mRSSServer.prettyName,
985 folderURI: "",
986 quickMode: this.mRSSServer.getBoolAttribute('quickMode')};
987
988 debug("importing feed: "+ feedProperties.feedName);
989
990 // Silently skip feeds that are already subscribed to.
991 if (!feedAlreadyExists(feedProperties.feedLocation, this.mRSSServer))
992 {
993 var feed = this.storeFeed(feedProperties);
994
995 if(feed)
996 {
997 feed.title = feedProperties.feedName;
998 if(outline.hasAttribute("htmlUrl"))
999 feed.link = outline.getAttribute("htmlUrl");
1000
1001 feed.createFolder();
1002 updateFolderFeedUrl(feed.folder, feed.url, false);
1003
1004 // add feed adds the feed we have validated and downloaded to our datasource
1005 // it also flushes the subscription datasource
1006 addFeed(feed.url, feed.name, feed.folder);
1007 feedsAdded = true;
1008 }
1009 }
1010 }
1011 }
1012
1013 if (!outlines.length || !feedsAdded)
1014 {
1015 promptService.alert(window, null, this.mBundle.getFormattedString("subscribe-errorInvalidOPMLFile", [rv.file.leafName]));
1016 return;
1017 }
1018
1019 //add the new feeds to our view
1020 refreshSubscriptionView();
1021 },
1022
findOutlineTitle
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1023 findOutlineTitle: function(anOutline)
1024 {
1025 var outlineTitle;
1026
1027 if (anOutline.hasAttribute("text"))
1028 outlineTitle = anOutline.getAttribute("text");
1029 else if (anOutline.hasAttribute("title"))
1030 outlineTitle = anOutline.getAttribute("title");
1031 else if (anOutline.hasAttribute("xmlUrl"))
1032 outlineTitle = anOutline.getAttribute("xmlUrl");
1033
1034 return outlineTitle;
1035 }
1036 };
1037
1038 // opens the feed properties dialog
openFeedEditor
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1039 function openFeedEditor(aFeedProperties)
1040 {
1041 window.openDialog('chrome://messenger-newsblog/content/feed-properties.xul', 'feedproperties', 'modal,titlebar,chrome,center', aFeedProperties);
1042 return aFeedProperties;
1043 }
1044
refreshSubscriptionView
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1045 function refreshSubscriptionView()
1046 {
1047 gFeedSubscriptionsWindow.loadSubscriptions();
1048 gFeedSubscriptionsWindow.mTree.treeBoxObject.invalidate();
1049 gFeedSubscriptionsWindow.mTree.treeBoxObject.view = gFeedSubscriptionsWindow.mView;
1050 if (gFeedSubscriptionsWindow.mView.rowCount > 0)
1051 gFeedSubscriptionsWindow.mTree.view.selection.select(0);
1052 }
1053
processDrop
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1054 function processDrop()
1055 {
1056 gFeedSubscriptionsWindow.addFeed(gFeedSubscriptionsWindow.mView.mDropUrl, gFeedSubscriptionsWindow.mView.mDropFolderUrl);
1057 }
1058
1059 // status helper routines
1060
updateStatusItem
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1061 function updateStatusItem(aID, aValue)
1062 {
1063 var el = document.getElementById(aID);
1064 if (el.getAttribute('collapsed'))
1065 el.removeAttribute('collapsed');
1066
1067 el.value = aValue;
1068 }
1069
clearStatusInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1070 function clearStatusInfo()
1071 {
1072 document.getElementById('statusText').value = "";
1073 document.getElementById('progressMeter').collapsed = true;
1074 }