Bug 955162 - Port the log tree and concatenated daily logs from TB, r=florian.
This commit is contained in:
Родитель
6c9b014b69
Коммит
0f27cd2b47
|
@ -15,6 +15,10 @@ XPCOMUtils.defineLazyGetter(this, "logDir", function() {
|
|||
return file;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "bundle", function()
|
||||
Services.strings.createBundle("chrome://chat/locale/logger.properties")
|
||||
);
|
||||
|
||||
const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
|
||||
"nsIFileInputStream",
|
||||
"init");
|
||||
|
@ -333,11 +337,24 @@ function LogConversation(aLineInputStreams)
|
|||
let firstFile = true;
|
||||
|
||||
for each (let inputStream in aLineInputStreams) {
|
||||
let stream = inputStream.stream;
|
||||
let line = {value: ""};
|
||||
let more = inputStream.readLine(line);
|
||||
|
||||
if (!line.value)
|
||||
throw "bad log file";
|
||||
let more = stream.readLine(line);
|
||||
let sessionMsg = {
|
||||
who: "sessionstart",
|
||||
date: getDateFromFilename(inputStream.filename)[0],
|
||||
text: "",
|
||||
flags: ["noLog", "notification"]
|
||||
}
|
||||
if (!line.value) {
|
||||
// Bad log file.
|
||||
sessionMsg.text = bundle.formatStringFromName("badLogfile",
|
||||
[inputStream.filename], 1);
|
||||
sessionMsg.flags.push("error", "system");
|
||||
this._messages.push(new LogMessage(sessionMsg, this));
|
||||
continue;
|
||||
}
|
||||
this._messages.push(new LogMessage(sessionMsg, this));
|
||||
|
||||
if (firstFile) {
|
||||
let data = JSON.parse(line.value);
|
||||
|
@ -351,7 +368,7 @@ function LogConversation(aLineInputStreams)
|
|||
}
|
||||
|
||||
while (more) {
|
||||
more = inputStream.readLine(line);
|
||||
more = stream.readLine(line);
|
||||
if (!line.value)
|
||||
break;
|
||||
try {
|
||||
|
@ -363,6 +380,9 @@ function LogConversation(aLineInputStreams)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstFile)
|
||||
throw "All selected log files are invalid";
|
||||
}
|
||||
LogConversation.prototype = {
|
||||
__proto__: ClassInfo("imILogConversation", "Log conversation object"),
|
||||
|
@ -409,7 +429,10 @@ Log.prototype = {
|
|||
let lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
|
||||
lis.QueryInterface(Ci.nsIUnicharLineInputStream);
|
||||
try {
|
||||
return new LogConversation(lis);
|
||||
return new LogConversation({
|
||||
stream: lis,
|
||||
filename: this.file.leafName
|
||||
});
|
||||
} catch (e) {
|
||||
// If the file contains some junk (invalid JSON), the
|
||||
// LogConversation code will still read the messages it can parse.
|
||||
|
@ -478,21 +501,28 @@ function DailyLogEnumerator(aEntries) {
|
|||
if (!(file instanceof Ci.nsIFile))
|
||||
continue;
|
||||
|
||||
let [logDate] = getDateFromFilename(file.leafName);
|
||||
let [logDate, logFormat] = getDateFromFilename(file.leafName);
|
||||
if (!logDate) {
|
||||
// We'll skip this one, since it's got a busted filename.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We want to cluster all of the logs that occur on the same day
|
||||
// into the same Arrays. We clone the date for the log, reset it to
|
||||
// the 0th hour/minute/second, and use that to construct an ID for the
|
||||
// Array we'll put the log in.
|
||||
let dateForID = new Date(logDate);
|
||||
dateForID.setHours(0);
|
||||
dateForID.setMinutes(0);
|
||||
dateForID.setSeconds(0);
|
||||
let dayID = dateForID.toISOString();
|
||||
let dayID;
|
||||
if (logFormat == "json") {
|
||||
// We want to cluster all of the logs that occur on the same day
|
||||
// into the same Arrays. We clone the date for the log, reset it to
|
||||
// the 0th hour/minute/second, and use that to construct an ID for the
|
||||
// Array we'll put the log in.
|
||||
dateForID.setHours(0);
|
||||
dateForID.setMinutes(0);
|
||||
dateForID.setSeconds(0);
|
||||
dayID = dateForID.toISOString();
|
||||
}
|
||||
else {
|
||||
// Add legacy text logs as individual entries.
|
||||
dayID = dateForID.toISOString() + "txt";
|
||||
}
|
||||
|
||||
if (!(dayID in this._entries))
|
||||
this._entries[dayID] = [];
|
||||
|
@ -512,7 +542,13 @@ DailyLogEnumerator.prototype = {
|
|||
_days: [],
|
||||
_index: 0,
|
||||
hasMoreElements: function() this._index < this._days.length,
|
||||
getNext: function() new LogCluster(this._entries[this._days[this._index++]]),
|
||||
getNext: function() {
|
||||
let dayID = this._days[this._index++];
|
||||
if (dayID.endsWith("txt"))
|
||||
return new Log(this._entries[dayID][0].file);
|
||||
else
|
||||
return new LogCluster(this._entries[dayID]);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator])
|
||||
};
|
||||
|
||||
|
@ -555,7 +591,10 @@ LogCluster.prototype = {
|
|||
// Pass in 0x0 so that we throw exceptions on unknown bytes.
|
||||
let lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
|
||||
lis.QueryInterface(Ci.nsIUnicharLineInputStream);
|
||||
streams.push(lis);
|
||||
streams.push({
|
||||
stream: lis,
|
||||
filename: entry.file.leafName
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -322,6 +322,7 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_sessions">[]</field>
|
||||
<method name="displayMessage">
|
||||
<parameter name="aMsg"/>
|
||||
<parameter name="aContext"/>
|
||||
|
@ -330,6 +331,23 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
let doc = this.contentDocument;
|
||||
|
||||
if (aMsg.noLog && aMsg.notification &&
|
||||
aMsg.who == "sessionstart") {
|
||||
// New session log.
|
||||
if (this._lastMessage) {
|
||||
let ruler = doc.createElement("hr");
|
||||
ruler.className = "sessionstart-ruler";
|
||||
doc.getElementById("Chat").appendChild(ruler);
|
||||
this._sessions.push(ruler);
|
||||
// Close any open bubble.
|
||||
this._lastMessage = null;
|
||||
}
|
||||
// Suppress this message unless it was an error message.
|
||||
if (!aMsg.error)
|
||||
return;
|
||||
}
|
||||
|
||||
let cs = Components.classes["@mozilla.org/txttohtmlconv;1"]
|
||||
.getService(Ci.mozITXTToHTMLConv);
|
||||
/*
|
||||
|
@ -511,6 +529,7 @@
|
|||
let ruler = this.contentDocument.getElementById("unread-ruler");
|
||||
if (ruler)
|
||||
sectionElements.push(ruler);
|
||||
sectionElements = sectionElements.concat(this._sessions);
|
||||
|
||||
// Return ordered array of sections with entries
|
||||
// [Y, scrollY such that Y is centered]
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE (badLogfile):
|
||||
# %S is the filename of the log file.
|
||||
badLogfile=Empty or corrupt log file: %S
|
|
@ -11,6 +11,7 @@
|
|||
locale/@AB_CD@/chat/conversations.properties (%conversations.properties)
|
||||
locale/@AB_CD@/chat/facebook.properties (%facebook.properties)
|
||||
locale/@AB_CD@/chat/irc.properties (%irc.properties)
|
||||
locale/@AB_CD@/chat/logger.properties (%logger.properties)
|
||||
locale/@AB_CD@/chat/status.properties (%status.properties)
|
||||
locale/@AB_CD@/chat/twitter.properties (%twitter.properties)
|
||||
locale/@AB_CD@/chat/xmpp.properties (%xmpp.properties)
|
||||
|
|
|
@ -202,23 +202,19 @@ buddyListContextMenu.prototype = {
|
|||
},
|
||||
_getLogs: function blcm_getLogs() {
|
||||
if (this.onContact)
|
||||
return Services.logs.getLogsForContact(this.target.contact);
|
||||
return Services.logs.getLogsForContact(this.target.contact, true);
|
||||
if (this.onBuddy)
|
||||
return Services.logs.getLogsForBuddy(this.target.buddy);
|
||||
return Services.logs.getLogsForBuddy(this.target.buddy, true);
|
||||
if (this.onConv)
|
||||
return Services.logs.getLogsForConversation(this.target.conv);
|
||||
return Services.logs.getLogsForConversation(this.target.conv, true);
|
||||
return null;
|
||||
},
|
||||
showLogs: function blcm_showLogs() {
|
||||
let enumerator = this._getLogs();
|
||||
if (!enumerator)
|
||||
return;
|
||||
|
||||
var logs = [];
|
||||
for (let log in getIter(enumerator))
|
||||
logs.push(log);
|
||||
window.openDialog("chrome://instantbird/content/viewlog.xul",
|
||||
"Logs", "chrome,resizable", {logs: logs},
|
||||
"Logs", "chrome,resizable", {logs: enumerator},
|
||||
this.target.displayName);
|
||||
},
|
||||
hideTag: function blcm_hideTag() {
|
||||
|
|
|
@ -1483,9 +1483,9 @@
|
|||
<method name="showLogs">
|
||||
<body>
|
||||
<![CDATA[
|
||||
var logs = [];
|
||||
for (let log in getIter(Services.logs.getLogsForConversation(this.conv)))
|
||||
logs.push(log);
|
||||
let logs = Services.logs.getLogsForConversation(this.conv, true);
|
||||
if (!logs.hasMoreElements())
|
||||
return;
|
||||
window.openDialog("chrome://instantbird/content/viewlog.xul",
|
||||
"Logs", "chrome,resizable", {logs: logs},
|
||||
this.conv.title);
|
||||
|
|
|
@ -44,6 +44,7 @@ instantbird.jar:
|
|||
content/instantbird/convZoom.js
|
||||
content/instantbird/joinchat.js
|
||||
content/instantbird/joinchat.xul
|
||||
content/instantbird/jsTreeView.js
|
||||
* content/instantbird/menus.xul
|
||||
* content/instantbird/menus.js
|
||||
content/instantbird/nsContextMenu.js
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* This file contains a prototype object designed to make the implementation of
|
||||
* nsITreeViews in javascript simpler. This object requires that consumers
|
||||
* override the _rebuild function. This function must set the _rowMap object to
|
||||
* an array of objects fitting the following interface:
|
||||
*
|
||||
* readonly attribute string id - a unique identifier for the row/object
|
||||
* readonly attribute integer level - the hierarchy level of the row
|
||||
* attribute boolean open - whether or not this item's children are exposed
|
||||
* string getText(aColName) - return the text to display for this row in the
|
||||
* specified column
|
||||
* void getProperties(aProps) - set the css-selectors on aProps when this is
|
||||
* called
|
||||
* attribute array children - return an array of child-objects also meeting this
|
||||
* interface
|
||||
*/
|
||||
|
||||
function PROTO_TREE_VIEW() {
|
||||
this._tree = null;
|
||||
this._rowMap = [];
|
||||
this._persistOpenMap = [];
|
||||
}
|
||||
|
||||
PROTO_TREE_VIEW.prototype = {
|
||||
get rowCount() {
|
||||
return this._rowMap.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* CSS files will cue off of these. Note that we reach into the rowMap's
|
||||
* items so that custom data-displays can define their own properties
|
||||
*/
|
||||
getCellProperties: function jstv_getCellProperties(aRow, aCol, aProps) {
|
||||
this._rowMap[aRow].getProperties(aProps, aCol);
|
||||
},
|
||||
|
||||
/**
|
||||
* The actual text to display in the tree
|
||||
*/
|
||||
getCellText: function jstv_getCellText(aRow, aCol) {
|
||||
return this._rowMap[aRow].getText(aCol.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* The jstv items take care of assigning this when building children lists
|
||||
*/
|
||||
getLevel: function jstv_getLevel(aIndex) {
|
||||
return this._rowMap[aIndex].level;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is easy since the jstv items assigned the _parent property when making
|
||||
* the child lists
|
||||
*/
|
||||
getParentIndex: function jstv_getParentIndex(aIndex) {
|
||||
return this._rowMap.indexOf(this._rowMap[aIndex]._parent);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is duplicative for our normal jstv views, but custom data-displays may
|
||||
* want to do something special here
|
||||
*/
|
||||
getRowProperties: function jstv_getRowProperties(aIndex, aProps) {
|
||||
this._rowMap[aIndex].getProperties(aProps);
|
||||
},
|
||||
|
||||
/**
|
||||
* If an item in our list has the same level and parent as us, it's a sibling
|
||||
*/
|
||||
hasNextSibling: function jstv_hasNextSibling(aIndex, aNextIndex) {
|
||||
let targetLevel = this._rowMap[aIndex].level;
|
||||
for (let i = aNextIndex + 1; i < this._rowMap.length; i++) {
|
||||
if (this._rowMap[i].level == targetLevel)
|
||||
return true;
|
||||
if (this._rowMap[i].level < targetLevel)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* If we have a child-list with at least one element, we are a container.
|
||||
*/
|
||||
isContainer: function jstv_isContainer(aIndex) {
|
||||
return this._rowMap[aIndex].children.length > 0;
|
||||
},
|
||||
|
||||
isContainerEmpty: function jstv_isContainerEmpty(aIndex) {
|
||||
// If the container has no children, the container is empty.
|
||||
return !this._rowMap[aIndex].children.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Just look at the jstv item here
|
||||
*/
|
||||
isContainerOpen: function jstv_isContainerOpen(aIndex) {
|
||||
return this._rowMap[aIndex].open;
|
||||
},
|
||||
|
||||
isEditable: function jstv_isEditable(aRow, aCol) {
|
||||
// We don't support editing rows in the tree yet.
|
||||
return false;
|
||||
},
|
||||
|
||||
isSeparator: function jstv_isSeparator(aIndex) {
|
||||
// There are no separators in our trees
|
||||
return false;
|
||||
},
|
||||
|
||||
isSorted: function jstv_isSorted() {
|
||||
// We do our own customized sorting
|
||||
return false;
|
||||
},
|
||||
|
||||
setTree: function jstv_setTree(aTree) {
|
||||
this._tree = aTree;
|
||||
},
|
||||
|
||||
recursivelyAddToMap: function jstv_recursivelyAddToMap(aChild, aNewIndex) {
|
||||
// When we add sub-children, we're going to need to increase our index
|
||||
// for the next add item at our own level.
|
||||
let currentCount = this._rowMap.length;
|
||||
if (aChild.children.length && aChild.open) {
|
||||
for (let [i, child] in Iterator(this._rowMap[aNewIndex].children)) {
|
||||
let index = aNewIndex + i + 1;
|
||||
this._rowMap.splice(index, 0, child);
|
||||
aNewIndex += this.recursivelyAddToMap(child, index);
|
||||
}
|
||||
}
|
||||
return this._rowMap.length - currentCount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens or closes a container with children. The logic here is a bit hairy, so
|
||||
* be very careful about changing anything.
|
||||
*/
|
||||
toggleOpenState: function jstv_toggleOpenState(aIndex) {
|
||||
|
||||
// Ok, this is a bit tricky.
|
||||
this._rowMap[aIndex]._open = !this._rowMap[aIndex].open;
|
||||
|
||||
if (!this._rowMap[aIndex].open) {
|
||||
// We're closing the current container. Remove the children
|
||||
|
||||
// Note that we can't simply splice out children.length, because some of
|
||||
// them might have children too. Find out how many items we're actually
|
||||
// going to splice
|
||||
let level = this._rowMap[aIndex].level;
|
||||
let row = aIndex + 1;
|
||||
while (row < this._rowMap.length && this._rowMap[row].level > level) {
|
||||
row++;
|
||||
}
|
||||
let count = row - aIndex - 1;
|
||||
this._rowMap.splice(aIndex + 1, count);
|
||||
|
||||
// Remove us from the persist map
|
||||
let index = this._persistOpenMap.indexOf(this._rowMap[aIndex].id);
|
||||
if (index != -1)
|
||||
this._persistOpenMap.splice(index, 1);
|
||||
|
||||
// Notify the tree of changes
|
||||
if (this._tree) {
|
||||
this._tree.rowCountChanged(aIndex + 1, -count);
|
||||
}
|
||||
} else {
|
||||
// We're opening the container. Add the children to our map
|
||||
|
||||
// Note that these children may have been open when we were last closed,
|
||||
// and if they are, we also have to add those grandchildren to the map
|
||||
let oldCount = this._rowMap.length;
|
||||
this.recursivelyAddToMap(this._rowMap[aIndex], aIndex);
|
||||
|
||||
// Add this container to the persist map
|
||||
let id = this._rowMap[aIndex].id;
|
||||
if (this._persistOpenMap.indexOf(id) == -1)
|
||||
this._persistOpenMap.push(id);
|
||||
|
||||
// Notify the tree of changes
|
||||
if (this._tree)
|
||||
this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
|
||||
}
|
||||
|
||||
// Invalidate the toggled row, so that the open/closed marker changes
|
||||
if (this._tree)
|
||||
this._tree.invalidateRow(aIndex);
|
||||
},
|
||||
|
||||
// We don't implement any of these at the moment
|
||||
canDrop: function jstv_canDrop(aIndex, aOrientation) {},
|
||||
drop: function jstv_drop(aRow, aOrientation) {},
|
||||
performAction: function jstv_performAction(aAction) {},
|
||||
performActionOnCell: function jstv_performActionOnCell(aAction, aRow, aCol) {},
|
||||
performActionOnRow: function jstv_performActionOnRow(aAction, aRow) {},
|
||||
selectionChanged: function jstv_selectionChanged() {},
|
||||
setCellText: function jstv_setCellText(aRow, aCol, aValue) {},
|
||||
setCellValue: function jstv_setCellValue(aRow, aCol, aValue) {},
|
||||
getCellValue: function jstv_getCellValue(aRow, aCol) {},
|
||||
getColumnProperties: function jstv_getColumnProperties(aCol, aProps) {},
|
||||
getImageSrc: function jstv_getImageSrc(aRow, aCol) {},
|
||||
getProgressMode: function jstv_getProgressMode(aRow, aCol) {},
|
||||
cycleCell: function jstv_cycleCell(aRow, aCol) {},
|
||||
cycleHeader: function jstv_cycleHeader(aCol) {},
|
||||
|
||||
_tree: null,
|
||||
|
||||
/**
|
||||
* An array of jstv items, where each item corresponds to a row in the tree
|
||||
*/
|
||||
_rowMap: null,
|
||||
|
||||
/**
|
||||
* This is a javascript map of which containers we had open, so that we can
|
||||
* persist their state over-time. It is designed to be used as a JSON object.
|
||||
*/
|
||||
_persistOpenMap: null,
|
||||
|
||||
_restoreOpenStates: function jstv__restoreOpenStates() {
|
||||
// Note that as we iterate through here, .length may grow
|
||||
for (let i = 0; i < this._rowMap.length; i++) {
|
||||
if (this._persistOpenMap.indexOf(this._rowMap[i].id) != -1)
|
||||
this.toggleOpenState(i);
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: function QueryInterface(aIID) {
|
||||
if (aIID.equals(Components.interfaces.nsITreeView) ||
|
||||
aIID.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
|
@ -2,7 +2,9 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// viewZoomOverlay.js uses this
|
||||
function getBrowser() {
|
||||
|
@ -14,9 +16,7 @@ function getBrowser() {
|
|||
}
|
||||
|
||||
var logWindow = {
|
||||
load: function mo_load() {
|
||||
let logs = window.arguments[0].logs;
|
||||
logs.sort(function(log1, log2) log2.time - log1.time);
|
||||
load: function lw_load() {
|
||||
let displayname = window.arguments[1];
|
||||
if (displayname) {
|
||||
let bundle = document.getElementById("bundle_instantbird");
|
||||
|
@ -25,48 +25,48 @@ var logWindow = {
|
|||
document.documentElement.getAttribute("titlemodifier");
|
||||
}
|
||||
|
||||
// Used to show the dates in the log list in the locale of the application.
|
||||
let appLocaleCode = Services.prefs.getCharPref("general.useragent.locale");
|
||||
let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"]
|
||||
.getService(Ci.nsIScriptableDateFormat);
|
||||
|
||||
let listbox = document.getElementById("logList");
|
||||
logs.forEach(function (aLog) {
|
||||
if (aLog.format == "invalid")
|
||||
return;
|
||||
let elt = document.createElement("listitem");
|
||||
let logDate = new Date(aLog.time * 1000);
|
||||
let localizedDateTimeString =
|
||||
dts.FormatDateTime(appLocaleCode, dts.dateFormatLong,
|
||||
dts.timeFormatNoSeconds, logDate.getFullYear(),
|
||||
logDate.getMonth() + 1, logDate.getDate(),
|
||||
logDate.getHours(), logDate.getMinutes(), 0);
|
||||
elt.setAttribute("label", localizedDateTimeString);
|
||||
elt.log = aLog;
|
||||
listbox.appendChild(elt);
|
||||
});
|
||||
listbox.focus();
|
||||
// Hack: Only select the first log after a brief delay, or the first
|
||||
// listitem never appears selected on Windows and Linux.
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
listbox.selectedIndex = 0;
|
||||
// Prevent closing the findbar, go back to list instead.
|
||||
let findbar = document.getElementById("findbar");
|
||||
findbar.close = function() { listbox.focus(); };
|
||||
// Requires findbar.browser to be set, which is only the case after
|
||||
// a log has been selected.
|
||||
findbar.open();
|
||||
}, Ci.nsIEventTarget.DISPATCH_NORMAL);
|
||||
// Prevent closing the findbar, go back to logTree instead.
|
||||
let findbar = document.getElementById("findbar");
|
||||
let logTree = document.getElementById("logTree");
|
||||
findbar.close = function() logTree.focus();
|
||||
// Ensure the findbar has something to look at.
|
||||
let browser = document.getElementById("text-browser");
|
||||
findbar.browser = browser;
|
||||
findbar.open(); // Requires findbar.browser to be set
|
||||
|
||||
document.getElementById("text-browser")
|
||||
.addEventListener("DOMContentLoaded", logWindow.contentLoaded, true);
|
||||
document.getElementById("conv-browser").progressBar =
|
||||
document.getElementById("browserProgress");
|
||||
|
||||
logTree.focus();
|
||||
let treeView = logWindow._treeView =
|
||||
new chatLogTreeView(logTree, window.arguments[0].logs);
|
||||
// Select the first line.
|
||||
let selectIndex = 0;
|
||||
if (treeView.isContainer(selectIndex)) {
|
||||
// If the first line is a group, open it and select the
|
||||
// next line instead.
|
||||
treeView.toggleOpenState(selectIndex++);
|
||||
}
|
||||
logTree.view.selection.select(selectIndex);
|
||||
},
|
||||
|
||||
pendingLoad: false,
|
||||
onselect: function lw_onselect() {
|
||||
let log = document.getElementById("logList").selectedItem.log;
|
||||
let selection = this._treeView.selection;
|
||||
let currentIndex = selection.currentIndex;
|
||||
// The current (focused) row may not be actually selected...
|
||||
if (!selection.isSelected(currentIndex))
|
||||
return;
|
||||
|
||||
let log = this._treeView._rowMap[currentIndex].log;
|
||||
if (!log)
|
||||
return;
|
||||
if (this._displayedLog && this._displayedLog == log.path)
|
||||
return;
|
||||
this._displayedLog = log.path;
|
||||
|
||||
let deck = document.getElementById("browserDeck");
|
||||
let findbar = document.getElementById("findbar");
|
||||
if (log.format == "json") {
|
||||
|
@ -74,7 +74,7 @@ var logWindow = {
|
|||
if (!conv) {
|
||||
// Empty or completely broken json log file.
|
||||
deck.selectedIndex = 2;
|
||||
// Ensure the findbar has something to look at.
|
||||
// Ensure the findbar looks at an empty file.
|
||||
let browser = document.getElementById("text-browser");
|
||||
findbar.browser = browser;
|
||||
browser.loadURI("about:blank");
|
||||
|
@ -91,19 +91,19 @@ var logWindow = {
|
|||
browser.init(conv);
|
||||
this.pendingLoad = true;
|
||||
Services.obs.addObserver(this, "conversation-loaded", false);
|
||||
return;
|
||||
}
|
||||
|
||||
deck.selectedIndex = 0;
|
||||
let browser = document.getElementById("text-browser");
|
||||
findbar.browser = browser;
|
||||
FullZoom.applyPrefValue();
|
||||
browser.docShell.forcedCharset =
|
||||
browser.mAtomService.getAtom("UTF-8");
|
||||
let file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
file.initWithPath(log.path);
|
||||
browser.loadURI(Services.io.newFileURI(file).spec);
|
||||
else {
|
||||
// Legacy text log.
|
||||
deck.selectedIndex = 0;
|
||||
let browser = document.getElementById("text-browser");
|
||||
findbar.browser = browser;
|
||||
FullZoom.applyPrefValue();
|
||||
browser.docShell.forcedCharset =
|
||||
browser.mAtomService.getAtom("UTF-8");
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(log.path);
|
||||
browser.loadURI(Services.io.newFileURI(file).spec);
|
||||
}
|
||||
},
|
||||
|
||||
_colorCache: {},
|
||||
|
@ -159,9 +159,178 @@ var logWindow = {
|
|||
return;
|
||||
}
|
||||
if (!("smileTextNode" in window))
|
||||
Components.utils.import("resource:///modules/imSmileys.jsm");
|
||||
Cu.import("resource:///modules/imSmileys.jsm");
|
||||
smileTextNode(elt);
|
||||
}
|
||||
};
|
||||
|
||||
function chatLogTreeGroupItem(aTitle, aLogItems) {
|
||||
this._title = aTitle;
|
||||
this._children = aLogItems;
|
||||
for each (let child in this._children)
|
||||
child._parent = this;
|
||||
this._open = false;
|
||||
}
|
||||
chatLogTreeGroupItem.prototype = {
|
||||
getText: function() this._title,
|
||||
get id() this._title,
|
||||
get open() this._open,
|
||||
get level() 0,
|
||||
get _parent() null,
|
||||
get children() this._children,
|
||||
getProperties: function(aProps) {}
|
||||
};
|
||||
|
||||
function chatLogTreeLogItem(aLog, aText, aLevel) {
|
||||
this.log = aLog;
|
||||
this._text = aText;
|
||||
this._level = aLevel;
|
||||
}
|
||||
chatLogTreeLogItem.prototype = {
|
||||
getText: function() this._text,
|
||||
get id() this.log.title,
|
||||
get open() false,
|
||||
get level() this._level,
|
||||
get children() [],
|
||||
getProperties: function(aProps) {}
|
||||
};
|
||||
|
||||
function chatLogTreeView(aTree, aLogs) {
|
||||
this._tree = aTree;
|
||||
this._logs = aLogs;
|
||||
this._tree.view = this;
|
||||
this._rebuild();
|
||||
}
|
||||
chatLogTreeView.prototype = {
|
||||
__proto__: new PROTO_TREE_VIEW(),
|
||||
|
||||
_rebuild: function cLTV__rebuild() {
|
||||
// Some date helpers...
|
||||
const kDayInMsecs = 24 * 60 * 60 * 1000;
|
||||
const kWeekInMsecs = 7 * kDayInMsecs;
|
||||
const kTwoWeeksInMsecs = 2 * kWeekInMsecs;
|
||||
|
||||
// Drop the old rowMap.
|
||||
if (this._tree)
|
||||
this._tree.rowCountChanged(0, -this._rowMap.length);
|
||||
this._rowMap = [];
|
||||
|
||||
// Used to show the dates in the log list in the locale of the application.
|
||||
let chatBundle = document.getElementById("bundle_instantbird");
|
||||
let dateFormatBundle = document.getElementById("bundle_dateformat");
|
||||
let placesBundle = document.getElementById("bundle_places");
|
||||
let appLocaleCode = Services.prefs.getCharPref("general.useragent.locale");
|
||||
let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"].getService(Ci.nsIScriptableDateFormat);
|
||||
let formatDate = function(aDate) {
|
||||
return dts.FormatDate(appLocaleCode, dts.dateFormatShort, aDate.getFullYear(),
|
||||
aDate.getMonth() + 1, aDate.getDate());
|
||||
};
|
||||
let formatDateTime = function(aDate) {
|
||||
return dts.FormatDateTime(appLocaleCode, dts.dateFormatShort,
|
||||
dts.timeFormatNoSeconds, aDate.getFullYear(),
|
||||
aDate.getMonth() + 1, aDate.getDate(),
|
||||
aDate.getHours(), aDate.getMinutes(), 0);
|
||||
};
|
||||
let formatMonth = function(aDate) {
|
||||
let month =
|
||||
dateFormatBundle.getString("month." + (aDate.getMonth() + 1) + ".name");
|
||||
return month;
|
||||
};
|
||||
let formatMonthYear = function(aDate) {
|
||||
let month = formatMonth(aDate);
|
||||
return placesBundle.getFormattedString("finduri-MonthYear",
|
||||
[month, aDate.getFullYear()]);
|
||||
};
|
||||
|
||||
let nowDate = new Date();
|
||||
let todayDate = new Date(nowDate.getFullYear(), nowDate.getMonth(),
|
||||
nowDate.getDate());
|
||||
|
||||
// The keys used in the 'firstgroups' object should match string ids in
|
||||
// messenger.properties. The order is the reverse of that in which
|
||||
// they will appear in the logTree.
|
||||
let firstgroups = {
|
||||
twoWeeksAgo: [],
|
||||
lastWeek: []
|
||||
};
|
||||
|
||||
// today and yesterday are treated differently, because they represent
|
||||
// individual logs, and are not "groups".
|
||||
let today = null, yesterday = null;
|
||||
|
||||
// Build a chatLogTreeLogItem for each log, and put it in the right group.
|
||||
let groups = {};
|
||||
for each (let log in getIter(this._logs)) {
|
||||
let logDate = new Date(log.time * 1000);
|
||||
let timeFromToday = todayDate - logDate;
|
||||
let title = (log.format == "json" ? formatDate : formatDateTime)(logDate);
|
||||
let group;
|
||||
if (timeFromToday <= 0) {
|
||||
today = new chatLogTreeLogItem(log, chatBundle.getString("log.today"), 0);
|
||||
continue;
|
||||
}
|
||||
else if (timeFromToday <= kDayInMsecs) {
|
||||
yesterday = new chatLogTreeLogItem(log, chatBundle.getString("log.yesterday"), 0);
|
||||
continue;
|
||||
}
|
||||
else if (timeFromToday <= kWeekInMsecs)
|
||||
group = firstgroups.lastWeek;
|
||||
else if (timeFromToday <= kTwoWeeksInMsecs)
|
||||
group = firstgroups.twoWeeksAgo;
|
||||
else {
|
||||
logDate.setHours(0);
|
||||
logDate.setMinutes(0);
|
||||
logDate.setSeconds(0);
|
||||
logDate.setDate(1);
|
||||
let groupID = logDate.toISOString();
|
||||
if (!(groupID in groups)) {
|
||||
let groupname;
|
||||
if (logDate.getFullYear() == nowDate.getFullYear()) {
|
||||
if (logDate.getMonth() == nowDate.getMonth())
|
||||
groupname = placesBundle.getString("finduri-AgeInMonths-is-0");
|
||||
else
|
||||
groupname = formatMonth(logDate);
|
||||
}
|
||||
else
|
||||
groupname = formatMonthYear(logDate);
|
||||
groups[groupID] = {
|
||||
entries: [],
|
||||
name: groupname
|
||||
};
|
||||
}
|
||||
group = groups[groupID].entries;
|
||||
}
|
||||
group.push(new chatLogTreeLogItem(log, title, 1));
|
||||
}
|
||||
|
||||
let groupIDs = Object.keys(groups).sort().reverse();
|
||||
|
||||
// Add firstgroups to groups and groupIDs.
|
||||
for each (let [groupID, group] in Iterator(firstgroups)) {
|
||||
if (!group.length)
|
||||
continue;
|
||||
groupIDs.unshift(groupID);
|
||||
groups[groupID] = {
|
||||
entries: firstgroups[groupID],
|
||||
name: chatBundle.getString("log." + groupID)
|
||||
};
|
||||
}
|
||||
|
||||
// Build tree.
|
||||
if (today)
|
||||
this._rowMap.push(today);
|
||||
if (yesterday)
|
||||
this._rowMap.push(yesterday);
|
||||
groupIDs.forEach(function (aGroupID) {
|
||||
let group = groups[aGroupID];
|
||||
group.entries.sort(function(l1, l2) l2.log.time - l1.log.time);
|
||||
this._rowMap.push(new chatLogTreeGroupItem(group.name, group.entries));
|
||||
}, this);
|
||||
|
||||
// Finally, notify the tree.
|
||||
if (this._tree)
|
||||
this._tree.rowCountChanged(0, this._rowMap.length);
|
||||
}
|
||||
};
|
||||
|
||||
this.addEventListener("load", logWindow.load);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
persist= "width height screenX screenY"
|
||||
xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript" src="chrome://instantbird/content/utilities.js"/>
|
||||
<script type="application/javascript" src="chrome://instantbird/content/jsTreeView.js"/>
|
||||
<script type="application/javascript" src="chrome://instantbird/content/viewlog.js"/>
|
||||
#ifdef XP_MACOSX
|
||||
<script type="application/javascript" src="chrome://instantbird/content/macgestures.js"/>
|
||||
|
@ -80,6 +81,8 @@
|
|||
|
||||
<stringbundleset id="stringbundleset">
|
||||
<stringbundle id="bundle_instantbird" src="chrome://instantbird/locale/instantbird.properties"/>
|
||||
<stringbundle id="bundle_dateformat" src="chrome://global/locale/dateFormat.properties"/>
|
||||
<stringbundle id="bundle_places" src="chrome://places/locale/places.properties"/>
|
||||
</stringbundleset>
|
||||
|
||||
<popupset id="mainPopupSet">
|
||||
|
@ -96,7 +99,15 @@
|
|||
</popupset>
|
||||
|
||||
<hbox flex="1">
|
||||
<listbox id="logList" onselect="logWindow.onselect();" width="160"/>
|
||||
<tree id="logTree" width="160" hidecolumnpicker="true"
|
||||
seltype="single" context="logTreeContext"
|
||||
onselect="logWindow.onselect();">
|
||||
<treecols>
|
||||
<treecol id="logCol" flex="1" primary="true" hideheader="true"
|
||||
crop="center" ignorecolumnpicker="true"/>
|
||||
</treecols>
|
||||
<treechildren/>
|
||||
</tree>
|
||||
<splitter/>
|
||||
<vbox flex="1">
|
||||
<deck flex="1" id="browserDeck" selectedIndex="0">
|
||||
|
|
|
@ -110,6 +110,11 @@ targetChangeable=\nClick to change
|
|||
# %S will be replaced by the name of a buddy of the name of a chat room.
|
||||
logs=%S logs
|
||||
|
||||
log.today=Today
|
||||
log.yesterday=Yesterday
|
||||
log.lastWeek=Last Week
|
||||
log.twoWeeksAgo=Two Weeks Ago
|
||||
|
||||
#LOCALIZATION NOTE
|
||||
# This is shown when an unknown command (/foo) is attempted. %S is the command.
|
||||
unknownCommand=%S is not a supported command. Type /help to see the list of commands.
|
||||
|
|
|
@ -256,6 +256,7 @@ function checkNewText(aEvent)
|
|||
|
||||
var prev = target.previousSibling;
|
||||
var shouldSetUnreadRuler = prev && prev.id && prev.id == "unread-ruler";
|
||||
var shouldSetSessionRuler = prev && prev.className && prev.className == "sessionstart-ruler";
|
||||
// We need an extra pixel of margin at the top to make the margins appear
|
||||
// to be of equal size, since the preceding bubble will have a shadow.
|
||||
var rulerMarginBottom = kRulerMarginTop - 1;
|
||||
|
@ -264,23 +265,31 @@ function checkNewText(aEvent)
|
|||
if (lastInsertTime && insertTime >= lastInsertTime) {
|
||||
var interval = insertTime - lastInsertTime;
|
||||
var margin = computeSpace(interval);
|
||||
if (interval >= timebeforetextdisplay) {
|
||||
let isTimetext = interval >= timebeforetextdisplay;
|
||||
if (isTimetext) {
|
||||
var p = document.createElement("p");
|
||||
p.className = "interval";
|
||||
if (shouldSetSessionRuler) {
|
||||
// Hide the hr and style the time text accordingly instead.
|
||||
p.classList.add("sessionstart-ruler");
|
||||
margin += 6;
|
||||
prev.style.display = "none";
|
||||
prev = p;
|
||||
}
|
||||
p.style.lineHeight = (margin + shadow) + "px";
|
||||
p.style.marginTop = -shadow + "px";
|
||||
p.setAttribute("class", "interval");
|
||||
p.textContent = prettyPrintTime(interval);
|
||||
target.parentNode.insertBefore(p, target);
|
||||
margin = 0;
|
||||
}
|
||||
target.style.marginTop = margin + "px";
|
||||
if (shouldSetUnreadRuler) {
|
||||
if (shouldSetUnreadRuler || shouldSetSessionRuler) {
|
||||
if (margin > rulerMarginBottom) {
|
||||
// Set the unread ruler margin so it is constant after margin collapse.
|
||||
// See https://developer.mozilla.org/en/CSS/margin_collapsing
|
||||
rulerMarginBottom -= margin;
|
||||
}
|
||||
if (interval >= timebeforetextdisplay) {
|
||||
if (isTimetext && shouldSetUnreadRuler) {
|
||||
// If a text display follows, use the minimum bubble margin after the
|
||||
// ruler, taking account of the absence of a shadow on the ruler.
|
||||
rulerMarginBottom = shadow - 1;
|
||||
|
@ -288,7 +297,7 @@ function checkNewText(aEvent)
|
|||
}
|
||||
}
|
||||
updateLastInsertTime();
|
||||
if (shouldSetUnreadRuler) {
|
||||
if (shouldSetUnreadRuler || shouldSetSessionRuler) {
|
||||
prev.style.marginBottom = rulerMarginBottom + "px";
|
||||
prev.style.marginTop = kRulerMarginTop + "px";
|
||||
}
|
||||
|
|
|
@ -132,6 +132,14 @@ p *:-moz-any-link img {
|
|||
border-bottom: 1px solid rgb(255,255,255) !important;
|
||||
}
|
||||
|
||||
.sessionstart-ruler {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
border: none;
|
||||
min-height: 13px;
|
||||
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(0,0,0,0.18));
|
||||
}
|
||||
|
||||
|
||||
/* used by javascript */
|
||||
.button {
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
%ifdef XP_MACOSX
|
||||
#logList {
|
||||
#logTree {
|
||||
-moz-appearance: none;
|
||||
background-color: #d2d8e2;
|
||||
border: 0px;
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
#logList:-moz-window-inactive {
|
||||
#logTree:-moz-window-inactive {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ splitter:-moz-window-inactive {
|
|||
}
|
||||
%else
|
||||
%ifdef XP_WIN
|
||||
#logList {
|
||||
#logTree {
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
margin: 0 0;
|
||||
|
@ -36,7 +36,7 @@ splitter:-moz-window-inactive {
|
|||
-moz-appearance: listbox;
|
||||
}
|
||||
|
||||
#logList {
|
||||
#logTree {
|
||||
margin: 0 0;
|
||||
}
|
||||
%endif
|
||||
|
|
Загрузка…
Ссылка в новой задаче