]> git.0d.be Git - empathy.git/blob - data/Template.html
libempathy-gtk: fix uninitialized variable
[empathy.git] / data / Template.html
1 <html>
2 <head>
3         <meta http-equiv="content-type" content="text/html; charset=utf-8" />
4         <base href="%@">
5         <script type="text/javascript" defer="defer">
6                 // NOTE:
7                 // Any percent signs in this file must be escaped!
8                 // Use two escape signs (%%) to display it, this is passed through a format call!
9                 
10                 function appendHTML(html) {
11                         var node = document.getElementById("Chat");
12                         var range = document.createRange();
13                         range.selectNode(node);
14                         var documentFragment = range.createContextualFragment(html);
15                         node.appendChild(documentFragment);
16                 }
17                 
18                 // a coalesced HTML object buffers and outputs DOM objects en masse.
19                 // saves A LOT of CSS recalculation time when loading many messages.
20                 // (ex. a long twitter timeline)
21                 function CoalescedHTML() {
22                         var self = this;
23                         this.fragment = document.createDocumentFragment();
24                         this.timeoutID = 0;
25                         this.coalesceRounds = 0;
26                         this.isCoalescing = false;
27                         this.isConsecutive = undefined;
28                         this.shouldScroll = undefined;
29                         
30                         var appendElement = function (elem) {
31                                 document.getElementById("Chat").appendChild(elem);
32                         };
33                         
34                         function outputHTML() {
35                                 var insert = document.getElementById("insert");
36                                 if(!!insert && self.isConsecutive) {
37                                         insert.parentNode.replaceChild(self.fragment, insert);
38                                 } else {
39                                         if(insert)
40                                                 insert.parentNode.removeChild(insert);
41                                         // insert the documentFragment into the live DOM
42                                         appendElement(self.fragment);
43                                 }
44                                 alignChat(self.shouldScroll);
45                                 
46                                 // reset state to empty/non-coalescing
47                                 self.shouldScroll = undefined;
48                                 self.isConsecutive = undefined;
49                                 self.isCoalescing = false;
50                                 self.coalesceRounds = 0;
51                         }
52                         
53                         // creates and returns a new documentFragment, containing all content nodes
54                         // which can be inserted as a single node.
55                         function createHTMLNode(html) {
56                                 var range = document.createRange();
57                                 range.selectNode(document.getElementById("Chat"));
58                                 return range.createContextualFragment(html);
59                         }
60                         
61                         // removes first insert node from the internal fragment.
62                         function rmInsertNode() {
63                                 var insert = self.fragment.querySelector("#insert");
64                                 if(insert)
65                                         insert.parentNode.removeChild(insert);
66                         }
67                         
68                         function setShouldScroll(flag) {
69                                 if(flag && undefined === self.shouldScroll)
70                                         self.shouldScroll = flag;
71                         }
72                         
73                         // hook in a custom method to append new data
74                         // to the chat.
75                         this.setAppendElementMethod = function (func) {
76                                 if(typeof func === 'function')
77                                         appendElement = func;
78                         }
79                                                 
80                         // (re)start the coalescing timer.
81                         //   we wait 25ms for a new message to come in.
82                         //   If we get one, restart the timer and wait another 10ms.
83                         //   If not, run outputHTML()
84                         //  We do this a maximum of 400 times, for 10s max that can be spent
85                         //  coalescing input, since this will block display.
86                         this.coalesce = function() {
87                                 window.clearTimeout(self.timeoutID);
88                                 self.timeoutID = window.setTimeout(outputHTML, 25);
89                                 self.isCoalescing = true;
90                                 self.coalesceRounds += 1;
91                                 if(400 < self.coalesceRounds)
92                                         self.cancel();
93                         }
94                         
95                         // if we need to append content into an insertion div,
96                         // we need to clear the buffer and cancel the timeout.
97                         this.cancel = function() {
98                                 if(self.isCoalescing) {
99                                         window.clearTimeout(self.timeoutID);
100                                         outputHTML();
101                                 }
102                         }
103                         
104                         
105                         // coalased analogs to the global functions
106                         
107                         this.append = function(html, shouldScroll) {
108                                 // if we started this fragment with a consecuative message,
109                                 // cancel and output before we continue
110                                 if(self.isConsecutive) {
111                                         self.cancel();
112                                 }
113                                 self.isConsecutive = false;
114                                 rmInsertNode();
115                                 var node = createHTMLNode(html);
116                                 self.fragment.appendChild(node);
117                                 
118                                 node = null;
119
120                                 setShouldScroll(shouldScroll);
121                                 self.coalesce();
122                         }
123                         
124                         this.appendNext = function(html, shouldScroll) {
125                                 if(undefined === self.isConsecutive)
126                                         self.isConsecutive = true;
127                                 var node = createHTMLNode(html);
128                                 var insert = self.fragment.querySelector("#insert");
129                                 if(insert) {
130                                         insert.parentNode.replaceChild(node, insert);
131                                 } else {
132                                         self.fragment.appendChild(node);
133                                 }
134                                 node = null;
135                                 setShouldScroll(shouldScroll);
136                                 self.coalesce();
137                         }
138                         
139                         this.replaceLast = function (html, shouldScroll) {
140                                 rmInsertNode();
141                                 var node = createHTMLNode(html);
142                                 var lastMessage = self.fragment.lastChild;
143                                 lastMessage.parentNode.replaceChild(node, lastMessage);
144                                 node = null;
145                                 setShouldScroll(shouldScroll);
146                         }
147                 }
148                 var coalescedHTML;
149
150                 //Appending new content to the message view
151                 function appendMessage(html) {
152                         var shouldScroll;
153                         
154                         // Only call nearBottom() if should scroll is undefined.
155                         if(undefined === coalescedHTML.shouldScroll) {
156                                 shouldScroll = nearBottom();
157                         } else {
158                                 shouldScroll = coalescedHTML.shouldScroll;
159                         }
160                         appendMessageNoScroll(html, shouldScroll);
161                 }
162                 
163                 function appendMessageNoScroll(html, shouldScroll) {                    
164                         shouldScroll = shouldScroll || false;
165                         // always try to coalesce new, non-griuped, messages
166                         coalescedHTML.append(html, shouldScroll)
167                 }
168                 
169                 function appendNextMessage(html){
170                         var shouldScroll;
171                         if(undefined === coalescedHTML.shouldScroll) {
172                                 shouldScroll = nearBottom();
173                         } else {
174                                 shouldScroll = coalescedHTML.shouldScroll;
175                         }
176                         appendNextMessageNoScroll(html, shouldScroll);
177                 }
178                 
179                 function appendNextMessageNoScroll(html, shouldScroll){
180                         shouldScroll = shouldScroll || false;
181                         // only group next messages if we're already coalescing input
182                         coalescedHTML.appendNext(html, shouldScroll);
183                 }
184
185                 function replaceLastMessage(html){
186                         var shouldScroll;
187                         // only replace messages if we're already coalescing
188                         if(coalescedHTML.isCoalescing){
189                                 if(undefined === coalescedHTML.shouldScroll) {
190                                         shouldScroll = nearBottom();
191                                 } else {
192                                         shouldScroll = coalescedHTML.shouldScroll;
193                                 }
194                                 coalescedHTML.replaceLast(html, shouldScroll);
195                         } else {
196                                 shouldScroll = nearBottom();
197                                 //Retrieve the current insertion point, then remove it
198                                 //This requires that there have been an insertion point... is there a better way to retrieve the last element? -evands
199                                 var insert = document.getElementById("insert");
200                                 if(insert){
201                                         var parentNode = insert.parentNode;
202                                         parentNode.removeChild(insert);
203                                         var lastMessage = document.getElementById("Chat").lastChild;
204                                         document.getElementById("Chat").removeChild(lastMessage);
205                                 }
206
207                                 //Now append the message itself
208                                 appendHTML(html);
209
210                                 alignChat(shouldScroll);
211                         }
212                 }
213
214                 //Auto-scroll to bottom.  Use nearBottom to determine if a scrollToBottom is desired.
215                 function nearBottom() {
216                         return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) );
217                 }
218                 function scrollToBottom() {
219                         document.body.scrollTop = document.body.offsetHeight;
220                 }
221
222                 //Dynamically exchange the active stylesheet
223                 function setStylesheet( id, url ) {
224                         var code = "<style id=\"" + id + "\" type=\"text/css\" media=\"screen,print\">";
225                         if( url.length ) 
226                                 code += "@import url( \"" + url + "\" );";
227                         code += "</style>";
228                         var range = document.createRange();
229                         var head = document.getElementsByTagName( "head" ).item(0);
230                         range.selectNode( head );
231                         var documentFragment = range.createContextualFragment( code );
232                         head.removeChild( document.getElementById( id ) );
233                         head.appendChild( documentFragment );
234                 }
235
236                 /* Converts emoticon images to textual emoticons; all emoticons in message if alt is held */
237                 document.onclick = function imageCheck() {
238                         var node = event.target;
239                         if (node.tagName.toLowerCase() != 'img')
240                                 return;
241                                 
242                         imageSwap(node, false);
243                 }
244                 
245                 /* Converts textual emoticons to images if textToImagesFlag is true, otherwise vice versa */
246                 function imageSwap(node, textToImagesFlag) {
247                         var shouldScroll = nearBottom();
248                         
249                         var images = [node];
250                         if (event.altKey) {
251                                 while (node.id != "Chat" && node.parentNode.id != "Chat")
252                                         node = node.parentNode;
253                                 images = node.querySelectorAll(textToImagesFlag ? "a" : "img");
254                         }
255                         
256                         for (var i = 0; i < images.length; i++) {
257                                 textToImagesFlag ? textToImage(images[i]) : imageToText(images[i]);
258                         }
259                         
260                         alignChat(shouldScroll);
261                 }
262
263                 function textToImage(node) {
264                         if (!node.getAttribute("isEmoticon"))
265                                 return;
266                         //Swap the image/text
267                         var img = document.createElement('img');
268                         img.setAttribute('src', node.getAttribute('src'));
269                         img.setAttribute('alt', node.firstChild.nodeValue);
270                         img.className = node.className;
271                         node.parentNode.replaceChild(img, node);
272                 }
273                 
274                 function imageToText(node)
275                 {
276                         if (client.zoomImage(node) || !node.alt)
277                                 return;
278                         var a = document.createElement('a');
279                         a.setAttribute('onclick', 'imageSwap(this, true)');
280                         a.setAttribute('src', node.getAttribute('src'));
281                         a.setAttribute('isEmoticon', true);
282                         a.className = node.className;
283                         var text = document.createTextNode(node.alt);
284                         a.appendChild(text);
285                         node.parentNode.replaceChild(a, node);
286                 }
287                 
288                 //Align our chat to the bottom of the window.  If true is passed, view will also be scrolled down
289                 function alignChat(shouldScroll) {
290                         var windowHeight = window.innerHeight;
291
292                         if (windowHeight > 0) {
293                                 var contentElement = document.getElementById('Chat');
294                                 var contentHeight = contentElement.offsetHeight;
295                                 if (windowHeight - contentHeight > 0) {
296                                         contentElement.style.position = 'relative';
297                                         contentElement.style.top = (windowHeight - contentHeight) + 'px';
298                                 } else {
299                                         contentElement.style.position = 'static';
300                                 }
301                         }
302
303                         if (shouldScroll) scrollToBottom();
304                 }
305
306                 window.onresize = function windowDidResize(){
307                         alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs
308                 }
309                 
310                 function initStyle() {
311                         alignChat(true);
312                         if(!coalescedHTML)
313                                 coalescedHTML = new CoalescedHTML();
314                 }
315         </script>
316
317         <style type="text/css">
318                 .actionMessageUserName { display:none; }
319                 .actionMessageBody:before { content:"*"; }
320                 .actionMessageBody:after { content:"*"; }
321                 * { word-wrap:break-word; text-rendering: optimizelegibility; }
322                 img.scaledToFitImage { height: auto; max-width: 100%%; }
323         </style>
324
325         <!-- This style is shared by all variants. !-->
326         <style id="baseStyle" type="text/css" media="screen,print">
327                 %@
328         </style>
329
330         <!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !-->
331         <style id="mainStyle" type="text/css" media="screen,print">
332                 @import url( "%@" );
333         </style>
334
335 </head>
336 <body onload="initStyle();" style="==bodyBackground==">
337 %@
338 <div id="Chat">
339 </div>
340 %@
341 </body>
342 </html>