lila/public/javascripts/big.js

2110 lines
66 KiB
JavaScript

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/jquery-1.8.js
// ==/ClosureCompiler==
// declare now, populate later in a distinct script.
var lichess_translations = [];
var lichess_sri = Math.random().toString(36).substring(5); // 8 chars
(function() {
//////////////////
// websocket.js //
//////////////////
var strongSocket = function(url, version, settings) {
var self = this;
self.settings = {
events: {},
params: {
sri: lichess_sri
},
options: {
name: "unnamed",
debug: false,
offlineDelay: 8000, // time before showing offlineTag
offlineTag: false, // jQuery object showing connection error
pingMaxLag: 8000, // time to wait for pong before reseting the connection
pingDelay: 1500, // time between pong and ping
syncFriendsDelay: 60 * 1000, // time between two syncs
lagTag: false, // jQuery object showing ping lag
ignoreUnknownMessages: false
}
};
$.extend(true, self.settings, settings);
self.url = url;
self.version = version;
self.options = self.settings.options;
self.ws = null;
self.fullUrl = null;
self.pingSchedule = null;
self.connectSchedule = null;
self.syncFriendsSchedule = null;
self.lastPingTime = self.now();
self.currentLag = 0;
self.averageLag = 0;
self.connect();
$(window).unload(function() {
self.destroy();
});
}
strongSocket.available = window.WebSocket || window.MozWebSocket;
strongSocket.prototype = {
connect: function() {
var self = this;
self.destroy();
self.fullUrl = "ws://" + self.url + "?" + $.param($.extend(self.settings.params, {
version: self.version
}));
self.debug("connection attempt to " + self.fullUrl);
if (window.MozWebSocket) self.ws = new MozWebSocket(self.fullUrl);
else if (window.WebSocket) self.ws = new WebSocket(self.fullUrl);
else throw "no websockets found on this browser!";
self.ws.onerror = self.onError;
self.ws.onopen = function() {
self.debug("connected to " + self.fullUrl);
self.onSuccess();
if (self.options.offlineTag) self.options.offlineTag.hide();
self.pingNow();
self.syncFriends();
$('body').trigger('socket.open');
};
self.ws.onmessage = function(e) {
var m = JSON.parse(e.data);
if (m.t == "n") {
self.pong();
} else self.debug(m);
if (m.t == "b") {
$(m.d || []).each(function() {
self.handle(this);
});
} else {
self.handle(m);
}
};
self.scheduleConnect(self.options.pingMaxLag);
},
send: function(t, d) {
var self = this;
var data = d || {};
var message = JSON.stringify({
t: t,
d: data
});
self.debug("send " + message);
self.ws.send(message);
},
scheduleConnect: function(delay) {
var self = this;
clearTimeout(self.connectSchedule);
//self.debug("schedule connect in " + delay + " ms");
self.connectSchedule = setTimeout(function() {
if (self.options.offlineTag) self.options.offlineTag.show();
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);
}
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.options.lagTag) {
self.options.lagTag.text(self.currentLag + " ms");
}
self.averageLag = self.averageLag * 0.8 + self.currentLag * 0.2;
},
pingData: function() {
return JSON.stringify({
t: "p",
v: this.version
});
},
syncFriends: function() {
var self = this;
clearTimeout(self.syncFriendsSchedule);
self.syncFriendsSchedule = setTimeout(function() {
self.syncFriends();
}, self.options.syncFriendsDelay);
this.ws.send(JSON.stringify({
t: "friends"
}));
},
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);
return;
}
self.version = m.v;
}
if (m.t) {
if (m.t == "resync") {
location.reload();
return;
}
var h = self.settings.events[m.t];
if ($.isFunction(h)) h(m.d || null);
else if (!self.options.ignoreUnknownMessages) {
self.debug(m.t + " not supported");
}
}
},
now: function() {
return new Date().getTime();
},
debug: function(msg) {
if (this.options.debug) console.debug("[" + this.options.name + "]", msg);
},
destroy: function() {
var self = this;
clearTimeout(self.pingSchedule);
clearTimeout(self.connectSchedule);
if (self.ws) {
self.ws.close();
self.ws = null;
}
},
onError: function(e) {
setTimeout(function() {
if (!$.cookie("wsok") && $("#websocket-fail").length == 0) {
$.ajax("/assets/websocket-fail.html", {
success: function(html) {
$('body').prepend("<div id='websocket-fail'>" + html + "</div>");
}
});
}
}, 1000);
},
onSuccess: function() {
$.cookie("wsok", 1);
$("#websocket-fail").remove();
}
};
/////////////
// ctrl.js //
/////////////
$.cookie.defaults = {
path: '/',
domain: document.domain.replace(/^\w+\.(.+)$/, '$1')
};
$.userLink = function(u) {
return $.userLinkLimit(u, false);
}
$.userLinkLimit = function(u, limit) {
return (u || false) ? '<a class="user_link" href="/@/' + u + '">' + ((limit || false) ? u.substring(0, limit) : u) + '</a>' : 'Anonymous';
}
var lichess = {
socket: null,
socketDefaults: {
events: {
friends: function(data) {
$('#friend_box').friends("set", data);
},
friend_enters: function(name) {
$('#friend_box').friends('enters', name);
},
friend_leaves: function(name) {
$('#friend_box').friends('leaves', name);
},
n: function(e) {
var $tag = $('#nb_connected_players');
if ($tag.length && e) {
$tag.html($tag.html().replace(/\d+/, e)).removeClass('none');
}
},
nbm: function(e) {
$('#nb_messages').text(e || "0").toggleClass("unread", e > 0);
},
tournamentReminder: function(data) {
if (!$('#tournament_reminder').length && $('body').data("tournament-id") != data.id) {
$('div.notifications').append(data.html).find("a.withdraw").click(function() {
$.post($(this).attr("href"));
$('#tournament_reminder').remove();
return false;
});
}
},
analysisAvailable: function() {
$("div.game_analysis").show().find('.status').remove();
$.playSound();
document.title = "/!\\ ANALYSIS READY! " + document.title;
}
},
params: {},
options: {
name: "site",
offlineTag: $('#reconnecting'),
lagTag: $('#connection_lag')
}
},
onProduction: /.+\.lichess\.org/.test(document.domain),
socketUrl: 'socket.' + document.domain
};
lichess.socketDefaults.options.debug = !lichess.onProduction;
$(function() {
// small layout
function onResize() {
if ($(document.body).width() < 1000) {
$(document.body).addClass("tight");
$('div.content').prepend($('div.header h1'));
$('#lichess h1').after($('div.header div.side_menu'));
// hack for gecko
if ($('div.content').offset().top > 40) {
$('div.content').css('marginTop', '-8px');
}
} else {
$(document.body).removeClass("tight");
}
}
$(window).resize(onResize);
onResize();
if (!strongSocket.available) {
setTimeout(function() {
var inUrFaceUrl = window.opera ? '/assets/opera-websocket.html' : '/assets/browser.html';
$.ajax(inUrFaceUrl, {
success: function(html) {
$('body').prepend(html);
}
});
}, 2000);
}
$('#lichess').on('click', 'a.socket-link', function() {
lichess.socket.send($(this).data('msg'), $(this).data('data'));
});
$('#friend_box').friends();
$('body').on('click', 'div.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 userPowertips() {
$('a.user_link:not(.jsed)').addClass('.jsed').powerTip({
placement: 's',
smartPlacement: true,
mouseOnToPopup: true,
closeDelay: 200
// closeDelay: 999999
}).on({
powerTipPreRender: function() {
$.ajax({
url: $(this).attr('href'),
success: function(html) {
$('#powerTip').html(html);
}
});
}
}).data('powertip', ' ');
}
setTimeout(userPowertips, 600);
$('body').on('lichess.content_loaded', userPowertips);
function setTimeAgo() {
$("time:not(.jsed)").addClass('.jsed').timeago();
}
setTimeAgo();
$('body').on('lichess.content_loaded', setTimeAgo);
// Start game
var $game = $('div.lichess_game').orNot();
if ($game) $game.game(_ld_);
setTimeout(function() {
if (lichess.socket == null && $('div.server_error_box').length == 0) {
lichess.socket = new strongSocket(lichess.socketUrl + "/socket", 0, lichess.socketDefaults);
}
}, 1000);
if ($board = $('div.with_marks').orNot()) {
$.displayBoardMarks($board.parent(), $('#lichess > div.lichess_player_white').length);
}
// themepicker
var $body = $('body');
var $themes = $('#top div.themepicker div.theme');
var themes = $.makeArray($themes.map(function() {
return $(this).data("theme");
}));
var theme = $.map(document.body.className.split(/\s+/), function(a) {
return $.inArray(a, themes) < 0 ? null : a;
})[0];
$themes.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
});
$('#top .themepicker').removeClass("shown");
});
// bgpicker
var bgs = ["light", "dark"];
var bg = $body.hasClass("dark") ? "dark" : "light";
function invertBg(bg) {
return bg == "dark" ? "light" : "dark";
}
$('#top a.bgpicker').click(function() {
bg = invertBg(bg);
$body.removeClass(bgs.join(' ')).addClass(bg);
$.post($(this).attr('href'), {
bg: bg
});
return false;
});
$.centerOverboard = function() {
if ($overboard = $('div.lichess_overboard.auto_center').orNot()) {
$overboard.css('top', Math.max(-30, 238 - $overboard.height() / 2) + 'px').show();
}
};
$.centerOverboard();
$('.js_email').one('click', function() {
var email = ['thibault.', 'dupl', 'essis@', 'gmail.com'].join('');
$(this).replaceWith($('<a/>').text(email).attr('href', 'mailto:' + email));
});
function translateTexts() {
$('.trans_me').each(function() {
$(this).removeClass('trans_me').text($.trans($(this).text()));
});
}
translateTexts();
$('body').on('lichess.content_loaded', translateTexts);
if ($autocomplete = $('input.autocomplete').orNot()) {
$autocomplete.autocomplete({
source: $autocomplete.attr('data-provider'),
minLength: 2,
delay: 100
});
}
$('.infinitescroll:has(.pager a)').each(function() {
$(this).infinitescroll({
navSelector: ".pager",
nextSelector: ".pager a:last",
itemSelector: ".infinitescroll .paginated_element",
loading: {
msgText: "",
img: "/assets/images/hloader3.gif",
finishedMsg: "---"
}
}, 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');
setTimeout(function() {
$p.click(function(e) {
e.stopPropagation();
});
$('html').one('click', function(e) {
$p.removeClass('shown').off('click');
});
}, 10);
return false;
});
});
$('#lichess_translation_form_code').change(function() {
if ("0" != $(this).val()) {
location.href = $(this).closest('form').attr('data-change-url').replace(/__/, $(this).val());
}
});
$('#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?');
});
$('a.ipban').one("click", function() {
var $a = $(this);
if (confirm($a.text() + "?")) {
$.post($a.attr('href'), function() {
$a.text('Done').attr('href', '#');
});
}
return false;
});
function bookmarks() {
$('span.bookmark a.icon:not(.jsed)').each(function() {
var t = $(this).addClass("jsed");
t.click(function() {
t.toggleClass("bookmarked");
$.post(t.attr("href"));
var count = (parseInt(t.html()) || 0) + (t.hasClass("bookmarked") ? 1 : -1);
t.html(count > 0 ? count : "");
return false;
});
});
}
bookmarks();
$('body').on('lichess.content_loaded', bookmarks);
if ($(window).width() < 1060) {
$("div.lichess_chat").addClass("small_chat");
}
$("a.view_pgn_toggle").one("click", function() {
var $this = $(this).text("...");
$.ajax({
url: $this.attr("href"),
success: function(text) {
$this.after("<textarea readonly>" + text + "</textarea>").text("Download PGN");
}
});
return false;
});
$("a.continue_from_here").one("click", function() {
$(this).hide().parent().find('.opponent_choice').show();
return false;
});
$("form.request_analysis a").click(function() {
$(this).parent().submit();
});
$("#import_game form").submit(function() {
var pgn = $(this).find('textarea').val();
var nbMoves = parseInt(pgn.replace(/\n/g, ' ').replace(/^.+\s(\d+)\..+$/, '$1'));
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;
});
var elem = document.createElement('audio');
var canPlayAudio = !! elem.canPlayType && elem.canPlayType('audio/ogg; codecs="vorbis"');
var $soundToggle = $('#sound_state');
function soundEnabled() {
return $soundToggle.hasClass("sound_state_on");
}
$.playSound = function() {
if (canPlayAudio && soundEnabled()) {
var sound = $('#lichess_sound_player').get(0);
sound.play();
setTimeout(function() {
sound.pause();
},
1000);
}
}
if (canPlayAudio) {
$('body').append($('<audio id="lichess_sound_player">').attr('src', $('body').attr('data-sound-file')));
$soundToggle.click(function() {
var enabled = !soundEnabled();
$soundToggle.toggleClass('sound_state_on', enabled);
$.playSound();
$.post($soundToggle.attr('href'), {
sound: enabled
});
return false;
});
$game && $game.trigger('lichess.audio_ready');
} else {
$soundToggle.addClass('unavailable');
}
if (Boolean(window.chrome)) {
$("div.addtochrome").show();
}
});
$.fn.orNot = function() {
return this.length == 0 ? false : this;
};
$.trans = function(text) {
return lichess_translations[text] ? lichess_translations[text] : text;
}
$.displayBoardMarks = function($board, isWhite) {
if (isWhite) {
var factor = 1;
var base = 0;
} else {
var factor = -1;
var base = 575;
}
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
marks = '';
for (i = 1; i < 9; i++) {
marks += '<span class="board_mark vert" style="bottom:' + (factor * i * 64 - 38 + base) + 'px;">' + i + '</span>';
marks += '<span class="board_mark horz" style="left:' + (factor * i * 64 - 35 + base) + 'px;">' + letters[i - 1] + '</span>';
}
$board.remove('span.board_mark').append(marks);
};
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>");
}
/////////////
// game.js //
/////////////
$.widget("lichess.game", {
_init: function() {
var self = this;
self.$board = self.element.find("div.lichess_board");
self.$table = self.element.find("div.lichess_table_wrap");
self.$tableInner = self.$table.find("div.table_inner");
self.$chat = $("div.lichess_chat").orNot();
self.$watchers = $("div.watchers");
self.initialTitle = document.title;
self.hasMovedOnce = false;
self.premove = null;
self.options.tableUrl = self.element.data('table-url');
self.options.playersUrl = self.element.data('players-url');
self.options.socketUrl = self.element.data('socket-url');
self.socketAckTimeout = null;
$("div.game_tournament .clock").each(function() {
$(this).clock({
time: $(this).data("time")
}).clock("start");
});
if (self.options.tournament_id) {
$('body').data('tournament-id', self.options.tournament_id);
}
if (self.options.game.started) {
self.indicateTurn();
self.initSquaresAndPieces();
self.initTable();
self.initClocks();
if (self.$chat) self.$chat.chat({
resize: true,
render: function(u, t) {
if (self.options.player.spectator) {
return '<li><span>' + $.userLinkLimit(u, 12) + '</span>' + urlToLink(t) + '</li>';
} else {
return '<li class="' + u + (u == 'system' ? ' trans_me' : '') + '">' + urlToLink(t) + '</li>';
}
}
});
self.$watchers.watchers();
if (self.isMyTurn() && self.options.game.turns == 0) {
self.element.one('lichess.audio_ready', function() {
$.playSound();
});
}
if (!self.options.game.finished && !self.options.player.spectator) {
self.blur = 0;
$(window).blur(function() {
self.blur = 1;
});
}
self.unloaded = false;
$(window).unload(function() {
self.unloaded = true;
});
if (self.options.game.last_move) {
self.highlightLastMove(self.options.game.last_move);
}
}
if (self.options.player.spectator) {
self.$board.find("div.lcs").mousedown(function() {
$("#dont_touch").toggle();
});
}
if (!self.options.opponent.ai && !self.options.player.spectator) {
setTimeout(self.updateTitle = function() {
document.title = (self.isMyTurn() && self.options.game.started && !self.options.game.finished) ? document.title = document.title.indexOf('/\\/') == 0 ? '\\/\\ ' + document.title.replace(/\/\\\/ /, '') : '/\\/ ' + document.title.replace(/\\\/\\ /, '') : document.title;
setTimeout(self.updateTitle, 400);
},
400);
}
lichess.socket = new strongSocket(
lichess.socketUrl + self.options.socketUrl,
self.options.player.version,
$.extend(true, lichess.socketDefaults, {
options: {
name: "game"
},
events: {
ack: function() {
clearTimeout(self.socketAckTimeout);
},
message: function(e) {
self.element.queue(function() {
if (self.$chat) self.$chat.chat("append", e.u, e.t);
self.element.dequeue();
});
},
possible_moves: function(event) {
self.element.queue(function() {
self.options.possible_moves = event;
self.indicateTurn();
self.element.dequeue();
});
},
move: function(event) {
self.element.queue(function() {
// if a draw was claimable, remove the zone
$('div.lichess_claim_draw_zone').remove();
self.$board.find("div.lcs.check").removeClass("check");
self.$board.find("div.droppable-hover").removeClass("droppable-hover");
// If I made the move, the piece is already moved on the board
if (self.hasMovedOnce && event.color == self.options.player.color) {
self.element.dequeue();
} else {
self.movePiece(event.from, event.to, function() {
self.element.dequeue();
}, false);
}
});
},
castling: function(event) {
self.element.queue(function() {
$("div#" + event.rook[1], self.$board).append($("div#" + event.rook[0] + " div.lichess_piece.rook", self.$board));
// if the king is beeing animated, stop it now
if ($king = $('body > div.king').orNot()) $king.stop(true, true);
$("div#" + event.king[1], self.$board).append($("div.lichess_piece.king." + event.color, self.$board));
self.element.dequeue();
});
},
promotion: function(event) {
self.element.queue(function() {
$("div#" + event.key + " div.lichess_piece").addClass(event.pieceClass).removeClass("pawn");
self.element.dequeue();
});
},
check: function(event) {
self.element.queue(function() {
$("div#" + event, self.$board).addClass("check");
self.element.dequeue();
});
},
enpassant: function(event) {
self.element.queue(function() {
self.killPiece($("div#" + event + " div.lichess_piece", self.$board));
self.element.dequeue();
});
},
redirect: function(event) {
// stop queue propagation here
self.element.queue(function() {
setTimeout(function() {
location.href = event;
}, 400);
});
},
threefold_repetition: function() {
self.element.queue(function() {
self.reloadTable(function() {
self.element.dequeue();
});
});
},
gone: function(event) {
if (!self.options.opponent.ai) {
self.$table.find("div.force_resign_zone").toggle(event);
self.centerTable();
}
},
end: function() {
// Game end must be applied firt: no queue
self.options.game.finished = true;
self.$table
.find("div.lichess_table").addClass("finished").end()
.find(".moretime").remove().end()
.find('div.clock').clock('stop');
self.element.find("div.ui-draggable").draggable("destroy");
// But enqueue the visible changes
self.element.queue(function() {
self.changeTitle($.trans('Game over'));
self.element.removeClass("my_turn");
self.reloadTable(function() {
self.reloadPlayers(function() {
$.playSound();
self.element.dequeue();
});
});
});
},
reload_table: function() {
self.element.queue(function() {
self.reloadTable(function() {
self.element.dequeue();
});
});
},
clock: function(event) {
self.element.queue(function() {
self.updateClocks(event);
self.element.dequeue();
});
},
premove: function() {
self.element.queue(function() {
self.applyPremove();
self.element.dequeue();
});
},
crowd: function(event) {
$(["white", "black"]).each(function() {
self.$table.find("div.username." + this).toggleClass("connected", event[this]).toggleClass("offline", !event[this]);
});
self.$watchers.watchers("set", event.watchers);
},
state: function(event) {
self.element.queue(function() {
self.options.game.player = event.color;
self.options.game.turns = event.turns;
self.element.dequeue();
});
}
}
}));
},
isMyTurn: function() {
return this.options.possible_moves != null;
},
changeTitle: function(text) {
if (this.options.player.spectator) return;
document.title = text + " - " + this.initialTitle;
},
indicateTurn: function() {
var self = this;
if (self.options.game.finished) {
self.changeTitle($.trans('Game over'));
} else if (self.isMyTurn()) {
self.element.addClass("my_turn");
self.changeTitle($.trans('Your turn'));
} else {
self.element.removeClass("my_turn");
self.changeTitle($.trans('Waiting for opponent'));
}
if (!self.$table.find('>div').hasClass('finished')) {
self.$tableInner.find("div.lichess_current_player div.lichess_player." + (self.isMyTurn() ? self.options.opponent.color : self.options.player.color)).hide();
self.$tableInner.find("div.lichess_current_player div.lichess_player." + (self.isMyTurn() ? self.options.player.color : self.options.opponent.color)).show();
}
},
movePiece: function(from, to, callback, mine) {
var self = this,
$piece = self.$board.find("div#" + from + " div.lichess_piece"),
$from = $("div#" + from, self.$board),
$to = $("div#" + to, self.$board);
// already moved
if (!$piece.length) {
self.onError(from + " " + to + ' empty from square!!', true);
return;
}
self.highlightLastMove(from + " " + to);
if (!self.isPlayerColor(self.getPieceColor($piece))) {
$.playSound();
}
var afterMove = function() {
var $killed = $to.find("div.lichess_piece");
if ($killed.length && self.getPieceColor($piece) != self.getPieceColor($killed)) {
self.killPiece($killed);
}
$piece.css({
top: 0,
left: 0
});
$to.append($piece);
$.isFunction(callback || null) && callback();
};
var animD = mine ? 0 : self.options.animation_delay;
$('body > div.lichess_piece').stop(true, true);
if (animD < 100) {
afterMove();
} else {
$("body").append($piece.css({
top: $from.offset().top,
left: $from.offset().left
}));
$piece.animate({
top: $to.offset().top,
left: $to.offset().left
}, animD, afterMove);
}
},
highlightLastMove: function(notation) {
var self = this;
var squareIds = notation.split(" ");
$("div.lcs.moved", self.$board).removeClass("moved");
$("#" + squareIds[0] + ",#" + squareIds[1], self.$board).addClass("moved");
},
killPiece: function($piece) {
if ($.data($piece, 'draggable')) $piece.draggable("destroy");
var self = this,
$deads = self.element.find("div.lichess_cemetery." + self.getPieceColor($piece)),
$square = $piece.parent();
$deads.append($("<div>").addClass('lichess_tomb'));
var $tomb = $("div.lichess_tomb:last", $deads),
tomb_offset = $tomb.offset();
$('body').append($piece.css($square.offset()));
$piece.css("opacity", 0.5).animate({
top: tomb_offset.top,
left: tomb_offset.left
},
self.options.animation_delay * 2, function() {
$tomb.append($piece.css({
position: "relative",
top: 0,
left: 0
}));
});
},
possibleMovesContain: function(from, to) {
return this.options.possible_moves != null && typeof this.options.possible_moves[from] !== 'undefined' && this.options.possible_moves[from].indexOf(to) != -1;
},
applyPremove: function() {
var self = this;
if (self.premove && self.isMyTurn()) {
var move = self.premove;
self.unsetPremove();
if (self.possibleMovesContain(move.from, move.to)) {
var $fromSquare = $("#" + move.from).orNot();
var $toSquare = $("#" + move.to).orNot();
var $piece = $fromSquare.find(".lichess_piece").orNot();
if ($fromSquare && $toSquare && $piece) {
self.dropPiece($piece, $fromSquare, $toSquare, true);
}
}
}
},
setPremove: function(move) {
var self = this;
if (self.isMyTurn()) return;
self.unsetPremove();
if (move.from == move.to) return;
self.premove = move;
$("#" + move.from + ",#" + move.to).addClass("premoved");
self.unselect();
$("#premove").show();
},
unsetPremove: function() {
var self = this;
self.premove = null;
self.$board.find('div.lcs.premoved').removeClass('premoved');
$("#premove").hide();
},
unselect: function() {
this.$board.find('> div.selected').removeClass('selected');
},
dropPiece: function($piece, $oldSquare, $newSquare, isPremove) {
var self = this,
isPremove = isPremove || false;
squareId = $newSquare.attr('id'),
moveData = {
from: $oldSquare.attr("id"),
to: squareId,
b: self.blur
};
if (!self.isMyTurn()) {
return self.setPremove({
from: moveData.from,
to: moveData.to
});
}
self.unselect();
self.hasMovedOnce = true;
self.blur = 0;
self.options.possible_moves = null;
self.movePiece($oldSquare.attr("id"), squareId, null, true);
function sendMoveRequest(moveData) {
if (self.canRunClock()) {
moveData.lag = parseInt(lichess.socket.averageLag);
}
lichess.socket.send("move", moveData);
self.socketAckTimeout = setTimeout(function() {
location.reload();
}, lichess.socket.options.pingMaxLag);
}
var color = self.options.player.color;
// promotion
if ($piece.hasClass('pawn') && ((color == "white" && squareId[1] == 8) || (color == "black" && squareId[1] == 1))) {
if (isPremove) {
moveData.promotion = "queen";
sendMoveRequest(moveData);
} else {
var $choices = $('<div class="lichess_promotion_choice">')
.appendTo(self.$board)
.html('<div data-piece="queen" class="lichess_piece queen ' + color + '"></div><div data-piece="knight" class="lichess_piece knight ' + color + '"></div><div data-piece="rook" class="lichess_piece rook ' + color + '"></div><div data-piece="bishop" class="lichess_piece bishop ' + color + '"></div>')
.fadeIn(self.options.animation_delay)
.find('div.lichess_piece')
.click(function() {
moveData.promotion = $(this).attr('data-piece');
sendMoveRequest(moveData);
$choices.fadeOut(self.options.animation_delay, function() {
$choices.remove();
});
}).end();
}
} else {
sendMoveRequest(moveData);
}
},
initSquaresAndPieces: function() {
var self = this;
if (self.options.game.finished || self.options.player.spectator) {
return;
}
var draggingKey = null;
var dropped = false;
// init squares
self.$board.find("div.lcs").each(function() {
var squareId = $(this).attr('id');
$(this).droppable({
accept: function(draggable) {
if (!self.isMyTurn()) {
return draggingKey != squareId;
} else {
return draggingKey && self.possibleMovesContain(draggingKey, squareId);
}
},
drop: function(ev, ui) {
self.dropPiece(ui.draggable, ui.draggable.parent(), $(this));
dropped = true;
},
hoverClass: 'droppable-hover'
});
});
// init pieces
self.$board.find("div.lichess_piece." + self.options.player.color).each(function() {
var $this = $(this);
$this.draggable({
containment: self.$board,
helper: function() {
return $('<div>').attr('class', $this.attr('class')).appendTo(self.$board);
},
start: function() {
draggingKey = $this.hide().parent().attr('id');
dropped = false;
self.unselect();
},
stop: function(e, ui) {
draggingKey = null;
var dist = Math.sqrt(Math.pow(ui.originalPosition.top - ui.position.top, 2) + Math.pow(ui.originalPosition.left - ui.position.left, 2));
if (!dropped && dist <= 32) $this.trigger('click');
$this.show();
},
scroll: false
});
});
/*
* Code for touch screens like android or iphone
*/
self.$board.find("div.lichess_piece." + self.options.player.color).each(function() {
$(this).click(function() {
self.unsetPremove();
var $square = $(this).parent();
if ($square.hasClass('selectable')) return;
var isSelected = $square.hasClass('selected');
self.unselect();
if (isSelected) return;
$square.addClass('selected');
});
});
self.$board.find("div.lcs").each(function() {
var $this = $(this);
$this.hover(function() {
if ($selected = self.$board.find('div.lcs.selected').orNot()) {
if (!self.isMyTurn() || self.possibleMovesContain($selected.attr('id'), $this.attr('id'))) {
$this.addClass('selectable');
}
}
}, function() {
$this.removeClass('selectable');
}).click(function() {
self.unsetPremove();
var $from = self.$board.find('div.lcs.selected').orNot();
var $to = $this;
if (!$from || $from == $to) return;
var $piece = $from.find('div.lichess_piece');
if (!self.isMyTurn() && $from) {
self.dropPiece($piece, $from, $to);
} else {
if (!self.possibleMovesContain($from.attr('id'), $this.attr('id'))) return;
if (!$to.hasClass('selectable')) return;
$to.removeClass('selectable');
self.dropPiece($piece, $from, $this);
}
});
});
/*
* End of code for touch screens
*/
},
reloadTable: function(callback) {
var self = this;
self.get(self.options.tableUrl, {
success: function(html) {
self.$tableInner.html(html);
self.initTable();
$.isFunction(callback) && callback();
$('body').trigger('lichess.content_loaded');
}
}, false);
},
reloadPlayers: function(callback) {
var self = this;
$.getJSON(self.options.playersUrl, function(data) {
$(['white', 'black']).each(function() {
if (data[this]) self.$table.find('div.username.' + this).html(data[this]);
});
if (data.me) $('#user_tag span').text(data.me);
$('body').trigger('lichess.content_loaded');
$.isFunction(callback) && callback();
});
},
initTable: function() {
var self = this;
self.centerTable();
self.$table.find('a.moretime').unbind("click").click(self.moretime);
},
moretime: _.throttle(function() {
lichess.socket.send('moretime');
}, 800),
centerTable: function() {
var self = this;
self.$table.find(".lichess_control").each(function() {
$(this).toggleClass("none", $(this).html().trim() == "");
});
self.$table.css('top', (256 - self.$table.height() / 2) + 'px');
},
outoftime: _.debounce(function() {
lichess.socket.send('outoftime');
}, 200),
initClocks: function() {
var self = this;
if (!self.canRunClock()) return;
self.$table.find('div.clock').each(function() {
$(this).clock({
time: $(this).attr('data-time'),
emerg: $(this).attr('data-emerg'),
buzzer: function() {
if (!self.options.game.finished && !self.options.player.spectator) {
self.outoftime();
}
}
});
});
self.updateClocks();
},
updateClocks: function(times) {
var self = this;
if (!self.canRunClock()) return;
if (times || false) {
for (color in times) {
self.$table.find('div.clock_' + color).clock('setTime', times[color]);
}
}
self.$table.find('div.clock').clock('stop');
if (self.options.game.turns > 0 || self.options.game.clockRunning) {
self.$table.find('div.clock_' + self.options.game.player).clock('start');
}
},
canRunClock: function() {
return this.options.game.clock && this.options.game.started && !this.options.game.finished;
},
getPieceColor: function($piece) {
return $piece.hasClass('white') ? 'white' : 'black';
},
isPlayerColor: function(color) {
return !this.options.player.spectator && this.options.player.color == color;
},
get: function(url, options, reloadIfFail) {
var self = this;
options = $.extend({
type: 'GET',
timeout: 8000,
cache: false
},
options || {});
$.ajax(url, options).complete(function(x, s) {
self.onXhrComplete(x, s, null, reloadIfFail);
});
},
post: function(url, options, reloadIfFail) {
var self = this;
options = $.extend({
type: 'POST',
timeout: 8000
},
options || {});
$.ajax(url, options).complete(function(x, s) {
self.onXhrComplete(x, s, 'ok', reloadIfFail);
});
},
onXhrComplete: function(xhr, status, expectation, reloadIfFail) {
if (status != 'success') {
this.onError('status is not success: ' + status, reloadIfFail);
}
if ((expectation || false) && expectation != xhr.responseText) {
this.onError('expectation failed: ' + xhr.responseText, reloadIfFail);
}
},
onError: function(error, reloadIfFail) {
var self = this;
if (reloadIfFail) {
location.reload();
}
}
});
$.widget("lichess.watchers", {
_create: function() {
this.list = this.element.find("span.list");
},
set: function(users) {
var self = this;
if (users.length > 0) {
self.list.html(_.map(users, $.userLink).join(", "));
self.element.show();
} else {
self.element.hide();
}
}
});
$.widget("lichess.friends", {
_create: function() {
this.nb = this.element.find('.title strong');
this.list = this.element.find("div.list");
this.nobody = this.element.find("div.nobody");
},
repaint: function() {
var nb = this.list.children().length;
this.nb.text(nb);
this.nobody.toggle(nb == 0);
},
set: function(users) {
this.list.html(_.map(users, this._renderUser).join(""));
this.repaint();
},
enters: function(user) {
this.list.append(renderUser(user));
this.repaint();
},
leaves: function(user) {
this.list.children().filter(function() {
return $(this).text() == user;
}).remove();
this.repaint();
},
_renderUser: function(user) {
return '<a href="/@/' + user + '">' + user + '</a>';
}
});
$.widget("lichess.chat", {
_create: function() {
this.options = $.extend({
// render: function(u, t) {},
resize: false
}, this.options);
var self = this;
self.$msgs = self.element.find('.lichess_messages');
if (this.options.resize) {
var headerHeight = self.element.parent().height();
self.element.css("top", headerHeight + 13);
self.$msgs.scrollTop(999999);
}
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 = self.element.find('input.toggle_chat');
$toggle.change(function() {
var enabled = $toggle.is(':checked');
self.element.toggleClass('hidden', !enabled);
$.post($toggle.data('href'), {
"chat": enabled
});
});
if (!$toggle.data("enabled")) {
self.element.addClass('hidden');
}
$toggle[0].checked = $toggle.data("enabled");
},
append: function(u, t) {
this._appendHtml(this.options.render(u, t));
},
appendMany: function(objs) {
var self = this,
html = "";
$.each(objs, function() {
html += self.options.render(this.u, this.t);
});
this._appendHtml(html);
},
_appendHtml: function(html) {
this.$msgs.append(html);
$('body').trigger('lichess.content_loaded');
this.$msgs.scrollTop(999999);
},
remove: function(regex) {
var r = new RegExp(regex);
$this.$msgs.find('li').filter(function() {
return r.test($(this).html());
}).remove();
}
});
$.widget("lichess.clock", {
_create: function() {
var self = this;
this.options.time = parseFloat(this.options.time) * 1000;
this.options.emerg = parseFloat(this.options.emerg) * 1000;
$.extend(this.options, {
duration: this.options.time,
state: 'ready'
});
this.element.addClass('clock_enabled');
},
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
current_time == 0 && $.isFunction(self.options.buzzer) && self.options.buzzer(self.element);
} else {
clearInterval(self.options.interval);
}
},
1000);
},
setTime: function(time) {
this.options.time = parseFloat(time) * 1000;
this._show();
},
stop: function() {
clearInterval(this.options.interval);
this.options.state = 'stop';
this.element.removeClass('running');
this.element.toggleClass('outoftime', this.options.time <= 0);
},
_show: function() {
this.element.text(this._formatDate(new Date(this.options.time)));
this.element.toggleClass('emerg', this.options.time < this.options.emerg);
},
_formatDate: function(date) {
minutes = this._prefixInteger(date.getUTCMinutes(), 2);
seconds = this._prefixInteger(date.getSeconds(), 2);
return minutes + ':' + seconds;
},
_prefixInteger: function(num, length) {
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}
});
/////////////////
// gamelist.js //
/////////////////
$(function() {
function parseFen($elem) {
if (!$elem || !$elem.jquery) {
$elem = $('.parse_fen');
}
$elem.each(function() {
var $this = $(this);
var color = $this.data('color') || "white";
var withKeys = $this.hasClass('with_keys');
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
var fen = $this.data('fen').split(' ')[0].replace(/\//g, '');
var lm = $this.data('lastmove');
var lastMove = lm ? [lm[0] + lm[1], lm[2] + lm[3]] : [];
var x, y, html = '',
scolor, pcolor, pclass, c, d, increment;
var pclasses = {
'p': 'pawn',
'r': 'rook',
'n': 'knight',
'b': 'bishop',
'q': 'queen',
'k': 'king'
};
var pregex = /(p|r|n|b|q|k)/;
if ('white' == color) {
var x = 8,
y = 1;
var increment = function() {
y++;
if (y > 8) {
y = 1;
x--;
}
};
} else {
var x = 1,
y = 8;
var increment = function() {
y--;
if (y < 1) {
y = 8;
x++;
}
};
}
function openSquare(x, y) {
var key = 'white' == color ? letters[y - 1] + x : letters[8 - y] + (9 - x);
var scolor = (x + y) % 2 ? 'white' : 'black';
if ($.inArray(key, lastMove) != -1) scolor += " moved";
var html = '<div class="lmcs ' + scolor + '" style="top:' + (28 * (8 - x)) + 'px;left:' + (28 * (y - 1)) + 'px;"';
if (withKeys) {
html += ' data-key="' + key + '"';
}
return html + '>';
}
function closeSquare() {
return '</div>';
}
for (var fenIndex in fen) {
c = fen[fenIndex];
html += openSquare(x, y);
if (!isNaN(c)) { // it is numeric
html += closeSquare();
increment();
for (d = 1; d < c; d++) {
html += openSquare(x, y) + closeSquare();
increment();
}
} else {
pcolor = pregex.test(c) ? 'black' : 'white';
pclass = pclasses[c.toLowerCase()];
html += '<div class="lcmp ' + pclass + ' ' + pcolor + '"></div>';
html += closeSquare();
increment();
}
}
$this.html(html).removeClass('parse_fen');
// attempt to free memory
html = pclasses = increment = pregex = fen = $this = 0;
});
}
parseFen();
$('body').on('lichess.content_loaded', parseFen);
var socketOpened = false;
function registerLiveGames() {
if (!socketOpened) return;
var ids = [];
$('a.mini_board.live').each(function() {
ids.push($(this).data("live"));
}).removeClass("live");
if (ids.length > 0) {
lichess.socket.send("liveGames", ids.join(" "));
}
}
$('body').on('lichess.content_loaded', registerLiveGames);
$('body').on('socket.open', function() {
socketOpened = true;
registerLiveGames();
});
lichess.socketDefaults.events.fen = function(e) {
$('a.live_' + e.id).each(function() {
var $this = $(this);
parseFen($this.data("fen", e.fen).data("lastmove", e.lm));
});
};
$('div.checkmateCaptcha').each(function() {
var $captcha = $(this);
var $squares = $captcha.find('div.lmcs');
var $input = $captcha.find('input').val('');
$captcha.find('button.retry').click(function() {
$input.val("");
$squares.removeClass('selected');
$captcha.removeClass("success failure");
return false;
});
$squares.click(function() {
var key = $(this).data('key');
$captcha.removeClass("success failure");
if ($input.val().length == 2) {
$input.val($.trim($input.val() + " " + key));
$.ajax({
url: $captcha.data('check-url'),
data: {
solution: $input.val()
},
success: function(data) {
$captcha.addClass(data == 1 ? "success" : "failure");
}
});
} else {
$squares.removeClass('selected');
$input.val(key);
}
$(this).addClass('selected');
});
});
});
////////////////
// lobby.js //
////////////////
$(function() {
var $startButtons = $('#start_buttons');
if (!strongSocket.available) {
$('#start_buttons a').attr('href', '#');
$("div.lichess_overboard.joining input.submit").remove();
return;
}
if (!$startButtons.length) {
return;
}
function prepareForm() {
var $form = $('div.lichess_overboard');
var $modeChoices = $form.find('.mode_choice input');
var $variantChoices = $form.find('.variants input');
var $casual = $modeChoices.eq(0),
$rated = $modeChoices.eq(1);
var $fenVariant = $variantChoices.eq(2);
var $fenPosition = $form.find(".fen_position");
var $clockCheckbox = $form.find('.clock_choice input');
var isHook = $form.hasClass('game_config_hook');
if (isHook) {
var $formTag = $form.find('form');
function ajaxSubmit(color) {
$.ajax({
url: $formTag.attr('action').replace(/uid-placeholder/, lichess_sri),
data: $formTag.serialize() + "&color=" + color,
type: 'post'
});
$form.find('a.close').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();
$form.find('.time_choice input, .increment_choice input').each(function() {
var $input = $(this),
$value = $input.parent().find('span');
var $timeInput = $form.find('.time_choice input');
var $incrementInput = $form.find('.increment_choice input');
$input.hide().after($('<div>').slider({
value: $input.val(),
min: $input.data('min'),
max: $input.data('max'),
range: 'min',
step: 1,
slide: function(event, ui) {
$value.text(ui.value);
$input.attr('value', ui.value);
$form.find('.color_submits button').toggle(
$timeInput.val() > 0 || $incrementInput.val() > 0);
}
}));
});
$form.find('.elo_range').each(function() {
var $this = $(this);
var $input = $this.find("input");
var $span = $this.parent().find("span.range");
var min = $input.data("min");
var max = $input.data("max");
if ($input.val()) {
var values = $input.val().split("-");
} else {
var values = [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 $eloRangeConfig = $this.parent();
$modeChoices.on('change', function() {
var rated = $rated.prop('checked');
$eloRangeConfig.toggle(rated);
if (isHook && rated && !$clockCheckbox.prop('checked')) {
$clockCheckbox.click();
}
$.centerOverboard();
}).trigger('change');
});
$clockCheckbox.on('change', function() {
var checked = $(this).is(':checked');
$form.find('.time_choice, .increment_choice').toggle(checked);
if (isHook && !checked) {
$casual.click();
}
$.centerOverboard();
}).trigger('change');
var $eloRangeConfig = $form.find('.elo_range_config');
var $fenInput = $fenPosition.find('input');
var validateFen = _.debounce(function() {
$fenInput.removeClass("success failure");
if ($fenInput.val()) {
$.ajax({
url: $fenInput.parent().data('validate-url'),
data: {
fen: $fenInput.val()
},
success: function(data) {
$fenInput.addClass("success");
$fenPosition.find('.preview').html(data);
$('body').trigger('lichess.content_loaded');
$.centerOverboard();
},
error: function() {
$fenInput.addClass("failure");
$fenPosition.find('.preview').html("");
$.centerOverboard();
}
});
}
}, 500);
$fenInput.on('keyup', validateFen);
$variantChoices.on('change', function() {
var fen = $fenVariant.prop('checked');
if (fen && $fenInput.val() != '') validateFen();
$fenPosition.toggle(fen);
$.centerOverboard();
}).trigger('change');
$form.prepend($('<a class="close"></a>').click(function() {
$form.remove();
$startButtons.find('a.active').removeClass('active');
}));
}
$startButtons.find('a').click(function() {
$startButtons.find('a.active').removeClass('active');
$(this).addClass('active');
$('div.lichess_overboard').remove();
$.ajax({
url: $(this).attr('href'),
success: function(html) {
$('div.lichess_overboard').remove();
$('div.lichess_board_wrap').prepend(html);
prepareForm();
$.centerOverboard();
}
});
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() {
$('div.lichess_overboard, div.hooks_wrap').hide();
};
// hooks
$(function() {
var $wrap = $('div.hooks_wrap');
if (!$wrap.length) return;
if (!strongSocket.available) return;
var $bot = $("div.lichess_bot");
var $newposts = $("div.new_posts");
var $newpostsinner = $newposts.find('.undertable_inner');
var $hooks = $wrap.find('div.hooks');
var $hooksTable = $hooks.find("table.some").on('click', 'a.join', $.lichessOpeningPreventClicks);
var $hooksTableEmpty = $hooks.find("table.empty");
var $userTag = $('#user_tag');
var isRegistered = $userTag.length > 0
var myElo = isRegistered ? parseInt($userTag.data('elo')) : null;
$wrap.find('a.filter').click(function() {
var $a = $(this);
var $div = $wrap.find('div.filter');
setTimeout(function() {
$div.click(function(e) {
e.stopPropagation();
});
$('html').one('click', function(e) {
$div.off('click').fadeOut(200);
$a.removeClass('active');
});
}, 10);
if ($(this).toggleClass('active').hasClass('active')) {
var $filter = $div.fadeIn(200);
if ($filter.is(':empty')) {
$.ajax({
url: $(this).attr('href'),
success: function(html) {
$filter.html(html).find('select').change(_.throttle(function() {
var $form = $filter.find('form');
$.ajax({
url: $form.attr('action'),
data: $form.serialize(),
type: 'post',
success: function(filter) {
lichess_preload.filter = filter;
updateHookTable();
}
});
}, 500));
$filter.find('button.reset').click(function() {
$filter.find('select').val('').change();
});
$filter.find('button').click(function() {
$wrap.find('a.filter').click();
return false;
});
}
});
}
} else {
$div.fadeOut(200);
}
return false;
});
$bot.on("click", "tr", function() {
location.href = $(this).find('a.watch').attr("href");
});
setInterval(function() {
$.ajax($newposts.data('url'), {
timeout: 10000,
success: function(data) {
$newpostsinner.find('ol').html(data);
$('body').trigger('lichess.content_loaded');
}
});
}, 120 * 1000);
addHooks(lichess_preload.pool);
renderTimeline(lichess_preload.timeline);
lichess.socket = new strongSocket(lichess.socketUrl + "/lobby/socket", lichess_preload.version, $.extend(true, lichess.socketDefaults, {
events: {
game_entry: function(e) {
renderTimeline([e]);
},
reload_timeline: function() {
// TODO
console.debug("reload timeline");
},
hook_add: addHook,
hook_remove: removeHook,
featured: changeFeatured,
redirect: function(e) {
$.lichessOpeningPreventClicks();
location.href = 'http://' + location.hostname + '/' + e;
},
tournaments: reloadTournaments
},
options: {
name: "lobby"
}
}));
$('body').trigger('lichess.content_loaded');
function reloadTournaments(data) {
$("table.tournaments").html(data);
}
function changeFeatured(html) {
$('#featured_game').html(html);
$('body').trigger('lichess.content_loaded');
}
function renderTimeline(data) {
var html = "";
for (i in data) {
html += '<tr>' + data[i] + '</tr>';
}
$bot.find('.lichess_messages').append(html).scrollTop(999999);
$('body').trigger('lichess.content_loaded');
}
function removeHook(id) {
$("#" + id).find('td.action').addClass('empty').html("").end().fadeOut(500, function() {
$(this).remove();
updateHookTable();
});
}
function addHooks(hooks) {
var html = "";
for (i in hooks) html += $hooksTable.append(renderHook(hooks[i]));
updateHookTable();
}
function addHook(hook) {
$hooksTable.append(renderHook(hook));
updateHookTable();
}
function updateHookTable() {
var filter = lichess_preload.filter;
$hooksTable.find('tr.hook').each(function() {
var hook = $(this).data('hook');
var hide = (filter.variant != null && filter.variant != hook.variant) ||
(filter.mode != null && filter.mode != hook.mode) ||
(filter.speed != null && filter.speed != hook.speed) ||
(filter.eloDiff > 0 && (!hook.elo || hook.elo > (myElo + filter.eloDiff) || hook.elo < (myElo - filter.eloDiff)));
$(this).toggleClass('none', hide && (hook.action != 'cancel'));
});
var nbVisibleHooks = $hooksTable.find('tr.hook:not(.none)').length;
$hooksTable.toggleClass("none", nbVisibleHooks == 0);
$hooksTableEmpty.toggleClass("none", nbVisibleHooks != 0);
$wrap
.toggleClass("large", nbVisibleHooks > 6)
.find('a.filter')
.toggleClass('on', filter.mode != null || filter.variant != null || filter.speed != null || filter.eloDiff > 0)
.find('span.number').text('(' + $hooksTable.find('tr.hook.none').length + ')');
}
function renderHook(hook) {
if (!isRegistered && hook.mode == "Rated") return "";
hook.action = hook.uid == lichess_sri ? "cancel" : "join";
if (hook.emin && hook.action == "join" && (myElo < parseInt(hook.emin) || myElo > parseInt(hook.emax))) return "";
var html = "",
isEngine, engineMark, userClass, mode;
html += '<tr id="' + hook.id + '" class="hook' + (hook.action == 'join' ? ' joinable' : '') + '">';
html += '<td class="color"><span class="' + hook.color + '"></span></td>';
isEngine = hook.engine && hook.action == 'join';
engineMark = isEngine ? '<span class="engine_mark"></span>' : '';
userClass = isEngine ? "user_link engine" : "user_link";
if (hook.elo) {
html += '<td><a class="' + userClass + '" href="/@/' + hook.username + '">' + hook.username.substr(0, 12) + '<br />' + '(' + hook.elo + ')' + engineMark + '</a></td>';
} else {
html += '<td>' + hook.username + '</td>';
}
html += '</td>';
if (isRegistered) {
mode = $.trans(hook.mode);
if (hook.emin && (hook.emin > 800 || hook.emax < 2500)) {
mode += "<span class='elorange'>" + hook.emin + ' - ' + hook.emax + '</span>';
}
} else {
mode = "";
}
if (hook.variant == 'Chess960') {
html += '<td><a href="http://en.wikipedia.org/wiki/Chess960"><strong>960</strong></a> ' + mode + '</td>';
} else {
html += '<td>' + mode + '</td>';
}
html += '<td>' + $.trans(hook.clock) + '</td>';
html += '<td class="action">';
if (hook.action == "cancel") {
html += '<a class="cancel socket-link" data-msg="cancel"></a>';
} else {
html += '<a class="join socket-link" data-msg="join" data-data="' + hook.id + '"></a>';
}
html += '</td>';
html += '</tr>';
var $hook = $(html).data('hook', hook);
if (hook.variant == "Chess960") {
$hook.find('a.join').click(function() {
if ($.cookie('c960') == 1) return true;
var c = confirm("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?");
if (c) $.cookie('c960', 1);
return c;
});
}
return $hook;
}
$hooks.on('click', 'table.empty tr', function() {
$('#start_buttons a.config_hook').click();
});
});
///////////////////
// tournament.js //
///////////////////
$(function() {
var $wrap = $('#tournament');
if (!$wrap.length) return;
var $userTag = $('#user_tag');
if (!strongSocket.available) return;
if (typeof _ld_ == "undefined") {
// handle tournament list
lichess.socketDefaults.params.flag = "tournament";
lichess.socketDefaults.events.reload = function() {
$wrap.load($wrap.data("href"), function() {
$('body').trigger('lichess.content_loaded');
});
};
return;
}
$('body').data('tournament-id', _ld_.tournament.id);
var $userList = $wrap.find("div.user_list");
var socketUrl = $wrap.data("socket-url");
var $watchers = $("div.watchers").watchers();
var $chat = $("div.lichess_chat").chat({
render: function(u, t) {
return '<li><span>' + $.userLinkLimit(u, 12) + '</span>' + urlToLink(t) + '</li>';
}
});
function startClock() {
$("span.tournament_clock").each(function() {
$(this).clock({
time: $(this).data("time")
}).clock("start");
});
}
startClock();
function reload() {
$wrap.load($wrap.data("href"), function() {
startClock();
$('body').trigger('lichess.content_loaded');
});
}
function start() {
alert("Tournament is starting!");
reload();
}
lichess.socket = new strongSocket(lichess.socketUrl + socketUrl, _ld_.version, $.extend(true, lichess.socketDefaults, {
events: {
talk: function(e) {
$chat.chat('append', e.u, e.t);
},
start: start,
reload: reload,
reloadPage: function() {
location.reload();
},
redirect: function(e) {
location.href = 'http://' + location.hostname + '/' + e;
},
crowd: function(data) {
$watchers.watchers("set", data);
}
},
options: {
name: "tournament"
}
}));
});
////////////////
// analyse.js //
////////////////
$(function() {
if (!$("#GameBoard").length) return;
SetImagePath("/assets/vendor/pgn4web/lichess/64"); // use "" path if images are in the same folder as this javascript file
SetImageType("png");
SetShortcutKeysEnabled(false);
clearShortcutSquares("BCDEFGH", "12345678");
clearShortcutSquares("A", "1234567");
var $game = $("#GameBoard");
var $chat = $("div.lichess_chat").chat({
resize: true
});
var $watchers = $("div.watchers").watchers();
lichess.socket = new strongSocket(
lichess.socketUrl + $game.data("socket-url"),
parseInt($game.data("version")),
$.extend(true, lichess.socketDefaults, {
options: {
name: "analyse",
ignoreUnknownMessages: true
},
events: {
message: function(e) {
$chat.chat("append", e.u, e.t);
},
crowd: function(event) {
$watchers.watchers("set", event.watchers);
}
}
}));
});
})();
if (/.+\.lichess\.org/.test(document.domain)) {
//analytics
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-7935029-3']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = 'http://www.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
}