Updated Spanish translation
[empathy-mirror.git] / data / Template.html
blob2b1ec215bada4e820093304fafe2c25d8eefedf9
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!
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);
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 this.interdiv = undefined;
31 var appendElement = function (elem) {
32 document.getElementById("Chat").appendChild(elem);
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);
45 alignChat(self.shouldScroll);
47 // reset state to empty/non-coalescing
48 self.shouldScroll = undefined;
49 self.isConsecutive = undefined;
50 self.isCoalescing = false;
51 self.coalesceRounds = 0;
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);
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);
69 function setShouldScroll(flag) {
70 if(flag && undefined === self.shouldScroll)
71 self.shouldScroll = flag;
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;
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();
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();
105 // This method is called, if the size of the chat window
106 // changes or after new messages have been appended.
107 // Adjusts the size of the interleaving page (if existing)
108 this.onchange = function() {
109 if (self.interdiv !== undefined){
110 var old_padding = parseInt(self.interdiv.style.paddingTop, 10);
111 var diff = document.height -
112 (self.interdiv.offsetTop + old_padding);
113 new_padding = window.innerHeight - diff;
115 if (new_padding <= 0)
116 removePage();
117 else
118 self.interdiv.style.paddingTop = new_padding + "px";
122 // coalased analogs to the global functions
124 this.append = function(html, shouldScroll) {
125 // if we started this fragment with a consecuative message,
126 // cancel and output before we continue
127 if(self.isConsecutive) {
128 self.cancel();
130 self.isConsecutive = false;
131 rmInsertNode();
132 var node = createHTMLNode(html);
133 self.fragment.appendChild(node);
135 node = null;
137 setShouldScroll(shouldScroll);
138 self.onchange();
139 self.coalesce();
142 this.appendNext = function(html, shouldScroll) {
143 if(undefined === self.isConsecutive)
144 self.isConsecutive = true;
145 var node = createHTMLNode(html);
146 var insert = self.fragment.querySelector("#insert");
147 if(insert) {
148 insert.parentNode.replaceChild(node, insert);
149 } else {
150 self.fragment.appendChild(node);
152 node = null;
153 setShouldScroll(shouldScroll);
154 self.onchange();
155 self.coalesce();
158 this.replaceLast = function (html, shouldScroll) {
159 rmInsertNode();
160 var node = createHTMLNode(html);
161 var lastMessage = self.fragment.lastChild;
162 lastMessage.parentNode.replaceChild(node, lastMessage);
163 node = null;
164 setShouldScroll(shouldScroll);
167 var coalescedHTML;
169 //Appending new content to the message view
170 function appendMessage(html) {
171 var shouldScroll;
173 // Only call nearBottom() if should scroll is undefined.
174 if(undefined === coalescedHTML.shouldScroll) {
175 shouldScroll = nearBottom();
176 } else {
177 shouldScroll = coalescedHTML.shouldScroll;
179 appendMessageNoScroll(html, shouldScroll);
182 function appendMessageNoScroll(html, shouldScroll) {
183 shouldScroll = shouldScroll || false;
184 // always try to coalesce new, non-griuped, messages
185 coalescedHTML.append(html, shouldScroll)
188 function appendNextMessage(html){
189 var shouldScroll;
190 if(undefined === coalescedHTML.shouldScroll) {
191 shouldScroll = nearBottom();
192 } else {
193 shouldScroll = coalescedHTML.shouldScroll;
195 appendNextMessageNoScroll(html, shouldScroll);
198 function appendNextMessageNoScroll(html, shouldScroll){
199 shouldScroll = shouldScroll || false;
200 // only group next messages if we're already coalescing input
201 coalescedHTML.appendNext(html, shouldScroll);
204 function replaceLastMessage(html){
205 var shouldScroll;
206 // only replace messages if we're already coalescing
207 if(coalescedHTML.isCoalescing){
208 if(undefined === coalescedHTML.shouldScroll) {
209 shouldScroll = nearBottom();
210 } else {
211 shouldScroll = coalescedHTML.shouldScroll;
213 coalescedHTML.replaceLast(html, shouldScroll);
214 } else {
215 shouldScroll = nearBottom();
216 //Retrieve the current insertion point, then remove it
217 //This requires that there have been an insertion point... is there a better way to retrieve the last element? -evands
218 var insert = document.getElementById("insert");
219 if(insert){
220 var parentNode = insert.parentNode;
221 parentNode.removeChild(insert);
222 var lastMessage = document.getElementById("Chat").lastChild;
223 document.getElementById("Chat").removeChild(lastMessage);
226 //Now append the message itself
227 appendHTML(html);
229 alignChat(shouldScroll);
233 //Auto-scroll to bottom. Use nearBottom to determine if a scrollToBottom is desired.
234 function nearBottom() {
235 return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) );
237 function scrollToBottom() {
238 document.body.scrollTop = document.body.offsetHeight;
241 //Dynamically exchange the active stylesheet
242 function setStylesheet( id, url ) {
243 var code = "<style id=\"" + id + "\" type=\"text/css\" media=\"screen,print\">";
244 if( url.length )
245 code += "@import url( \"" + url + "\" );";
246 code += "</style>";
247 var range = document.createRange();
248 var head = document.getElementsByTagName( "head" ).item(0);
249 range.selectNode( head );
250 var documentFragment = range.createContextualFragment( code );
251 head.removeChild( document.getElementById( id ) );
252 head.appendChild( documentFragment );
255 /* Converts emoticon images to textual emoticons; all emoticons in message if alt is held */
256 document.onclick = function imageCheck() {
257 var node = event.target;
258 if (node.tagName.toLowerCase() != 'img')
259 return;
261 imageSwap(node, false);
264 /* Converts textual emoticons to images if textToImagesFlag is true, otherwise vice versa */
265 function imageSwap(node, textToImagesFlag) {
266 var shouldScroll = nearBottom();
268 var images = [node];
269 if (event.altKey) {
270 while (node.id != "Chat" && node.parentNode.id != "Chat")
271 node = node.parentNode;
272 images = node.querySelectorAll(textToImagesFlag ? "a" : "img");
275 for (var i = 0; i < images.length; i++) {
276 textToImagesFlag ? textToImage(images[i]) : imageToText(images[i]);
279 alignChat(shouldScroll);
282 function textToImage(node) {
283 if (!node.getAttribute("isEmoticon"))
284 return;
285 //Swap the image/text
286 var img = document.createElement('img');
287 img.setAttribute('src', node.getAttribute('src'));
288 img.setAttribute('alt', node.firstChild.nodeValue);
289 img.className = node.className;
290 node.parentNode.replaceChild(img, node);
293 function imageToText(node)
295 if (client.zoomImage(node) || !node.alt)
296 return;
297 var a = document.createElement('a');
298 a.setAttribute('onclick', 'imageSwap(this, true)');
299 a.setAttribute('src', node.getAttribute('src'));
300 a.setAttribute('isEmoticon', true);
301 a.className = node.className;
302 var text = document.createTextNode(node.alt);
303 a.appendChild(text);
304 node.parentNode.replaceChild(a, node);
307 //Align our chat to the bottom of the window. If true is passed, view will also be scrolled down
308 function alignChat(shouldScroll) {
309 var windowHeight = window.innerHeight;
311 if (windowHeight > 0) {
312 var contentElement = document.getElementById('Chat');
313 var contentHeight = contentElement.offsetHeight;
314 if (windowHeight - contentHeight > 0) {
315 contentElement.style.position = 'relative';
316 contentElement.style.top = (windowHeight - contentHeight) + 'px';
317 } else {
318 contentElement.style.position = 'static';
322 if (shouldScroll) scrollToBottom();
325 window.onresize = function windowDidResize(){
326 alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs
327 window.onresize = coalescedHTML.onchange;
330 function initStyle() {
331 alignChat(true);
332 if(!coalescedHTML)
333 coalescedHTML = new CoalescedHTML();
336 /* Implementation of the CTRL-L command to clear the chat
337 * window. (Similar to a linux terminal)
339 function clearPage(){
340 removePage();
341 if (checkForAndRemoveInsertNodes()){
342 var interdiv = document.createElement('div');
343 interdiv.id = 'interleaving_page';
344 interdiv.style.paddingTop = window.innerHeight + 'px';
345 chat.appendChild(interdiv);
346 coalescedHTML.interdiv = interdiv;
350 function checkForAndRemoveInsertNodes() {
351 var inserts = chat.querySelectorAll("#insert");
352 if (inserts.length <= 0){
353 return false;
355 for (var n = 0; n < inserts.length-1; n++) {
356 var rm = inserts[n];
357 rm.parentNode.removeChild(rm);
359 return true;
362 function removePage(){
363 var interdivs = chat.querySelectorAll("#interleaving_page");
364 if (interdivs.length > 0){
365 var rm = interdivs[0];
366 rm.parentNode.removeChild(rm);
368 coalescedHTML.interdiv = undefined;
370 </script>
372 <style type="text/css">
373 .actionMessageUserName { display:none; }
374 .actionMessageBody:before { content:"*"; }
375 .actionMessageBody:after { content:"*"; }
376 * { word-wrap:break-word; text-rendering: optimizelegibility; }
377 img.scaledToFitImage { height: auto; max-width: 100%%; }
378 </style>
380 <!-- This style is shared by all variants. !-->
381 <style id="baseStyle" type="text/css" media="screen,print">
383 </style>
385 <!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !-->
386 <style id="mainStyle" type="text/css" media="screen,print">
387 @import url( "%@" );
388 </style>
390 </head>
391 <body onload="initStyle();" style="==bodyBackground==">
393 <div id="Chat">
394 </div>
396 </body>
397 </html>