!import
1 /*
2 * ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Mozilla Communicator client code, released
16 * March 31, 1998.
17 *
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998-1999
21 * the Initial Developer. All Rights Reserved.
22 *
23 * Contributor(s):
24 * Markus Hossner <markushossner@gmx.de>
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 var gSubscribeTree = null;
41 var gSearchTree;
42 var okCallback = null;
43 var gChangeTable = {};
44 var gServerURI = null;
45 var gSubscribableServer = null;
46 var gStatusBar = null;
47 var gNameField = null;
48 var gNameFieldLabel = null;
49 var gFolderDelimiter = ".";
50 var gStatusFeedback = new nsMsgStatusFeedback;
51 var gMessengerBundle = null;
52 var gSubscribeDeck = null;
53 var gSearchView = null;
54 var gSearchTreeBoxObject = null;
55 // the rdf service
56 var RDF = Components.classes['@mozilla.org/rdf/rdf-service;1'].getService(Components.interfaces.nsIRDFService);
57 var subscribeDS = RDF.GetDataSource("rdf:subscribe");
58
59 // get the "subscribed" atom
60 var atomService = Components.classes["@mozilla.org/atom-service;1"].getService().QueryInterface(Components.interfaces.nsIAtomService);
61 var gSubscribedAtom = atomService.getAtom("subscribed").QueryInterface(Components.interfaces.nsISupports);
62
63 var gSubscribeBundle;
64
goDoCommand
65 function goDoCommand()
66 {
67 }
68
Stop
69 function Stop()
70 {
71 //dump("Stop()\n")
72 if (gSubscribableServer) {
73 gSubscribableServer.stopPopulating(msgWindow);
74 }
75 }
76
SetServerTypeSpecificTextValues
77 function SetServerTypeSpecificTextValues()
78 {
79 if (!gServerURI) return;
80
81 var serverType = GetMsgFolderFromUri(gServerURI, true).server.type;
82
83 // set the server specific ui elements
84 var subscribeLabelString = gSubscribeBundle.getString("subscribeLabel-" + serverType);
85 var currentListTab = "currentListTab-" + serverType;
86 var currentListTabLabel = gSubscribeBundle.getString(currentListTab + ".label");
87 var currentListTabAccesskey = gSubscribeBundle.getString(currentListTab + ".accesskey");
88
89 document.getElementById("currentListTab").setAttribute('label', currentListTabLabel);
90 document.getElementById("currentListTab").setAttribute('accesskey', currentListTabAccesskey);
91 document.getElementById("newGroupsTab").collapsed = (serverType != "nntp"); // show newGroupsTab only for nntp servers
92 document.getElementById("subscribeLabel").setAttribute('value', subscribeLabelString);
93
94
95
96 //set the delimiter
97 try {
98 gFolderDelimiter = gSubscribableServer.delimiter;
99 }
100 catch (ex) {
101 //dump(ex + "\n");
102 gFolderDelimiter = ".";
103 }
104 }
105
onServerClick
106 function onServerClick(aFolder)
107 {
108 gServerURI = aFolder.server.serverURI;
109
110 SetServerTypeSpecificTextValues();
111 ShowCurrentList();
112 }
113
114 function SetUpServerMenu()
115 {
116 //dump("SetUpServerMenu()\n");
117
118 var serverMenu = document.getElementById("serverMenu");
119 var menuitems = serverMenu.getElementsByAttribute("id", gServerURI);
120
121 try {
122 //dump("gServerURI="+gServerURI+"\n");
123 //dump("menuitems="+menuitems+"\n");
124 //dump("menuitems[0]="+menuitems[0]+"\n");
125 //dump("serverMenu="+serverMenu+"\n");
126 serverMenu.selectedItem = menuitems[0];
127 }
128 catch (ex) {
129 //dump("failed to set the selected server: " + ex + "\n");
130 }
131
132 SetServerTypeSpecificTextValues();
133 }
134
135 var MySubscribeListener = {
OnDonePopulating
136 OnDonePopulating: function() {
137 gStatusFeedback._stopMeteors();
138
139 // only re-root the tree, if it is null.
140 // otherwise, we are in here because we are populating
141 // a part of the tree
142 var refValue = gSubscribeTree.getAttribute('ref');
143 if (!refValue) {
144 //dump("root subscribe tree at: "+ gServerURI +"\n");
145 gSubscribeTree.database.AddDataSource(subscribeDS);
146 gSubscribeTree.setAttribute('ref',gServerURI);
147 }
148
149 document.getElementById("refreshButton").disabled = false;
150 document.getElementById("currentListTab").disabled = false;
151 document.getElementById("newGroupsTab").disabled = false;
152 }
153 };
154
SetUpTree
155 function SetUpTree(forceToServer, getOnlyNew)
156 {
157 gStatusBar = document.getElementById('statusbar-icon');
158 if (!gServerURI)
159 return;
160
161 var server = GetMsgFolderFromUri(gServerURI, true).server;
162 try
163 {
164 CleanUpSearchView();
165 gSubscribableServer = server.QueryInterface(Components.interfaces.nsISubscribableServer);
166 gSubscribeTree.setAttribute('ref',null);
167
168 // enable (or disable) the search related UI
169 EnableSearchUI();
170
171 // clear out the text field when switching server
172 gNameField.value = "";
173
174 // since there is no text, switch to the non-search view...
175 SwitchToNormalView();
176
177 gSubscribeTree.database.RemoveDataSource(subscribeDS);
178 gSubscribableServer.subscribeListener = MySubscribeListener;
179
180 var currentListTab = document.getElementById("currentListTab");
181 if (currentListTab.selected)
182 document.getElementById("newGroupsTab").disabled = true;
183 else
184 currentListTab.disabled = true;
185
186 document.getElementById("refreshButton").disabled = true;
187
188 gStatusFeedback._startMeteors();
189 gStatusFeedback.showStatusString(gSubscribeBundle.getString("pleaseWaitString"));
190
191 gSubscribableServer.startPopulating(msgWindow, forceToServer, getOnlyNew);
192 }
193 catch (ex)
194 {
195 dump("failed to populate subscribe ds: " + ex + "\n");
196 }
197 }
198
199
SubscribeOnUnload
200 function SubscribeOnUnload()
201 {
202 try {
203 CleanUpSearchView();
204 gSubscribeTree.database.RemoveDataSource(subscribeDS);
205 }
206 catch (ex) {
207 dump("failed to remove the subscribe ds: " + ex + "\n");
208 }
209 }
210
EnableSearchUI
211 function EnableSearchUI()
212 {
213 if (gSubscribableServer.supportsSubscribeSearch) {
214 gNameField.removeAttribute('disabled');
215 gNameFieldLabel.removeAttribute('disabled');
216 }
217 else {
218 gNameField.setAttribute('disabled',true);
219 gNameFieldLabel.setAttribute('disabled',true);
220 }
221 }
222
SubscribeOnLoad
223 function SubscribeOnLoad()
224 {
225 //dump("SubscribeOnLoad()\n");
226 gSubscribeBundle = document.getElementById("bundle_subscribe");
227 gMessengerBundle = document.getElementById("bundle_messenger");
228
229 gSubscribeTree = document.getElementById("subscribeTree");
230 gSearchTree = document.getElementById("searchTree");
231 gSearchTreeBoxObject = document.getElementById("searchTree").treeBoxObject;
232 gNameField = document.getElementById("namefield");
233 gNameFieldLabel = document.getElementById("namefieldlabel");
234
235 gSubscribeDeck = document.getElementById("subscribedeck");
236
237 msgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
238 .createInstance(Components.interfaces.nsIMsgWindow);
239 msgWindow.domWindow = window;
240 msgWindow.statusFeedback = gStatusFeedback;
241 msgWindow.rootDocShell.allowAuth = true;
242 msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
243
244 // look in arguments[0] for parameters
245 if (window.arguments && window.arguments[0]) {
246 if ( window.arguments[0].okCallback ) {
247 top.okCallback = window.arguments[0].okCallback;
248 }
249 }
250
251 gServerURI = null;
252 if (window.arguments[0].preselectedURI) {
253 var uri = window.arguments[0].preselectedURI;
254 //dump("subscribe: got a uri," + uri + "\n");
255 var folder = GetMsgFolderFromUri(uri, true);
256 //dump("folder="+folder+"\n");
257 //dump("folder.server="+folder.server+"\n");
258 var serverMenu = document.getElementById("serverMenu");
259 serverMenu.menupopup.selectFolder(folder);
260 try {
261 CleanUpSearchView();
262 gSubscribableServer = folder.server.QueryInterface(Components.interfaces.nsISubscribableServer);
263 // enable (or disable) the search related UI
264 EnableSearchUI();
265 gServerURI = folder.server.serverURI;
266 }
267 catch (ex) {
268 //dump("not a subscribable server\n");
269 CleanUpSearchView();
270 gSubscribableServer = null;
271 gServerURI = null;
272 }
273 }
274
275 if (!gServerURI) {
276 //dump("subscribe: no uri\n");
277 //dump("xxx todo: use the default news server. right now, I'm just using the first server\n");
278
279 serverMenu.selectedIndex = 0;
280
281 if (serverMenu.selectedItem) {
282 gServerURI = serverMenu.selectedItem.getAttribute("id");
283 }
284 else {
285 //dump("xxx todo none of your servers are subscribable\n");
286 //dump("xxx todo fix this by disabling subscribe if no subscribable server or, add a CREATE SERVER button, like in 4.x\n");
287 return;
288 }
289 }
290
291 SetUpServerMenu();
292
293 ShowCurrentList();
294
295 gNameField.focus();
296 }
297
subscribeOK
298 function subscribeOK()
299 {
300 //dump("in subscribeOK()\n")
301 if (top.okCallback) {
302 top.okCallback(top.gChangeTable);
303 }
304 Stop();
305 if (gSubscribableServer) {
306 gSubscribableServer.subscribeCleanup();
307 }
308 return true;
309 }
310
subscribeCancel
311 function subscribeCancel()
312 {
313 Stop();
314 if (gSubscribableServer) {
315 gSubscribableServer.subscribeCleanup();
316 }
317 return true;
318 }
319
SetState
320 function SetState(name,state)
321 {
322 var changed = gSubscribableServer.setState(name, state);
323 if (changed)
324 StateChanged(name,state);
325 }
326
changeTableRecord
327 function changeTableRecord(server, name, state)
328 {
329 this.server = server;
330 this.name = name;
331 this.state = state;
332 }
333
StateChanged
334 function StateChanged(name,state)
335 {
336 if (gServerURI in gChangeTable) {
337 if (name in gChangeTable[gServerURI]) {
338 var oldValue = gChangeTable[gServerURI][name];
339 if (oldValue != state)
340 delete gChangeTable[gServerURI][name];
341 }
342 else {
343 gChangeTable[gServerURI][name] = state;
344 }
345 }
346 else {
347 gChangeTable[gServerURI] = {};
348 gChangeTable[gServerURI][name] = state;
349 }
350 }
351
InSearchMode
352 function InSearchMode()
353 {
354 // search is the second card in the deck
355 return (gSubscribeDeck.getAttribute("selectedIndex") == "1");
356 }
357
SearchOnClick
358 function SearchOnClick(event)
359 {
360 // we only care about button 0 (left click) events
361 if (event.button != 0 || event.originalTarget.localName != "treechildren") return;
362
363 var row = {}, col = {}, childElt = {};
364 gSearchTreeBoxObject.getCellAt(event.clientX, event.clientY, row, col, childElt);
365 if (row.value == -1 || row.value > gSearchView.rowCount-1)
366 return;
367
368 if (col.value.id == "subscribedColumn2") {
369 if (event.detail != 2) {
370 // single clicked on the check box
371 // (in the "subscribedColumn2" column) reverse state
372 // if double click, do nothing
373 ReverseStateFromRow(row.value);
374 }
375 } else if (event.detail == 2) {
376 // double clicked on a row, reverse state
377 ReverseStateFromRow(row.value);
378 }
379
380 // invalidate the row
381 InvalidateSearchTreeRow(row.value);
382 }
383
ReverseStateFromRow
384 function ReverseStateFromRow(row)
385 {
386 // to determine if the row is subscribed or not,
387 // we get the properties for the "subscribedColumn2" cell in the row
388 // and look for the "subscribed" property
389 // if the "subscribed" atom is in the list of properties
390 // we are subscribed
391 var properties = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
392 var col = gSearchTree.columns["subscribedColumn2"];
393 gSearchView.getCellProperties(row, col, properties);
394
395 var isSubscribed = (properties.GetIndexOf(gSubscribedAtom) != -1);
396 SetStateFromRow(row, !isSubscribed);
397 }
398
SetStateFromRow
399 function SetStateFromRow(row, state)
400 {
401 var col = gSearchTree.columns["nameColumn2"];
402 var name = gSearchView.getCellText(row, col);
403 SetState(name, state);
404 }
405
SetSubscribeState
406 function SetSubscribeState(state)
407 {
408 try {
409 // we need to iterate over the tree selection, and set the state for
410 // all rows in the selection
411 var inSearchMode = InSearchMode();
412 var view = inSearchMode ? gSearchView : gSubscribeTree.view;
413 var colId = inSearchMode ? "nameColumn2" : "nameColumn";
414
415 var sel = view.selection;
416 for (var i = 0; i < sel.getRangeCount(); ++i) {
417 var start = {}, end = {};
418 sel.getRangeAt(i, start, end);
419 for (var k = start.value; k <= end.value; ++k) {
420 if (inSearchMode)
421 SetStateFromRow(k, state);
422 else {
423 var rowRes = gSubscribeTree.builderView.getResourceAtIndex(k);
424 var name = GetRDFProperty(rowRes, "Name");
425 SetState(name, state);
426 }
427 }
428 }
429
430 if (inSearchMode) {
431 // force a repaint
432 InvalidateSearchTree();
433 }
434 }
435 catch (ex) {
436 dump("SetSubscribedState failed: " + ex + "\n");
437 }
438 }
439
ReverseStateFromNode
440 function ReverseStateFromNode(row)
441 {
442 var rowRes = gSubscribeTree.builderView.getResourceAtIndex(row);
443 var isSubscribed = GetRDFProperty(rowRes, "Subscribed");
444 var name = GetRDFProperty(rowRes, "Name");
445
446 SetState(name, isSubscribed != "true");
447 }
448
GetRDFProperty
449 function GetRDFProperty(aRes, aProp)
450 {
451 var propRes = RDF.GetResource("http://home.netscape.com/NC-rdf#"+aProp);
452 var valueRes = gSubscribeTree.database.GetTarget(aRes, propRes, true);
453 return valueRes ? valueRes.QueryInterface(Components.interfaces.nsIRDFLiteral).Value : null;
454 }
455
SubscribeOnClick
456 function SubscribeOnClick(event)
457 {
458 // we only care about button 0 (left click) events
459 if (event.button != 0 || event.originalTarget.localName != "treechildren")
460 return;
461
462 var row = {}, col = {}, obj = {};
463 gSubscribeTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
464 if (row.value == -1 || row.value > (gSubscribeTree.view.rowCount - 1))
465 return;
466
467 if (event.detail == 2) {
468 // only toggle subscribed state when double clicking something
469 // that isn't a container
470 if (!gSubscribeTree.view.isContainer(row.value)) {
471 ReverseStateFromNode(row.value);
472 return;
473 }
474 }
475 else if (event.detail == 1)
476 {
477 if (obj.value == "twisty") {
478 if (gSubscribeTree.view.isContainerOpen(row.value)) {
479 var uri = gSubscribeTree.builderView.getResourceAtIndex(row.value).Value;
480
481 gStatusFeedback._startMeteors();
482 gStatusFeedback.showStatusString(gSubscribeBundle.getString("pleaseWaitString"));
483
484 gSubscribableServer.startPopulatingWithUri(msgWindow, true /* force to server */, uri);
485 }
486 }
487 else {
488 // if the user single clicks on the subscribe check box, we handle it here
489 if (col.value.id == "subscribedColumn")
490 ReverseStateFromNode(row.value);
491 }
492 }
493 }
494
Refresh
495 function Refresh()
496 {
497 // clear out the textfield's entry
498 gNameField.value = "";
499
500 var newGroupsTab = document.getElementById("newGroupsTab");
501 SetUpTree(true, newGroupsTab.selected);
502 }
503
ShowCurrentList
504 function ShowCurrentList()
505 {
506 // clear out the textfield's entry on call of Refresh()
507 gNameField.value = "";
508
509 // make sure the current list tab is selected
510 document.getElementById("subscribeTabs").selectedIndex = 0;
511
512 // try loading the hostinfo before talk to server
513 SetUpTree(false, false);
514 }
515
ShowNewGroupsList
516 function ShowNewGroupsList()
517 {
518 // clear out the textfield's entry
519 gNameField.value = "";
520
521 // make sure the new groups tab is selected
522 document.getElementById("subscribeTabs").selectedIndex = 1;
523
524 // force it to talk to the server and get new groups
525 SetUpTree(true, true);
526 }
527
InvalidateSearchTreeRow
528 function InvalidateSearchTreeRow(row)
529 {
530 gSearchTreeBoxObject.invalidateRow(row);
531 }
532
InvalidateSearchTree
533 function InvalidateSearchTree()
534 {
535 gSearchTreeBoxObject.invalidate();
536 }
537
SwitchToNormalView
538 function SwitchToNormalView()
539 {
540 // the first card in the deck is the "normal" view
541 gSubscribeDeck.setAttribute("selectedIndex","0");
542 }
543
SwitchToSearchView
544 function SwitchToSearchView()
545 {
546 // the second card in the deck is the "search" view
547 gSubscribeDeck.setAttribute("selectedIndex","1");
548 }
549
Search
550 function Search()
551 {
552 var searchValue = gNameField.value;
553 if (searchValue.length && gSubscribableServer.supportsSubscribeSearch) {
554 SwitchToSearchView();
555 gSubscribableServer.setSearchValue(searchValue);
556
557 if (!gSearchView && gSubscribableServer) {
558 gSearchView = gSubscribableServer.QueryInterface(Components.interfaces.nsITreeView);
559 gSearchView.selection = null;
560 gSearchTreeBoxObject.view = gSearchView;
561 }
562 }
563 else {
564 SwitchToNormalView();
565 }
566 }
567
CleanUpSearchView
568 function CleanUpSearchView()
569 {
570 if (gSearchView) {
571 gSearchView.selection = null;
572 gSearchView = null;
573 }
574 }
575
onSearchTreeKeyPress
576 function onSearchTreeKeyPress(event)
577 {
578 // for now, only do something on space key
579 if (event.charCode != KeyEvent.DOM_VK_SPACE)
580 return;
581
582 var treeSelection = gSearchView.selection;
583 for (var i=0;i<treeSelection.getRangeCount();i++) {
584 var start = {}, end = {};
585 treeSelection.getRangeAt(i,start,end);
586 for (var k=start.value;k<=end.value;k++)
587 ReverseStateFromRow(k);
588
589 // force a repaint
590 InvalidateSearchTree();
591 }
592 }
593
onSubscribeTreeKeyPress
594 function onSubscribeTreeKeyPress(event)
595 {
596 // for now, only do something on space key
597 if (event.charCode != KeyEvent.DOM_VK_SPACE)
598 return;
599
600 var treeSelection = gSubscribeTree.view.selection;
601 for (var i=0;i<treeSelection.getRangeCount();i++) {
602 var start = {}, end = {};
603 treeSelection.getRangeAt(i,start,end);
604 for (var k=start.value;k<=end.value;k++)
605 ReverseStateFromNode(k);
606 }
607 }
608
609
doHelpButton
610 function doHelpButton()
611 {
612 openHelp("mail-subscribe");
613 }