2008-05-02 02:06:42 +04:00
|
|
|
/* ***** 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 the Feed Subscribe Handler.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is Google Inc.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Ben Goodger <beng@google.com>
|
|
|
|
* Asaf Romano <mano@mozilla.com>
|
2008-07-10 12:32:14 +04:00
|
|
|
* Myk Melez <myk@mozilla.org>
|
2008-05-02 02:06:42 +04:00
|
|
|
*
|
|
|
|
* 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 ***** */
|
|
|
|
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cr = Components.results;
|
|
|
|
const Cu = Components.utils;
|
|
|
|
|
2008-10-18 04:30:25 +04:00
|
|
|
// modules that come with Firefox
|
2008-05-13 10:19:55 +04:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2008-07-10 12:32:14 +04:00
|
|
|
|
2008-10-18 04:30:25 +04:00
|
|
|
// modules that should come with Firefox
|
2008-05-13 10:19:55 +04:00
|
|
|
Cu.import("resource://snowl/modules/log4moz.js");
|
2008-05-15 09:33:20 +04:00
|
|
|
Cu.import("resource://snowl/modules/URI.js");
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-10-18 04:30:25 +04:00
|
|
|
// modules that are Snowl-specific
|
2008-07-10 12:32:14 +04:00
|
|
|
Cu.import("resource://snowl/modules/datastore.js");
|
|
|
|
Cu.import("resource://snowl/modules/collection.js");
|
2008-08-26 05:20:52 +04:00
|
|
|
Cu.import("resource://snowl/modules/utils.js");
|
2008-05-13 10:19:55 +04:00
|
|
|
|
|
|
|
const XML_NS = "http://www.w3.org/XML/1998/namespace"
|
2008-05-13 22:09:16 +04:00
|
|
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
2008-05-13 10:19:55 +04:00
|
|
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|
|
|
|
2008-07-21 08:03:10 +04:00
|
|
|
let gBrowserWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).
|
|
|
|
getInterface(Ci.nsIWebNavigation).
|
|
|
|
QueryInterface(Ci.nsIDocShellTreeItem).
|
|
|
|
rootTreeItem.
|
|
|
|
QueryInterface(Ci.nsIInterfaceRequestor).
|
|
|
|
getInterface(Ci.nsIDOMWindow);
|
|
|
|
|
|
|
|
let gMessageViewWindow = window;
|
|
|
|
|
|
|
|
let SnowlMessageView = {
|
2008-07-10 12:32:14 +04:00
|
|
|
get _log() {
|
|
|
|
delete this._log;
|
|
|
|
return this._log = Log4Moz.Service.getLogger("Snowl.River");
|
|
|
|
},
|
2008-05-02 02:06:42 +04:00
|
|
|
|
2008-05-15 09:33:20 +04:00
|
|
|
// Favicon Service
|
|
|
|
get _faviconSvc() {
|
|
|
|
let faviconSvc = Cc["@mozilla.org/browser/favicon-service;1"].
|
|
|
|
getService(Ci.nsIFaviconService);
|
|
|
|
delete this._faviconSvc;
|
|
|
|
this._faviconSvc = faviconSvc;
|
|
|
|
return this._faviconSvc;
|
|
|
|
},
|
|
|
|
|
2008-05-19 01:41:03 +04:00
|
|
|
get _currentButton() {
|
|
|
|
let currentButton = document.getElementById("currentButton");
|
|
|
|
delete this._currentButton;
|
|
|
|
this._currentButton = currentButton;
|
|
|
|
return this._currentButton;
|
2008-05-16 01:50:37 +04:00
|
|
|
},
|
|
|
|
|
2008-05-19 12:18:29 +04:00
|
|
|
get _bodyButton() {
|
|
|
|
let bodyButton = document.getElementById("bodyButton");
|
|
|
|
delete this._bodyButton;
|
|
|
|
this._bodyButton = bodyButton;
|
|
|
|
return this._bodyButton;
|
|
|
|
},
|
|
|
|
|
2008-10-02 05:57:46 +04:00
|
|
|
get _columnsButton() {
|
|
|
|
let columnsButton = document.getElementById("columnsButton");
|
|
|
|
delete this._columnsButton;
|
|
|
|
this._columnsButton = columnsButton;
|
|
|
|
return this._columnsButton;
|
|
|
|
},
|
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
get _filterTextbox() {
|
2008-10-14 22:34:46 +04:00
|
|
|
delete this._filterTextbox;
|
|
|
|
return this._filterTextbox = document.getElementById("filterTextbox");
|
|
|
|
},
|
|
|
|
|
|
|
|
get _periodMenu() {
|
|
|
|
delete this._periodMenu;
|
|
|
|
return this._periodMenu = document.getElementById("periodMenu");
|
|
|
|
},
|
|
|
|
|
|
|
|
get _periodMenuPopup() {
|
|
|
|
delete this._periodMenuPopup;
|
|
|
|
return this._periodMenuPopup = document.getElementById("periodMenuPopup");
|
|
|
|
},
|
|
|
|
|
|
|
|
get _periodStartTime() {
|
|
|
|
if (!this._periodMenu.selectedItem)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
switch (this._periodMenu.selectedItem.value) {
|
|
|
|
case "today":
|
2008-11-02 20:20:12 +03:00
|
|
|
return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.today);
|
2008-10-14 22:34:46 +04:00
|
|
|
case "yesterday":
|
2008-11-02 20:20:12 +03:00
|
|
|
return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.yesterday);
|
2008-10-14 22:34:46 +04:00
|
|
|
case "last7days":
|
2008-11-02 20:20:12 +03:00
|
|
|
return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.sixDaysAgo.epoch);
|
2008-10-20 12:31:30 +04:00
|
|
|
case "last4weeks":
|
2008-11-02 20:20:12 +03:00
|
|
|
return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.twentySevenDaysAgo.epoch);
|
2008-10-14 22:34:46 +04:00
|
|
|
case "all":
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
get _periodEndTime() {
|
|
|
|
if (!this._periodMenu.selectedItem)
|
|
|
|
return Number.MAX_VALUE;
|
|
|
|
|
|
|
|
switch (this._periodMenu.selectedItem.value) {
|
|
|
|
// Yesterday means only that day, but the rest of the periods are fairly
|
|
|
|
// open-ended, since they all include today, and in theory there shouldn't
|
|
|
|
// be any messages received after today. I suppose we could exclude
|
|
|
|
// messages received in the future from these categories, but since that
|
|
|
|
// situation is exceptional, it's probably better to show those.
|
|
|
|
case "yesterday":
|
2008-11-02 20:20:12 +03:00
|
|
|
return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.today);
|
2008-10-14 22:34:46 +04:00
|
|
|
case "today":
|
|
|
|
case "last7days":
|
2008-10-20 12:31:30 +04:00
|
|
|
case "last4weeks":
|
2008-10-14 22:34:46 +04:00
|
|
|
case "all":
|
|
|
|
default:
|
|
|
|
return Number.MAX_VALUE;
|
|
|
|
}
|
2008-07-21 06:13:25 +04:00
|
|
|
},
|
|
|
|
|
2008-05-08 00:45:18 +04:00
|
|
|
// The set of messages to display in the view.
|
|
|
|
_collection: null,
|
2008-05-02 02:06:42 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// whether or not the content area has scrollbars
|
|
|
|
_hasHorizontalScrollbar: false,
|
|
|
|
_hasVerticalScrollbar: false,
|
|
|
|
|
|
|
|
// the width (narrower dimension) of the vertical and horizontal scrollbars;
|
|
|
|
// useful for calculating the viewable size of the viewport, since window.
|
|
|
|
// innerWidth and innerHeight include the area taken up by the scrollbars
|
|
|
|
// XXX Is this value correct, and does it vary by platform?
|
|
|
|
scrollbarWidth: 15,
|
|
|
|
|
2008-07-21 05:50:34 +04:00
|
|
|
get contentStylesheet() {
|
|
|
|
for (let i = 0; i < document.styleSheets.length; i++)
|
|
|
|
if (document.styleSheets[i].href == "chrome://snowl/content/riverContent.css")
|
|
|
|
return document.styleSheets[i];
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
set columnWidth(newVal) {
|
2008-11-08 04:45:07 +03:00
|
|
|
this._updateContentRule(0, "#contentStack[columns] > #contentBox " +
|
|
|
|
"{ -moz-column-width: " + newVal + "px }");
|
2008-07-21 05:50:34 +04:00
|
|
|
|
|
|
|
// Set the maximum width for images in the content so they don't stick out
|
|
|
|
// the side of the columns.
|
2008-10-02 05:57:46 +04:00
|
|
|
this._updateContentRule(1, "#contentBox img { max-width: " + newVal + "px }");
|
2008-07-28 05:58:49 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateContentRule: function(position, newValue) {
|
|
|
|
this.contentStylesheet.deleteRule(position);
|
|
|
|
this.contentStylesheet.insertRule(newValue, position);
|
2008-07-21 05:50:34 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
set contentHeight(newVal) {
|
|
|
|
document.getElementById("contentBox").style.height = newVal + "px";
|
|
|
|
|
|
|
|
// Make the column splitter as tall as the content box. It doesn't
|
|
|
|
// resize itself, perhaps because it's (absolutely positioned) in a stack.
|
|
|
|
document.getElementById("columnResizeSplitter").style.height = newVal + "px";
|
|
|
|
|
2008-07-28 05:58:49 +04:00
|
|
|
// Set the maximum height for images and tables in the content so they
|
|
|
|
// don't make the columns taller than the height of the content box.
|
2008-10-02 05:57:46 +04:00
|
|
|
this._updateContentRule(2, "#contentBox img { max-height: " + newVal + "px }");
|
|
|
|
this._updateContentRule(3, "#contentBox table { max-height: " + newVal + "px }");
|
2008-07-21 05:50:34 +04:00
|
|
|
},
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
_window: null,
|
|
|
|
_document: null,
|
|
|
|
|
|
|
|
|
|
|
|
//**************************************************************************//
|
|
|
|
// Initialization
|
|
|
|
|
|
|
|
init: function() {
|
2008-11-04 23:11:07 +03:00
|
|
|
// FIXME: use the Observers module to observe message change
|
|
|
|
// or message added notifications and rebuild the view when they happen.
|
2008-10-18 04:30:25 +04:00
|
|
|
|
|
|
|
// FIXME: simplify the way the view gets built after the collections view
|
|
|
|
// gets loaded to make this code less buggy and easier to hack.
|
|
|
|
|
2008-10-10 02:36:28 +04:00
|
|
|
// Finish initializing after a brief timeout to give the collections view
|
|
|
|
// time to initialize itself.
|
|
|
|
let t = this;
|
|
|
|
window.setTimeout(function() { t._initDelayed() }, 0);
|
|
|
|
},
|
|
|
|
|
|
|
|
_initDelayed: function() {
|
2008-10-10 00:49:22 +04:00
|
|
|
// Resize the content box to the initial size of the browser.
|
2008-07-10 12:32:14 +04:00
|
|
|
this.resizeContentBox();
|
|
|
|
|
2008-10-10 00:49:22 +04:00
|
|
|
// Listen for resize events so we can resize the content box when the size
|
|
|
|
// of the browser changes. We set this event listener here rather than
|
2008-10-10 02:09:07 +04:00
|
|
|
// in an onresize attribute on the page element because loading this view
|
|
|
|
// on startup can cause a resize event to fire before the view is loaded
|
|
|
|
// (and thus before SnowlMessageView has been defined), which would cause
|
|
|
|
// an attribute-based listener to throw an exception.
|
|
|
|
window.addEventListener("resize",
|
|
|
|
function() SnowlMessageView.resizeContentBox(),
|
|
|
|
false);
|
2008-10-10 00:49:22 +04:00
|
|
|
|
2008-05-13 22:09:16 +04:00
|
|
|
// Explicitly wrap |window| in an XPCNativeWrapper to make sure
|
|
|
|
// it's a real native object! This will throw an exception if we
|
|
|
|
// get a non-native object.
|
|
|
|
this._window = new XPCNativeWrapper(window);
|
|
|
|
this._document = this._window.document;
|
|
|
|
|
2008-07-28 11:59:13 +04:00
|
|
|
//this._collection = new SnowlCollection();
|
2008-05-13 22:09:16 +04:00
|
|
|
this._updateToolbar();
|
2008-07-28 11:59:13 +04:00
|
|
|
//this.writeContent();
|
2008-10-17 06:11:46 +04:00
|
|
|
|
|
|
|
this._setMidnightTimout();
|
2008-05-13 22:09:16 +04:00
|
|
|
},
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
/**
|
|
|
|
* Resize the content box to the height of the viewport. We have to do this
|
|
|
|
* because of bug 434683.
|
|
|
|
*/
|
|
|
|
resizeContentBox: function() {
|
|
|
|
let toolbarHeight = document.getElementById("toolbar").boxObject.height;
|
2008-07-21 05:50:34 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// We do this on load, when there isn't yet a horizontal scrollbar,
|
|
|
|
// but we anticipate that there probably will be one, so we include it
|
|
|
|
// in the calculation. Perhap we should instead wait to resize
|
|
|
|
// the content box until the content actually overflows horizontally.
|
|
|
|
// XXX Why do I have to subtract *double* the width of the scrollbar???
|
2008-10-10 01:54:00 +04:00
|
|
|
// Maybe it's because of the 7px padding all around the contentBox?
|
2008-07-21 05:50:34 +04:00
|
|
|
this.contentHeight =
|
|
|
|
window.innerHeight - (this.scrollbarWidth*2) - toolbarHeight;
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
|
|
|
|
2008-10-17 06:11:46 +04:00
|
|
|
_setMidnightTimout: function() {
|
|
|
|
let t = this;
|
|
|
|
let now = new Date();
|
2008-11-02 20:20:12 +03:00
|
|
|
let msUntilMidnight = SnowlDateUtils.tomorrow - now;
|
2008-10-17 06:11:46 +04:00
|
|
|
this._log.info("setting midnight timeout for " + new Date(now.getTime() + msUntilMidnight));
|
|
|
|
window.setTimeout(function() { t.onMidnight() }, msUntilMidnight);
|
|
|
|
},
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
|
|
|
|
//**************************************************************************//
|
|
|
|
// Toolbar
|
|
|
|
|
2008-05-13 22:09:16 +04:00
|
|
|
_updateToolbar: function() {
|
2008-05-13 08:32:18 +04:00
|
|
|
this._params = {};
|
|
|
|
let query = window.location.search.substr(1);
|
|
|
|
for each (let param in query.split("&")) {
|
|
|
|
let name, value;
|
|
|
|
if (param.indexOf("=") != -1) {
|
|
|
|
[name, value] = param.split("=");
|
|
|
|
value = decodeURIComponent(value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
name = param;
|
|
|
|
this._params[name] = value;
|
|
|
|
}
|
|
|
|
|
2008-07-28 12:21:38 +04:00
|
|
|
if ("current" in this._params)
|
2008-05-19 01:41:03 +04:00
|
|
|
this._currentButton.checked = true;
|
|
|
|
|
2008-11-08 03:14:41 +03:00
|
|
|
if ("body" in this._params) {
|
2008-05-19 12:18:29 +04:00
|
|
|
this._bodyButton.checked = true;
|
2008-11-08 03:14:41 +03:00
|
|
|
this._setBody(true);
|
|
|
|
}
|
2008-05-19 12:18:29 +04:00
|
|
|
|
2008-07-28 12:21:38 +04:00
|
|
|
if ("filter" in this._params)
|
2008-05-13 22:09:16 +04:00
|
|
|
document.getElementById("filterTextbox").value = this._params.filter;
|
2008-05-14 05:25:31 +04:00
|
|
|
|
2008-10-14 22:34:46 +04:00
|
|
|
if ("period" in this._params) {
|
|
|
|
let item = this._periodMenuPopup.getElementsByAttribute("value", this._params.period)[0];
|
|
|
|
if (item)
|
|
|
|
this._periodMenu.selectedItem = item;
|
|
|
|
}
|
|
|
|
|
2008-10-02 05:57:46 +04:00
|
|
|
if ("columns" in this._params) {
|
|
|
|
this._columnsButton.checked = true;
|
|
|
|
// XXX This feels like the wrong place to do this, but I don't see
|
|
|
|
// a better place at the moment. Yuck, the whole process by which
|
|
|
|
// the view gets built needs to get cleaned up and documented.
|
|
|
|
this._setColumns(this._columnsButton.checked);
|
|
|
|
}
|
|
|
|
|
2008-07-28 11:59:13 +04:00
|
|
|
let selected = false;
|
|
|
|
if ("collection" in this._params) {
|
|
|
|
//dump("this._params.collection: " + this._params.collection + "; this._params.group: " + this._params.group + "\n");
|
2008-10-10 02:42:00 +04:00
|
|
|
for (let i = 0; i < CollectionsView._rows.length; i++) {
|
|
|
|
let collection = CollectionsView._rows[i];
|
2008-07-28 11:59:13 +04:00
|
|
|
//dump("collection id: " + collection.id + "; parent id: " + (collection.parent ? collection.parent.id : "no parent") + "; collection.name = " + collection.name + "\n");
|
|
|
|
if (collection.id == this._params.collection) {
|
2008-10-10 02:42:00 +04:00
|
|
|
CollectionsView._tree.view.selection.select(i);
|
2008-07-28 11:59:13 +04:00
|
|
|
selected = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if ("group" in this._params &&
|
|
|
|
collection.parent &&
|
|
|
|
collection.parent.id == this._params.collection &&
|
|
|
|
collection.name == this._params.group) {
|
2008-10-10 02:42:00 +04:00
|
|
|
CollectionsView._tree.view.selection.select(i);
|
2008-07-28 11:59:13 +04:00
|
|
|
selected = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!selected)
|
2008-10-10 02:42:00 +04:00
|
|
|
CollectionsView._tree.view.selection.select(0);
|
2008-07-28 12:21:38 +04:00
|
|
|
|
2008-05-09 05:50:38 +04:00
|
|
|
},
|
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
onFilter: function(aEvent) {
|
|
|
|
this._updateURI();
|
|
|
|
this._applyFilters();
|
|
|
|
},
|
2008-05-19 01:41:03 +04:00
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
onCommandCurrentButton: function(aEvent) {
|
2008-05-19 01:41:03 +04:00
|
|
|
this._updateURI();
|
2008-07-21 06:13:25 +04:00
|
|
|
this._applyFilters();
|
2008-05-19 01:41:03 +04:00
|
|
|
},
|
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
_applyFilters: function() {
|
|
|
|
let filters = [];
|
|
|
|
|
|
|
|
if (this._currentButton.checked)
|
|
|
|
filters.push({ expression: "current = 1", parameters: {} });
|
2008-05-05 06:54:47 +04:00
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
// FIXME: use a left join here once the SQLite bug breaking left joins to
|
|
|
|
// virtual tables has been fixed (i.e. after we upgrade to SQLite 3.5.7+).
|
|
|
|
if (this._filterTextbox.value)
|
|
|
|
filters.push({ expression: "messages.id IN (SELECT messageID FROM parts WHERE content MATCH :filter)",
|
|
|
|
parameters: { filter: this._filterTextbox.value } });
|
2008-05-13 08:32:18 +04:00
|
|
|
|
2008-10-14 22:34:46 +04:00
|
|
|
if (this._periodMenu.selectedItem)
|
|
|
|
filters.push({ expression: "received >= :startTime AND received < :endTime",
|
|
|
|
parameters: { startTime: this._periodStartTime,
|
|
|
|
endTime: this._periodEndTime } });
|
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
this._collection.filters = filters;
|
2008-07-28 12:21:38 +04:00
|
|
|
|
2008-07-21 06:13:25 +04:00
|
|
|
this._collection.invalidate();
|
2008-05-19 00:06:12 +04:00
|
|
|
this.rebuildView();
|
2008-05-13 22:09:16 +04:00
|
|
|
},
|
|
|
|
|
2008-11-08 03:14:41 +03:00
|
|
|
onCommandBodyButton: function() {
|
|
|
|
this._setBody(this._bodyButton.checked);
|
|
|
|
this._updateURI();
|
|
|
|
},
|
|
|
|
|
|
|
|
_setBody: function(showBody) {
|
2008-11-07 05:55:26 +03:00
|
|
|
let contentBox = document.getElementById("contentBox");
|
2008-11-08 03:14:41 +03:00
|
|
|
if (showBody) {
|
2008-11-07 05:55:26 +03:00
|
|
|
let classes = contentBox.className.split(/\s/);
|
2008-11-08 03:14:41 +03:00
|
|
|
classes.push("showBody");
|
2008-11-07 05:55:26 +03:00
|
|
|
contentBox.className = classes.join(" ");
|
|
|
|
}
|
2008-11-08 03:14:41 +03:00
|
|
|
else {
|
|
|
|
contentBox.className = contentBox.className.
|
|
|
|
split(/\s/).
|
|
|
|
filter(function(v) v != "showBody").
|
|
|
|
join(" ");
|
|
|
|
}
|
2008-05-19 12:18:29 +04:00
|
|
|
},
|
|
|
|
|
2008-10-02 05:57:46 +04:00
|
|
|
onCommandColumnsButton: function() {
|
|
|
|
this._setColumns(this._columnsButton.checked);
|
|
|
|
this._updateURI();
|
|
|
|
},
|
|
|
|
|
|
|
|
_setColumns: function(columns) {
|
|
|
|
if (columns) {
|
|
|
|
document.getElementById("contentStack").setAttribute("columns", true);
|
|
|
|
// Enable the keys that map PageUp and PageDown to PageLeft and PageRight.
|
|
|
|
document.getElementById("pageLeftKey").removeAttribute("disabled");
|
|
|
|
document.getElementById("pageRightKey").removeAttribute("disabled");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
document.getElementById("contentStack").removeAttribute("columns");
|
|
|
|
document.getElementById("pageLeftKey").setAttribute("disabled", "true");
|
|
|
|
document.getElementById("pageRightKey").setAttribute("disabled", "true");
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
2008-10-14 22:34:46 +04:00
|
|
|
onCommandPeriodMenu: function(event) {
|
|
|
|
this._updateURI();
|
|
|
|
this._applyFilters();
|
|
|
|
},
|
|
|
|
|
2008-05-13 22:09:16 +04:00
|
|
|
_updateURI: function() {
|
|
|
|
let params = [];
|
|
|
|
|
2008-07-28 12:21:38 +04:00
|
|
|
if (this._currentButton.checked)
|
2008-05-19 01:41:03 +04:00
|
|
|
params.push("current");
|
|
|
|
|
2008-05-19 12:18:29 +04:00
|
|
|
if (this._bodyButton.checked)
|
|
|
|
params.push("body");
|
|
|
|
|
2008-10-02 05:57:46 +04:00
|
|
|
if (this._columnsButton.checked)
|
|
|
|
params.push("columns");
|
|
|
|
|
2008-10-14 22:34:46 +04:00
|
|
|
// FIXME: don't add the collection if it's the default All collection,
|
|
|
|
// but do add it if it's already in the list of params.
|
2008-07-28 11:59:13 +04:00
|
|
|
if (this._collection.id)
|
|
|
|
params.push("collection=" + this._collection.id);
|
|
|
|
else if (this._collection.parent) {
|
|
|
|
params.push("collection=" + this._collection.parent.id);
|
|
|
|
params.push("group=" + encodeURIComponent(this._collection.name));
|
|
|
|
}
|
|
|
|
|
2008-07-28 12:21:38 +04:00
|
|
|
if (this._filterTextbox.value)
|
|
|
|
params.push("filter=" + encodeURIComponent(this._filterTextbox.value));
|
2008-05-13 22:09:16 +04:00
|
|
|
|
2008-10-14 22:34:46 +04:00
|
|
|
// FIXME: do add the All period if it's already in the list of params.
|
|
|
|
if (this._periodMenu.selectedItem && this._periodMenu.selectedItem.value != "all")
|
|
|
|
params.push("period=" + encodeURIComponent(this._periodMenu.selectedItem.value));
|
|
|
|
|
2008-10-25 02:01:24 +04:00
|
|
|
let browser = gBrowserWindow.gBrowser.getBrowserForDocument(document);
|
2008-05-13 08:32:18 +04:00
|
|
|
|
2008-10-25 02:01:24 +04:00
|
|
|
let currentURI = browser.docShell.currentURI.QueryInterface(Ci.nsIURL);
|
2008-07-10 12:32:14 +04:00
|
|
|
|
|
|
|
let query = params.length > 0 ? "?" + params.join("&") : "";
|
|
|
|
let spec = currentURI.prePath + currentURI.filePath + query;
|
|
|
|
let uri = Cc["@mozilla.org/network/io-service;1"].
|
|
|
|
getService(Ci.nsIIOService).
|
|
|
|
newURI(spec, null, null);
|
|
|
|
|
2008-05-14 05:34:52 +04:00
|
|
|
// Update the docshell with the new URI. This updates the location bar
|
|
|
|
// and gets used by the bookmarks service when the user bookmarks the page.
|
2008-10-25 02:01:24 +04:00
|
|
|
browser.docShell.setCurrentURI(uri);
|
2008-05-13 22:09:16 +04:00
|
|
|
|
2008-05-14 05:34:52 +04:00
|
|
|
// Update the session history entry for the page with the new URI.
|
|
|
|
// This gets used when the user reloads the page or traverses history.
|
2008-10-25 02:01:24 +04:00
|
|
|
let history = browser.sessionHistory;
|
2008-05-14 05:34:52 +04:00
|
|
|
let historyEntry = history.getEntryAtIndex(history.index, false);
|
2008-05-15 22:10:48 +04:00
|
|
|
if (historyEntry instanceof Ci.nsISHEntry)
|
|
|
|
historyEntry.setURI(uri);
|
|
|
|
else
|
|
|
|
this._log.error("can't update session history URI for " +
|
|
|
|
"'" + historyEntry.title + "' " +
|
|
|
|
"<" + historyEntry.URI.spec + ">; " +
|
|
|
|
"entry is not an instance of nsISHEntry");
|
2008-05-13 08:32:18 +04:00
|
|
|
},
|
2008-05-13 22:09:16 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
|
|
|
|
//**************************************************************************//
|
|
|
|
// Event Handlers
|
|
|
|
|
2008-10-17 06:11:46 +04:00
|
|
|
onMidnight: function() {
|
|
|
|
this._setMidnightTimout();
|
|
|
|
this.rebuildView();
|
|
|
|
},
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
doPageMove: function(direction) {
|
2008-11-08 05:32:41 +03:00
|
|
|
let contentBox = document.getElementById("contentBox");
|
|
|
|
|
|
|
|
// element.clientWidth is the width of an element not including the space
|
|
|
|
// taken up by a vertical scrollbar, if any, so it should be the right
|
|
|
|
// number of pixels to scroll whether or not there is a vertical scrollbar
|
|
|
|
// (which there shouldn't ever be but sometimes is anyway because of bugs
|
|
|
|
// or limitations in the column breaking algorithm). However, for some
|
|
|
|
// reason clientWidth is actually 18 pixels less than the number of pixels
|
|
|
|
// to scroll, so we have to add back that number of pixels.
|
|
|
|
let pixelsToScroll = contentBox.clientWidth + 18;
|
|
|
|
|
|
|
|
// FIXME: file a bug on clientWidth being 18 pixels less than the width
|
|
|
|
// of the page (if it really is; first, measure to make sure it's the case,
|
|
|
|
// as the bug could be an issue with column placement instead).
|
|
|
|
|
|
|
|
this.doMove(direction * pixelsToScroll);
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
|
|
|
|
2008-11-08 23:30:23 +03:00
|
|
|
// Note: this doesn't really work because the computed column width
|
|
|
|
// isn't the same as the actual width. Bug 463828 is about Gecko providing
|
|
|
|
// a way to get the actual width (perhaps by making the computed width
|
|
|
|
// be the actual width).
|
|
|
|
// FIXME: fix this once bug 463828 is fixed.
|
2008-07-10 12:32:14 +04:00
|
|
|
doColumnMove: function(direction) {
|
|
|
|
let contentBox = document.getElementById("contentBox");
|
|
|
|
let computedStyle = window.getComputedStyle(contentBox, null);
|
|
|
|
let columnWidth = parseInt(computedStyle.MozColumnWidth) +
|
|
|
|
parseInt(computedStyle.MozColumnGap);
|
|
|
|
this.doMove(direction * columnWidth);
|
|
|
|
},
|
|
|
|
|
|
|
|
doMove: function(pixels) {
|
2008-11-08 04:45:07 +03:00
|
|
|
let contentBox = document.getElementById("contentBox");
|
|
|
|
contentBox.scrollLeft = contentBox.scrollLeft + pixels;
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
onHome: function() {
|
2008-11-08 04:45:07 +03:00
|
|
|
let contentBox = document.getElementById("contentBox");
|
|
|
|
contentBox.scrollLeft = 0;
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
onEnd: function() {
|
2008-11-08 04:45:07 +03:00
|
|
|
let contentBox = document.getElementById("contentBox");
|
|
|
|
contentBox.scrollLeft = contentBox.scrollWidth;
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle overflow and underflow events. |event.detail| is 0 if vertical flow
|
|
|
|
* changed, 1 if horizontal flow changed, and 2 if both changed.
|
|
|
|
*/
|
|
|
|
onFlowChange: function(event) {
|
|
|
|
let val = event.type == "overflow" ? true : false;
|
|
|
|
|
|
|
|
switch(event.detail) {
|
|
|
|
case 0:
|
|
|
|
this._hasVerticalScrollbar = val;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
this._hasHorizontalScrollbar = val;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
this._hasVerticalScrollbar = val;
|
|
|
|
this._hasHorizontalScrollbar = val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2008-07-21 08:03:10 +04:00
|
|
|
setCollection: function(collection) {
|
|
|
|
this._collection = collection;
|
2008-10-03 04:34:22 +04:00
|
|
|
this._collection.sortOrder = -1;
|
2008-10-13 08:30:14 +04:00
|
|
|
this._collection.sortProperties = ["received", "timestamp"];
|
2008-07-28 11:59:13 +04:00
|
|
|
this._updateURI();
|
2008-07-21 08:03:10 +04:00
|
|
|
this._applyFilters();
|
|
|
|
// No need to rebuild the view here, as _applyFilters will do it for us.
|
|
|
|
// XXX Should we pull the call to rebuildView out of _applyFilters?
|
|
|
|
},
|
|
|
|
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
//**************************************************************************//
|
|
|
|
// Safe DOM Manipulation
|
|
|
|
|
|
|
|
/**
|
2008-08-20 05:08:37 +04:00
|
|
|
* Use this sandbox to run any DOM manipulation code on nodes
|
|
|
|
* which are already inserted into the content document.
|
2008-07-10 12:32:14 +04:00
|
|
|
*/
|
|
|
|
get _contentSandbox() {
|
2008-08-20 05:08:37 +04:00
|
|
|
delete this._contentSandbox;
|
|
|
|
return this._contentSandbox = new Cu.Sandbox(this._window);
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
// FIXME: use this when setting story title and byline.
|
2008-05-13 10:19:55 +04:00
|
|
|
_setContentText: function FW__setContentText(id, text) {
|
|
|
|
this._contentSandbox.element = this._document.getElementById(id);
|
|
|
|
this._contentSandbox.textNode = this._document.createTextNode(text);
|
2008-07-10 12:32:14 +04:00
|
|
|
let codeStr =
|
2008-05-13 10:19:55 +04:00
|
|
|
"while (element.hasChildNodes()) " +
|
|
|
|
" element.removeChild(element.firstChild);" +
|
|
|
|
"element.appendChild(textNode);";
|
|
|
|
Cu.evalInSandbox(codeStr, this._contentSandbox);
|
|
|
|
this._contentSandbox.element = null;
|
|
|
|
this._contentSandbox.textNode = null;
|
|
|
|
},
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// FIXME: use this when linkifying the story title and source.
|
2008-05-13 10:19:55 +04:00
|
|
|
/**
|
|
|
|
* Safely sets the href attribute on an anchor tag, providing the URI
|
|
|
|
* specified can be loaded according to rules.
|
|
|
|
*
|
|
|
|
* XXX Renamed from safeSetURIAttribute to unsafeSetURIAttribute to reflect
|
|
|
|
* that we've commented out the stuff that makes it safe.
|
|
|
|
*
|
|
|
|
* FIXME: I don't understand the security implications here, but presumably
|
|
|
|
* there's a reason this is here, and we should be respecting it, so make this
|
|
|
|
* work by giving each message in a collection have a reference to its source
|
|
|
|
* and then use the source's URI to create the principal with which we compare
|
|
|
|
* the URI.
|
|
|
|
*
|
|
|
|
* @param element
|
|
|
|
* The element to set a URI attribute on
|
|
|
|
* @param attribute
|
|
|
|
* The attribute of the element to set the URI to, e.g. href or src
|
|
|
|
* @param uri
|
|
|
|
* The URI spec to set as the href
|
|
|
|
*/
|
|
|
|
_unsafeSetURIAttribute:
|
|
|
|
function FW__unsafeSetURIAttribute(element, attribute, uri) {
|
|
|
|
/*
|
2008-07-10 12:32:14 +04:00
|
|
|
let secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
|
2008-05-13 10:19:55 +04:00
|
|
|
getService(Ci.nsIScriptSecurityManager);
|
|
|
|
const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
|
|
|
|
try {
|
|
|
|
secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
|
|
|
|
// checkLoadURIStrWithPrincipal will throw if the link URI should not be
|
|
|
|
// loaded, either because our feedURI isn't allowed to load it or per
|
|
|
|
// the rules specified in |flags|, so we'll never "linkify" the link...
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
// Not allowed to load this link because secman.checkLoadURIStr threw
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
this._contentSandbox.element = element;
|
|
|
|
this._contentSandbox.uri = uri;
|
2008-07-10 12:32:14 +04:00
|
|
|
let codeStr = "element.setAttribute('" + attribute + "', uri);";
|
2008-05-13 10:19:55 +04:00
|
|
|
Cu.evalInSandbox(codeStr, this._contentSandbox);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
//**************************************************************************//
|
|
|
|
// Content Generation
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
rebuildView: function() {
|
|
|
|
let contentBox = this._document.getElementById("contentBox");
|
|
|
|
while (contentBox.hasChildNodes())
|
|
|
|
contentBox.removeChild(contentBox.lastChild);
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
this.writeContent();
|
2008-05-13 10:19:55 +04:00
|
|
|
},
|
|
|
|
|
2008-05-18 11:52:28 +04:00
|
|
|
/**
|
|
|
|
* A JavaScript Strands Future with which we pause the writing of messages
|
|
|
|
* so as not to hork the UI thread.
|
|
|
|
*/
|
|
|
|
_futureWriteMessages: null,
|
|
|
|
|
2008-10-20 12:31:30 +04:00
|
|
|
// FIXME: make group names localizable.
|
|
|
|
// FIXME: move group names into the SnowlUtils module.
|
|
|
|
|
|
|
|
// I wonder if it makes more sense to define groups in a data structure
|
|
|
|
// that also defines the periods to which they apply and specify start/end
|
|
|
|
// times rather than epochs (which are essentially start times).
|
|
|
|
// Among other benefits, we could potentially eliminate unused epochs
|
|
|
|
// like "The Future" and (in some cases) "Older" while fixing the bug
|
|
|
|
// that epochs never reached don't appear in the view.
|
|
|
|
|
|
|
|
// The groups into which we break up messages. These vary by period,
|
|
|
|
// since different periods are optimally split up into different groupings.
|
|
|
|
_groups: {
|
|
|
|
today: [
|
|
|
|
{ name: "The Future", epoch: Number.MAX_VALUE },
|
2008-11-02 20:20:12 +03:00
|
|
|
{ name: "Evening", epoch: SnowlDateUtils.evening(SnowlDateUtils.today) },
|
|
|
|
{ name: "Afternoon", epoch: SnowlDateUtils.afternoon(SnowlDateUtils.today) },
|
|
|
|
{ name: "Morning", epoch: SnowlDateUtils.morning(SnowlDateUtils.today) },
|
|
|
|
{ name: "Wee Hours", epoch: SnowlDateUtils.today },
|
2008-10-20 12:31:30 +04:00
|
|
|
{ name: "Older", epoch: 0 }
|
|
|
|
],
|
|
|
|
yesterday: [
|
|
|
|
{ name: "The Future", epoch: Number.MAX_VALUE },
|
2008-11-02 20:20:12 +03:00
|
|
|
{ name: "Evening", epoch: SnowlDateUtils.evening(SnowlDateUtils.yesterday) },
|
|
|
|
{ name: "Afternoon", epoch: SnowlDateUtils.afternoon(SnowlDateUtils.yesterday) },
|
|
|
|
{ name: "Morning", epoch: SnowlDateUtils.morning(SnowlDateUtils.yesterday) },
|
|
|
|
{ name: "Wee Hours", epoch: SnowlDateUtils.yesterday },
|
2008-10-20 12:31:30 +04:00
|
|
|
{ name: "Older", epoch: 0 }
|
|
|
|
],
|
|
|
|
last7days: [
|
|
|
|
{ name: "The Future", epoch: Number.MAX_VALUE },
|
2008-11-02 20:20:12 +03:00
|
|
|
{ name: "Today", epoch: SnowlDateUtils.today },
|
|
|
|
{ name: "Yesterday", epoch: SnowlDateUtils.yesterday },
|
|
|
|
{ name: SnowlDateUtils.twoDaysAgo.name, epoch: SnowlDateUtils.twoDaysAgo.epoch },
|
|
|
|
{ name: SnowlDateUtils.threeDaysAgo.name, epoch: SnowlDateUtils.threeDaysAgo.epoch },
|
|
|
|
{ name: SnowlDateUtils.fourDaysAgo.name, epoch: SnowlDateUtils.fourDaysAgo.epoch },
|
|
|
|
{ name: SnowlDateUtils.fiveDaysAgo.name, epoch: SnowlDateUtils.fiveDaysAgo.epoch },
|
|
|
|
{ name: SnowlDateUtils.sixDaysAgo.name, epoch: SnowlDateUtils.sixDaysAgo.epoch },
|
2008-10-20 12:31:30 +04:00
|
|
|
{ name: "Older", epoch: 0 }
|
|
|
|
],
|
|
|
|
last4weeks: [
|
|
|
|
{ name: "The Future", epoch: Number.MAX_VALUE },
|
2008-11-02 20:20:12 +03:00
|
|
|
{ name: "Week One", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 7) },
|
|
|
|
{ name: "Week Two", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 14) },
|
|
|
|
{ name: "Week Three", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 21) },
|
|
|
|
{ name: "Week Four", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 28) },
|
2008-10-20 12:31:30 +04:00
|
|
|
{ name: "Older", epoch: 0 }
|
|
|
|
],
|
|
|
|
all: [
|
|
|
|
{ name: "The Future", epoch: Number.MAX_VALUE },
|
2008-11-02 20:20:12 +03:00
|
|
|
{ name: "Today", epoch: SnowlDateUtils.today },
|
|
|
|
{ name: "Yesterday", epoch: SnowlDateUtils.yesterday },
|
2008-10-20 12:31:30 +04:00
|
|
|
{ name: "Older", epoch: 0 }
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
2008-05-18 11:52:28 +04:00
|
|
|
/**
|
|
|
|
* Sleep the specified number of milliseconds before continuing at the point
|
|
|
|
* in the caller where this function was called. For the most part, this is
|
|
|
|
* a generic sleep routine like the one provided by JavaScript Strands,
|
|
|
|
* but we store the Future this function creates in the _futureWriteMessages
|
|
|
|
* property so we can interrupt it when writeMessages gets called again
|
|
|
|
* while it is currently writing messages.
|
|
|
|
*/
|
|
|
|
_sleepWriteMessages: strand(function(millis) {
|
|
|
|
this._futureWriteMessages = new Future();
|
|
|
|
setTimeout(this._futureWriteMessages.fulfill, millis);
|
|
|
|
yield this._futureWriteMessages.result();
|
|
|
|
}),
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
writeContent: strand(function() {
|
2008-07-10 12:33:57 +04:00
|
|
|
let begin = new Date();
|
|
|
|
|
2008-05-18 11:52:28 +04:00
|
|
|
// Interrupt a strand currently writing messages so we don't both try
|
|
|
|
// to write messages at the same time.
|
|
|
|
// FIXME: figure out how to suppress the exception this throws to the error
|
|
|
|
// console, since this interruption is expected and normal behavior.
|
|
|
|
if (this._futureWriteMessages)
|
|
|
|
this._futureWriteMessages.interrupt();
|
|
|
|
|
2008-10-03 04:34:22 +04:00
|
|
|
let contentBox = this._document.getElementById("contentBox");
|
|
|
|
this._contentSandbox.messages = contentBox;
|
|
|
|
|
2008-10-20 12:31:30 +04:00
|
|
|
let period = this._periodMenu.selectedItem ? this._periodMenu.selectedItem.value : "all";
|
|
|
|
let groups = this._groups[period];
|
2008-10-03 04:34:22 +04:00
|
|
|
let groupIndex = 0;
|
2008-05-13 10:19:55 +04:00
|
|
|
|
|
|
|
for (let i = 0; i < this._collection.messages.length; ++i) {
|
2008-05-16 05:01:24 +04:00
|
|
|
let message = this._collection.messages[i];
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-10-17 03:38:10 +04:00
|
|
|
while (message.received < groups[groupIndex].epoch) {
|
2008-10-03 04:34:22 +04:00
|
|
|
++groupIndex;
|
|
|
|
|
|
|
|
let header = this._document.createElementNS(XUL_NS, "checkbox");
|
|
|
|
header.className = "groupHeader";
|
|
|
|
header.setAttribute("label", groups[groupIndex].name);
|
|
|
|
header.setAttribute("checked", "true");
|
|
|
|
let listener = function(event) {
|
|
|
|
event.target.nextSibling.style.display = event.target.checked ? "block" : "none";
|
|
|
|
};
|
|
|
|
header.addEventListener("command", listener, false);
|
|
|
|
contentBox.appendChild(header);
|
|
|
|
|
2008-10-10 01:54:00 +04:00
|
|
|
let container = this._document.createElementNS(HTML_NS, "div");
|
2008-10-03 04:34:22 +04:00
|
|
|
container.className = "groupBox";
|
|
|
|
contentBox.appendChild(container);
|
|
|
|
|
|
|
|
this._contentSandbox.messages = container;
|
|
|
|
}
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
let messageBox = this._document.createElementNS(HTML_NS, "div");
|
|
|
|
messageBox.className = "message";
|
|
|
|
messageBox.setAttribute("index", i);
|
|
|
|
|
2008-11-07 05:55:26 +03:00
|
|
|
// These are the elements that will be appended to the message box.
|
2008-11-08 01:54:10 +03:00
|
|
|
let messageIcon, bylineBox, title, excerpt, body;
|
2008-11-07 05:55:26 +03:00
|
|
|
|
|
|
|
messageIcon = document.createElementNS(HTML_NS, "img");
|
2008-11-08 01:54:10 +03:00
|
|
|
excerpt = document.createElementNS(HTML_NS, "span");
|
|
|
|
excerpt.className = "excerpt";
|
2008-11-07 05:55:26 +03:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// Byline
|
2008-11-07 05:55:26 +03:00
|
|
|
bylineBox = this._document.createElementNS(HTML_NS, "div");
|
2008-07-10 12:32:14 +04:00
|
|
|
bylineBox.className = "byline";
|
|
|
|
|
2008-11-07 04:04:44 +03:00
|
|
|
// Author and/or Source
|
|
|
|
if (message.author)
|
|
|
|
bylineBox.appendChild(this._document.createTextNode(message.author));
|
|
|
|
if (message.source) {
|
|
|
|
if (message.author)
|
|
|
|
bylineBox.appendChild(this._document.createTextNode(" - "));
|
|
|
|
bylineBox.appendChild(this._document.createTextNode(message.source.name));
|
|
|
|
}
|
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// Source
|
|
|
|
//let source = this._document.createElementNS(HTML_NS, "a");
|
|
|
|
//source.className = "source";
|
|
|
|
//let sourceIcon = document.createElementNS(HTML_NS, "img");
|
|
|
|
//let sourceFaviconURI = message.source.humanURI || URI.get("urn:use-default-icon");
|
|
|
|
//sourceIcon.src = this._faviconSvc.getFaviconImageForPage(sourceFaviconURI).spec;
|
|
|
|
//source.appendChild(sourceIcon);
|
|
|
|
//source.appendChild(this._document.createTextNode(message.source.name));
|
|
|
|
//if (message.source.humanURI)
|
|
|
|
// this._unsafeSetURIAttribute(source, "href", message.source.humanURI.spec);
|
|
|
|
//bylineBox.appendChild(source);
|
|
|
|
|
2008-11-07 04:04:44 +03:00
|
|
|
// Title
|
2008-11-07 05:55:26 +03:00
|
|
|
title = this._document.createElementNS(HTML_NS, "h2");
|
2008-11-07 04:04:44 +03:00
|
|
|
title.className = "title";
|
|
|
|
let titleLink = this._document.createElementNS(HTML_NS, "a");
|
|
|
|
titleLink.appendChild(this._document.createTextNode(message.subject || "untitled"));
|
|
|
|
if (message.link)
|
|
|
|
this._unsafeSetURIAttribute(titleLink, "href", message.link);
|
|
|
|
title.appendChild(titleLink);
|
2008-11-07 05:55:26 +03:00
|
|
|
|
|
|
|
// Body
|
|
|
|
let bodyText = message.content || message.summary;
|
|
|
|
if (bodyText) {
|
|
|
|
body = this._document.createElementNS(HTML_NS, "div");
|
|
|
|
body.className = "body";
|
|
|
|
if (bodyText.base)
|
|
|
|
body.setAttributeNS(XML_NS, "base", bodyText.base.spec);
|
|
|
|
|
|
|
|
let docFragment = bodyText.createDocumentFragment(body);
|
|
|
|
if (docFragment) {
|
|
|
|
body.appendChild(docFragment);
|
2008-11-08 01:54:10 +03:00
|
|
|
|
|
|
|
// Generate a excerpt of the message.
|
|
|
|
// FIXME: use an actual ellipsis.
|
|
|
|
// XXX Does an ellipsis need to be localizable?
|
|
|
|
excerpt.appendChild(document.createTextNode(
|
|
|
|
body.textContent.substring(0, 140) + ((body.textContent.length > 140) ? "..." : "")));
|
|
|
|
|
|
|
|
// Generate an icon representing the message.
|
2008-11-07 05:55:26 +03:00
|
|
|
let firstImage = body.getElementsByTagName("img")[0];
|
|
|
|
if (firstImage) {
|
|
|
|
messageIcon = firstImage.cloneNode(false);
|
|
|
|
messageIcon.removeAttribute("width");
|
|
|
|
messageIcon.removeAttribute("height");
|
|
|
|
messageIcon.className = "messageIcon";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-07-10 12:32:14 +04:00
|
|
|
|
2008-10-20 12:38:56 +04:00
|
|
|
//// Timestamp
|
2008-11-02 20:20:12 +03:00
|
|
|
//let lastUpdated = SnowlDateUtils._formatDate(message.timestamp);
|
2008-10-20 12:38:56 +04:00
|
|
|
//if (lastUpdated) {
|
|
|
|
// let timestamp = this._document.createElementNS(HTML_NS, "span");
|
|
|
|
// timestamp.className = "timestamp";
|
|
|
|
// timestamp.appendChild(document.createTextNode(lastUpdated));
|
|
|
|
// if (bylineBox.hasChildNodes())
|
|
|
|
// bylineBox.appendChild(this._document.createTextNode(" - "));
|
|
|
|
// bylineBox.appendChild(timestamp);
|
|
|
|
//}
|
2008-05-13 10:55:12 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// FIXME: implement support for enclosures.
|
|
|
|
|
2008-11-07 05:55:26 +03:00
|
|
|
messageBox.appendChild(messageIcon);
|
|
|
|
messageBox.appendChild(title);
|
2008-11-08 01:54:10 +03:00
|
|
|
messageBox.appendChild(excerpt);
|
2008-11-07 05:55:26 +03:00
|
|
|
messageBox.appendChild(body);
|
2008-11-08 03:14:41 +03:00
|
|
|
messageBox.appendChild(bylineBox);
|
2008-11-07 05:55:26 +03:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
this._contentSandbox.messageBox = messageBox;
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
let codeStr = "messages.appendChild(messageBox)";
|
2008-05-13 10:19:55 +04:00
|
|
|
Cu.evalInSandbox(codeStr, this._contentSandbox);
|
2008-05-18 11:19:25 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// Sleep after every tenth message so we don't hork the UI thread and users
|
2008-05-18 11:19:25 +04:00
|
|
|
// can immediately start reading messages while we finish writing them.
|
2008-07-10 12:32:14 +04:00
|
|
|
if (!(i % 10))
|
|
|
|
yield this._sleepWriteMessages(0);
|
2008-05-13 10:19:55 +04:00
|
|
|
}
|
|
|
|
|
2008-05-20 11:29:09 +04:00
|
|
|
this._contentSandbox.messages = null;
|
2008-07-10 12:32:14 +04:00
|
|
|
this._contentSandbox.messageBox = null;
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
this._log.info("time spent building view: " + (new Date() - begin) + "ms\n");
|
2008-08-26 05:20:52 +04:00
|
|
|
})
|
2008-05-14 03:03:33 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
};
|
2008-05-14 03:03:33 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
let splitterDragObserver = {
|
|
|
|
onMouseDown: function(event) {
|
|
|
|
document.documentElement.addEventListener("mousemove", this, false);
|
|
|
|
},
|
2008-05-14 03:03:33 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
onMouseUp: function(event) {
|
|
|
|
document.documentElement.removeEventListener("mousemove", this, false);
|
|
|
|
},
|
2008-05-14 03:03:33 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
// Note: because this function gets passed directly to setTimeout,
|
|
|
|
// |this| doesn't reference splitterDragObserver inside the function.
|
|
|
|
callback: function(width) {
|
2008-07-21 08:03:10 +04:00
|
|
|
SnowlMessageView.columnWidth = width;
|
2008-07-10 12:32:14 +04:00
|
|
|
},
|
2008-05-13 10:19:55 +04:00
|
|
|
|
2008-07-10 12:32:14 +04:00
|
|
|
handleEvent: function(event) {
|
|
|
|
if (this._timeout)
|
|
|
|
this._timeout = window.clearTimeout(this._timeout);
|
2008-07-28 06:05:57 +04:00
|
|
|
let width = event.clientX - document.getElementById("contentBox").offsetLeft;
|
|
|
|
document.getElementById("columnResizeSplitter").left = width;
|
|
|
|
this._timeout = window.setTimeout(this.callback, 500, width);
|
2008-05-14 03:03:33 +04:00
|
|
|
}
|
2008-07-10 12:32:14 +04:00
|
|
|
}
|