lila/public/javascripts/big.js
2014-12-23 22:45:37 +01:00

2336 lines
75 KiB
JavaScript

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
// declare now, populate later in a distinct script.
var lichess_translations = lichess_translations || [];
function withStorage(f) {
// can throw an exception when storage is full
try {
return !!window.localStorage ? f(window.localStorage) : null;
} catch (e) {}
}
var storage = {
get: function(k) {
return withStorage(function(s) {
return s.getItem(k);
});
},
remove: function(k) {
withStorage(function(s) {
s.removeItem(k);
});
},
set: function(k, v) {
// removing first may help http://stackoverflow.com/questions/2603682/is-anyone-else-receiving-a-quota-exceeded-err-on-their-ipad-when-accessing-local
withStorage(function(s) {
s.removeItem(k);
s.setItem(k, v);
});
}
};
(function() {
/////////////
// ctrl.js //
/////////////
$.userLink = function(u) {
return $.userLinkLimit(u, false);
};
$.userLinkLimit = function(u, limit, klass) {
var split = u.split(' ');
var id = split.length == 1 ? split[0] : split[1];
return (u || false) ? '<a class="user_link ulpt ' + (klass || '') + '" href="/@/' + id + '">' + ((limit || false) ? u.substring(0, limit) : u) + '</a>' : 'Anonymous';
};
$.redirect = function(obj) {
var url;
if (typeof obj == "string") url = obj;
else {
url = obj.url;
if (obj.cookie) {
var domain = document.domain.replace(/^.+(\.[^\.]+\.[^\.]+)$/, '$1');
var cookie = [
encodeURIComponent(obj.cookie.name) + '=' + obj.cookie.value,
'; max-age=' + obj.cookie.maxAge,
'; path=/',
'; domain=' + domain
].join('');
document.cookie = cookie;
}
}
location.href = 'http://' + location.hostname + '/' + url.replace(/^\//, '');
};
$.fp = {};
$.fp.range = function(to) {
return Array.apply(null, Array(to)).map(function(_, i) {
return i;
});
};
$.fp.contains = function(list, needle) {
return list.indexOf(needle) !== -1;
};
$.fp.find = function(list, pred) {
for (var i = 0, len = list.length; i < len; i++) {
if (pred(list[i])) return list[i];
}
return undefined;
};
$.fp.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var lichess = window.lichess = window.lichess || {};
lichess.socket = null;
lichess.idleTime = 20 * 60 * 1000;
$.extend(true, lichess.StrongSocket.defaults, {
events: {
following_onlines: function(us) {
$('#friend_box').friends("set", us);
},
following_enters: function(name) {
$('#friend_box').friends('enters', name);
},
following_leaves: function(name) {
$('#friend_box').friends('leaves', name);
},
n: function(e) {
var $tag = $('#nb_connected_players > strong');
if ($tag.length && e) {
var prev = parseInt($tag.text(), 10) || Math.max(0, (e - 10));
var k = 6;
var interv = lichess.socket.pingInterval() / k;
$.fp.range(k).forEach(function(it) {
setTimeout(function() {
var val = Math.round(((prev * (k - 1 - it)) + (e * (it + 1))) / k);
if (val != prev) {
$tag.text(val);
prev = val;
}
}, Math.round(it * interv));
});
}
},
message: function(msg) {
$('#chat').chat("append", msg);
},
nbm: function(e) {
$('#nb_messages').text(e || "0").toggleClass("unread", e > 0);
},
redirect: function(o) {
setTimeout(function() {
lichess.hasToReload = true;
$.redirect(o);
}, 300);
},
tournamentReminder: function(data) {
if (!$('#tournament_reminder').length && $('body').data("tournament-id") != data.id) {
$('#notifications').append(data.html).find("a.withdraw").click(function() {
$.post($(this).attr("href"));
$('#tournament_reminder').remove();
return false;
});
}
},
challengeReminder: function(data) {
if (!storage.get('challenge-refused-' + data.id)) {
var refreshButton = function() {
var nb = $('#challenge_notifications > div').length;
$('#nb_challenges').text(nb);
$('#challenge_notifications_tag').toggleClass('none', !nb);
};
var htmlId = 'challenge_reminder_' + data.id;
var $notif = $('#' + htmlId);
var declineListener = function($a, callback) {
return $a.click(function() {
$.post($(this).attr("href"));
storage.set('challenge-refused-' + data.id, 1);
$('#' + htmlId).remove();
if ($.isFunction(callback)) callback();
refreshButton();
return false;
});
};
if ($notif.length) clearTimeout($notif.data('timeout'));
else {
$('#challenge_notifications').append(data.html);
$notif = $('#' + htmlId);
declineListener($notif.find('a.decline'));
$('body').trigger('lichess.content_loaded');
if (!storage.get('challenge-' + data.id)) {
$('#top .challenge_notifications').addClass('shown');
$.sound.dong();
storage.set('challenge-' + data.id, 1);
}
refreshButton();
}
$('div.lichess_overboard.joining.' + data.id).each(function() {
if (!$(this).find('a.decline').length) $(this).find('form').append(
declineListener($(data.html).find('a.decline').text($.trans('decline')), function() {
location.href = "/";
})
);
});
$notif.data('timeout', setTimeout(function() {
$notif.remove();
refreshButton();
}, 3000));
}
},
deployPre: function(html) {
$('#notifications').append(html);
setTimeout(function() {
$('#deploy_pre').fadeOut(1000).remove();
}, 10000);
},
deployPost: function(html) {
$('#notifications').append(html);
setTimeout(function() {
$('#deploy_post').fadeOut(1000).remove();
}, 10000);
lichess.socket.disconnect();
}
},
params: {},
options: {
name: "site",
lagTag: $('#top .ping strong'),
debug: location.search.indexOf('debug-ws') != -1,
prodPipe: location.search.indexOf('prod-ws') != -1,
resetUrl: location.search.indexOf('reset-ws') != -1
}
});
lichess.hasToReload = false;
lichess.reload = function() {
lichess.hasToReload = true;
location.reload();
};
$(function() {
// small layout
function onResize() {
if ($(document.body).width() < 1000) {
$(document.body).addClass("tight");
$('#site_header .side_menu').prependTo('div.content_box:first');
} else {
$(document.body).removeClass("tight");
$('#timeline, div.side_box, div.under_chat').each(function() {
var ol = $(this).offset().left;
if (ol < 3) {
var dec = 3 - ol;
var pad = $(this).outerWidth() - $(this).width();
$(this).css({
'margin-left': (dec - 30) + 'px',
'width': (230 - pad - dec) + 'px'
});
$(this).find('input.lichess_say').css('width', (204 - dec) + 'px');
}
});
$('#featured_game').each(function() {
$(this).children().toggle($(this).width() >= 220);
});
$('div.content_box .side_menu').appendTo('#site_header');
}
}
$(window).resize(onResize);
onResize();
if (!lichess.StrongSocket.available) {
$('#lichess').on('mouseover', function() {
$('#lichess').off('mouseover');
var inUrFaceUrl = window.opera ? '/assets/opera-websocket.html' : '/assets/browser.html';
$.ajax(inUrFaceUrl, {
success: function(html) {
$('body').prepend(html);
}
});
});
}
$('#lichess').on('click', '.socket-link:not(.disabled)', function() {
lichess.socket.send($(this).data('msg'), $(this).data('data'));
});
$('#friend_box').friends();
$('#lichess').on('click', '.copyable', function() {
$(this).select();
});
$('body').on('click', '.relation_actions a.relation', function() {
var $a = $(this).addClass('processing');
$.ajax({
url: $a.attr('href'),
type: 'post',
success: function(html) {
$a.parent().html(html);
}
});
return false;
});
$('body').on('click', '.game_row', function() {
location.href = $(this).find('a.mini_board').attr('href');
});
function userPowertips() {
var header = document.getElementById('site_header');
$('.ulpt').removeClass('ulpt').each(function() {
$(this).powerTip({
fadeInTime: 100,
fadeOutTime: 100,
placement: $(this).data('placement') || ($.contains(header, this) ? 'e' : 'w'),
mouseOnToPopup: true,
closeDelay: 200
}).on({
powerTipPreRender: function() {
$.ajax({
url: ($(this).attr('href') || $(this).data('href')).replace(/\?.+$/, '') + '/mini',
success: function(html) {
$('#powerTip').html(html);
$('body').trigger('lichess.content_loaded');
}
});
}
}).data('powertip', ' ');
});
}
setTimeout(userPowertips, 600);
$('body').on('lichess.content_loaded', userPowertips);
$('#message_notifications_tag').on('click', function() {
$.ajax({
url: $(this).data('href'),
success: function(html) {
$('#message_notifications_display').html(html)
.find('a.mark_as_read').click(function() {
$.ajax({
url: $(this).attr('href'),
method: 'post'
});
$(this).parents('.notification').remove();
if ($('#message_notifications_display').children().length === 0)
$('#message_notifications_tag').click();
return false;
});
$('body').trigger('lichess.content_loaded');
}
});
});
function setMoment() {
$("time.moment").removeClass('moment').each(function() {
var parsed = moment(this.getAttribute('datetime'));
var format = this.getAttribute('data-format');
this.textContent = format == 'calendar' ? parsed.calendar() : parsed.format(format);
});
}
setMoment();
$('body').on('lichess.content_loaded', setMoment);
function setMomentFromNow() {
$("time.moment-from-now").each(function() {
this.textContent = moment(this.getAttribute('datetime')).fromNow();
});
}
setMomentFromNow();
$('body').on('lichess.content_loaded', setMomentFromNow);
setInterval(setMomentFromNow, 2000);
if ($('body').hasClass('blind_mode')) {
var setBlindMode = function() {
$('[data-hint]').each(function() {
$(this).attr('aria-label', $(this).data('hint'));
});
};
setBlindMode();
$('body').on('lichess.content_loaded', setBlindMode);
}
if (lichess.round) startRound(document.getElementById('lichess'), lichess.round);
else if (lichess.prelude) startPrelude(document.querySelector('.lichess_game'), lichess.prelude);
else if (lichess.analyse) startAnalyse(document.getElementById('lichess'), lichess.analyse);
else if (lichess.user_analysis) startUserAnalysis(document.getElementById('lichess'), lichess.user_analysis);
setTimeout(function() {
if (lichess.socket === null) {
lichess.socket = new lichess.StrongSocket("/socket", 0);
}
$.idleTimer(lichess.idleTime, lichess.socket.destroy.bind(lichess.socket), lichess.socket.connect.bind(lichess.socket));
}, 200);
// themepicker
$('#themepicker_toggle').one('click', function() {
var $body = $('body');
var $content = $body.children('.content');
var $themepicker = $('#themepicker');
var themes = $themepicker.data('themes').split(' ');
var theme = $.fp.find(document.body.classList, function(a) {
return $.fp.contains(themes, a);
});
var sets = $themepicker.data('sets').split(' ');
var set = $.fp.find(document.body.classList, function(a) {
return $.fp.contains(sets, a);
});
var theme3ds = $themepicker.data('theme3ds').split(' ');
var theme3d = $.fp.find(document.body.classList, function(a) {
return $.fp.contains(theme3ds, a);
});
var set3ds = $themepicker.data('set3ds').split(' ');
var set3d = $.fp.find(document.body.classList, function(a) {
return $.fp.contains(set3ds, a);
});
var background = $body.hasClass('dark') ? 'dark' : 'light';
var is3d = $content.hasClass('is3d');
$themepicker.find('.is2d div.theme').hover(function() {
$body.removeClass(themes.join(' ')).addClass($(this).data("theme"));
}, function() {
$body.removeClass(themes.join(' ')).addClass(theme);
}).click(function() {
theme = $(this).data("theme");
$.post($(this).parent().data("href"), {
theme: theme
});
$themepicker.removeClass("shown");
});
$themepicker.find('.is2d div.no-square').hover(function() {
$body.removeClass(sets.join(' ')).addClass($(this).data("set"));
}, function() {
$body.removeClass(sets.join(' ')).addClass(set);
}).click(function() {
set = $(this).data("set");
$.post($(this).parent().data("href"), {
set: set
});
$themepicker.removeClass("shown");
});
$themepicker.find('.is3d div.theme').hover(function() {
$body.removeClass(theme3ds.join(' ')).addClass($(this).data("theme"));
}, function() {
$body.removeClass(theme3ds.join(' ')).addClass(theme3d);
}).click(function() {
theme3d = $(this).data("theme");
$.post($(this).parent().data("href"), {
theme: theme3d
});
$themepicker.removeClass("shown");
});
$themepicker.find('.is3d div.no-square').hover(function() {
$body.removeClass(set3ds.join(' ')).addClass($(this).data("set"));
}, function() {
$body.removeClass(set3ds.join(' ')).addClass(set3d);
}).click(function() {
set3d = $(this).data("set");
$.post($(this).parent().data("href"), {
set: set3d
});
$themepicker.removeClass("shown");
});
var showBg = function(bg) {
$body.removeClass('light dark').addClass(bg);
if (bg == 'dark' && $('link[href*="dark.css"]').length === 0) {
$('link[href*="common.css"]').clone().each(function() {
$(this).attr('href', $(this).attr('href').replace(/common\.css/, 'dark.css')).appendTo('head');
});
}
};
var showDimensions = function(is3d) {
$content.add('#top').removeClass('is2d is3d').addClass(is3d ? 'is3d' : 'is2d');
setZoom(getZoom());
};
$themepicker.find('.background a').click(function() {
background = $(this).data('bg');
$.post($(this).parent().data('href'), {
bg: background
});
$(this).addClass('active').siblings().removeClass('active');
$themepicker.removeClass("shown");
return false;
}).hover(function() {
showBg($(this).data('bg'));
}, function() {
showBg(background);
}).filter('.' + background).addClass('active');
$themepicker.find('.dimensions a').click(function() {
is3d = $(this).data('is3d');
$.post($(this).parent().data('href'), {
is3d: is3d
});
$(this).addClass('active').siblings().removeClass('active');
$themepicker.removeClass("shown");
return false;
}).hover(function() {
showDimensions($(this).data('is3d'));
}, function() {
showDimensions(is3d);
}).filter('.' + (is3d ? 'd3' : 'd2')).addClass('active');
$themepicker.find('.slider').slider({
orientation: "horizontal",
min: 1,
max: 2,
range: 'min',
step: 0.01,
value: getZoom(),
slide: function(e, ui) {
manuallySetZoom(ui.value);
}
});
});
// Zoom
var getZoom = function() {
return storage.get('zoom') || 1;
};
var setZoom = function(v) {
storage.set('zoom', v);
var $boardWrap = $(".lichess_game .cg-board-wrap");
var $lichessGame = $(".lichess_game");
var px = function(i) {
return Math.round(i) + 'px';
};
$boardWrap.css("width", px(512 * getZoom()));
$('.underboard').css("margin-left", px((getZoom() - 1) * 250));
$('.lichess_game > .lichess_overboard').css("left", px(56 + (getZoom() - 1) * 254));
if ($('body > .content').hasClass('is3d')) {
$boardWrap.css("height", px(479.08572 * getZoom()));
$lichessGame.css({
height: px(479.08572 * getZoom()),
paddingTop: px(50 * (getZoom() - 1))
});
$('#tv_history > .content').css("height", px(250 + 540 * (getZoom() - 1)));
$('.chat_panels').css("height", px(290 + 529 * (getZoom() - 1)));
} else {
$boardWrap.css("height", px(512 * getZoom()));
$lichessGame.css({
height: px(512 * getZoom()),
paddingTop: px(0)
});
$('#tv_history > .content').css("height", px(270 + 525 * (getZoom() - 1)));
$('.chat_panels').css("height", px(325 + 510 * (getZoom() - 1)));
}
if ($lichessGame.length) {
// if on a board with a game
$('body > .content').css("margin-left", 'calc(50% - ' + px(246.5 + 256 * getZoom()) + ')');
}
};
var manuallySetZoom = $.fp.debounce(setZoom, 10);
var initialZoom = getZoom();
if (initialZoom > 1) setZoom(initialZoom); // Instantiate the page's zoom
function translateTexts() {
$('.trans_me').each(function() {
$(this).removeClass('trans_me');
if ($(this).val()) $(this).val($.trans($(this).val()));
else $(this).text($.trans($(this).text()));
});
}
translateTexts();
$('body').on('lichess.content_loaded', translateTexts);
$('input.autocomplete').each(function() {
var $a = $(this);
$a.autocomplete({
source: $a.data('provider'),
minLength: 2,
delay: 100
});
});
$('.infinitescroll:has(.pager a)').each(function() {
$(this).infinitescroll({
navSelector: ".pager",
nextSelector: ".pager a:last",
itemSelector: ".infinitescroll .paginated_element",
errorCallback: function() {
$("#infscr-loading").remove();
}
}, function() {
$("#infscr-loading").remove();
$('body').trigger('lichess.content_loaded');
}).find('div.pager').hide();
});
$('#top a.toggle').each(function() {
var $this = $(this);
var $p = $this.parent();
$this.click(function() {
$p.toggleClass('shown');
$p.siblings('.shown').removeClass('shown');
setTimeout(function() {
$p.click(function(e) {
e.stopPropagation();
});
$('html').one('click', function(e) {
$p.removeClass('shown').off('click');
});
}, 10);
return false;
});
});
var acceptLanguages = $('body').data('accept-languages');
if (acceptLanguages) {
$('#top .lichess_language').one('mouseover', function() {
var $links = $(this).find('.language_links'),
langs = acceptLanguages.split(',');
$.ajax({
url: $links.data('url'),
success: function(list) {
$links.prepend(list.map(function(lang) {
var href = location.href.replace(/\/\/\w+\./, '//' + lang[0] + '.');
var klass = $.fp.contains(langs, lang[0]) ? 'class="accepted"' : '';
return '<li><a ' + klass + ' lang="' + lang[0] + '" href="' + href + '">' + lang[1] + '</a></li>';
}).join(''));
}
});
});
}
$('#incomplete_translation a.close').one('click', function() {
$(this).parent().remove();
});
$('#translation_call .close').click(function() {
$.post($(this).data("href"));
$(this).parent().fadeOut(500);
return false;
});
$('a.delete, input.delete').click(function() {
return confirm('Delete?');
});
$('input.confirm, button.confirm').click(function() {
return confirm('Confirm this action?');
});
$('div.content').on('click', 'a.bookmark', function() {
var t = $(this).toggleClass("bookmarked");
$.post(t.attr("href"));
var count = (parseInt(t.text(), 10) || 0) + (t.hasClass("bookmarked") ? 1 : -1);
t.find('span').html(count > 0 ? ' ' + count : "");
return false;
});
$("#import_game form").submit(function() {
var pgn = $(this).find('textarea').val();
var nbMoves = parseInt(pgn.replace(/\n/g, ' ').replace(/^.+\s(\d+)\..+$/, '$1'), 10);
var delay = 50;
var duration = nbMoves * delay * 2.1 + 1000;
$(this).find('button').hide().end()
.find('.error').hide().end()
.find('.progression').show().animate({
width: '100%'
}, duration);
return true;
});
setInterval(function() {
$('.glowing').toggleClass('glow');
}, 1500);
});
$.sound = (function() {
var baseUrl = $('body').data('sound-dir') + '/';
var a = new Audio();
var hasOgg = !!a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"');
var hasMp3 = !!a.canPlayType && a.canPlayType('audio/mpeg;');
var ext = hasOgg ? 'ogg' : 'mp3';
var audio = {
dong: new Audio(baseUrl + 'dong2.' + ext),
moveW: new Audio(baseUrl + 'move3.' + ext),
moveB: new Audio(baseUrl + 'move3.' + ext),
take: new Audio(baseUrl + 'take2.' + ext),
lowtime: new Audio(baseUrl + 'lowtime.' + ext)
};
var volumes = {
lowtime: 0.6
};
var canPlay = hasOgg || hasMp3;
var $control = $('#sound_control');
var $toggle = $('#sound_state');
$control.add($toggle).toggleClass('sound_state_on', storage.get('sound') == 1);
var enabled = function() {
return $toggle.hasClass("sound_state_on");
};
var shouldPlay = function() {
return canPlay && enabled();
};
var play = {
move: function(white) {
if (shouldPlay()) {
if (white) audio.moveW.play();
else audio.moveB.play();
}
},
take: function() {
if (shouldPlay()) audio.take.play();
},
dong: function() {
if (shouldPlay()) audio.dong.play();
},
lowtime: function() {
if (shouldPlay()) audio.lowtime.play();
}
};
var getVolume = function() {
return storage.get('sound-volume') || 0.8;
};
var setVolume = function(v) {
storage.set('sound-volume', v);
Object.keys(audio).forEach(function(k) {
audio[k].volume = v * (volumes[k] ? volumes[k] : 1);
});
};
var manuallySetVolume = $.fp.debounce(function(v) {
setVolume(v);
play.move(true);
}, 100);
setVolume(getVolume());
if (canPlay) {
$toggle.click(function() {
$control.add($toggle).toggleClass('sound_state_on', !enabled());
if (enabled()) storage.set('sound', 1);
else storage.remove('sound');
play.dong();
return false;
});
$toggle.one('mouseover', function() {
$toggle.parent().find('.slider').slider({
orientation: "vertical",
min: 0,
max: 1,
range: 'min',
step: 0.01,
value: getVolume(),
slide: function(e, ui) {
manuallySetVolume(ui.value);
}
});
});
} else $toggle.addClass('unavailable');
return play;
})();
$.fn.orNot = function() {
return this.length === 0 ? false : this;
};
$.trans = function() {
var str = lichess_translations[arguments[0]];
if (!str) return arguments[0];
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
str = str.replace('%s', arg);
});
return str;
};
function urlToLink(text) {
var exp = /\bhttp:\/\/(?:[a-z]{0,3}\.)?(lichess\.org[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
return text.replace(exp, "<a href='http://$1'>$1</a>");
}
var startTournamentClock = function() {
$("div.game_tournament div.clock").each(function() {
$(this).clock({
time: $(this).data("time"),
showTenths: false
}).clock("start");
});
};
/////////////
// game.js //
/////////////
function startRound(element, cfg) {
var data = cfg.data;
if (data.chat) $('#chat').chat({
messages: data.chat,
initialNote: data.note,
gameId: data.game.id
});
var $watchers = $('#site_header div.watchers').watchers();
var $nowPlaying = $('#now_playing');
var round;
if (data.tournament) $('body').data('tournament-id', data.tournament.id);
lichess.socket = new lichess.StrongSocket(
data.url.socket,
data.player.version, {
options: {
name: "round"
},
params: {
ran: "--ranph--",
userTv: $('.user_tv').data('user-tv')
},
receive: function(t, d) {
round.socketReceive(t, d);
},
events: {
crowd: function(e) {
$watchers.watchers("set", e.watchers);
},
featured: function(o) {
if (data.tv) lichess.reload();
},
end: function() {
var url = (data.tv ? cfg.routes.Tv.side : cfg.routes.Round.sideWatcher)(data.game.id, data.player.color).url;
$.get(url, function(html) {
$('#site_header div.side').replaceWith(html);
$('body').trigger('lichess.content_loaded');
startTournamentClock();
});
},
checkCount: function(e) {
$('div.check_count')
.find('.white').text(e.black).end()
.find('.black').text(e.white);
},
opponent_play: function(e) {
$.ajax({
url: $nowPlaying.data('reload-url'),
success: function(html) {
$nowPlaying.html(html);
$('body').trigger('lichess.content_loaded');
loadPlaying();
var nextId = $nowPlaying.find('input.next_id').val();
if (nextId) round.moveOn.next(nextId);
}
});
}
}
});
cfg.element = element.querySelector('.round');
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
round = LichessRound(cfg);
startTournamentClock();
$('.crosstable', element).prependTo($('.underboard .center', element)).show();
$('#tv_history').on("click", "tr", function() {
location.href = $(this).find('a.view').attr('href');
});
var loadPlaying = function() {
var $moveOn = $nowPlaying.find('.move_on').click(function() {
setMoveOn(round.moveOn.toggle());
});
var setMoveOn = function(value) {
$moveOn.toggleClass('enabled', value);
};
setMoveOn(round.moveOn.get());
};
loadPlaying();
$nowPlaying.on('click', '>a', function() {
lichess.hasToReload = true;
return true;
});
}
function startPrelude(element, cfg) {
var data = cfg.data;
lichess.socket = new lichess.StrongSocket(
data.url.socket,
data.player.version, {
options: {
name: "prelude"
},
params: {
ran: "--ranph--"
},
events: {
declined: function() {
$('#challenge_await').remove();
$('#challenge_declined').show();
}
}
});
Chessground(element.querySelector('.lichess_board'), {
viewOnly: true,
fen: data.game.fen,
orientation: data.player.color,
check: data.game.check,
coordinates: data.pref.coords !== 0,
highlight: {
check: data.pref.highlight
}
});
setTimeout(function() {
$('.lichess_overboard_wrap', element).addClass('visible');
}, 100);
$('#challenge_await').each(function() {
setInterval(function() {
$('#challenge_await').each(function() {
lichess.socket.send('challenge', $(this).data('user'));
});
}, 1500);
});
}
$.widget("lichess.watchers", {
_create: function() {
this.list = this.element.find("span.list");
this.number = this.element.find("span.number");
},
set: function(users) {
var self = this;
if (users.length > 0) {
self.list.html(users.map(function(u) {
return u.indexOf('(') === -1 ? $.userLink(u) : u.replace(/\s\(1\)/, '');
}).join(", "));
var nb = 0;
users.forEach(function(u) {
nb += (u.indexOf('(') === -1 ? 1 : parseInt(u.replace(/^.+\((\d+)\)$/, '$1')));
});
self.number.html(nb);
self.element.show();
} else {
self.element.hide();
}
}
});
$.widget("lichess.friends", {
_create: function() {
var self = this;
self.$list = self.element.find("div.list");
self.$title = self.element.find('.title').click(function() {
self.element.find('.content_wrap').toggle(100, function() {
storage.set('friends-hide', $(this).is(':visible') ? 0 : 1);
});
});
if (storage.get('friends-hide') == 1) self.$title.click();
self.$nbOnline = self.$title.find('.online');
self.$nobody = self.element.find("div.nobody");
self.set(self.element.data('preload').split(','));
},
repaint: function() {
this.users = $.unique(this.users).filter(function(u) {
return u !== '';
});
this.$nbOnline.text(this.users.length);
this.$nobody.toggle(this.users.length === 0);
this.$list.html(this.users.map(this._renderUser).join(""));
$('body').trigger('lichess.content_loaded');
},
set: function(us) {
this.users = us || [];
this.repaint();
},
enters: function(user) {
this.users.push(user);
this.repaint();
},
leaves: function(user) {
this.users = this.users.filter(function(u) {
return u != user
});
this.repaint();
},
_renderUser: function(user) {
var id = $.fp.contains(user, ' ') ? user.split(' ')[1] : user;
return '<a class="ulpt" data-placement="nw" href="/@/' + id + '">' + user + '</a>';
}
});
$.widget("lichess.chat", {
_create: function() {
this.options = $.extend({
messages: [],
initialNote: '',
gameId: null
}, this.options);
var self = this;
self.$msgs = self.element.find('.messages');
self.$msgs.on('click', 'a', function() {
$(this).attr('target', '_blank');
});
var $form = self.element.find('form');
var $input = self.element.find('input.lichess_say');
var $parent = self.element.parent();
// send a message
$form.submit(function() {
var text = $.trim($input.val());
if (!text) return false;
if (text.length > 140) {
alert('Max length: 140 chars. ' + text.length + ' chars used.');
return false;
}
$input.val('');
lichess.socket.send('talk', text);
return false;
});
self.element.find('a.send').click(function() {
$input.trigger('click');
$form.submit();
});
// toggle the chat
var $toggle = $parent.find('input.toggle_chat');
$toggle.change(function() {
var enabled = $toggle.is(':checked');
self.element.toggleClass('hidden', !enabled);
if (!enabled) storage.set('nochat', 1);
else storage.remove('nochat');
});
$toggle[0].checked = storage.get('nochat') != 1;
if (!$toggle[0].checked) {
self.element.addClass('hidden');
}
if (self.options.messages.length > 0) self._appendMany(self.options.messages);
// Toggle Notes/Chat display
$panels = self.element.find('div.chat_panels > div');
$parent.find('.chat_menu').on('click', 'a', function() {
var panel = $(this).data('panel');
$(this).siblings('.active').removeClass('active').end().addClass('active');
$panels.removeClass('active').filter('.' + panel).addClass('active');
}).find('a:first').click();
$notes = self.element.find('.notes textarea');
if (self.options.gameId && $notes.length) {
$notes.on('change keyup paste', $.fp.debounce(function() {
$.post('/' + self.options.gameId + '/note', {
text: $notes.val()
});
}, 1000));
$notes.val(self.options.initialNote || '');
}
},
append: function(msg) {
this._appendHtml(this._render(msg));
},
_appendMany: function(objs) {
var self = this,
html = "";
$.each(objs, function() {
html += self._render(this);
});
self._appendHtml(html);
},
_render: function(msg) {
var user, sys = false;
if (msg.c) {
user = '<span class="color">[' + msg.c + ']</span>';
} else if (msg.u == 'lichess') {
sys = true;
user = '<span class="system"></span>';
} else {
user = '<span class="user">' + $.userLinkLimit(msg.u, 14) + '</span>';
}
return '<li class="' + (sys ? 'system trans_me' : '') + (msg.r ? ' troll' : '') + '">' + user + urlToLink(msg.t) + '</li>';
},
_appendHtml: function(html) {
if (!html) return;
this.$msgs.append(html);
$('body').trigger('lichess.content_loaded');
this.$msgs.scrollTop(999999);
}
});
$.widget("lichess.clock", {
_create: function() {
var o = this.options;
this.options.time = parseFloat(this.options.time) * 1000;
this.options.barTime = parseFloat(this.options.barTime) * 1000;
this.options.emerg = parseFloat(this.options.emerg) * 1000;
$.extend(this.options, {
state: 'ready'
});
this.$time = this.element.find('>div.time');
this.$bar = this.element.find('>div.bar>span');
this._show();
},
destroy: function() {
this.stop();
$.Widget.prototype.destroy.apply(this);
},
start: function() {
var self = this;
self.options.state = 'running';
self.element.addClass('running');
var end_time = new Date().getTime() + self.options.time;
self.options.interval = setInterval(function() {
if (self.options.state == 'running') {
var current_time = Math.round(end_time - new Date().getTime());
if (current_time <= 0) {
clearInterval(self.options.interval);
current_time = 0;
}
self.options.time = current_time;
self._show();
//If the timer completed, fire the buzzer callback
if (current_time === 0 && $.isFunction(self.options.buzzer)) self.options.buzzer(self.element);
} else {
clearInterval(self.options.interval);
}
},
100);
},
setTime: function(time) {
this.options.time = parseFloat(time) * 1000;
this._show();
},
getSeconds: function() {
return Math.round(this.options.time / 1000);
},
stop: function() {
clearInterval(this.options.interval);
this.options.state = 'stop';
this.element.removeClass('running');
this.element.toggleClass('outoftime', this.options.time <= 0);
},
_show: function() {
var html = this._formatDate(new Date(this.options.time));
if (html != this.$time.html()) {
this.$time.html(html);
this.element.toggleClass('emerg', this.options.time < this.options.emerg);
}
if (this.options.showBar) {
var barWidth = Math.max(0, Math.min(100, (this.options.time / this.options.barTime) * 100));
this.$bar.css('width', barWidth + '%');
}
},
_formatDate: function(date) {
var minutes = this._prefixInteger(date.getUTCMinutes(), 2);
var seconds = this._prefixInteger(date.getSeconds(), 2);
var b = function(x) {
return '<b>' + x + '</b>';
};
if (this.options.showTenths && this.options.time < 10000) {
tenths = Math.floor(date.getMilliseconds() / 100);
return b(minutes) + ':' + b(seconds) + '<span>.' + b(tenths) + '</span>';
} else if (this.options.time >= 3600000) {
var hours = this._prefixInteger(date.getUTCHours(), 2);
return b(hours) + ':' + b(minutes) + ':' + b(seconds);
} else {
return b(minutes) + ':' + b(seconds);
}
},
_prefixInteger: function(num, length) {
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}
});
/////////////////
// gamelist.js //
/////////////////
$(function() {
parseFen();
$('body').on('lichess.content_loaded', parseFen);
var socketOpened = false;
function startWatching() {
if (!socketOpened) return;
var ids = [];
$('.mini_board.live').removeClass("live").each(function() {
ids.push(this.getAttribute("data-live"));
});
if (ids.length > 0) {
lichess.socket.send("startWatching", ids.join(" "));
}
}
$('body').on('lichess.content_loaded', startWatching);
$('body').on('socket.open', function() {
socketOpened = true;
startWatching();
});
setTimeout(function() {
$('div.checkmateCaptcha').each(function() {
var $captcha = $(this);
var $board = $captcha.find('.mini_board');
var $input = $captcha.find('input').val('');
var color = $board.data('color');
$board.data('chessground').set({
orientation: color,
turnColor: color,
movable: {
free: false,
dests: $board.data('moves'),
color: color,
coordinates: false,
events: {
after: function(orig, dest) {
$captcha.removeClass("success failure");
submit(orig + ' ' + dest);
}
}
}
});
var submit = function(solution) {
$input.val(solution);
$.ajax({
url: $captcha.data('check-url'),
data: {
solution: solution
},
success: function(data) {
$captcha.toggleClass('success', data == 1);
$captcha.toggleClass('failure', data != 1);
if (data == 1) $board.data('chessground').stop();
else setTimeout(function() {
parseFen($board);
$board.data('chessground').set({
turnColor: color,
movable: {
dests: $board.data('moves')
}
});
}, 300);
}
});
};
});
}, 100);
});
////////////////
// lobby.js //
////////////////
$(function() {
var $startButtons = $('#start_buttons');
if (!lichess.StrongSocket.available) {
$startButtons.find('a').attr('href', '#');
$("div.lichess_overboard.joining input.submit").remove();
return;
}
if (!$startButtons.length) {
return;
}
function sliderTime(v) {
if (v <= 20) return v;
switch (v) {
case 21:
return 25;
case 22:
return 30;
case 23:
return 35;
case 24:
return 40;
case 25:
return 45;
case 26:
return 60;
case 27:
return 90;
case 28:
return 120;
case 29:
return 150;
default:
return 180;
}
}
function sliderDays(v) {
if (v <= 3) return v;
switch (v) {
case 4:
return 5;
case 5:
return 7;
case 6:
return 10;
default:
return 14;
}
}
function prepareForm() {
var $form = $('div.lichess_overboard');
var $timeModeSelect = $form.find('#timeMode');
var $modeChoicesWrap = $form.find('.mode_choice');
var $modeChoices = $modeChoicesWrap.find('input');
var $casual = $modeChoices.eq(0),
$rated = $modeChoices.eq(1);
var $variantSelect = $form.find('#variant');
var $fenPosition = $form.find(".fen_position");
var $timeInput = $form.find('.time_choice input');
var $incrementInput = $form.find('.increment_choice input');
var $daysInput = $form.find('.days_choice input');
var isHook = $form.hasClass('game_config_hook');
var $ratings = $form.find('.ratings > div');
var toggleButtons = function() {
var timeMode = $timeModeSelect.val();
var rated = $rated.prop('checked');
var timeOk = timeMode != '1' || $timeInput.val() > 0 || $incrementInput.val() > 0;
var ratedOk = !isHook || !rated || timeMode != '0';
$form.find('.color_submits button').toggle(timeOk && ratedOk);
};
var showRating = function() {
var timeMode = $timeModeSelect.val();
var key;
switch ($variantSelect.val()) {
case '1':
if (timeMode == '1') {
var time = $timeInput.val() * 60 + $incrementInput.val() * 30;
if (time < 180) key = 'bullet';
else if (time < 480) key = 'blitz';
else key = 'classical';
} else key = 'correspondence';
break;
case '2':
key = 'chess960';
break;
case '4':
key = 'kingOfTheHill';
break;
case '5':
key = 'threeCheck';
}
$ratings.hide().filter('.' + key).show();
};
if (isHook) {
var $formTag = $form.find('form');
if ($form.data('anon')) {
$timeModeSelect.val(1)
.children('.timeMode_2, .timeMode_0')
.prop('disabled', true)
.attr('title', $.trans('You need an account to do that'));
}
var ajaxSubmit = function(color) {
$.ajax({
url: $formTag.attr('action').replace(/uid-placeholder/, lichess.StrongSocket.sri),
data: $formTag.serialize() + "&color=" + color,
type: 'post'
});
$form.find('a.close').click();
if ($timeModeSelect.val() == '1')
$('#hooks_wrap .tabs .real_time').click();
else
$('#hooks_wrap .tabs .seeks').click();
return false;
};
$formTag.find('.color_submits button').click(function() {
return ajaxSubmit($(this).val());
});
$formTag.submit(function() {
return ajaxSubmit('random');
});
}
$form.find('div.buttons').buttonset().disableSelection();
$form.find('button.submit').button().disableSelection();
$timeInput.add($incrementInput).each(function() {
var $input = $(this),
$value = $input.siblings('span');
$input.hide().after($('<div>').slider({
value: $input.val(),
min: 0,
max: 30,
range: 'min',
step: 1,
slide: function(event, ui) {
var time = sliderTime(ui.value);
$value.text(time);
$input.attr('value', time);
showRating();
toggleButtons();
}
}));
});
$daysInput.each(function() {
var $input = $(this),
$value = $input.siblings('span');
$input.hide().after($('<div>').slider({
value: $input.val(),
min: 1,
max: 7,
range: 'min',
step: 1,
slide: function(event, ui) {
var days = sliderDays(ui.value);
$value.text(days);
$input.attr('value', days);
}
}));
});
$form.find('.rating_range').each(function() {
var $this = $(this);
var $input = $this.find("input");
var $span = $this.siblings("span.range");
var min = $input.data("min");
var max = $input.data("max");
var values = $input.val() ? $input.val().split("-") : [min, max];
$span.text(values.join(' - '));
$this.slider({
range: true,
min: min,
max: max,
values: values,
step: 50,
slide: function(event, ui) {
$input.val(ui.values[0] + "-" + ui.values[1]);
$span.text(ui.values[0] + " - " + ui.values[1]);
}
});
var $ratingRangeConfig = $this.parent();
$modeChoices.add($form.find('.members_only input')).on('change', function() {
var rated = $rated.prop('checked');
var membersOnly = $form.find('.members_only input').prop('checked');
$ratingRangeConfig.toggle(rated || membersOnly);
$form.find('.members_only').toggle(!rated);
toggleButtons();
}).trigger('change');
});
$timeModeSelect.on('change', function() {
var timeMode = $(this).val();
$form.find('.time_choice, .increment_choice').toggle(timeMode == '1');
$form.find('.days_choice').toggle(timeMode == '2');
toggleButtons();
showRating();
}).trigger('change');
var $ratingRangeConfig = $form.find('.rating_range_config');
var $fenInput = $fenPosition.find('input');
var validateFen = $.fp.debounce(function() {
$fenInput.removeClass("success failure");
var fen = $fenInput.val();
if (fen) {
$.ajax({
url: $fenInput.parent().data('validate-url'),
data: {
fen: fen
},
success: function(data) {
$fenInput.addClass("success");
$fenPosition.find('.preview').html(data);
$fenPosition.find('a.board_editor').each(function() {
$(this).attr('href', $(this).attr('href').replace(/editor\/.+$/, "editor/" + fen));
});
$('body').trigger('lichess.content_loaded');
},
error: function() {
$fenInput.addClass("failure");
$fenPosition.find('.preview').html("");
}
});
}
}, 500);
$fenInput.on('keyup', validateFen);
$variantSelect.on('change', function() {
var fen = $(this).val() == '3';
if (fen && $fenInput.val() !== '') validateFen();
$fenPosition.toggle(fen);
$modeChoicesWrap.toggle(!fen);
if (fen) $casual.click();
showRating();
}).trigger('change');
$form.find('div.level').each(function() {
var $infos = $(this).find('.ai_info > div');
$(this).find('label').mouseenter(function() {
$infos.hide().filter('.' + $(this).attr('for')).show();
});
$(this).find('#config_level').mouseleave(function() {
var level = $(this).find('input:checked').val();
$infos.hide().filter('.level_' + level).show();
}).trigger('mouseout');
});
$form.find('a.close.icon').click(function() {
$form.remove();
$startButtons.find('a.active').removeClass('active');
return false;
});
}
$startButtons.find('a').click(function() {
$(this).addClass('active').siblings().removeClass('active');
$('div.lichess_overboard').remove();
$.ajax({
url: $(this).attr('href'),
success: function(html) {
$('div.lichess_overboard').remove();
$('#hooks_wrap').prepend(html);
prepareForm();
$('body').trigger('lichess.content_loaded');
}
});
return false;
});
$('#lichess').on('submit', 'form', $.lichessOpeningPreventClicks);
if (window.location.hash) {
$startButtons
.find('a.config_' + location.hash.replace(/#/, ''))
.each(function() {
$(this).attr("href", $(this).attr("href") + location.search);
}).click();
}
});
$.lichessOpeningPreventClicks = function() {
$('#hooks_list, #hooks_chart').hide();
};
// hooks
$(function() {
var $wrap = $('#hooks_wrap');
if (!$wrap.length) return;
if (!lichess.StrongSocket.available) return;
var socketUrl = $wrap.data('socket-url');
var $nowPlaying = $('#now_playing');
var $seeks = $('#seeks');
var $timeline = $("#timeline");
var $newposts = $("div.new_posts");
var $realTime = $('#real_time');
var $canvas = $wrap.find('.canvas');
var $hooksList = $wrap.find('#hooks_list');
var $tableWrap = $hooksList.find('div.table_wrap');
var $table = $tableWrap.find('table').sortable();
var $tbody = $table.find('tbody');
var animation = 500;
var hookPool = [];
var nextHooks = [];
var flushHooksTimeout;
var flushHooksSchedule = function() {
flushHooksTimeout = setTimeout(flushHooks, 8000);
};
var flushHooks = function() {
clearTimeout(flushHooksTimeout);
$tbody.fadeIn(500);
$tableWrap.clone().attr('id', 'tableclone').appendTo($hooksList).fadeOut(500, function() {
$(this).remove();
});
$tbody.find('tr.disabled').remove();
$tbody.append(nextHooks);
nextHooks = [];
$table.trigger('sortable.sort');
flushHooksSchedule();
};
flushHooksSchedule();
$('body').on('lichess.hook-flush', flushHooks);
$wrap.on('click', '>div.tabs>a', function() {
var tab = $(this).data('tab');
$(this).siblings().removeClass('active').end().addClass('active');
$wrap.find('>.tab').hide().filter('.' + tab).show();
storage.set('lobbytab', tab);
reloadSeeksIfVisible();
});
var active = storage.get('lobbytab');
if (['real_time', 'seeks', 'now_playing'].indexOf(active) === -1) active = 'real_time';
if (!$wrap.find('>div.tabs>.' + active).length) active = 'real_time';
$wrap.find('>div.tabs>.' + active).addClass('active');
$wrap.find('>.' + active).show();
$realTime.on('click', '.toggle', function() {
var mode = $(this).data('mode');
$realTime.children().hide().filter('#hooks_' + mode).show();
storage.set('lobbymode', mode);
});
var mode = storage.get('lobbymode') || 'list';
$('#hooks_' + mode).show();
$wrap.find('a.filter').click(function() {
var $a = $(this);
var $div = $wrap.find('#hook_filter');
setTimeout(function() {
$div.click(function(e) {
e.stopPropagation();
});
$('html').one('click', function(e) {
$div.off('click').removeClass('active');
$a.removeClass('active');
});
}, 10);
if ($(this).toggleClass('active').hasClass('active')) {
$div.addClass('active');
if ($div.is(':empty')) {
$.ajax({
url: $(this).attr('href'),
success: function(html) {
var save = $.fp.debounce(function() {
var $form = $div.find('form');
$.ajax({
url: $form.attr('action'),
data: $form.serialize(),
type: 'post',
success: function(filter) {
lichess_preload.filter = filter;
drawHooks();
$('body').trigger('lichess.hook-flush');
}
});
}, 500);
$div.html(html).find('input').change(save);
$div.find('button.reset').click(function() {
$div.find('label input').prop('checked', true).trigger('change');
$div.find('.rating_range').each(function() {
var s = $(this);
s.slider('values', [s.slider('option', 'min'), s.slider('option', 'max')]).trigger('change');
});
});
$div.find('button').click(function() {
$wrap.find('a.filter').click();
return false;
});
$div.find('.rating_range').each(function() {
var $this = $(this);
var $input = $this.find("input");
var $span = $this.siblings(".range");
var min = $input.data("min");
var max = $input.data("max");
var values = $input.val() ? $input.val().split("-") : [min, max];
$span.text(values.join(' - '));
function change() {
setTimeout(function() {
var values = $this.slider('values');
$input.val(values[0] + "-" + values[1]);
$span.text(values[0] + " - " + values[1]);
save();
}, 50);
}
$this.slider({
range: true,
min: min,
max: max,
values: values,
step: 50,
slide: change
}).change(change);
});
}
});
}
} else {
$div.removeClass('active');
}
return false;
});
function resizeTimeline() {
if ($timeline.length) {
var pos = $timeline.offset().top,
max = $('#lichess').offset().top + 536;
while (pos + $timeline.outerHeight() > max) {
$timeline.find('div.entry:last').remove();
}
}
}
resizeTimeline();
lichess_preload.hookPool.forEach(addHook);
drawHooks(true);
$table.find('th:eq(2)').click().end();
lichess.socket = new lichess.StrongSocket(socketUrl, lichess_preload.version, {
events: {
reload_timeline: function() {
$.ajax({
url: $timeline.data('href'),
success: function(html) {
$timeline.html(html);
resizeTimeline();
$('body').trigger('lichess.content_loaded');
}
});
},
streams: function(html) {
$('#streams_on_air').html(html);
},
hook_add: function(hook) {
addHook(hook);
drawHooks();
if (hook.action == 'cancel') $('body').trigger('lichess.hook-flush');
},
hook_remove: removeHook,
hook_list: syncHookIds,
featured: changeFeatured,
redirect: function(e) {
$.lichessOpeningPreventClicks();
$.redirect(e);
},
tournaments: function(data) {
$("#enterable_tournaments").html(data);
$('body').trigger('lichess.content_loaded');
},
reload_forum: function() {
setTimeout(function() {
$.ajax($newposts.data('url'), {
success: function(data) {
$newposts.find('ol').html(data).end().scrollTop(0);
$('body').trigger('lichess.content_loaded');
}
});
}, Math.round(Math.random() * 5000));
},
reload_seeks: reloadSeeksIfVisible,
nbr: function(e) {
var $tag = $('#site_baseline span');
if ($tag.length && e) {
var prev = parseInt($tag.text(), 10);
var k = 5;
var interv = 2000 / k;
$.fp.range(k).forEach(function(it) {
setTimeout(function() {
var val = Math.round(((prev * (k - 1 - it)) + (e * (it + 1))) / k);
if (val != prev) {
$tag.text(val);
prev = val;
}
}, Math.round(it * interv));
});
}
},
// override fen event to reload playing games list
fen: function(e) {
lichess.StrongSocket.defaults.events.fen(e);
if ($nowPlaying.find('.mini_board_' + e.id).length) $.ajax({
url: $nowPlaying.data('href'),
success: function(html) {
$nowPlaying.html(html);
$('body').trigger('lichess.content_loaded');
var nb = $nowPlaying.find('.my_turn').length;
$wrap.find('.tabs .now_playing').toggleClass('hilight', nb).find('.unread').text(nb);
}
});
}
},
options: {
name: "lobby"
}
});
var variantConfirms = {
'960': "This is a Chess960 game!\n\nThe starting position of the pieces on the players' home ranks is randomized.\nRead more: http://wikipedia.org/wiki/Chess960\n\nDo you want to play Chess960?",
'KotH': "This is a King of the Hill game!\n\nThe game can be won by bringing the king to the center.\nRead more: http://lichess.org/king-of-the-hill",
'3+': "This is a Three-check game!\n\nThe game can be won by checking the opponent 3 times.\nRead more: http://en.wikipedia.org/wiki/Three-check_chess"
};
function confirmVariant(variant) {
return Object.keys(variantConfirms).every(function(key) {
var v = variantConfirms[key]
if (variant == key && !storage.get(key)) {
var c = confirm(v);
if (c) storage.set(key, 1);
return c;
} else return true;
})
}
$seeks.on('click', 'tr', function() {
if ($(this).hasClass('must_login')) {
if (confirm($.trans('You need an account to do that'))) {
location.href = '/signup';
}
return false;
}
if ($(this).data('action') != 'joinSeek' || confirmVariant($(this).data('variant'))) {
lichess.socket.send($(this).data('action'), $(this).data('id'));
}
});
function reloadSeeksIfVisible() {
if ($seeks.is(':visible')) {
$.ajax($seeks.data('reload-url'), {
success: function(html) {
$seeks.html(html);
}
});
}
}
function changeFeatured(o) {
$('#featured_game').html(o.html);
$('body').trigger('lichess.content_loaded');
}
function removeHook(id) {
hookPool = hookPool.filter(function(h) {
return h.id != id;
});
drawHooks();
}
function syncHookIds(ids) {
hookPool = hookPool.filter(function(h) {
return $.fp.contains(ids, h.id);
});
drawHooks();
}
function addHook(hook) {
hook.action = hook.uid == lichess.StrongSocket.sri ? "cancel" : "join";
hookPool.push(hook);
}
function disableHook(id) {
$tbody.children('.' + id).addClass('disabled').attr('title', '');
destroyChartHook(id);
}
function destroyHook(id) {
$tbody.children('.' + id).remove();
destroyChartHook(id);
}
function destroyChartHook(id) {
$('#' + id).not('.hiding').addClass('hiding').fadeOut(animation, function() {
$.powerTip.destroy($(this));
$(this).remove();
});
}
function drawHooks(initial) {
var filter = lichess_preload.filter;
var seen = [];
var hidden = 0;
var visible = 0;
hookPool.forEach(function(hook) {
var hide = !$.fp.contains(filter.variant, hook.variant) || !$.fp.contains(filter.mode, hook.mode) || !$.fp.contains(filter.speed, hook.speed) ||
(filter.rating && (!hook.rating || (hook.rating < filter.rating[0] || hook.rating > filter.rating[1])));
var hash = hook.mode + hook.variant + hook.time + hook.rating;
if (hide && hook.action != 'cancel') {
destroyHook(hook.id);
hidden++;
} else if ($.fp.contains(seen, hash) && hook.action != 'cancel') {
$('#' + hook.id).filter(':visible').hide();
$tbody.children('.' + hook.id).hide();
} else {
visible++;
if (!$('#' + hook.id).length) {
$canvas.append($(renderPlot(hook)).fadeIn(animation));
if (initial) $tbody.append(renderTr(hook));
else nextHooks.push(renderTr(hook));
} else {
$('#' + hook.id).not(':visible').fadeIn(animation);
$tbody.children('.' + hook.id).show();
}
}
if (hook.action != 'cancel') seen.push(hash);
});
$.makeArray($canvas.find('>span.plot')).map(function(o) {
return o.getAttribute('data-id');
}).concat(
$.makeArray($tbody.children()).map(function(o) {
return o.getAttribute('data-id');
})).forEach(function(id) {
if (!$.fp.find(hookPool, function(x) {
return x.id == id;
})) disableHook(id);
});
$wrap
.find('a.filter')
.toggleClass('on', hidden > 0)
.find('span.number').text('(' + hidden + ')');
$('body').trigger('lichess.content_loaded');
}
function renderPlot(hook) {
var bottom = Math.max(0, ratingY(hook.rating) - 7);
var left = Math.max(0, clockX(hook.time) - 4);
var klass = [
'plot',
hook.mode == "Rated" ? 'rated' : 'casual',
hook.action == 'cancel' ? 'cancel' : '',
hook.variant != 'STD' ? 'variant' : ''
].join(' ');
var $plot = $('<span id="' + hook.id + '" class="' + klass + '" style="bottom:' + bottom + 'px;left:' + left + 'px;"></span>');
return $plot.data('hook', hook).powerTip({
fadeInTime: 0,
fadeOutTime: 0,
placement: hook.rating > 2200 ? 'se' : 'ne',
mouseOnToPopup: true,
closeDelay: 200,
intentPollInterval: 50,
popupId: 'hook'
}).data('powertipjq', $(renderHook(hook)));
}
function ratingY(e) {
function ratingLog(a) {
return Math.log(a / 150 + 1);
}
var rating = Math.max(800, Math.min(2800, e || 1500));
var ratio;
if (rating == 1500) {
ratio = 0.25;
} else if (rating > 1500) {
ratio = 0.25 + (ratingLog(rating - 1500) / ratingLog(1300)) * 3 / 4;
} else {
ratio = 0.25 - (ratingLog(1500 - rating) / ratingLog(500)) / 4;
}
return Math.round(ratio * 489);
}
function clockX(dur) {
function durLog(a) {
return Math.log((a - 30) / 200 + 1);
}
var max = 2000;
return Math.round(durLog(Math.min(max, dur || max)) / durLog(max) * 489);
}
function renderHook(hook) {
var html = '';
if (hook.rating) {
html += '<a class="opponent" href="/@/' + hook.username + '">' + hook.username.substr(0, 14) + '</a>';
html += '<span class="rating">' + hook.rating + '</span>';
} else {
html += '<span class="opponent anon">Anonymous</span>';
}
html += '<span class="clock">' + hook.clock + '</span>';
html += '<span class="mode">' +
'<span class="varicon" data-icon="' + hook.perf.icon + '"></span>' + $.trans(hook.mode) + '</span>';
html += '<span class="is is2 color-icon ' + (hook.color || "random") + '"></span>';
return html;
}
function renderTr(hook) {
var title = (hook.action == "join") ? $.trans('Join the game') + ' - ' + hook.perf.name : $.trans('cancel');
return '<tr title="' + title + '" data-id="' + hook.id + '" class="' + hook.id + ' ' + hook.action + '">' + [
['', '<span class="is is2 color-icon ' + (hook.color || "random") + '"></span>'],
[hook.username, (hook.rating ? '<a href="/@/' + hook.username + '" class="ulink">' + hook.username + '</a>' : 'Anonymous')],
[hook.rating || 0, hook.rating ? hook.rating : ''],
[hook.time, hook.clock],
[hook.mode,
'<span class="varicon" data-icon="' + hook.perf.icon + '"></span>' +
$.trans(hook.mode)
]
].map(function(x) {
return '<td data-sort-value="' + x[0] + '">' + x[1] + '</td>';
}).join('') + '</tr>';
}
$('#hooks_chart').append(
[1000, 1200, 1400, 1500, 1600, 1800, 2000, 2200, 2400].map(function(v) {
var b = ratingY(v);
return '<span class="y label" style="bottom:' + (b + 5) + 'px">' + v + '</span>' +
'<div class="grid horiz" style="height:' + (b + 4) + 'px"></div>';
}).join('') + [1, 2, 3, 5, 7, 10, 15, 20, 30].map(function(v) {
var l = clockX(v * 60);
return '<span class="x label" style="left:' + l + 'px">' + v + '</span>' +
'<div class="grid vert" style="width:' + (l + 7) + 'px"></div>';
}).join(''));
$tbody.on('click', 'a.ulink', function(e) {
e.stopPropagation();
});
$tbody.on('click', 'td:not(.disabled)', function() {
$('#' + $(this).parent().data('id')).click();
});
$canvas.on('click', '>span.plot:not(.hiding)', function() {
var data = $(this).data('hook');
if (data.action != 'join' || confirmVariant(data.variant)) {
lichess.socket.send(data.action, data.id);
}
});
});
///////////////////
// tournament.js //
///////////////////
$(function() {
var $wrap = $('#tournament');
if (!$wrap.length) return;
if (!lichess.StrongSocket.available) return;
if (typeof _ld_ == "undefined") {
// handle tournament list
lichess.StrongSocket.defaults.params.flag = "tournament";
lichess.StrongSocket.defaults.events.reload = function() {
$wrap.load($wrap.data("href"), function() {
$('body').trigger('lichess.content_loaded');
});
};
return;
}
$('body').data('tournament-id', _ld_.tournament.id);
var $watchers = $("div.watchers").watchers();
var $chat = $('#chat');
if ($chat.length) $chat.chat({
messages: lichess_chat
});
function startClock() {
$("div.tournament_clock").each(function() {
$(this).clock({
time: $(this).data("time")
}).clock("start");
});
}
startClock();
function drawBars() {
$wrap.find('table.standing').each(function() {
var $bars = $(this).find('.bar');
var max = Math.max.apply(Math, $bars.map(function() {
return parseInt(this.getAttribute('data-value'));
}));
$bars.each(function() {
var width = Math.ceil((parseInt($(this).data('value')) * 100) / max);
$(this).css('width', width + '%');
});
});
}
drawBars();
function reload() {
$.ajax({
url: $wrap.data('href'),
success: function(html) {
var $tour = $(html);
if ($wrap.find('table.standing').length) {
// started
$wrap.find('table.standing thead').replaceWith($tour.find('table.standing thead'));
$wrap.find('table.standing tbody').replaceWith($tour.find('table.standing tbody'));
drawBars();
$wrap.find('div.pairings').replaceWith($tour.find('div.pairings'));
$wrap.find('div.game_list').replaceWith($tour.find('div.game_list'));
} else {
// created
$wrap.find('table.user_list').replaceWith($tour.find('table.user_list'));
}
$('body').trigger('lichess.content_loaded');
}
});
}
lichess.socket = new lichess.StrongSocket($wrap.data("socket-url"), _ld_.version, {
events: {
start: reload,
reload: reload,
reloadPage: function() {
location.reload();
},
crowd: function(data) {
$watchers.watchers("set", data);
}
},
options: {
name: "tournament"
}
});
});
////////////////
// analyse.js //
////////////////
function startAnalyse(element, cfg) {
var data = cfg.data;
if (data.chat) $('#chat').chat({
messages: data.chat,
initialNote: data.note,
gameId: data.game.id
});
var $watchers = $('#site_header div.watchers').watchers();
var analyse, $panels;
lichess.socket = new lichess.StrongSocket(
data.url.socket,
data.player.version, {
options: {
name: "analyse"
},
params: {
ran: "--ranph--"
},
events: {
analysisAvailable: function() {
$.sound.dong();
location.href = location.href.split('#')[0] + '#' + analyse.pathStr();
location.reload();
},
crowd: function(event) {
$watchers.watchers("set", event.watchers);
}
}
});
var $advChart = $("#adv_chart");
var $timeChart = $("#movetimes_chart");
var $inputFen = $('input.fen', element);
var unselect = function(chart) {
chart.getSelectedPoints().forEach(function(point) {
point.select(false);
});
};
var lastFen, lastPath;
lichess.analyse.onChange = function(fen, path) {
lastFen = fen = fen || lastFen;
lastPath = path = path || lastPath;
var chart, point;
$inputFen.val(fen);
if ($advChart.length) {
chart = $advChart.highcharts();
if (chart) {
if (path.length > 1) unselect(chart);
else {
point = chart.series[0].data[path[0].ply - 1];
if (typeof point != "undefined") {
point.select();
chart.setTitle({
text: point.name + ' ' + 'Advantage: <strong>' + point.y + '</strong>'
});
} else unselect(chart);
}
}
}
var timeChartFirstDisplay = true;
if ($timeChart.length) {
chart = $timeChart.highcharts();
if (chart) {
if (path.length > 1) unselect(chart);
else {
var white = path[0].ply % 2 !== 0;
var serie = white ? 0 : 1;
var turn = Math.floor((path[0].ply - 1) / 2);
point = chart.series[serie].data[turn];
if (typeof point != "undefined") {
point.select();
var title = point.name + ' ' + 'Time used: <strong>' + (point.y * (white ? 1 : -1)) + '</strong> s';
chart.setTitle({
text: title
});
if (timeChartFirstDisplay) {
chart.setTitle({
text: title
});
timeChartFirstDisplay = false;
}
} else unselect(chart);
}
}
}
};
data.path = window.location.hash ? location.hash.replace(/#/, '') : '';
analyse = LichessAnalyse(element.querySelector('.analyse'), cfg.data, cfg.routes, cfg.i18n, lichess.analyse.onChange);
lichess.analyse.jump = analyse.jump;
$('.underboard_content', element).appendTo($('.underboard .center', element)).show();
$('.advice_summary', element).appendTo($('.underboard .right', element)).show();
$panels = $('div.analysis_panels > div');
$('div.analysis_menu').on('click', 'a', function() {
var panel = $(this).data('panel');
$(this).siblings('.active').removeClass('active').end().addClass('active');
$panels.removeClass('active').filter('.' + panel).addClass('active');
if (panel == 'move_times') try {
$.renderMoveTimesChart();
} catch (e) {}
}).find('a:first').click();
$panels.find('form.future_game_analysis').submit(function() {
if ($(this).hasClass('must_login')) {
if (confirm($.trans('You need an account to do that'))) {
location.href = '/signup';
}
return false;
}
$.ajax({
method: 'post',
url: $(this).attr('action'),
success: function(html) {
$panels.filter('.panel.computer_analysis').html(html);
}
});
return false;
});
}
////////////////
// user_analysis.js //
////////////////
function startUserAnalysis(element, cfg) {
var analyse = LichessAnalyse(element.querySelector('.analyse'), cfg.data, cfg.routes, cfg.i18n, null);
}
/////////////// forum.js ////////////////////
$('#lichess_forum').on('click', 'a.delete', function() {
$.post($(this).attr("href"));
$(this).closest(".post").slideUp(100);
return false;
});
$.fn.sortable = function(sortFns) {
return this.each(function() {
var $table = $(this);
var $ths = $table.find('th');
// Enum containing sorting directions
var dir = {
ASC: "asc",
DESC: "desc"
};
// Merge sort functions with some default sort functions.
sortFns = $.extend({}, {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
}, sortFns || {});
// Return the resulting indexes of a sort so we can apply
// this result elsewhere. This returns an array of index numbers.
// return[0] = x means "arr's 0th element is now at x"
function sort_map(arr, sort_function, sort_dir) {
var map = [];
var index = 0;
var sorted = arr.slice(0).sort(sort_function);
if (sort_dir == dir.DESC) sorted = sorted.reverse();
for (var i = 0; i < arr.length; i++) {
index = $.inArray(arr[i], sorted);
// If this index is already in the map, look for the next index.
// This handles the case of duplicate entries.
while ($.inArray(index, map) != -1) {
index++;
}
map.push(index);
}
return map;
}
// Apply a sort map to the array.
function apply_sort_map(arr, map) {
var clone = arr.slice(0),
newIndex = 0;
for (var i = 0; i < map.length; i++) {
newIndex = map[i];
clone[newIndex] = arr[i];
}
return clone;
}
$table.on("click", "th", function() {
var sort_dir = $(this).data("sort-dir") === dir.DESC ? dir.ASC : dir.DESC;
$ths.data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$(this).data("sort-dir", sort_dir).addClass("sorting-" + sort_dir);
$table.trigger('sortable.sort');
});
$table.on("sortable.sort", function() {
var $th = $ths.filter('.sorting-desc,.sorting-asc').first();
if (!$th.length) return;
var th_index = $th.index();
var type = $th.data("sort");
if (!type) return;
var sort_dir = $th.data("sort-dir") || dir.DESC;
var trs = $table.children("tbody").children("tr");
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortMethod = sortFns[type];
// Push either the value of the `data-order-by` attribute if specified
// or just the text() value in this column to column[] for comparison.
trs.each(function(index, tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
column.push(order_by);
});
var theMap = sort_map(column, sortMethod, sort_dir);
$table.children("tbody").html($(apply_sort_map(trs, theMap)));
}, 10);
});
});
};
$.idleTimer = function(delay, onIdle, onWakeUp) {
var eventType = 'mousemove';
var listening = false;
var active = true;
var lastSeenActive = new Date();
var onActivity = function() {
if (!active) onWakeUp();
active = true;
lastSeenActive = new Date();
stopListening();
};
var startListening = function() {
if (!listening) {
document.addEventListener(eventType, onActivity);
listening = true;
}
};
var stopListening = function() {
if (listening) {
document.removeEventListener(eventType, onActivity);
listening = false;
}
};
setInterval(function() {
if (active && new Date() - lastSeenActive > delay) {
onIdle();
active = false;
startListening();
} else startListening();
}, 5000);
};
})();