true
false
= this.orderedGroups.length) {
this.mode = "all";
}
else {
// top mode must be used
this.modes.push("top");
this.mode = "top";
this.topGroups = FacetUtils.makeTopGroups(this.attrDef,
this.orderedGroups,
this.maxDisplayRows);
// setup the more button string
let groupCount = this.orderedGroups.length;
this.moreButton.textContent =
PluralForm.get(groupCount,
glodaFacetStrings.get(
"glodaFacetView.facets.mode.top.listAllLabel"))
.replace("#1", groupCount);
}
// -- Row Building
this.buildRows();
]]>
1;
labelNode.setAttribute("state", "some");
// We used to use jQuery here, but it turns out that it has issues with
// XML namespaces and stringification, so we now resort to poking the
// nodes directly.
let showNodes = [labelNode];
if (remListVisible != remListShouldBeVisible)
showNodes = [labelNode, this.remainderLabel];
showNodes.forEach(node => node.style.display = "block");
aCallback();
]]>
(contentRect.top + contentRect.height))
dest.top = contentRect.top + contentRect.height - dest.height;
flyingNode.style.position = "absolute";
flyingNode.style.width = origin.width + "px";
flyingNode.style.height = origin.height + "px";
flyingNode.style.top = origin.top + "px";
flyingNode.style.left = origin.left + "px";
flyingNode.style.zIndex = 1000;
flyingNode.style.transitionDuration = (Math.abs(dest.top - origin.top) * 2) + "ms";
flyingNode.style.transitionProperty = "top, left";
flyingNode.addEventListener("transitionend", function() {
aBarNode.parentNode.removeChild(aBarNode);
targetNode.style.display = "block";
flyingNode.parentNode.removeChild(flyingNode);
if (aCallback)
setTimeout(aCallback, 50);
});
document.body.appendChild(flyingNode);
// animate the flying clone... flying!
window.requestAnimationFrame(function() {
flyingNode.style.top = dest.top + "px";
flyingNode.style.left = dest.left + "px";
});
// hide the target (cloned) node
targetNode.style.display = "none";
// hide the original node and remove its JS properties
aBarNode.style.visibility = "hidden";
delete aBarNode.groupValue;
delete aBarNode.groupItems;
]]>
node, or a span inside
// of it, or perhaps the facet-more button, or maybe something
// else that we'll handle in the next version. We walk up its
// parent chain until we get to the right level of the DOM
// hierarchy, or the facet-content which seems to be the root.
if (this.currentNode)
this.currentNode.removeAttribute("selected");
let node = event.originalTarget;
while ((! (node && node.hasAttribute && node.hasAttribute("class"))) ||
(!node.classList.contains("bar") &&
!node.classList.contains("facet-more") &&
!node.classList.contains("facet-content")))
node = node.parentNode;
if (! (node && node.hasAttribute && node.hasAttribute("class")))
return false;
this.currentNode = node;
node.setAttribute("selected", "true");
if (node.classList.contains("bar"))
document.getElementById("popup-menu").show(event, this, node);
else if (node.classList.contains("facet-more"))
this.changeMode("all");
return false;
} catch (e) {
logException(e);
}
]]>
true
m instanceof GlodaMessage);
showNode.style.display = visible ? "inline" : "none";
showNode.textContent = glodaFacetStrings.get(
"glodaFacetView.results.message.openEmailAsList.label");
showNode.setAttribute("title", glodaFacetStrings.get(
"glodaFacetView.results.message.openEmailAsList.tooltip"));
showNode.onkeypress = function(event) {
if (event.charCode == KeyEvent.DOM_VK_SPACE) {
FacetContext.showActiveSetInTab()
event.preventDefault();
}
}
let sortLabelNode = document.getAnonymousElementByAttribute(
this, "anonid", "sort-label");
sortLabelNode.textContent = glodaFacetStrings.get(
"glodaFacetView.results.message.sort.label");
let sortRelevanceNode = document.getAnonymousElementByAttribute(
this, "anonid", "sort-relevance");
sortRelevanceNode.textContent = glodaFacetStrings.get(
"glodaFacetView.results.message.sort.relevance");
let dis = this;
sortRelevanceNode.onclick = function() {
FacetContext.sortBy = '-dascore';
dis.updateSortLabels();
}
sortRelevanceNode.onkeypress = function(event) {
if (event.charCode == KeyEvent.DOM_VK_SPACE) {
FacetContext.sortBy = '-dascore';
dis.updateSortLabels();
event.preventDefault();
}
}
let sortDateNode = document.getAnonymousElementByAttribute(
this, "anonid", "sort-date");
sortDateNode.textContent = glodaFacetStrings.get(
"glodaFacetView.results.message.sort.date");
sortDateNode.onclick = function() {
FacetContext.sortBy = '-date';
dis.updateSortLabels();
}
sortDateNode.onkeypress = function(event) {
if (event.charCode == KeyEvent.DOM_VK_SPACE) {
FacetContext.sortBy = '-date';
dis.updateSortLabels();
event.preventDefault();
}
}
this.updateSortLabels(FacetContext.sortBy);
let messagesNode = document.getAnonymousElementByAttribute(
this, "anonid", "messages");
while (messagesNode.hasChildNodes())
messagesNode.lastChild.remove();
try {
// -- Messages
for (let message of aMessages) {
let msgNode = document.createElement("message");
msgNode.message = message;
msgNode.setAttribute("class", "message");
messagesNode.appendChild(msgNode);
}
} catch (e) {
logException(e);
}
]]>
MAX_RECIPIENTS) {
let nOthers = totalRecipientCount - recipientCount;
let andNOthers = document.createElement("span");
andNOthers.setAttribute("class", "message-recipients-andothers");
let andOthersLabel= PluralForm.get(nOthers, glodaFacetStrings.get(
"glodaFacetView.results.message.andOthers"))
.replace("#1", nOthers);
andNOthers.textContent = andOthersLabel;
recipientsNode.appendChild(andNOthers);
}
}
} catch (e) {
logException(e);
}
// - Starred
let starNode = anonElem("star");
if (message.starred) {
starNode.setAttribute("starred", "true")
}
// - Attachments
if (message.attachmentNames) {
let attachmentsNode = anonElem("attachments");
let imgNode = document.createElement("div");
imgNode.setAttribute("class", "message-attachment-icon");
attachmentsNode.appendChild(imgNode);
for (let attach of message.attachmentNames) {
let attachNode = document.createElement("div");
attachNode.setAttribute("class", "message-attachment");
if (attach.length >= 28)
attach = attach.substring(0, 24) + "…";
attachNode.textContent = attach;
attachmentsNode.appendChild(attachNode);
}
}
// - Tags
let tagsNode = anonElem("tags");
if ("tags" in message && message.tags.length) {
for (let tag of message.tags) {
let tagNode = document.createElement("span");
let colorClass = "blc-" + MailServices.tags.getColorForKey(tag.key).substr(1);
tagNode.setAttribute("class", "message-tag tag " + colorClass);
tagNode.textContent = tag.tag;
tagsNode.appendChild(tagNode);
}
}
// - Body
if (message.indexedBodyText) {
let bodyText = message.indexedBodyText;
let matches = [];
if ("stashedColumns" in FacetContext.collection) {
let collection;
if ("IMCollection" in FacetContext &&
message instanceof Gloda.lookupNounDef("im-conversation").clazz)
collection = FacetContext.IMCollection;
else
collection = FacetContext.collection;
let offsets = collection.stashedColumns[message.id][0];
let offsetNums = offsets.split(" ").map(x => parseInt(x));
for (let i = 0; i < offsetNums.length; i += 4) {
// i is the column index. The indexedBodyText is in the column 0.
// Ignore matches for other columns.
if (offsetNums[i] != 0)
continue;
// i+1 is the term index, indicating which queried term was found.
// We can ignore for now...
// i+2 is the *byte* offset at which the term is in the string.
// i+3 is the term's length.
matches.push([offsetNums[i + 2], offsetNums[i + 3]]);
}
// Sort the matches by index, just to be sure.
// They are probably already sorted, but if they aren't it could
// mess things up at the next step.
matches.sort((a, b) => a[0] - b[0]);
// Convert the byte offsets and lengths into character indexes.
let charCodeToByteCount = function(c) {
// UTF-8 stores:
// - code points below U+0080 on 1 byte,
// - code points below U+0800 on 2 bytes,
// - code points U+D800 through U+DFFF are UTF-16 surrogate halves
// (they indicate that JS has split a 4 bytes UTF-8 character
// in two halves of 2 bytes each),
// - other code points on 3 bytes.
return c < 0x80 ? 1 : (c < 0x800 || (c >= 0xD800 && c <= 0xDFFF)) ? 2 : 3;
}
let byteOffset = 0;
let offset = 0;
for (let match of matches) {
while (byteOffset < match[0])
byteOffset += charCodeToByteCount(bodyText.charCodeAt(offset++));
match[0] = offset;
for (let i = offset; i < offset + match[1]; ++i) {
let size = charCodeToByteCount(bodyText.charCodeAt(i));
if (size > 1)
match[1] -= size - 1;
}
}
}
// how many lines of context we want before the first match:
const kContextLines = 2;
let startIndex = 0;
if (matches.length > 0) {
// Find where the snippet should begin to show at least the
// first match and kContextLines of context before the match.
startIndex = matches[0][0];
for (let context = kContextLines; context >= 0; --context) {
startIndex = bodyText.lastIndexOf("\n", startIndex - 1);
if (startIndex == -1) {
startIndex = 0;
break;
}
}
}
// start assuming it's just one line that we want to show
let idxNewline = -1;
let ellipses = "…";
let maxLineCount = 5;
if (startIndex != 0) {
// Avoid displaying an ellipses followed by an empty line.
while (bodyText[startIndex + 1] == "\n")
++startIndex;
bodyText = ellipses + bodyText.substring(startIndex);
// The first line will only contain the ellipsis as the character
// at startIndex is always \n, so we show an additional line.
++maxLineCount;
}
for (let newlineCount = 0; newlineCount < maxLineCount; newlineCount++) {
idxNewline = bodyText.indexOf("\n", idxNewline+1);
if (idxNewline == -1) {
ellipses = '';
break;
}
}
let snippet = "";
if (idxNewline > -1)
snippet = bodyText.substring(0, idxNewline);
else
snippet = bodyText;
if (ellipses)
snippet = snippet.trimRight() + ellipses;
let parent = anonElem("snippet");
let node = document.createTextNode(snippet);
parent.appendChild(node);
let offset = startIndex ? startIndex - 1 : 0; // The ellipsis takes 1 character.
for (let match of matches) {
if (idxNewline > -1 && match[0] > startIndex + idxNewline)
break;
let secondNode = node.splitText(match[0] - offset);
node = secondNode.splitText(match[1]);
offset += match[0] + match[1] - offset;
let span = document.createElement("span");
span.textContent = secondNode.data;
if (!this.firstMatchText)
this.firstMatchText = secondNode.data;
span.setAttribute("class", "message-body-fulltext-match");
parent.replaceChild(span, secondNode);
}
}
// - Misc attributes
if (!message.read)
this.setAttribute("unread", "true");
]]>