merge gloda-facet
authorDavid Ascher <david@mozillamessaging.com>
Tue, 08 Sep 2009 23:20:32 -0700
branchgloda-facet
changeset 3718 0abb2352aa0c200d95bff9eab65be2b407a5af46
parent 3717 03a6d5fd16f10d1c184355bbe2c61f70356b2cfd (current diff)
parent 3715 62adfc2a96cc5f9db6bb15bb33c41891231ac64c (diff)
child 3719 0849d1f27da42718391defda0b53fc3392f309ea
push idunknown
push userunknown
push dateunknown
merge
mail/base/content/glodaFacetBindings.xml
mail/base/content/glodaFacetView.css
mail/base/content/glodaFacetView.js
--- a/mail/base/content/glodaFacetBindings.xml
+++ b/mail/base/content/glodaFacetBindings.xml
@@ -498,22 +498,25 @@
 
 <binding id="facet-discrete">
   <content>
   <!-- without this explicit div here, the sibling selectors used to span
        included-label/included and excluded-label/excluded fail to apply.
        so. magic! (this is why our binding node's class is facetious. -->
   <html:div class="facet">
     <html:h2 anonid="name"></html:h2>
-    <html:h3 anonid="included-label" class="facet-included-header"></html:h3>
-    <html:ul anonid="included" class="facet-included barry"></html:ul>
-    <html:h3 anonid="excluded-label" class="facet-excluded-header"></html:h3>
-    <html:ul anonid="excluded" class="facet-excluded barry"></html:ul>
-    <html:h3 anonid="remainder-label" class="facet-remaindered-header"></html:h3>
-    <html:ul anonid="remainder" class="facet-remaindered barry"></html:ul>
+    <html:div anonid="content-box" class="facet-content">
+      <html:h3 anonid="included-label" class="facet-included-header"></html:h3>
+      <html:ul anonid="included" class="facet-included barry"></html:ul>
+      <html:h3 anonid="excluded-label" class="facet-excluded-header"></html:h3>
+      <html:ul anonid="excluded" class="facet-excluded barry"></html:ul>
+      <html:h3 anonid="remainder-label" class="facet-remaindered-header"></html:h3>
+      <html:ul anonid="remainder" class="facet-remaindered barry"></html:ul>
+      <html:div anonid="more" class="facet-more" needed="false" />
+    </html:div>
   </html:div>
   </content>
   <implementation>
     <constructor><![CDATA[
       if ("faceter" in this)
         this.build(true);
     ]]></constructor>
     <field name="canUpdate" readonly="true">false</field>
@@ -534,17 +537,16 @@
     </method>
     <method name="build">
       <parameter name="aFirstTime" />
       <body><![CDATA[
         // -- Header Building
         let nameNode = document.getAnonymousElementByAttribute(this, "anonid",
                                                                "name");
         nameNode.textContent = this.attrDef.strings.facetLabel;
-        nameNode.setAttribute("title", this.attrDef.strings.facetTooltip);
 
         // - include
         // setup the include label
         this.includeLabel = document.getAnonymousElementByAttribute(
                            this, "anonid", "included-label");
         if ("includeLabel" in this.attrDef.strings)
           this.includeLabel.textContent = this.attrDef.strings.includeLabel;
         else
@@ -582,54 +584,77 @@
         else
           this.remainderLabel.textContent =
             glodaFacetStrings.get(
               "glodaFacetView.facets.remainder.fallbackLabel");
         // remainder list ref
         this.remainderList = document.getAnonymousElementByAttribute(
                                         this, "anonid", "remainder");
 
+        // - more button
+        this.moreButton = document.getAnonymousElementByAttribute(
+                            this, "anonid", "more");
+
+        // we need to know who the content box is for flying fun
+        this.contentBox = document.getAnonymousElementByAttribute(
+                            this, "anonid", "content-box");
+
 
         // -- House-cleaning
         // -- All/Top mode decision
-        // to simplify our logic, we always need an other group sentinel even
-        //  if we are operating in "all" mode.
         this.modes = ["all"];
         if (this.maxDisplayRows >= 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
+          this.moreButton.textContent =
+            glodaFacetStrings.get(
+                "glodaFacetView.facets.mode.top.moreLabel")
+              .replace("#1", this.orderedGroups.length.toLocaleString());
         }
 
         // -- Row Building
         this.buildRows();
 
       ]]></body>
     </method>
+    <method name="changeMode">
+      <parameter name="aNewMode" />
+      <body><![CDATA[
+        this.mode = aNewMode;
+        this.setAttribute("mode", aNewMode);
+        this.buildRows();
+      ]]></body>
+    </method>
     <method name="buildRows">
       <body><![CDATA[
         let nounDef = this.nounDef;
         let useGroups = (this.mode == "all") ? this.orderedGroups
                                              : this.topGroups;
 
-        let constraint = this.constraint;
+        // should we just rely on automatic string coercion?
+        this.moreButton.setAttribute("needed",
+                                     (this.mode == "top") ? "true" : "false");
+
+        let constraint = this.faceter.constraint;
 
         // -- empty all of our display buckets...
         let remainderList = this.remainderList;
         while (remainderList.lastChild)
           remainderList.removeChild(remainderList.lastChild);
-        let inclList = this.includeList, excludeList = this.excludeList;
-        while (inclList.lastChild)
-          inclList.removeChild(inclList.lastChild);
+        let includeList = this.includeList, excludeList = this.excludeList;
+        while (includeList.lastChild)
+          includeList.removeChild(includeList.lastChild);
         while (excludeList.lastChild)
           excludeList.removeChild(excludeList.lastChild);
 
         // -- first pass, check for ambiguous labels
         // It's possible that multiple groups are identified by the same short
         //  string, in which case we want to use the longer string to
         //  disambiguate.  For example, un-merged contacts can result in
         //  multiple identities having contacts with the same name.  In that
@@ -812,16 +837,28 @@
 
         // create a flying clone
         let flyingNode = aBarNode.cloneNode(true);
 
         // animate the flying clone... flying!
         let $targetNode = $(targetNode);
         let dest = $targetNode.offset();
 
+        // if the flying box wants to go higher than the content box goes, just
+        //  send it to the top of the content box instead.
+        let $contentBox = $(this.contentBox);
+        let contentOffset = $contentBox.offset();
+        if (dest.top < contentOffset.top)
+          dest.top = contentOffset.top;
+        // likewise if it wants to go further south than the content box, stop
+        //  that
+        if (dest.top > (contentOffset.top + $contentBox.height()))
+          dest.top = contentOffset.top + $contentBox.height() -
+                       $targetNode.height();
+
         let $flyingNode = $(flyingNode)
           .appendTo("body")
           .css("position", "absolute")
           .css("width", $barNode.width())
           .css("height", $barNode.height())
           .css("top", origin.top)
           .css("left", origin.left)
           .css("zIndex", 1000)
@@ -938,16 +975,19 @@
       if (nodeClass == "bar-exclude")
         this.barClicked(event.originalTarget.parentNode, "exclude");
       else if (nodeClass == "bar-link") {
         let barNode = event.originalTarget.parentNode;
         this.barClicked(barNode,
                         (barNode.getAttribute("variety") == "remainder") ?
                           "include" : "remainder");
       }
+      else if (nodeClass == "facet-more") {
+        this.changeMode("all");
+      }
     ]]></handler>
     <handler event="mouseover"><![CDATA[
       // we dispatch based on the class of the thing we clicked on.
       // there are other ways we could accomplish this, but they all sorta suck.
       let nodeClasses = event.originalTarget.getAttribute("class");
       if (!nodeClasses)
         return;
 
@@ -1059,19 +1099,16 @@
           PluralForm.get(FacetContext.activeSet.length, messagesPlural));
         countNode.textContent = countLabel;
 
         // -- Show All
         let showNode = document.getAnonymousElementByAttribute(
                           this, "anonid", "showall");
         showNode.textContent = glodaFacetStrings.get(
           "glodaFacetView.results.message.showAllInList.label");
-        showNode.setAttribute("title",
-          glodaFacetStrings.get(
-            "glodaFacetView.results.message.showAllInList.tooltip"));
 
         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");
--- a/mail/base/content/glodaFacetView.css
+++ b/mail/base/content/glodaFacetView.css
@@ -159,17 +159,18 @@ h2 {
   margin-bottom: 0.5em;
 }
 
 h3 {
   font-weight: normal;
 }
 
 
-.facet > h3 {
+.facet > h3,
+.facet-content > h3 {
   font-size: 105%;
   padding-left: 0em;
   margin-top: 0.5em;
   margin-bottom: 0.5em;
 }
 
 .facet-included-header[state="empty"],
 .facet-excluded-header[state="empty"],
@@ -243,21 +244,28 @@ h3 {
 }
 
 .facet-filter-list {
   display: block;
 }
 
 /* === Discrete Facet === */
 
-/*
- * Our basis for the bar-chart comes from:
- *  http://www.alistapart.com/articles/accessibledatavisualization/
- * Thank you, Wilson Miner.
- */
+.facet-content {
+  max-height: 32em;
+  overflow: auto;
+}
+
+.facet-more[needed="false"] {
+  display: none;
+}
+
+.facet-more[needed="true"] {
+
+}
 
 .bar {
   display: block;
 }
 
 .bar-count {
   position: absolute;
   right: 3px;
@@ -267,41 +275,16 @@ h3 {
 
 .barry {
   border-top: 1px solid #ccc;
   margin: 0;
   padding: 0; 
   /*padding: 4px;*/
 }
 
-.facet-modes {
-  font-size: 80%;
-  cursor: pointer;
-  color: #888888;
-}
-
-.facet-modes:before {
-  font-weight: normal;
-  content: "( ";
-}
-
-.facet-mode:not(:first-child):before {
-  font-weight: normal;
-  content: " | ";
-}
-
-.facet-modes:after {
-  font-weight: normal;
-  content: " )";
-}
-
-.facet-mode[selected] {
-  font-weight: bold;
-}
-
 .bar {
   display: block;
   position: relative;
   border-bottom: 1px solid #ccc;
   cursor: pointer;
   font-size: 80%;
 }
 
--- a/mail/base/content/glodaFacetView.js
+++ b/mail/base/content/glodaFacetView.js
@@ -374,16 +374,17 @@ var FacetContext = {
     if (this.searcher)
       queryExplanation.setFulltext(this.searcher);
     else
       queryExplanation.setQuery(this.collection.query);
     // we like to sort them so should clone the list
     this.faceters = this.facetDriver.faceters.concat();
 
     this.everFaceted = false;
+    this._activeConstraints = {};
     try {
       if (this.searcher) {
         this._sortBy = '-dascore';
         this._relevantSortedItems = this._collection.items.concat();
         this._dateSortedItems = this._relevantSortedItems.concat().sort(function(a,b) b.date-a.date);
         this._activeSet = this._relevantSortedItems;
       } else {
         this._sortBy = '-date';
@@ -416,16 +417,28 @@ var FacetContext = {
    *  impacted by layout concerns (since we want to avoid scrolling).
    */
   planLayout: function() {
     // XXX arbitrary!
     this.maxDisplayRows = 8;
     this.maxMessagesToShow = 10;
   },
 
+  /**
+   * Clean up the UI in preparation for a new query to come in.
+   */
+  _resetUI: function() {
+    for each (let [, faceter] in Iterator(this.faceters)) {
+      if (faceter.xblNode && !faceter.xblNode.explicit)
+        faceter.xblNode.parentNode.removeChild(faceter.xblNode);
+      faceter.xblNode = null;
+      faceter.constraint = null;
+    }
+  },
+
   _groupCountComparator: function(a, b) {
     return b.groupCount - a.groupCount;
   },
   /**
    * Tells the UI about all the facets when notified by the |facetDriver| when
    *  it is done faceting everything.
    */
   facetingCompleted: function() {
@@ -443,17 +456,17 @@ var FacetContext = {
         if (explicitBinding) {
           explicitBinding.explicit = true;
           explicitBinding.faceter = faceter;
           explicitBinding.attrDef = faceter.attrDef;
           explicitBinding.nounDef = faceter.attrDef.objectNounDef;
           explicitBinding.orderedGroups = faceter.orderedGroups;
           // explicit booleans should always be displayed for consistency
           if (faceter.groupCount >= 1 ||
-              faceter.type == "boolean") {
+              (explicitBinding.getAttribute("type").indexOf("boolean") != -1)) {
             explicitBinding.build(true);
             explicitBinding.removeAttribute("uninitialized");
           }
           faceter.xblNode = explicitBinding;
           continue;
         }
 
         // ignore facets that do not vary!
@@ -569,17 +582,17 @@ var FacetContext = {
     // reuse hover facet to null everyone out
     this.hoverFacet(null, null, null, null);
   },
 
   /**
    * Maps attribute names to their corresponding |ActiveConstraint|, if they
    *  have one.
    */
-  _activeConstraints: {},
+  _activeConstraints: null,
   /**
    * Called by facet bindings when the user does some clicking and wants to
    *  impose a new constraint.
    *
    * @param aFaceter
    * @param aAttrDef
    * @param {Boolean} aInclusive
    * @param aGroupValues
@@ -677,16 +690,17 @@ var FacetContext = {
       items = constraint.sieve(items);
     }
 
     return items;
   },
 
   toggleFulltextCriteria: function() {
     this.tab.searcher.andTerms = !this.tab.searcher.andTerms;
+    this._resetUI();
     this.collection = this.tab.searcher.getCollection(this);
   },
 
   /**
    * Show the active message set in a glodaList tab, closing the current tab.
    */
   showActiveSetInTab: function() {
     let tabmail = this.rootWin.document.getElementById("tabmail");
--- a/mail/locales/en-US/chrome/messenger/gloda.properties
+++ b/mail/locales/en-US/chrome/messenger/gloda.properties
@@ -31,27 +31,17 @@
 # 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 *****
 
 # LOCALIZATION NOTE (*.facetLabel): These are the labels used to label the facet
-#  displays in the global search facet display mechanism.  Like thread pane
-#  column headers these should be relatively short but can take advantage of
-#  attributes with longer displays to be slightly longer.  For example, the
-#  conversation attribute is larger than most attributes, so can have a slightly
-#  longer label.  You can and should use the facetTooltip to provide a better
-#  and longer explanation of what the facet attribute is.
-
-# LOCALIZATION NOTE (*.facetTooltip): These are the tooltips that will be
-#  displayed if you hover over the facet's label.  These should be longer than
-#  the facetLabel and reduce confusion about what the facet is, but do not
-#  need to be a book on the subject.
+#  displays in the global search facet display mechanism.
 
 # LOCALIZATION NOTE (*.includeLabel): The label to use for the included group
 #  in the facet display.  If not provided, we will fall back to
 #  "glodaFacetView.facets.included.fallbackLabel".
 
 # LOCALIZATION NOTE (*.excludeLabel): The label to use for the excluded group
 #  in the facet display.  If not provided, we will fall back to
 #  "glodaFacetView.facets.excluded.fallbackLabel".
@@ -59,92 +49,80 @@
 # LOCALIZATION NOTE (*.remainderLabel): The label to use for the remaining items
 #  that are neither part of the included group or the excluded group in the
 #  facet display.  If not provided, we will fall back to
 #  "glodaFacetView.facets.remainder.fallbackLabel".
 
 # LOCALIZATION NOTE (gloda.message.attr.folder.*): Stores the message folder in
 #  which the message is stored.
 gloda.message.attr.folder.facetLabel=Mail Folder
-gloda.message.attr.folder.facetTooltip=The folder where the message is stored.
 
 # LOCALIZATION NOTE (gloda.message.attr.fromMe.*): Stores everyone involved
 #  with the message.  This means from/to/cc/bcc.
 gloda.message.attr.fromMe.facetLabel=From Me
-gloda.message.attr.fromMe.facetTooltip=Messages where I was the author.
 
 # LOCALIZATION NOTE (gloda.message.attr.toMe.*): Stores everyone involved
 #  with the message.  This means from/to/cc/bcc.
 gloda.message.attr.toMe.facetLabel=To Me
-gloda.message.attr.toMe.facetTooltip=Messages where I was on the To or Cc lines.
 
 
 # LOCALIZATION NOTE (gloda.message.attr.involves.*): Stores everyone involved
 #  with the message.  This means from/to/cc/bcc.
 gloda.message.attr.involves.facetLabel=People
-gloda.message.attr.involves.facetTooltip=People explicitly involved in the message by being listed on the From, To, Cc, or Bcc lines.
 gloda.message.attr.involves.includeLabel=involving any of:
 gloda.message.attr.involves.excludeLabel=not involving:
 gloda.message.attr.involves.remainderLabel=other participants:
 
 # LOCALIZATION NOTE (gloda.message.attr.date.*): Stores the date of the message.
 #  Thunderbird normally stores the date the message claims it was composed
 #  according to the "Date" header.  This is not the same as when the message
 #  was sent or when it was eventually received by the user.  In the future we
 #  may change this to be one of the other dates, but not anytime soon.
 gloda.message.attr.date.facetLabel=Date
-gloda.message.attr.date.facetTooltip=The date the message claims it was authored.
 
 # LOCALIZATION NOTE (gloda.message.attr.attachmentTypes.*): Stores the list of
 #  MIME types (ex: image/png, text/plain) of real attachments (not just part of
 #  the message content but explicitly named attachments) on the message.
 #  Although we hope to be able to provide localized human-readable explanations
 #  of the MIME type (ex: "PowerPoint document"), I don't know if that is going
 #  to happen.
 gloda.message.attr.attachmentTypes.facetLabel=Attachments
-gloda.message.attr.attachmentTypes.facetTooltip=The type of attachments found on the message, if any.
 
 # LOCALIZATION NOTE (gloda.message.attr.mailing-list.*): Stores the mailing
 #  lists detected in the message.  This will normally be the e-mail address of
 #  the mailing list and only be detected in messages received from the mailing
 #  list.  Extensions may contribute additional detected mailing-list-like
 #  things.
 gloda.message.attr.mailing-list.facetLabel=Mail List Involved
-gloda.message.attr.mailing-list.facetTooltip=If the message was sent via a mailing list, the e-mail address associated with the mailing list.
 
 # LOCALIZATION NOTE (gloda.message.attr.tag.*): Stores the tags applied to the
 #  message.  Notably, gmail's labels are not currently exposed via IMAP and we
 #  do not do anything clever with gmail, so this is indepdendent of gmail
 #  labels.  This may change in the future, but it's a safe bet it's not
 #  happening on Thunderbird's side prior to 3.0.
 gloda.message.attr.tag.facetLabel=Tags
-gloda.message.attr.tag.facetTooltip=Tags applied to the message.
 
 # LOCALIZATION NOTE (gloda.message.attr.tag.*): Stores whether the message is
 #  starred or not, as indicated by a pretty star icon.  In the past, the icon
 #  used to be a flag.  The IMAP terminology continues to be "flagged".
 gloda.message.attr.star.facetLabel=Starred
-gloda.message.attr.star.facetTooltip=Is the message starred / flagged?
 
 # LOCALIZATION NOTE (gloda.message.attr.read.*): Stores whether the user has
 #  read the message or not.
 gloda.message.attr.read.facetLabel=Read
-gloda.message.attr.read.facetTooltip=Is the message marked read, or is it unread?
 
 # LOCALIZATION NOTE (gloda.message.attr.repliedTo.*): Stores whether we believe
 #  the user has ever replied to the message.  We normally show a little icon in
 #  the thread pane when this is the case.
 gloda.message.attr.repliedTo.facetLabel=Replied To
-gloda.message.attr.repliedTo.facetTooltip=Has this message been replied to?
 
 # LOCALIZATION NOTE (gloda.message.attr.forwarded.*): Stores whether we believe
 #  the user has ever forwarded the message.  We normally show a little icon in
 #  the thread pane when this is the case.
 gloda.message.attr.forwarded.facetLabel=Forwarded
-gloda.message.attr.forwarded.facetTooltip=Has this message been forwarded to anyone?
 
 # LOCALIZATION NOTE (gloda.mimetype.category.*.label): Map categories of MIME
 #  types defined in mimeTypeCategories.js to labels.
 # LOCALIZATION NOTE (gloda.mimetype.category.archives.label): Archive is
 #  referring to things like zip files, tar files, tar.gz files, etc.
 gloda.mimetype.category.archives.label=Archives
 gloda.mimetype.category.documents.label=Documents
 gloda.mimetype.category.images.label=Images
--- a/mail/locales/en-US/chrome/messenger/glodaFacetView.properties
+++ b/mail/locales/en-US/chrome/messenger/glodaFacetView.properties
@@ -63,21 +63,22 @@ glodaFacetView.constraints.query.initial
 glodaFacetView.constraints.query.involves.label=involving #1
 
 # LOCALIZATION NOTE(glodaFacetView.constraints.query.contact.label):
 #  The label to display to describe when our base query was on messages
 #  tagged with a specific tag.  The tag is displayed following the label.
 glodaFacetView.constraints.query.tagged.label=tagged:
 
 
-# LOCALIZATION NOTE (glodaFacetView.facets.mode.top.otherLabel): The label to
-#  use for the "other" category where we lump everything that is not one of the
-#  top groups.  Includes an argument which is the number of groups that are
-#  collapsed into this group.
-glodaFacetView.facets.mode.top.otherLabel=Other (%S)
+# LOCALIZATION NOTE (glodaFacetView.facets.mode.top.moreLabel): The label to
+#  use when we are only displaying the top entries for a facet.  When the
+#  label is clicked on, it results in us displaying all of the values for that
+#  facet.  The value "#1" (if present) is replaced with the total number of
+#  values that will be displayed (rather than the number currently hidden).
+glodaFacetView.facets.mode.top.moreLabel=List all #1
 
 # LOCALIZATION NOTE (glodaFacetView.facets.included.fallbackLabel): The label to
 #  use for groups in a facet that have been explicitly included by the user if
 #  there is no explicit attribute "includeLabel" defined.  (The explicit label
 #  would be named "gloda.message.attr.ATTRIBUTE.includeLabel".)
 glodaFacetView.facets.included.fallbackLabel=including any of:
 # LOCALIZATION NOTE (glodaFacetView.facets.excluded.fallbackLabel): The label to
 #  use for groups in a facet that have been explicitly excluded by the user if
@@ -141,9 +142,9 @@ glodaFacetView.results.message.showAllIn
 glodaFacetView.results.message.sort.label=sort by:
 # LOCALIZATION NOTE(glodaFacetView.results.message.sort.relevance):
 # a clickable label causing the sort to be done by most relevant messages first.
 glodaFacetView.results.message.sort.relevance=relevance
 # LOCALIZATION NOTE(glodaFacetView.results.message.sort.date):
 # a clickable label causing the sort to be done by most recent messages first.
 glodaFacetView.results.message.sort.date=date
 
-glodaFacetView.results.message.andNOthers=and #1 other;and #1 others
\ No newline at end of file
+glodaFacetView.results.message.andNOthers=and #1 other;and #1 others
--- a/mailnews/db/gloda/modules/gloda.js
+++ b/mailnews/db/gloda/modules/gloda.js
@@ -1541,17 +1541,16 @@ var Gloda = {
    *  "gloda.SUBJECT-NOUN-NAME.attr.ATTR-NAME.*".
    *
    * Please consult the localization notes in gloda.properties to understand
    *  what these are used for.
    */
   _ATTR_LOCALIZED_STRINGS: {
     /* - Faceting */
     facetLabel: "facetLabel",
-    facetTooltip: "facetTooltip",
     includeLabel: "includeLabel",
     excludeLabel: "excludeLabel",
     remainderLabel: "remainderLabel",
   },
   /**
    * Define an attribute and all its meta-data.  Takes a single dictionary as
    *  its argument, with the following required properties:
    *