Auto-update qctoggled="true" of nested quotes on expand
[QuoteCollapse.git] / src / chrome / content / quotecollapse / quotecollapse.js
blob3c391a35f3df79d25c2f54a84acc7e498ec7e690
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * Contributor(s): Michael J Gruber  http://quotecollapse.mozdev.org/
15  *
16  * Alternatively, the contents of this file may be used under the terms of
17  * either the GNU General Public License Version 2 or later (the "GPL"), or
18  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
19  * in which case the provisions of the GPL or the LGPL are applicable instead
20  * of those above. If you wish to allow use of your version of this file only
21  * under the terms of either the GPL or the LGPL, and not to allow others to
22  * use your version of this file under the terms of the MPL, indicate your
23  * decision by deleting the provisions above and replace them with the notice
24  * and other provisions required by the GPL or the LGPL. If you do not delete
25  * the provisions above, a recipient may use your version of this file under
26  * the terms of any one of the MPL, the GPL or the LGPL.
27  *
28  * ***** END LICENSE BLOCK ***** */
31 var QuoteCollapse = {
32 //  _URIFixup : Components.classes["@mozilla.org/docshell/urifixup;1"].getService(Components.interfaces.nsIURIFixup),
33 //  _pref : Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch(null),
36   // Taken from LinkVisitor.
37   // This function is invoked in window (?) context,
38   // so use 'QuoteCollapse' instead of 'this'.
39   // event.originalTarget is the loaded document.
41   messagePane : null,
43   onMailWindowLoad : function(event) {
44     QuoteCollapse._messagePane = document.getElementById('messagepane'); // browser parenting the document
45     // messagePane.addEventListener("click", QuoteCollapse._onClick, false); // cpould also reg. on doc
46     QuoteCollapse._messagePane.addEventListener("load", QuoteCollapse._onLoad, true); // wait for doc to be loaded
47   },
49  // this is called when loading the document; time to insert style
50   _onLoad: function(event) {
51     var messageDocument = QuoteCollapse._messagePane.contentDocument; 
52     if( ! messageDocument.getElementsByTagName("blockquote").item(0) ) return; // nothing to be done
53     messageDocument.addEventListener("click", QuoteCollapse._onClick, false);
54     messageDocument.getElementsByTagName("body").item(0).className='mailview'; // class for customizing
56     // the following is inspired by code from quotecolors
57     var StyleElement = messageDocument.createElement("style");
58     StyleElement.type = "text/css";
59     // we don't need a BODY.mailview qualifier here
60     var stylecontent='\
61 blockquote[type="cite"] {\n\
62  background-image: url("chrome://quotecollapse/skin/twisty-clsd.png");\n\
63  background-repeat: no-repeat;\n\
64  background-position: top left;\n\
65  max-height: 2.25ex;\n\
66  padding-bottom: 0px ! important;\n\
67  overflow: -moz-hidden-unscrollable;\n\
68 }\n\
69 \n\
70 blockquote[type="cite"][qctoggled="true"] {\n\
71  background-image: url("chrome://quotecollapse/skin/twisty-open.png");\n\
72  max-height: none;\n\
73  overflow: visible;\n\
74 }\n\
76     var styletext = document.createTextNode(stylecontent);
77     StyleElement.appendChild(styletext);
78     messageDocument.getElementsByTagName("head").item(0).appendChild(StyleElement);
80     for(let quote of messageDocument.querySelectorAll("blockquote")) {
81       QuoteCollapse._toggleFullyVisible(quote);
82     }
83   },
84   
85   _toggleFullyVisible: function toggleFullyVisible(quote) {
86     if(quote.clientHeight < quote.scrollHeight)
87       return false;
89     for(let nested of quote.querySelectorAll("blockquote")) {
90       if(!toggleFullyVisible(nested))
91         return false;
92     }
93     quote.setAttribute("qctoggled", "true");
94     return true;
95   },
97   _getState: function(node) {
98     let current = node;
99     while(current) {
100       if(current.nodeName == "BLOCKQUOTE" && current.getAttribute("qctoggled") != "true")
101         return false;
103       current = current.parentNode
104     }
105     return true;
106   },
108   _setState: function(node, state, bubble) {
109     if(state)
110       node.setAttribute("qctoggled","true");
111     else
112       node.setAttribute("qctoggled","false");
114     if(bubble) {
115       var currentParent = node.parentNode;
116       while(currentParent) {
117         if(currentParent.nodeName == 'BLOCKQUOTE')
118           QuoteCollapse._setState(currentParent, state);
120         currentParent = currentParent.parentNode;
121       }
122     }
123   },
125   _setSubTree: function(node, state) {
126     if(node.nodeName == 'BLOCKQUOTE')
127       QuoteCollapse._setState(node, state);
128    
129     for (var i=0; i<node.childNodes.length; i++) {
130       QuoteCollapse._setSubTree(node.childNodes.item(i), state);
131     }
132   },
134   _setSubTreeLevel: function(node, state, level) {
135     if(node.nodeName == 'BLOCKQUOTE') {
136       if(level<=0) {
137         QuoteCollapse._setState(node, state);
138         return; // no need to go deeper
139       }
140       level--; // only BQs count for the level magic
141     } 
142     for (var i=0; i<node.childNodes.length; i++) {
143       QuoteCollapse._setSubTreeLevel(node.childNodes.item(i), state, level);
144     }
145   },
147   // we could use subtree on BODY, but the following is more efficient
148   _setTree : function(doc, newstate) {
149     var tree =  doc.getElementsByTagName("blockquote");
150     for(var i=0; i<tree.length; i++)
151       QuoteCollapse._setState(tree.item(i), newstate);
152   },
153   
154   _setLevel : function(target, newstate) {
155     var level=0;
156     var node=target;
157     do {
158       node = node.parentNode;
159       if(node.nodeName == 'BLOCKQUOTE')
160         level++;
161     } while(node.nodeName != 'BODY');
162     QuoteCollapse._setSubTreeLevel(node, newstate, level); // node is the BODY element
163   },
164   
165  // this is called by a click event
166   _onClick: function(event) {
167     var target = event.target;
168     if(target.nodeName == 'SPAN')
169       target = target.parentNode; // cite-tags span?
170     if(target.nodeName == 'PRE')
171       target = target.parentNode; // PRE inside; don't walk all the way up
172     if(target.nodeName != 'BLOCKQUOTE')
173       return true;
175     var newstate= ! QuoteCollapse._getState(target);
178 // react only to active spot (leave rest for copy etc.)
179     if(event.pageX > target.offsetLeft+12) return true;
180     
181     if(event.shiftKey)
182       if(event.ctrlKey || event.metaKey)
183         QuoteCollapse._setTree(target.ownerDocument, newstate);
184       else
185        QuoteCollapse._setSubTree(target, newstate);
186     else
187       if(event.ctrlKey || event.metaKey)
188         QuoteCollapse._setLevel(target, newstate);
189       else {
190         QuoteCollapse._setState(target, newstate, newstate);
191         if(newstate)
192           for(let nested of target.querySelectorAll("blockquote")) {
193             QuoteCollapse._toggleFullyVisible(nested);
194           }
195       }
196     return true;
197   },
203 // register listener so that we can update the popup
204 // This is done from the corresponding xul 
205 // window.addEventListener("load", QuoteCollapse.onWindowLoad, true);