Bug 955162 - Port the log tree and concatenated daily logs from TB, r=florian.

This commit is contained in:
aleth 2013-04-06 23:52:58 +02:00
Родитель 6c9b014b69
Коммит 0f27cd2b47
14 изменённых файлов: 590 добавлений и 89 удалений

Просмотреть файл

@ -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

236
im/content/jsTreeView.js Normal file
Просмотреть файл

@ -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