lila/public/javascripts/big.js

2301 lines
73 KiB
JavaScript

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
var lichess = window.lichess = window.lichess || {};
lichess.getParameterByName = function(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
};
lichess.StrongSocket = function(url, version, settings) {
var self = this;
self.settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
self.url = url;
self.version = version;
self.options = self.settings.options;
self.ws = null;
self.pingSchedule = null;
self.connectSchedule = null;
self.ackableMessages = [];
self.lastPingTime = self.now();
self.currentLag = 0;
self.averageLag = 0;
self.tryOtherUrl = false;
self.autoReconnect = true;
self.debug('Debug is enabled');
if (self.options.resetUrl || self.options.prodPipe) {
lichess.storage.remove(self.options.baseUrlKey);
}
if (self.options.prodPipe) {
self.options.baseUrls = ['socket.en.lichess.org:9021'];
}
self.connect();
window.addEventListener('unload', function() {
self.destroy();
});
};
lichess.StrongSocket.available = window.WebSocket || window.MozWebSocket;
lichess.StrongSocket.sri = Math.random().toString(36).substring(2);
lichess.StrongSocket.defaults = {
events: {
fen: function(e) {
$('.live_' + e.id).each(function() {
lichess.parseFen($(this).data("fen", e.fen).data("lastmove", e.lm));
});
}
},
params: {
sri: lichess.StrongSocket.sri
},
options: {
name: "unnamed",
pingMaxLag: 8000, // time to wait for pong before reseting the connection
pingDelay: 1000, // time between pong and ping
autoReconnectDelay: 2000,
lagTag: false, // jQuery object showing ping lag
ignoreUnknownMessages: true,
baseUrls: ['socket.' + document.domain].concat(
($('body').data('ports') + '').split(',').map(function(port) {
return 'socket.' + document.domain + ':' + port;
})),
onFirstConnect: $.noop,
baseUrlKey: 'surl3'
}
};
lichess.StrongSocket.prototype = {
connect: function() {
var self = this;
self.destroy();
self.autoReconnect = true;
var fullUrl = "ws://" + self.baseUrl() + self.url + "?" + $.param($.extend(self.settings.params, {
version: self.version
}));
self.debug("connection attempt to " + fullUrl, true);
try {
if (window.MozWebSocket) self.ws = new MozWebSocket(fullUrl);
else if (window.WebSocket) self.ws = new WebSocket(fullUrl);
else throw "[lila] no websockets found on this browser!";
self.ws.onerror = function(e) {
self.onError(e);
};
self.ws.onclose = function(e) {
if (self.autoReconnect) {
self.debug('Will autoreconnect in ' + self.options.autoReconnectDelay);
self.scheduleConnect(self.options.autoReconnectDelay);
}
};
self.ws.onopen = function() {
self.debug("connected to " + fullUrl, true);
self.onSuccess();
$('body').removeClass('offline');
self.pingNow();
$('body').trigger('socket.open');
if ($('#user_tag').length) setTimeout(function() {
self.send("following_onlines");
}, 500);
var resend = self.ackableMessages;
self.ackableMessages = [];
resend.forEach(function(x) {
self.send(x.t, x.d);
});
};
self.ws.onmessage = function(e) {
var m = JSON.parse(e.data);
if (m.t == "n") {
self.pong();
} else self.debug(e.data);
if (m.t == "b") {
m.d.forEach(function(mm) {
self.handle(mm);
});
} else self.handle(m);
};
} catch (e) {
self.onError(e);
}
self.scheduleConnect(self.options.pingMaxLag);
},
send: function(t, d, o, again) {
var self = this;
var data = d || {},
options = o || {};
if (options && options.ackable)
self.ackableMessages.push({
t: t,
d: d
});
var message = JSON.stringify({
t: t,
d: data
});
self.debug("send " + message);
try {
self.ws.send(message);
} catch (e) {
// maybe sent before socket opens,
// try again a second later,once.
if (!again) setTimeout(function() {
this.send(t, d, o, true);
}.bind(this), 1000);
else console.log(e, 'again');
}
},
sendAckable: function(t, d) {
this.send(t, d, {
ackable: true
});
},
scheduleConnect: function(delay) {
var self = this;
// self.debug('schedule connect ' + delay);
clearTimeout(self.pingSchedule);
clearTimeout(self.connectSchedule);
self.connectSchedule = setTimeout(function() {
$('body').addClass('offline');
self.tryOtherUrl = true;
self.connect();
}, delay);
},
schedulePing: function(delay) {
var self = this;
clearTimeout(self.pingSchedule);
self.pingSchedule = setTimeout(function() {
self.pingNow();
}, delay);
},
pingNow: function() {
var self = this;
clearTimeout(self.pingSchedule);
clearTimeout(self.connectSchedule);
try {
self.ws.send(self.pingData());
self.lastPingTime = self.now();
} catch (e) {
self.debug(e, true);
}
self.scheduleConnect(self.options.pingMaxLag);
},
pong: function() {
var self = this;
clearTimeout(self.connectSchedule);
self.schedulePing(self.options.pingDelay);
self.currentLag = self.now() - self.lastPingTime;
if (!self.averageLag) self.averageLag = self.currentLag;
else self.averageLag = 0.2 * (self.currentLag - self.averageLag) + self.averageLag;
if (self.options.lagTag) {
self.options.lagTag.html(Math.round(self.averageLag));
}
},
pingData: function() {
return JSON.stringify({
t: "p",
v: this.version
});
},
handle: function(m) {
var self = this;
if (m.v) {
if (m.v <= self.version) {
self.debug("already has event " + m.v);
return;
}
if (m.v > self.version + 1) {
self.debug("event gap detected from " + self.version + " to " + m.v);
if (!self.options.prodPipe) return;
}
self.version = m.v;
}
switch (m.t || false) {
case false:
break;
case 'resync':
if (!self.options.prodPipe) lichess.reload();
break;
case 'ack':
self.ackableMessages = [];
break;
default:
if (self.settings.receive) self.settings.receive(m.t, m.d);
var h = self.settings.events[m.t];
if (h) h(m.d || null);
else if (!self.options.ignoreUnknownMessages) {
self.debug('Message not supported ' + JSON.stringify(m));
}
}
},
now: function() {
return new Date().getTime();
},
debug: function(msg, always) {
if ((always || this.options.debug) && window.console && console.debug) {
console.debug("[" + this.options.name + " " + this.settings.params.sri + "]", msg);
}
},
destroy: function() {
clearTimeout(this.pingSchedule);
clearTimeout(this.connectSchedule);
this.disconnect();
this.ws = null;
},
disconnect: function() {
if (this.ws) {
this.debug("Disconnect", true);
this.autoReconnect = false;
this.ws.onerror = $.noop;
this.ws.onclose = $.noop;
this.ws.onopen = $.noop;
this.ws.onmessage = $.noop;
this.ws.close();
}
},
onError: function(e) {
var self = this;
self.options.debug = true;
self.debug('error: ' + JSON.stringify(e));
self.tryOtherUrl = true;
setTimeout(function() {
if (!$('#network_error').length) {
var msg = "Your browser supports websockets, but cannot get a connection. Maybe you are behind a proxy that does not support websockets. Ask your system administrator to fix it!";
$('#top').append('<span class="fright link text" id="network_error" title="' + msg + '" data-icon="j">Network error</span>');
}
}, 1000);
clearTimeout(self.pingSchedule);
},
onSuccess: function() {
$('#network_error').remove();
this.nbConnects = (this.nbConnects || 0) + 1;
if (this.nbConnects === 1) this.options.onFirstConnect();
},
baseUrl: function() {
var key = this.options.baseUrlKey;
var urls = this.options.baseUrls;
var url = lichess.storage.get(key);
if (!url) {
url = urls[0];
lichess.storage.set(key, url);
} else if (this.tryOtherUrl) {
this.tryOtherUrl = false;
url = urls[(urls.indexOf(url) + 1) % urls.length];
lichess.storage.set(key, url);
}
return url;
},
pingInterval: function() {
return this.options.pingDelay + this.averageLag;
}
};
// 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) {}
}
lichess.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);
};
};
$.spreadNumber = function(el, nbSteps, getDuration) {
var previous, displayed;
var display = function(prev, cur, it) {
var val = Math.round(((prev * (nbSteps - 1 - it)) + (cur * (it + 1))) / nbSteps);
if (val !== displayed) {
el.textContent = val;
displayed = val;
}
};
return function(nb) {
if (!el || !nb) return;
var prev = previous || nb;
previous = nb;
var interv = getDuration() / nbSteps;
for (var i = 0; i < nbSteps; i++) {
setTimeout(display.bind(null, prev, nb, i), Math.round(i * interv));
}
};
};
var nbUserSpread = $.spreadNumber(
document.querySelector('#nb_connected_players > strong'),
5,
function() {
return lichess.socket.pingInterval();
});
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: nbUserSpread,
message: function(msg) {
$('#chat').chat("append", msg);
},
nbm: function(e) {
$('#nb_messages').text(e || "0").parent().parent().toggle(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;
});
$('body').trigger('lichess.content_loaded');
}
},
challengeReminder: function(data) {
if (!lichess.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"));
lichess.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);
$notif.find('> a').click(function() {
lichess.hasToReload = true; // allow quit by accept challenge (simul)
});
declineListener($notif.find('a.decline'));
$('body').trigger('lichess.content_loaded');
if (!lichess.storage.get('challenge-' + data.id)) {
if (!lichess.quietMode) {
$('#top .challenge_notifications').addClass('shown');
$.sound.genericNotify();
}
lichess.storage.set('challenge-' + data.id, 1);
}
refreshButton();
}
$('.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();
},
simulEnd: function(simul) {
$.modal($(
'<p>Simul complete!</p><br /><br />' +
'<a class="button" href="/simul/' + simul.id + '">Back to ' + simul.name + ' simul</a>'
));
}
},
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();
};
lichess.readServerFen = function(t) {
return atob(t.split("").reverse().join(""));
};
lichess.openInMobileApp = function(gameId) {
if (!/android.+mobile|ipad|iphone|ipod/i.test(navigator.userAgent || navigator.vendor)) return false;
var storageKey = 'open-game-in-mobile';
var open = function(v) {
if (v > 0) {
lichess.storage.set(storageKey, v - 1);
location.href = 'lichess://' + gameId;
return true;
}
lichess.storage.set(storageKey, v + 1);
return false;
};
var stored = parseInt(lichess.storage.get(storageKey));
if (stored) return open(stored);
return open(confirm('Open in lichess mobile app?') ? 10 : -10);
};
lichess.parseFen = function($elem) {
if (!$elem || !$elem.jquery) {
$elem = $('.parse_fen');
}
$elem.each(function() {
var $this = $(this).removeClass('parse_fen');
var lm = $this.data('lastmove');
var lastMove = lm ? [lm[0] + lm[1], lm[2] + lm[3]] : [];
var color = $this.data('color') || lichess.readServerFen($(this).data('y'));
var ground = $this.data('chessground');
var playable = $this.data('playable');
var config = {
coordinates: false,
viewOnly: !playable,
minimalDom: !playable,
fen: $this.data('fen') || lichess.readServerFen($this.data('z')),
lastMove: lastMove
};
if (color) config.orientation = color;
if (ground) ground.set(config);
else $this.data('chessground', Chessground($this[0], config));
});
}
$(function() {
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);
}
});
});
}
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);
else if (lichess.lobby) startLobby(document.getElementById('hooks_wrap'), lichess.lobby);
else if (lichess.tournament) startTournament(document.getElementById('tournament'), lichess.tournament);
else if (lichess.simul) startSimul(document.getElementById('simul'), lichess.simul);
else if (lichess.relay) startRelay(document.getElementById('relay'), lichess.relay);
document.body.classList.remove('preload');
$('#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;
});
function applyPowertip($els, placement) {
$els.removeClass('ulpt').powerTip({
fadeInTime: 100,
fadeOutTime: 100,
placement: placement,
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', ' ');
}
function userPowertips() {
applyPowertip($('#site_header .ulpt'), 'e');
applyPowertip($('#friend_box .ulpt'), 'nw');
applyPowertip($('.ulpt'), 'w');
}
setTimeout(userPowertips, 600);
$('body').on('lichess.content_loaded', userPowertips);
$('#message_notifications_tag').on('click', function() {
$.ajax({
url: $(this).data('href'),
cache: false,
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);
}
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('mouseover', function() {
var $themepicker = $('#themepicker');
$.ajax({
url: $(this).data('url'),
cache: false,
success: function(html) {
$themepicker.append(html);
var $body = $('body');
var $content = $body.children('.content');
var $dropdown = $themepicker.find('.dropdown');
var $pieceSprite = $('#piece-sprite');
var themes = $dropdown.data('themes').split(' ');
var theme = $.fp.find(document.body.classList, function(a) {
return $.fp.contains(themes, a);
});
var set = $body.data('piece-set');
var theme3ds = $dropdown.data('theme3ds').split(' ');
var theme3d = $.fp.find(document.body.classList, function(a) {
return $.fp.contains(theme3ds, a);
});
var set3ds = $dropdown.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() {
var s = $(this).data("set");
$pieceSprite.attr('href', $pieceSprite.attr('href').replace(/\w+\.css/, s + '.css'));
}, function() {
$pieceSprite.attr('href', $pieceSprite.attr('href').replace(/\w+\.css/, set + '.css'));
}).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 lichess.storage.get('zoom') || 1;
};
var setZoom = function(v) {
lichess.storage.set('zoom', v);
var $lichessGame = $('.lichess_game, .board_and_ground');
var $boardWrap = $lichessGame.find('.cg-board-wrap');
var $coordinateProgress = $('.progress_bar_container');
var px = function(i) {
return Math.round(i) + 'px';
};
$boardWrap.css("width", px(512 * getZoom()));
$coordinateProgress.css("width", px(512 * getZoom()));
$('.underboard').css("margin-left", px((getZoom() - 1) * 250));
$lichessGame.find('.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))
});
$('.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)
});
$('.chat_panels').css("height", px(325 + 510 * (getZoom() - 1)));
}
$('#trainer .overlay_container').css({
top: px((getZoom() - 1) * 250),
left: px((getZoom() - 1) * 250)
});
// doesn't vertical center score at the end, close enough
$('#trainer .score_container').css("top", px((getZoom() - 1) * 250));
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);
if (getZoom() > 1) setZoom(getZoom()); // Instantiate the page's zoom
$('body').on('lichess.coordinate_trainer_loaded', function() {
setZoom(getZoom());
});
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'),
cache: false,
success: function(list) {
$links.prepend(list.map(function(lang) {
var klass = $.fp.contains(langs, lang[0]) ? 'class="accepted"' : '';
return '<li><button type="submit" ' + klass + '" name="lang" value="' + lang[0] + '">' + lang[1] + '</button></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;
});
// minimal touchscreen support for topmenu
if ("ontouchstart" in window)
$('#topmenu').on('click', '> section > a', function() {
return false;
});
$('#ham-plate').click(function() {
document.body.classList.toggle('fpmenu');
});
$('#top button.signin').click(function() {
$('#ham-plate').click();
$('#fpmenu input.username').focus();
});
if (location.hash === '#fpmenu') $('#ham-plate').click();
Mousetrap.bind('esc', function() {
$('#ham-plate').click();
return false;
});
Mousetrap.bind('g h', function() {
location.href = '/';
});
// konami code!
Mousetrap.bind('up up down down left right left right b a', function() {
if (!document.getElementById('konami')) {
$('body').prepend($('<div id="konami"></div>'));
}
$('#konami').show(800);
setTimeout(function() {
$('#konami').hide(800);
}, 3000);
});
});
$.lazy = function(factory) {
var loaded = {};
var f = function(key) {
if (!loaded[key]) loaded[key] = factory(key);
return loaded[key];
};
f.clear = function() {
loaded = {};
};
return f;
};
$.sound = (function() {
var baseUrl = $('body').data('sound-dir');
var soundSet = $('body').data('sound-set');
Howler.volume(lichess.storage.get('sound-volume') || 0.7);
var names = {
genericNotify: 'GenericNotify',
move: 'Move',
capture: 'Capture',
explode: 'Explosion',
lowtime: 'LowTime',
victory: 'Victory',
defeat: 'Defeat'
};
var volumes = {
lowtime: 0.5,
explode: 0.35
};
var collection = new $.lazy(function(k) {
return new Howl({
src: ['ogg', 'mp3'].map(function(ext) {
return [baseUrl, soundSet, names[k] + '.' + ext].join('/');
}),
volume: volumes[k] || 1
});
});
var $control = $('#sound_control');
var $toggle = $('#sound_state');
var enabled = function() {
return lichess.storage.get('sound') !== 'no';
};
$control.add($toggle).toggleClass('sound_state_on', enabled());
var player = function(s) {
return function() {
if (enabled()) collection(s).play();
};
}
var play = {};
Object.keys(names).forEach(function(name) {
play[name] = function() {
if (enabled()) collection(name).play();
}
});
var setVolume = function(v) {
lichess.storage.set('sound-volume', v);
Howler.volume(v);
};
var manuallySetVolume = $.fp.debounce(function(v) {
setVolume(v);
play.move(true);
}, 50);
$toggle.click(function() {
var enab = !enabled();
lichess.storage.set('sound', enab ? 'yes' : 'no');
$control.add($toggle).toggleClass('sound_state_on', enab);
play.genericNotify();
return false;
});
$toggle.one('mouseover', function() {
$toggle.parent().find('.slider').slider({
orientation: "vertical",
min: 0,
max: 1,
range: 'min',
step: 0.01,
value: Howler.volume(),
slide: function(e, ui) {
manuallySetVolume(ui.value);
}
});
var $selector = $toggle.parent().find('form');
$selector.find('input').on('change', function() {
soundSet = $(this).val();
collection.clear();
play.genericNotify();
$.post($selector.attr('action'), {
set: soundSet
});
return false;
});
});
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: parseFloat($(this).data("time"))
});
});
};
/////////////
// game.js //
/////////////
function startRound(element, cfg) {
var data = cfg.data;
if (data.player.spectator && lichess.openInMobileApp(data.game.id)) return;
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);
},
tvSelect: function(o) {
if (data.tv && data.tv.channel == o.channel) lichess.reload();
else $('#tv_channels a.' + o.channel + ' span').text(
o.player ? o.player.name + ' (' + o.player.rating + ')' : 'Anonymous');
},
end: function() {
var url = data.tv ? ['/tv', data.tv.channel, data.game.id, data.player.color, 'sides'].join('/') : ((
data.player.spectator ? cfg.routes.Round.sidesWatcher : cfg.routes.Round.sidesPlayer
)(data.game.id, data.player.color).url);
$.ajax({
url: url,
cache: false,
success: function(html) {
var $html = $(html);
$('#site_header div.side').replaceWith($html.find('>.side'));
$('#lichess div.crosstable').replaceWith($html.find('>.crosstable'));
$('body').trigger('lichess.content_loaded');
startTournamentClock();
}
});
},
checkCount: function(e) {
$('div.check_count')
.find('.white').text(e.black).end()
.find('.black').text(e.white);
},
tournamentStanding: function(id) {
$.ajax({
url: '/tournament/' + id + '/game-standing',
cache: false,
success: function(html) {
$('#site_header div.game_tournament').replaceWith(html);
startTournamentClock();
}
});
}
}
});
cfg.element = element.querySelector('.round');
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
round = LichessRound(cfg);
$('.crosstable', element).prependTo($('.underboard .center', element)).show();
$('#chat').chat({
messages: data.chat,
initialNote: data.note,
gameId: data.game.id
});
var $watchers = $('#site_header div.watchers').watchers();
var $nowPlaying = $('#now_playing');
startTournamentClock();
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;
});
if (location.pathname.lastIndexOf('/round-next/', 0) === 0)
window.history.replaceState(null, null, '/' + data.game.id);
}
function startPrelude(element, cfg) {
var data = cfg.data;
if (data.player.spectator && lichess.openInMobileApp(data.game.id)) return;
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
},
disableContextMenu: true
});
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() {
lichess.storage.set('friends-hide', $(this).is(':visible') ? 0 : 1);
});
});
if (lichess.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" href="/@/' + id + '">' + user + '</a>';
}
});
$.widget("lichess.chat", {
_create: function() {
this.options = $.extend({
messages: [],
initialNote: '',
gameId: null
}, this.options);
var self = this;
var $parent = self.element.parent();
self.$msgs = self.element.find('.messages');
self.withMsgs = !!self.$msgs.length;
if (self.withMsgs) {
self.$msgs.on('click', 'a', function() {
$(this).attr('target', '_blank');
});
var $form = self.element.find('form');
var $input = self.element.find('input.lichess_say');
// 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) lichess.storage.set('nochat', 1);
else lichess.storage.remove('nochat');
});
$toggle[0].checked = lichess.storage.get('nochat') != 1;
if (!$toggle[0].checked) {
self.element.addClass('hidden');
}
if (self.options.messages.length > 0) self._appendMany(self.options.messages);
}
$panels = self.element.find('div.chat_panels > div');
$menu = $parent.find('.chat_menu');
$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[data-panel=preferences]').one('click', function() {
self.element.find('.preferences form').each(function() {
var $form = $(this);
$form.find('input').change(function() {
$.ajax({
url: $form.attr('action'),
method: $form.attr('method'),
data: $form.serialize()
});
});
});
});
if (lichess.storage.get('game.settings.seen')) $menu.find('a:first').click();
else {
lichess.storage.set('game.settings.seen', 1);
$menu.find('a[data-panel=preferences]').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.each(function(i, el) {
var autoScroll = (el.scrollTop == 0 || (el.scrollTop > (el.scrollHeight - el.clientHeight - 50)));
$(el).append(html);
if (autoScroll) el.scrollTop = 999999;
});
$('body').trigger('lichess.content_loaded');
}
});
$.widget("lichess.clock", {
_create: function() {
var self = this;
this.options.time = this.options.time * 1000;
this.timeEl = this.element.find('>.time')[0];
var end_time = new Date().getTime() + self.options.time;
var tick = function() {
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();
};
self.options.interval = setInterval(tick, 1000);
tick();
},
destroy: function() {
this.stop();
$.Widget.prototype.destroy.apply(this);
},
_show: function() {
this.timeEl.innerHTML = this._formatDate(new Date(this.options.time));
},
_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.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() {
$('body').on('lichess.content_loaded', lichess.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) {
lichess.socket.send("startWatching", ids.join(" "));
}
}
$('body').on('lichess.content_loaded', startWatching);
$('body').on('socket.open', function() {
socketOpened = true;
startWatching();
});
setTimeout(function() {
lichess.parseFen();
$('div.checkmateCaptcha').each(function() {
var $captcha = $(this);
var $board = $captcha.find('.mini_board');
var $input = $captcha.find('input').val('');
var cg = $board.data('chessground');
var dests = JSON.parse(lichess.readServerFen($board.data('x')));
cg.set({
turnColor: cg.getOrientation(),
movable: {
free: false,
dests: dests,
color: cg.getOrientation(),
coordinates: false,
events: {
after: function(orig, dest) {
$captcha.removeClass("success failure");
submit(orig + ' ' + dest);
}
}
},
disableContextMenu: true
});
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() {
lichess.parseFen($board);
$board.data('chessground').set({
turnColor: cg.getOrientation(),
movable: {
dests: dests
}
});
}, 300);
}
});
};
});
}, 200);
});
function startLobby(element, cfg) {
var lobby;
var nbRoundSpread = $.spreadNumber(
document.querySelector('#site_baseline span'),
4,
function() {
return lichess.socket.pingInterval();
});
var onFirstConnect = function() {
var gameId = lichess.getParameterByName('hook_like');
if (!gameId) return;
$.post('/setup/hook/' + lichess.StrongSocket.sri + '/like/' + gameId);
lobby.setTab('real_time');
window.history.replaceState(null, null, '/');
};
lichess.socket = new lichess.StrongSocket(
'/lobby/socket/v1',
cfg.data.version, {
receive: function(t, d) {
lobby.socketReceive(t, d);
},
events: {
reload_timeline: function() {
$.ajax({
url: $("#timeline").data('href'),
cache: false,
success: function(html) {
$('#timeline').html(html);
$('body').trigger('lichess.content_loaded');
}
});
},
streams: function(html) {
$('#streams_on_air').html(html);
},
featured: function(o) {
$('#featured_game').html(o.html);
$('body').trigger('lichess.content_loaded');
},
redirect: function(e) {
lobby.setRedirecting();
$.redirect(e);
},
tournaments: function(data) {
$("#enterable_tournaments").html(data);
$('body').trigger('lichess.content_loaded');
},
simuls: function(data) {
$("#enterable_simuls").html(data).parent().toggle($('#enterable_simuls tr').length > 0);
$('body').trigger('lichess.content_loaded');
},
reload_forum: function() {
var $newposts = $("div.new_posts");
setTimeout(function() {
$.ajax({
url: $newposts.data('url'),
cache: false,
success: function(data) {
$newposts.find('ol').html(data).end().scrollTop(0);
$('body').trigger('lichess.content_loaded');
}
});
}, Math.round(Math.random() * 5000));
},
nbr: nbRoundSpread,
fen: function(e) {
lichess.StrongSocket.defaults.events.fen(e);
lobby.gameActivity(e.id);
}
},
options: {
name: 'lobby',
onFirstConnect: onFirstConnect
}
});
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
lobby = LichessLobby(element, cfg);
var $startButtons = $('#start_buttons');
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 sliderInitVal(v, f, max) {
for (var i = 0; i < max; i++) {
if (f(i) === v) return i;
}
}
function prepareForm() {
var $form = $('.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 randomColorVariants = $form.data('random-color-variants').split(',');
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';
if (timeOk && ratedOk) {
$form.find('.color_submits button').show();
$form.find('.color_submits button:not(.random)').toggle(!rated || randomColorVariants.indexOf($variantSelect.val()) === -1);
} else
$form.find('.color_submits button').hide();
};
var showRating = function() {
var timeMode = $timeModeSelect.val();
var key;
switch ($variantSelect.val()) {
case '1':
if (timeMode == '1') {
var time = $timeInput.val() * 60 + $incrementInput.val() * 40;
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';
break;
case '6':
key = 'antichess'
break;
case '7':
key = 'atomic'
break;
case '8':
key = "horde"
break;
}
$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();
lobby.setTab($timeModeSelect.val() === '1' ? 'real_time' : 'seeks');
return false;
};
$formTag.find('.color_submits button').click(function() {
return ajaxSubmit($(this).val());
}).attr('disabled', false);
$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: sliderInitVal(parseInt($input.val()), sliderTime, 100),
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: sliderInitVal(parseInt($input.val()), sliderDays, 20),
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("");
}
});
}
}, 200);
$fenInput.on('keyup', validateFen);
$variantSelect.on('change', function() {
var fen = $(this).val() == '3';
$fenPosition.toggle(fen);
$modeChoicesWrap.toggle(!fen);
if (fen) $casual.click();
showRating();
toggleButtons();
}).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').not('.disabled').click(function() {
$(this).addClass('active').siblings().removeClass('active');
$('.lichess_overboard').remove();
$.ajax({
url: $(this).attr('href'),
cache: false,
success: function(html) {
$('.lichess_overboard').remove();
$('#hooks_wrap').prepend(html);
prepareForm();
$('body').trigger('lichess.content_loaded');
},
error: function() {
location.reload();
}
});
return false;
});
if (['#ai', '#friend', '#hook'].indexOf(location.hash) !== -1) {
$startButtons
.find('a.config_' + location.hash.replace('#', ''))
.each(function() {
$(this).attr("href", $(this).attr("href") + location.search);
}).click();
if (location.hash === '#hook') {
if (/time=realTime/.test(location.search))
lobby.setTab('real_time');
else if (/time=correspondence/.test(location.search))
lobby.setTab('seeks');
}
window.history.replaceState(null, null, '/');
}
};
///////////////////
// tournament.js //
///////////////////
$(function() {
if (lichess.tournamentSchedule) {
lichess.StrongSocket.defaults.params.flag = "tournament";
lichess.StrongSocket.defaults.events.reload = lichess.tournamentSchedule.update;
}
});
function startTournament(element, cfg) {
$('body').data('tournament-id', cfg.data.id);
var $watchers = $("div.watchers").watchers();
if (typeof lichess_chat !== 'undefined') $('#chat').chat({
messages: lichess_chat
});
var tournament;
lichess.socket = new lichess.StrongSocket(
'/tournament/' + cfg.data.id + '/socket/v1', cfg.socketVersion, {
receive: function(t, d) {
tournament.socketReceive(t, d)
},
events: {
crowd: function(data) {
$watchers.watchers("set", data);
}
},
options: {
name: "tournament"
}
});
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
tournament = LichessTournament(element, cfg);
};
///////////////////
// simul.js //
///////////////////
$(function() {
var $simulList = $('#simul_list');
if ($simulList.length) {
// handle simul list
lichess.StrongSocket.defaults.params.flag = "simul";
lichess.StrongSocket.defaults.events.reload = function() {
$simulList.load($simulList.data("href"), function() {
$('body').trigger('lichess.content_loaded');
});
};
$('#site_header .help a.more').click(function() {
$.modal($(this).parent().find('div.more').clone()).addClass('card');
});
return;
}
});
function startSimul(element, cfg) {
$('body').data('simul-id', cfg.data.id);
var $watchers = $("div.watchers").watchers();
if (typeof lichess_chat !== 'undefined') $('#chat').chat({
messages: lichess_chat
});
var simul;
lichess.socket = new lichess.StrongSocket(
'/simul/' + cfg.data.id + '/socket/v1', cfg.socketVersion, {
receive: function(t, d) {
simul.socketReceive(t, d)
},
events: {
crowd: function(data) {
$watchers.watchers("set", data);
}
},
options: {
name: "simul"
}
});
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
simul = LichessSimul(element, cfg);
};
function startRelay(element, cfg) {
var $watchers = $("div.watchers").watchers();
if (typeof lichess_chat !== 'undefined') $('#chat').chat({
messages: lichess_chat
});
var relay;
lichess.socket = new lichess.StrongSocket(
'/watch/' + cfg.data.id + '/socket/v1', cfg.socketVersion, {
receive: function(t, d) {
relay.socketReceive(t, d)
},
events: {
crowd: function(data) {
$watchers.watchers("set", data);
}
},
options: {
name: "relay"
}
});
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
relay = LichessRelay(element, cfg);
};
////////////////
// analyse.js //
////////////////
function startAnalyse(element, cfg) {
var data = cfg.data;
if (lichess.openInMobileApp(data.game.id)) return;
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--",
userTv: $('.user_tv').data('user-tv')
},
receive: function(t, d) {
analyse.socketReceive(t, d);
},
events: {
analysisAvailable: function() {
$.sound.genericNotify();
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;
cfg.onChange = function(fen, path) {
if (fen === lastFen) return;
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 - cfg.data.game.startedAtTurn];
if (typeof point != "undefined") point.select();
else unselect(chart);
}
}
}
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 - cfg.data.game.startedAtTurn) / 2);
point = chart.series[serie].data[turn];
if (typeof point != "undefined") point.select();
else unselect(chart);
}
}
}
};
cfg.path = location.hash ? location.hash.replace(/#/, '') : '';
cfg.element = element.querySelector('.analyse');
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
analyse = LichessAnalyse(cfg);
cfg.jumpToIndex = analyse.jumpToIndex;
$('.underboard_content', element).appendTo($('.underboard .center', element)).show();
$('.advice_summary', element).appendTo($('.underboard .right', element)).show();
$panels = $('div.analysis_panels > div');
var $menu = $('div.analysis_menu');
var storageKey = 'lichess.analysis.panel';
var setPanel = function(panel) {
$menu.children('.active').removeClass('active').end().find('.' + panel).addClass('active');
$panels.removeClass('active').filter('.' + panel).addClass('active');
if (panel == 'move_times') try {
$.renderMoveTimesChart();
} catch (e) {}
};
$menu.on('click', 'a', function() {
var panel = $(this).data('panel');
lichess.storage.set(storageKey, panel);
setPanel(panel);
});
if (cfg.data.analysis) setPanel('computer_analysis');
else {
var stored = lichess.storage.get(storageKey);
if (stored && $menu.children('.' + stored).length) setPanel(stored);
else if ($menu.children('.crosstable').length) $menu.children('.crosstable').click();
else $menu.children(':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;
});
$panels.find('div.pgn').click(function() {
var range, selection;
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText($(this)[0]);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents($(this)[0]);
selection.removeAllRanges();
selection.addRange(range);
}
});
}
////////////////
// user_analysis.js //
////////////////
function startUserAnalysis(element, cfg) {
var analyse;
cfg.path = location.hash ? location.hash.replace(/#/, '') : '';
cfg.element = element.querySelector('.analyse');
lichess.socket = new lichess.StrongSocket('/socket', 0, {
options: {
name: "analyse"
},
params: {
ran: "--ranph--"
},
receive: function(t, d) {
analyse.socketReceive(t, d);
}
});
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
analyse = LichessAnalyse(cfg);
}
/////////////// forum.js ////////////////////
$('#lichess_forum').on('click', 'a.delete', function() {
$.post($(this).attr("href"));
$(this).closest(".post").slideUp(100);
return false;
});
$('#lichess_forum').on('click', 'form.unsub button', function() {
var $form = $(this).parent().toggleClass('on off');
$.post($form.attr("action") + '?unsub=' + $(this).data('unsub'));
return false;
});
$.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();
}, 5000);
};
$.modal = function(html) {
var $wrap = $('<div id="modal-wrap">').html(html.clone().show());
var $overlay = $('<div id="modal-overlay">').html($wrap);
$overlay.one('click', $.modal.close);
$wrap.click(function(e) {
e.stopPropagation();
});
$('body').prepend($overlay);
return $wrap;
};
$.modal.close = function() {
$('#modal-overlay').remove();
};
})();