";
51 | forEach(completions, function(c) { html += "
" + htmlEsc(c) + "
"; })
52 | setStatus(html + "
", true);
53 | }
54 | }
55 | }
56 | });
57 | connect(document.getElementById("statusclose"), "click", function(e) {setStatus("");});
58 | connect(document.body, "click", function(e) {
59 | var pclass = e.target.parentNode && e.target.parentNode.className;
60 | if (pclass == "names") whoIs(e.target.innerText);
61 | else if (e.target.className == "name") whoIs(e.target.innerText);
62 | else if (pclass == "completions")
63 | complete(Number(e.target.parentNode.getAttribute("data-start")), e.target.innerText);
64 | });
65 | connect(window, "focus", function() { winFocused = true; })
66 | connect(window, "blur", function() { winFocused = false; })
67 | connect(document.getElementById("loadday"), "click", function(){loadMore(1);});
68 | connect(document.getElementById("loadweek"), "click", function(){loadMore(7);});
69 | connect(document.body, "mouseover", function(e) {
70 | if (e.target.parentNode == output) {
71 | var n = e.target.appendChild(document.createElement("div"));
72 | n.className = "date";
73 | n.innerHTML = renderTime(timeFor(e.target.logLine));
74 | }
75 | });
76 | connect(document.body, "mouseout", function(e) {
77 | if (e.target.parentNode == output && e.target.lastChild.className == "date")
78 | e.target.removeChild(e.target.lastChild);
79 | });
80 | connect(window, "scroll", scrolled);
81 |
82 | fetchData();
83 | };
84 |
85 | function complete(start, text) {
86 | var end = input.selectionStart, val = input.value;
87 | input.value = val.slice(0, start) + text + " " + val.slice(end);
88 | var cur = start + text.length + 1;
89 | input.setSelectionRange(cur, cur);
90 | }
91 |
92 | var commands = {
93 | "msg": function(line) {
94 | var m = line.match(/^\s*(\S+)\s+(.+)$/);
95 | if (m) sendCommand("PRIVMSG", [m[1]], m[2]);
96 | },
97 | "me": function(line) {
98 | sendCommand("PRIVMSG", [channel], "\01ACTION " + line + "\01");
99 | },
100 | "whois": whoIs,
101 | "names": function() {
102 | var html = "";
103 | forEachIn(curState.names, function(name, present) {
104 | if (present) html += "
" + htmlEsc(name) + "
";
105 | });
106 | setStatus(html + "
");
107 | }
108 | };
109 | var winFocused = true;
110 |
111 | function whoIs(name) {
112 | startSend();
113 | getWhoIs(name.match(/^\s*(.*?)\s*$/)[1], function(info) {
114 | var nm = htmlEsc(name);
115 | stopSend();
116 | if (info == "") return setStatus("";
304 | if (newName) {
305 | state.prevName = from;
306 | html += "
" + htmlEsc(from) + "
";
307 | }
308 | var act = msg.match(/^\01ACTION (.*)\01$/);
309 | var msgHTML = act ? "
" + htmlEsc(act[1]) + "" : htmlEsc(msg);
310 | msgHTML = msgHTML.replace(new RegExp("\\b" + nick + "\\b", "gi"), function(match) {
311 | direct = true;
312 | return "
" + match + "";
313 | });
314 | msgHTML = msgHTML.replace(/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}\.|[a-z0-9.\-]+\.[a-z]{2,4}\/)(?:[^\s()<>]+)+[^\s`!()\[\]{};:'".,<>?])\b/g, function(url) {
315 | return "
" + url + "";
316 | });
317 | html += msgHTML + "
";
318 | scratchDiv.innerHTML = html;
319 | var node = scratchDiv.firstChild;
320 | node.logLine = line;
321 | if (direct && state.unread) {
322 | state.unread.push(node);
323 | if (state == curState) updateTitle();
324 | }
325 | return node;
326 | }
327 |
328 | if (type == "_" || type == ">") {
329 | if (type == ">") curState.lastDirect = name;
330 | return buildOutput(name, type == ">", type == ">", msg);
331 | } else if (type == "<") {
332 | return buildOutput("⇝" + name, true, false, msg);
333 | } else if (type == "+") {
334 | state.names[name] = true;
335 | } else if (type == "-") {
336 | state.names[name] = false;
337 | } else if (type == "x") {
338 | state.names[name] = false;
339 | state.names[msg] = true;
340 | }
341 | }
342 |
343 | function repaint() {
344 | output.innerHTML = "";
345 | for (var i = 0, e = knownHistory.length; i < e; ++i) {
346 | var node = processLine(curState, knownHistory[i]);
347 | if (node) output.appendChild(node);
348 | }
349 | }
350 |
351 | function updateTitle() {
352 | var msgs = curState.unread.length;
353 | document.title = channel + (msgs ? " (" + msgs + ")" : "");
354 | }
355 |
356 | function scrollTop() {
357 | return window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
358 | }
359 | function winHeight() {
360 | return window.innerHeight || document.documentElement.clientHeight;
361 | }
362 | function bodyHeight() {
363 | return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
364 | }
365 | function isScrolledToBottom() {
366 | return scrollTop() + winHeight() >= bodyHeight() - 2;
367 | }
368 |
369 | function timeAtScrollPos(at) {
370 | var lo = 0, hi = output.childNodes.length;
371 | var node = output.firstChild, pos = at + 12;
372 | if (!node) return 0;
373 | if (pos >= output.offsetTop + output.offsetHeight) {
374 | node = output.lastChild;
375 | } else if (pos > output.offsetTop) {
376 | while (true) {
377 | var mid = (lo + hi) >> 1;
378 | node = output.childNodes[mid];
379 | var top = node.offsetTop, bot = top + node.offsetHeight;
380 | if (top > pos) hi = mid;
381 | else if (bot >= pos || lo == mid) break;
382 | else lo = mid;
383 | }
384 | }
385 | return timeFor(node.logLine);
386 | }
387 | var maxScroll = 0, sendingScroll = false;
388 | function scrolled() {
389 | var scroll = scrollTop();
390 | // Clear seen messages from unread list
391 | var endVis = scroll + winHeight() - 12;
392 | while (curState.unread.length) {
393 | var msg = curState.unread[0];
394 | if (msg.offsetTop + msg.offsetHeight < endVis) {
395 | curState.unread.shift();
396 | updateTitle();
397 | } else break;
398 | }
399 | if (scroll > maxScroll + 50) {
400 | maxScroll = scroll;
401 | if (!sendingScroll) {
402 | sendingScroll = true;
403 | setTimeout(function send() {
404 | var time = timeAtScrollPos(maxScroll);
405 | setBookmark(time, false, function() {sendingScroll = false;}, function() {
406 | setTimeout(send, 10000);
407 | });
408 | }, 1000);
409 | }
410 | }
411 | }
412 |
413 | function addLines(lines) {
414 | var atBottom = isScrolledToBottom();
415 | if (!lines) return;
416 | lines = lines.split("\n");
417 | lines.pop();
418 | knownUpto = timeFor(lines[lines.length - 1]);
419 | for (var i = 0; i < lines.length; ++i) {
420 | var node = processLine(curState, lines[i]);
421 | if (node) output.appendChild(node);
422 | knownHistory.push(lines[i]);
423 | }
424 | if (atBottom && winFocused) window.scrollTo(0, document.body.scrollHeight);
425 | }
426 |
427 | var pollGeneration = 0, lastPoll;
428 | function poll() { poll_(pollGeneration, 2); }
429 | function poll_(generation, backOff) {
430 | lastPoll = new Date().getTime();
431 | var skip = 0;
432 | while (skip < knownHistory.length &&
433 | timeFor(knownHistory[knownHistory.length - 1 - skip]) == knownUpto)
434 | ++skip;
435 | getHistory(knownUpto, null, skip, function(lines) {
436 | if (pollGeneration != generation) return;
437 | addLines(lines);
438 | poll_(generation, 2);
439 | }, function(msg) {
440 | if (pollGeneration != generation) return;
441 | console.log("Polling failed: " + msg);
442 | var time = Math.min(backOff * 2, 30);
443 | setTimeout(function() {poll_(generation, time);}, time * 1000);
444 | });
445 | }
446 |
447 | // Try to notice when the computer has gone to sleep and resumed
448 | // again, which tends to kill long-polling requests, or some other
449 | // circumstance has messed with our polling.
450 | setInterval(function() {
451 | var now = new Date().getTime();
452 | if (now - lastPoll > 90000) {
453 | console.log("Resetting polling");
454 | ++pollGeneration;
455 | poll();
456 | }
457 | }, 60000);
458 |
--------------------------------------------------------------------------------