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