зеркало из https://github.com/mozilla/snowl.git
383 строки
12 KiB
JavaScript
383 строки
12 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Snowl.
|
|
*
|
|
* The Initial Developer of the Original Code is Mozilla.
|
|
* Portions created by the Initial Developer are Copyright (C) 2008
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Myk Melez <myk@mozilla.org>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
// modules that come with Firefox
|
|
|
|
// modules that are generic
|
|
Cu.import("resource://snowl/modules/log4moz.js");
|
|
Cu.import("resource://snowl/modules/Observers.js");
|
|
Cu.import("resource://snowl/modules/URI.js");
|
|
|
|
// modules that are Snowl-specific
|
|
Cu.import("resource://snowl/modules/service.js");
|
|
Cu.import("resource://snowl/modules/datastore.js");
|
|
Cu.import("resource://snowl/modules/source.js");
|
|
Cu.import("resource://snowl/modules/feed.js");
|
|
Cu.import("resource://snowl/modules/identity.js");
|
|
Cu.import("resource://snowl/modules/collection.js");
|
|
Cu.import("resource://snowl/modules/opml.js");
|
|
|
|
let gBrowserWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).
|
|
getInterface(Ci.nsIWebNavigation).
|
|
QueryInterface(Ci.nsIDocShellTreeItem).
|
|
rootTreeItem.
|
|
QueryInterface(Ci.nsIInterfaceRequestor).
|
|
getInterface(Ci.nsIDOMWindow);
|
|
|
|
let CollectionsView = {
|
|
_log: null,
|
|
|
|
get _tree() {
|
|
delete this._tree;
|
|
return this._tree = document.getElementById("sourcesView");
|
|
},
|
|
|
|
get _children() {
|
|
delete this._children;
|
|
return this._children = this._tree.getElementsByTagName("treechildren")[0];
|
|
},
|
|
|
|
isHierarchical: gBrowserWindow.Snowl._prefs.get("collection.hierarchicalView"),
|
|
|
|
//**************************************************************************//
|
|
// Initialization & Destruction
|
|
|
|
init: function() {
|
|
this._log = Log4Moz.Service.getLogger("Snowl.Sidebar");
|
|
Observers.add(this, "snowl:sources:changed");
|
|
this._getCollections();
|
|
this._buildCollectionTree();
|
|
|
|
// Ensure collection selection maintained, if in List sidebar
|
|
if (document.getElementById("snowlSidebar"))
|
|
this._tree.view.selection.select(SnowlUtils.gListViewCollectionIndex);
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// nsITreeView
|
|
|
|
selection: null,
|
|
|
|
get rowCount() {
|
|
return this._rows.length;
|
|
},
|
|
|
|
// FIXME: consolidate these two references.
|
|
_treebox: null,
|
|
setTree: function(treeBox) {
|
|
this._treeBox = treeBox;
|
|
},
|
|
|
|
getCellText : function(row, column) {
|
|
return this._rows[row].name;
|
|
},
|
|
|
|
isContainer: function(row) {
|
|
//this._log.info("isContainer: " + (this._rows[row].groups ? true : false));
|
|
return (this._rows[row].groups ? true : false);
|
|
},
|
|
isContainerOpen: function(row) {
|
|
//this._log.info("isContainerOpen: " + this._rows[row].isOpen);
|
|
return this._rows[row].isOpen;
|
|
},
|
|
isContainerEmpty: function(row) {
|
|
//this._log.info("isContainerEmpty: " + row + " " + this._rows[row].groups.length + " " + (this._rows[row].groups.length == 0));
|
|
return (this._rows[row].groups.length == 0);
|
|
},
|
|
|
|
isSeparator: function(row) { return false },
|
|
isSorted: function() { return false },
|
|
|
|
// FIXME: make this return true for collection names that are editable,
|
|
// and then implement name editing on the new architecture.
|
|
isEditable: function(row, column) { return false },
|
|
|
|
getParentIndex: function(row) {
|
|
//this._log.info("getParentIndex: " + row);
|
|
|
|
let thisLevel = this.getLevel(row);
|
|
|
|
if (thisLevel == 0)
|
|
return -1;
|
|
for (let t = row - 1; t >= 0; t--)
|
|
if (this.getLevel(t) < thisLevel)
|
|
return t;
|
|
|
|
throw "getParentIndex: couldn't figure out parent index for row " + row;
|
|
},
|
|
|
|
getLevel: function(row) {
|
|
//this._log.info("getLevel: " + row);
|
|
|
|
if (!this.isHierarchical)
|
|
return 0;
|
|
|
|
return this._rows[row].level;
|
|
},
|
|
|
|
hasNextSibling: function(idx, after) {
|
|
//this._log.info("hasNextSibling: " + idx + " " + after);
|
|
|
|
let thisLevel = this.getLevel(idx);
|
|
for (let t = idx + 1; t < this._rows.length; t++) {
|
|
let nextLevel = this.getLevel(t);
|
|
if (nextLevel == thisLevel)
|
|
return true;
|
|
if (nextLevel < thisLevel)
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
getImageSrc: function(row, column) {
|
|
if (column.id == "nameCol") {
|
|
let iconURL = this._rows[row].iconURL;
|
|
if (iconURL)
|
|
return iconURL.spec;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
toggleOpenState: function(idx) {
|
|
//this._log.info("toggleOpenState: " + idx);
|
|
|
|
let item = this._rows[idx];
|
|
if (!item.groups)
|
|
return;
|
|
|
|
if (item.isOpen) {
|
|
item.isOpen = false;
|
|
|
|
let thisLevel = this.getLevel(idx);
|
|
let numToDelete = 0;
|
|
for (let t = idx + 1; t < this._rows.length; t++) {
|
|
if (this.getLevel(t) > thisLevel)
|
|
numToDelete++;
|
|
else
|
|
break;
|
|
}
|
|
if (numToDelete) {
|
|
this._rows.splice(idx + 1, numToDelete);
|
|
this._treeBox.rowCountChanged(idx + 1, -numToDelete);
|
|
}
|
|
}
|
|
else {
|
|
item.isOpen = true;
|
|
|
|
let groups = this._rows[idx].groups;
|
|
for (let i = 0; i < groups.length; i++)
|
|
this._rows.splice(idx + 1 + i, 0, groups[i]);
|
|
this._treeBox.rowCountChanged(idx + 1, groups.length);
|
|
}
|
|
},
|
|
|
|
getRowProperties: function (row, properties) {},
|
|
getCellProperties: function (row, column, properties) {},
|
|
getColumnProperties: function(columnID, column, properties) {},
|
|
|
|
setCellText: function(aRow, aCol, aValue) {
|
|
let statement = SnowlDatastore.createStatement(
|
|
"UPDATE sources SET name = :name WHERE id = :id");
|
|
statement.params.name = this._rows[aRow].name = aValue;
|
|
statement.params.id = this._rows[aRow].id;
|
|
|
|
try {
|
|
statement.execute();
|
|
}
|
|
finally {
|
|
statement.reset();
|
|
}
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Misc XPCOM Interfaces
|
|
|
|
// nsIObserver
|
|
observe: function(subject, topic, data) {
|
|
switch (topic) {
|
|
case "snowl:sources:changed":
|
|
this._getCollections();
|
|
// Rebuild the view to reflect the new collection of messages.
|
|
// Since the number of rows might have changed, we do this by reinitializing
|
|
// the view instead of merely invalidating the box object (which doesn't
|
|
// expect changes to the number of rows).
|
|
this._buildCollectionTree();
|
|
break;
|
|
}
|
|
},
|
|
|
|
_collections: null,
|
|
_getCollections: function() {
|
|
this._collections = [];
|
|
|
|
let statement = SnowlDatastore.createStatement(
|
|
"SELECT id, name, iconURL, grouped, groupIDColumn, groupNameColumn, " +
|
|
"groupHomeURLColumn, groupIconURLColumn " +
|
|
"FROM collections ORDER BY orderKey"
|
|
);
|
|
|
|
statement.QueryInterface(Ci.mozIStorageStatementWrapper);
|
|
|
|
try {
|
|
while (statement.step()) {
|
|
this._collections.push(new SnowlCollection(statement.row.id,
|
|
statement.row.name,
|
|
URI.get(statement.row.iconURL),
|
|
null,
|
|
null,
|
|
statement.row.grouped ? true : false,
|
|
statement.row.groupIDColumn,
|
|
statement.row.groupNameColumn,
|
|
statement.row.groupHomeURLColumn,
|
|
statement.row.groupIconURLColumn));
|
|
}
|
|
}
|
|
finally {
|
|
statement.reset();
|
|
}
|
|
},
|
|
|
|
// Build the list of rows in the tree. By default, all containers
|
|
// are closed, so this is the same as the list of collections, although
|
|
// in the future we might persist and restore the open state.
|
|
_buildCollectionTree: function() {
|
|
// XXX: add in proper scrollling/row selection code
|
|
this._tree.view.selection.select(-1);
|
|
|
|
if (this.isHierarchical) {
|
|
this._rows = [collection for each (collection in this._collections)];
|
|
}
|
|
else {
|
|
this._rows = [];
|
|
for each (let collection in this._collections) {
|
|
if (collection.grouped)
|
|
for each (let group in collection.groups)
|
|
this._rows.push(group);
|
|
else
|
|
this._rows.push(collection);
|
|
}
|
|
}
|
|
|
|
this._tree.view = this;
|
|
},
|
|
|
|
onSelect: function(aEvent) {
|
|
if (this._tree.currentIndex == -1 || SnowlUtils.gRightMouseButtonDown)
|
|
return;
|
|
|
|
let collection = this._rows[this._tree.currentIndex];
|
|
SnowlUtils.gListViewCollectionIndex = this._tree.currentIndex;
|
|
gMessageViewWindow.SnowlMessageView.setCollection(collection);
|
|
},
|
|
|
|
onCollectionsTreeMouseDown: function(aEvent) {
|
|
SnowlUtils.onTreeMouseDown(aEvent, this._tree);
|
|
},
|
|
|
|
onTreeContextPopupHidden: function() {
|
|
if (!SnowlUtils.gSelectOnRtClick)
|
|
SnowlUtils.RestoreSelectionWithoutContentLoad(this._tree);
|
|
},
|
|
|
|
refreshSource: function() {
|
|
let selectedSourceIDs = [];
|
|
|
|
// XXX: put in a loop for multiselected collections?
|
|
let selectedSource = this._rows[SnowlUtils.gListViewCollectionIndex];
|
|
|
|
if (!selectedSource.parent || selectedSource.parent.groupIDColumn != "sources.id")
|
|
return;
|
|
//this._log.info("refreshing selected source ID: "+selectedSource.groupID);
|
|
|
|
selectedSourceIDs.push(selectedSource.groupID);
|
|
|
|
let selectedSources = SnowlService.selectedSources(selectedSourceIDs);
|
|
SnowlService.refreshAllSources(selectedSources);
|
|
},
|
|
|
|
unsubscribe: function() {
|
|
let selectedSourceIDs = [];
|
|
let currentSourceID = this._rows[this._tree.currentIndex] ?
|
|
this._rows[this._tree.currentIndex].groupID : null;
|
|
let notifyID = null;
|
|
|
|
// XXX: put in a loop for multiselected collections?
|
|
let selectedSource = this._rows[SnowlUtils.gListViewCollectionIndex];
|
|
|
|
if (!selectedSource.parent || selectedSource.parent.groupIDColumn != "sources.id")
|
|
return;
|
|
this._log.info("unsubscribing source: "+selectedSource.name);
|
|
|
|
selectedSourceIDs.push(selectedSource.groupID);
|
|
|
|
// Delete loop here
|
|
for (let i = 0; i < selectedSourceIDs.length; ++i) {
|
|
sourceID = selectedSourceIDs[i];
|
|
SnowlDatastore.dbConnection.beginTransaction();
|
|
try {
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM metadata " +
|
|
"WHERE messageID IN " +
|
|
"(SELECT id FROM messages WHERE sourceID = " + sourceID + ")");
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM parts " +
|
|
"WHERE messageID IN" +
|
|
"(SELECT id FROM messages WHERE sourceID = " + sourceID + ")");
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM messages " +
|
|
"WHERE sourceID = " + sourceID);
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM sources " +
|
|
"WHERE id = " + sourceID);
|
|
SnowlDatastore.dbConnection.commitTransaction();
|
|
}
|
|
catch(ex) {
|
|
SnowlDatastore.dbConnection.rollbackTransaction();
|
|
throw ex;
|
|
}
|
|
if (sourceID == currentSourceID)
|
|
notifyID = sourceID;
|
|
}
|
|
|
|
Observers.notify(null, "snowl:sources:changed", null);
|
|
// If the current selection is unsubscribed, pass its id on to list view
|
|
Observers.notify(null, "snowl:messages:changed", notifyID);
|
|
}
|
|
|
|
};
|
|
|
|
window.addEventListener("load", function() { CollectionsView.init() }, true);
|