Merge branch 'master' into play22

* master:
  cache more game collection queries
  cache confrontation count
  complete autopairing implementation
  bs "bosanski jezik" translation #4246. Author: MirhadS.
  improve autopairing wip
  implement hook auto pairing
  disable global wiretap
  upgrade mongodb driver
  fix pgn export date format
  remove index comments
  upgrade js vendors
  upgrade reactivemongo

Conflicts:
	project/Dependencies.scala
pull/83/head
Thibault Duplessis 2013-09-18 13:46:07 +02:00
commit 3894e4fb02
25 changed files with 193 additions and 138 deletions

View File

@ -29,6 +29,7 @@ final class Env(
bookmarkApi = Env.bookmark.api,
eloCalculator = Env.round.eloCalculator,
relationApi = Env.relation.api,
gameCached = Env.game.cached,
postApi = Env.forum.postApi,
getRank = Env.user.ranking.get) _

View File

@ -18,9 +18,8 @@ object Global extends GlobalSettings {
else Action(NotFound("I am an AI server")).some
}
else {
// if (!Env.api.isProd) println(req)
Env.monitor.reporting ! AddRequest
Env.security.wiretap(req)
// Env.security.wiretap(req)
Env.security.firewall.requestHandler(req).await orElse
Env.i18n.requestHandler(req) orElse
super.onRouteRequest(req)

View File

@ -63,7 +63,7 @@ object Setup extends LilaController with TheftPrevention {
def hook(uid: String) = OpenBody { implicit ctx
implicit val req = ctx.body
env.forms.hook(ctx).bindFromRequest.value ?? { config
env.processor.hook(config, uid, lila.common.HTTPRequest sid req)
env.processor.hook(config, uid, lila.common.HTTPRequest sid req, ctx.me)
}
}

View File

@ -36,7 +36,7 @@ object Tv extends LilaController {
private def confrontation(game: GameModel): Fu[Option[Confrontation]] = ~{
(game.creator.userId |@| game.invited.userId) apply {
case (id1, id2) (UserRepo byId id1) zip (UserRepo byId id2) flatMap {
case (Some(user1), Some(user2)) GameRepo.confrontation(user1, user2) map (_.some)
case (Some(user1), Some(user2)) Env.game.cached.confrontation(user1, user2) map (_.some)
case _ fuccess(none)
}
}

View File

@ -36,16 +36,17 @@ object UserInfo {
bookmarkApi: BookmarkApi,
eloCalculator: EloCalculator,
relationApi: RelationApi,
gameCached: lila.game.Cached,
postApi: PostApi,
getRank: String Fu[Option[Int]])(user: User, ctx: Context): Fu[UserInfo] =
(getRank(user.id) flatMap {
_ ?? { rank countUsers() map { nb (rank -> nb).some } }
}) zip
((ctx is user) ?? {
GameRepo count (_ notFinished user.id) map (_.some)
gameCached nbPlaying user.id map (_.some)
}) zip
(ctx.me.filter(user!=) ?? { me
GameRepo.confrontation(me, user) map (_.some filterNot (_.empty))
gameCached.confrontation(me, user) map (_.some filterNot (_.empty))
}) zip
(bookmarkApi countByUser user) zip
EloChart(user) zip

View File

@ -1,7 +1,7 @@
@(c: lila.user.Confrontation)(implicit ctx: Context)
<div class="confrontation boxed_data">
<div class="vs">@userLink(c.user1, withElo = false, withOnline = false) vs @userLink(c.user2, withElo = false, withOnline = false)</div>
<div class="vs">@userIdLink(c.user1.some, withOnline = false) vs @userIdLink(c.user2.some, withOnline = false)</div>
@trans.nbWins("<strong>"+c.wins+"</strong>"),
@trans.nbDraws("<strong>"+c.draws+"</strong>"),
@trans.nbLosses("<strong>"+c.losses+"</strong>")

View File

@ -1,87 +1,100 @@
playWithAFriend=Igraj s prijateljem
playWithAFriend=Igraj protiv prijatelja
inviteAFriendToPlayWithYou=Pozovi prijatelja
playWithTheMachine=Igraj protiv računara
challengeTheArtificialIntelligence=Izazovi vještačku inteligenciju
toInviteSomeoneToPlayGiveThisUrl=Kako bi pozvao nekoga, pošalji slijedeći link:
gameOver=Kraj igre
waitingForOpponent=Čekaj protivnika
waiting=Čekamo
yourTurn=Ti si na potezu
aiNameLevelAiLevel=%s Nivo %s
level=Nivo
toggleTheChat=Aktiviraj/deaktiviraj chat
toggleSound=Uključi/Isključi zvuk
chat=Chat
resign=Predaj
toInviteSomeoneToPlayGiveThisUrl=Za poziv u igru, posalji ovu adresu
gameOver=Igra je završila
waitingForOpponent=Čekanje protivnika
waiting=% na potezu
yourTurn=Vi ste na potezu
aiNameLevelAiLevel=%s Razina %s
level=Razina
toggleTheChat=Uključi/isključi dopisivanje
toggleSound=Uključi/ isključi zvuk
chat=Dopisivanje
resign=Predati igru
checkmate=Šah-mat
stalemate=Pat
white=Bijeli
black=Crni
createAGame=Kreiraj partiju
noGameAvailableRightNowCreateOne=Trenutno nema raspoložive partije, kreiraj je!
whiteIsVictorious=Bijeli pobjeđuje
blackIsVictorious=Crni pobjeđuje
playWithTheSameOpponentAgain=Igraj opet sa istim protivnikom
createAGame=Stvoriti igru
noGameAvailableRightNowCreateOne=Nema dostupnih igara
whiteIsVictorious=Bijeli je pobjednik
blackIsVictorious=Crni je pobjednik
playWithTheSameOpponentAgain=Igrati ponovo sa istim protivnikom
newOpponent=Novi protivnik
playWithAnotherOpponent=Igraj protiv nekog drugog
yourOpponentWantsToPlayANewGameWithYou=Tvoj protivnik želi opet ograti s tobom
joinTheGame=Uključi se u igru
playWithAnotherOpponent=Igrati sa drugim protivnikom
yourOpponentWantsToPlayANewGameWithYou=Vaš protivnik želi novu igru
joinTheGame=Pridruži se igri
whitePlays=Bijeli je na potezu
blackPlays=Crni je na potezu
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=Protivnički igrač je napustio igru. Možete tražiti da preda partiju ili čekati.
makeYourOpponentResign=Tražite da preda igru.
forceResignation=Tražite da preda igru.
talkInChat=Chat
theFirstPersonToComeOnThisUrlWillPlayWithYou=Prva osoba koja iskoristi ovaj link će igrati s vama
whiteCreatesTheGame=Bijeli kreira igru
blackCreatesTheGame=Crni kreira igru
whiteJoinsTheGame=Bijeli se pridružio
blackJoinsTheGame=Crni se pridružio
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=Vaš protivnik je napustio igru. Možete ga prisiliti na predaju ili čekati.
makeYourOpponentResign=Tražite da protivnik preda igru
forceResignation=Prisilite protivnika na predaju
talkInChat=Dopisivati se
theFirstPersonToComeOnThisUrlWillPlayWithYou=Prva osoba koja se pojavi, na ovoj adresi,će igrati protiv vas
whiteCreatesTheGame=Bijeli stvara igru
blackCreatesTheGame=Crni stvara igru
whiteJoinsTheGame=Bijeli se pridružuje igri
blackJoinsTheGame=Crni se pridružuje igri
whiteResigned=Bijeli je predao
blackResigned=Crni je predao
whiteLeftTheGame=Bijeli je napustio igru
blackLeftTheGame=Crni je napustio igru
shareThisUrlToLetSpectatorsSeeTheGame=Podijeli ovaj link kako bi posmatrači mogli vidjeti igru
youAreViewingThisGameAsASpectator=Gledate ovu igru kao promatrač
replayAndAnalyse=Repriza i analiza
viewGameStats=Pogledaj statistiku igre
flipBoard=Zamijeni strane
threefoldRepetition=Pozicija tri puta ponovljena
claimADraw=Zatraži neriješeno
offerDraw=Ponudi remi
draw=neriješeno
nbConnectedPlayers=%s spojenih igrača
talkAboutChessAndDiscussLichessFeaturesInTheForum=Razgovaraj o šahu i raspravljaj lichess značajke na forumu
seeTheGamesBeingPlayedInRealTime=Pogledaj trenutne igre
shareThisUrlToLetSpectatorsSeeTheGame=Podijeliti ovaj link kako bi drugi mogli vidjeti igru
youAreViewingThisGameAsASpectator=Ovdje ste kao promatrač
replayAndAnalyse=Ponovi i analiziraj
computerAnalysisInProgress=Računarska analiza u tijeku
theComputerAnalysisYouRequestedIsNowAvailable=Računarska analiza koju ste trazili je sada dostupna
theComputerAnalysisHasFailed=Raćunarska analiza nije uspjela
viewTheComputerAnalysis=Pogledati računarsku analizu
requestAComputerAnalysis=Zahtijevaj računarsku analizu
blunders=Zabuna
mistakes=Greški
inaccuracies=Nepravilnosti
viewGameStats=Pogledati statistiku igre
flipBoard=Okrenuti ploču
threefoldRepetition=Trostruko ponavljanje
claimADraw=Zahtijevati remi
offerDraw=Ponuditi remi
draw=Remi
nbConnectedPlayers=%s prisutnih igrača
talkAboutChessAndDiscussLichessFeaturesInTheForum=Razgovarati o šahu i diskutovati o lichess svojstvima na forumu
seeTheGamesBeingPlayedInRealTime=Pogledati trenutne igre
gamesBeingPlayedRightNow=Trenutne igre
viewAllNbGames=Pogledaj sve %s igre
viewNbCheckmates=Pogledaj %s matova
nbBookmarks=%s Favoriti
viewAllNbGames=%s igre
viewNbCheckmates=%s Šah - matovi
nbBookmarks=%s Označeno
nbPopularGames=%s Popularne Igre
nbAnalysedGames=%s Analizirane Igre
bookmarkedByNbPlayers=Dodano u favorite od strane %s igraca
viewInFullSize=Pogledaj u punoj veličini
bookmarkedByNbPlayers=Igra je označena od strane %s protivnika
viewInFullSize=Pogledati u punoj veličini
logOut=Odjava
signIn=Prijava
signUp=Registracija
people=Ljudi
newToLichess=Novi u lichess-u?
youNeedAnAccountToDoThat=Trebate račun da bi to napravili
signUp=Registrovati se
people=Prijatelji
games=Igre
forum=Forum
xPostedInForumY=%s objavio u forumu %s
chessPlayers=Igrači
minutesPerSide=Minuta po strani
minutesPerSide=Minuta po igraču
variant=Varijanta
timeControl=Upravljanje vremenom
timeControl=Vremenska kontrola
time=Vrijeme
start=Početak
username=Korisničko ime
password=Lozinka
haveAnAccount=Imate li račun?
haveAnAccount=Već imate raćun?
allYouNeedIsAUsernameAndAPassword=Sve što trebate je korisničko ime i lozinka.
learnMoreAboutLichess=Nauči više o Lichess-u
rank=Rank
gamesPlayed=Igara odigrano
declineInvitation=Odbi pozivnicu
cancel=Odgodi
timeOut=Vrijeme za partiju isteklo
learnMoreAboutLichess=Naučiti više o Lichess-u
rank=Poredak
gamesPlayed=Broj odigranih igara
nbGamesWithYou=%s odigranih igara protiv vas
declineInvitation=Odbiti pozivnicu
cancel=Otkazati
timeOut=Vrijeme je isteklo
drawOfferSent=Ponuda za remi poslana
drawOfferDeclined=Punuda za remi odbijena
drawOfferAccepted=Ponuda za remi prihvaćena
@ -90,6 +103,7 @@ yourOpponentOffersADraw=Protivnik nudi remi
accept=Prihvati
decline=Odbij
playingRightNow=Upravo igra
finished=Završeno
abortGame=Prekini partiju
gameAborted=Partija prekinuta
standard=Standardan
@ -111,6 +125,10 @@ chatRoom=Razgovaranje
spectatorRoom=Soba za posmatrače
composeMessage=Skladaj poruku
sentMessages=Poslane pruke
noNewMessages=Nema novih poruka
subject=Subjekt
recipient=Primalac
send=Pošalji
incrementInSeconds=Dodatnih sekundi po potezu
freeOnlineChess=Besplatni internet šah
spectators=Posmatrača
@ -135,8 +153,10 @@ takebackPropositionCanceled=Prijedlog za povlačenje poteza otkazan
yourOpponentProposesATakeback=Vaš protivnik predlaže da povučete potez
bookmarkThisGame=Dodaj ovu igru u favorite
toggleBackground=Izmijeni boju pozadine
search=Traži
advancedSearch=Napredna pretraga
tournament=Turnir
tournaments=Turniri
tournamentPoints=Bodovi na turniru
viewTournament=Pogledaj turnir
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Besplatni internet šah. Igrajte šah sa čistim interface-om. Registracija nije neophodna, nema reklama, ne zahtijeva browser plugins. Igrajte protiv računara, prijatelja ili slučajnih protivnika.

View File

@ -10,9 +10,6 @@ import tube.bookmarkTube
case class Bookmark(game: lila.game.Game, user: lila.user.User)
// db.bookmark.ensureIndex({g:1})
// db.bookmark.ensureIndex({u:1})
// db.bookmark.ensureIndex({d: -1})
private[bookmark] object BookmarkRepo {
def toggle(gameId: String, userId: String): Fu[Boolean] =

View File

@ -6,14 +6,29 @@ import play.api.libs.json.JsObject
import lila.db.api.$count
import lila.memo.AsyncCache
import lila.user.{ User, Confrontation }
import tube.gameTube
private[game] final class Cached(ttl: Duration) {
final class Cached(ttl: Duration) {
def nbGames: Fu[Int] = count(Query.all)
def nbMates: Fu[Int] = count(Query.mate)
def nbPopular: Fu[Int] = count(Query.popular)
def nbImported: Fu[Int] = count(Query.imported)
def nbPlaying(userId: String): Fu[Int] = count(Query notFinished userId)
def confrontation(user1: User, user2: User): Fu[Confrontation] = {
def idGame(user: User) = (user.id, user.count.game)
confrontationCache {
(user1 < user2).fold(
idGame(user1) -> idGame(user2),
idGame(user2) -> idGame(user1))
}
}
private val confrontationCache =
AsyncCache(GameRepo.confrontation, timeToLive = 1.minute)
private val count = AsyncCache((o: JsObject) $count(o), timeToLive = ttl)
}

View File

@ -226,32 +226,37 @@ object GameRepo {
_ sort Query.sortCreated skip (Random nextInt 1000)
)
// user1 wins, draws, losses
def confrontation(user1: User, user2: User): Fu[Confrontation] = {
import reactivemongo.bson._
import reactivemongo.core.commands._
val userIds = List(user1, user2).sortBy(_.count.game).map(_.id)
val command = Aggregate(gameTube.coll.name, Seq(
Match(BSONDocument(
"uids" -> BSONDocument("$all" -> userIds),
"s" -> BSONDocument("$gte" -> chess.Status.Mate.id)
)),
GroupField("wid")("nb" -> SumValue(1))
))
gameTube.coll.db.command(command) map { stream
val res = (stream.toList map { obj
toJSON(obj).asOpt[JsObject] flatMap { o
o int "nb" map { nb
~(o str "_id") -> nb
// gets 2 users (id, nbGames)
// returns user1 wins, draws, losses
// the 2 userIds SHOULD be sorted by game count desc
// this method is cached in lila.game.Cached
private[game] def confrontation(users: ((String, Int), (String, Int))): Fu[Confrontation] = users match {
case (user1, user2) {
import reactivemongo.bson._
import reactivemongo.core.commands._
val userIds = List(user1, user2).sortBy(_._2).map(_._1)
val command = Aggregate(gameTube.coll.name, Seq(
Match(BSONDocument(
"uids" -> BSONDocument("$all" -> userIds),
"s" -> BSONDocument("$gte" -> chess.Status.Mate.id)
)),
GroupField("wid")("nb" -> SumValue(1))
))
gameTube.coll.db.command(command) map { stream
val res = (stream.toList map { obj
toJSON(obj).asOpt[JsObject] flatMap { o
o int "nb" map { nb
~(o str "_id") -> nb
}
}
}
}).flatten.toMap
Confrontation(
user1, user2,
~(res get user1.id),
~(res get ""),
~(res get user2.id)
)
}).flatten.toMap
Confrontation(
user1._1, user2._1,
~(res get user1._1),
~(res get ""),
~(res get user2._1)
)
}
}
}
}

View File

@ -28,7 +28,7 @@ final class PgnDump(
player(game.blackPlayer, blackUser),
game.id)
private val dateFormat = DateTimeFormat forPattern "yyyy-MM-dd";
private val dateFormat = DateTimeFormat forPattern "yyyy.MM.dd";
private def elo(p: Player) = p.elo.fold("?")(_.toString)

View File

@ -49,13 +49,13 @@ private[lobby] final class Biter(
source = lila.game.Source.Lobby,
pgnImport = None)
private def canJoin(hook: Hook, userOption: Option[User]) =
def canJoin(hook: Hook, user: Option[User]) =
hook.open &&
hook.realMode.casual.fold(
userOption.isDefined || hook.allowAnon,
userOption ?? { u hook.realEloRange.fold(true)(_ contains u.elo) }
user.isDefined || hook.allowAnon,
user ?? { u hook.realEloRange.fold(true)(_ contains u.elo) }
) && !{
userOption ?? { u
user ?? { u
hook.userId ?? { blocks(_, u.id).await }
}
}

View File

@ -5,6 +5,8 @@ import scala.util.Random.nextBoolean
sealed abstract class Color(val name: String) {
def resolve: chess.Color
def compatibleWith(c: Color) = (c == this).fold(c == Color.Random, true)
}
object Color {

View File

@ -1,10 +1,10 @@
package lila.lobby
import chess.{ Variant, Mode, Clock }
import org.joda.time.DateTime
import ornicar.scalalib.Random
import play.api.libs.json._
import chess.{ Variant, Mode, Clock }
import lila.common.EloRange
import lila.user.User
@ -19,11 +19,8 @@ case class Hook(
mode: Int,
allowAnon: Boolean,
color: String,
userId: Option[String],
username: String,
elo: Option[Int],
user: Option[User],
eloRange: String,
engine: Boolean,
gameId: Option[String] = None,
createdAt: DateTime) {
@ -36,8 +33,23 @@ case class Hook(
def realMode = Mode orDefault mode
def memberOnly = !allowAnon
def compatibleWith(h: Hook) =
compatibilityProperties == h.compatibilityProperties &&
(realColor compatibleWith h.realColor) &&
(memberOnly || h.memberOnly).fold(isMember && h.isMember, true)
private def compatibilityProperties = (variant, time, increment, mode)
lazy val realEloRange: Option[EloRange] = EloRange noneIfDefault eloRange
def userId = user map (_.id)
def isMember = user.nonEmpty
def username = user.fold(User.anonymous)(_.username)
def elo = user map (_.elo)
def engine = user ?? (_.engine)
def render: JsObject = Json.obj(
"id" -> id,
"uid" -> uid,
@ -82,11 +94,8 @@ object Hook {
mode = mode.id,
allowAnon = allowAnon || user.isEmpty,
color = color,
userId = user map (_.id),
username = user.fold(User.anonymous)(_.username),
user = user,
sid = sid,
elo = user map (_.elo),
eloRange = eloRange.toString,
engine = user.??(_.engine),
createdAt = DateTime.now)
}

View File

@ -7,6 +7,8 @@ object HookRepo {
private var hooks = Vector[Hook]()
def findCompatible(hook: Hook): List[Hook] = allOpen filter (_ compatibleWith hook)
def list = hooks.toList
def byId(id: String) = hooks find (_.id == id)

View File

@ -5,11 +5,12 @@ import scala.concurrent.duration._
import actorApi._
import akka.actor._
import akka.pattern.{ ask, pipe }
import makeTimeout.short
import lila.db.api._
import lila.hub.actorApi.GetUids
import lila.memo.ExpireSetMemo
import lila.socket.actorApi.Broom
import makeTimeout.short
private[lobby] final class Lobby(
biter: Biter,
@ -19,11 +20,16 @@ private[lobby] final class Lobby(
case GetOpen sender ! HookRepo.allOpen
case msg @ AddHook(hook) {
case msg @ AddHook(hook, user) {
HookRepo byUid hook.uid foreach remove
hook.sid ?? { sid HookRepo bySid sid foreach remove }
HookRepo save hook
socket ! msg
HookRepo findCompatible hook find { biter.canJoin(_, user) } match {
case Some(h) self ! BiteHook(h.id, hook.uid, user map (_.id))
case None {
HookRepo save hook
socket ! msg
}
}
}
case CancelHook(uid) {

View File

@ -5,16 +5,17 @@ import scala.concurrent.duration._
import actorApi._
import akka.actor._
import akka.pattern.ask
import makeTimeout.short
import play.api.libs.iteratee._
import play.api.libs.json._
import play.api.templates.Html
import lila.game.actorApi._
import lila.hub.actorApi.lobby._
import lila.hub.actorApi.router.{ Homepage, Player }
import lila.hub.actorApi.timeline._
import lila.socket.actorApi.{ Connected _, _ }
import lila.socket.{ SocketActor, History, Historical }
import makeTimeout.short
import play.api.libs.iteratee._
import play.api.libs.json._
import play.api.templates.Html
private[lobby] final class Socket(
val history: History,
@ -43,7 +44,7 @@ private[lobby] final class Socket(
case ReloadTimeline(user) sendTo(user, makeMessage("reload_timeline", JsNull))
case AddHook(hook) notifyVersion("hook_add", hook.render)
case AddHook(hook, _) notifyVersion("hook_add", hook.render)
case RemoveHook(hookId) notifyVersion("hook_remove", hookId)

View File

@ -20,7 +20,7 @@ object Member {
case class Connected(enumerator: JsEnumerator, member: Member)
case class WithHooks(op: Iterable[String] Unit)
case class AddHook(hook: Hook)
case class AddHook(hook: Hook, user: Option[User])
case class RemoveHook(hookId: String)
case class CancelHook(uid: String)
case class BiteHook(hookId: String, uid: String, userId: Option[String])

View File

@ -1,9 +1,10 @@
package lila.setup
import akka.pattern.ask
import chess.{ Game ChessGame, Board, Color ChessColor }
import makeTimeout.short
import play.api.libs.json.{ Json, JsObject }
import chess.{ Game ChessGame, Board, Color ChessColor }
import lila.ai.Ai
import lila.db.api._
import lila.game.tube.gameTube
@ -13,7 +14,6 @@ import lila.i18n.I18nDomain
import lila.lobby.actorApi.AddHook
import lila.lobby.Hook
import lila.user.{ User, Context }
import makeTimeout.short
import tube.{ userConfigTube, anonConfigTube }
private[setup] final class Processor(
@ -50,12 +50,14 @@ private[setup] final class Processor(
friendConfigMemo.set(pov.game.id, config) inject pov
}
def hook(config: HookConfig, uid: String, sid: Option[String])(implicit ctx: Context): Funit = {
val hook = config.hook(uid, ctx.me, sid)
def hook(
config: HookConfig,
uid: String,
sid: Option[String],
user: Option[User])(implicit ctx: Context): Funit =
saveConfig(_ withHook config) >>- {
lobby ! AddHook(hook)
lobby ! AddHook(config.hook(uid, ctx.me, sid), user)
}
}
def api(implicit ctx: Context): Fu[JsObject] = {
val config = ApiConfig

View File

@ -6,9 +6,6 @@ import reactivemongo.api._
import lila.db.api._
import tube.memberTube
// db.team_member.ensureIndex({team:1})
// db.team_member.ensureIndex({user:1})
// db.team_member.ensureIndex({date: -1})
object MemberRepo {
type ID = String

View File

@ -6,8 +6,6 @@ import reactivemongo.api._
import lila.db.api._
import tube.requestTube
// db.team_request.ensureIndex({team:1})
// db.team_request.ensureIndex({date: -1})
object RequestRepo {
type ID = String

View File

@ -1,8 +1,8 @@
package lila.user
case class Confrontation(
user1: User,
user2: User,
user1: String,
user2: String,
wins: Int,
draws: Int,
losses: Int) {

View File

@ -43,15 +43,15 @@ object Dependencies {
val jodaTime = "joda-time" % "joda-time" % "2.2"
val jodaConvert = "org.joda" % "joda-convert" % "1.3.1"
val scalastic = "scalastic" %% "scalastic" % "0.90.0-thib"
val reactivemongo = "org.reactivemongo" %% "reactivemongo" % "0.10.0"
val playReactivemongo = "org.reactivemongo" %% "play2-reactivemongo" % "0.10-THIB"
val reactivemongo = "org.reactivemongo" %% "reactivemongo" % "0.10.5-THIB"
val playReactivemongo = "org.reactivemongo" %% "play2-reactivemongo" % "0.10.5-THIB"
object play {
val version = "2.2.0-RC1"
val version = "2.2.0-RC2"
val api = "com.typesafe.play" %% "play" % version
val test = "com.typesafe.play" %% "play-test" % version
}
object akka {
val version = "2.1.0"
val version = "2.2.0"
val agent = "com.typesafe.akka" %% "akka-agent" % version
}
object spray {

View File

@ -20,5 +20,5 @@
// jquery.timeago 1.2.0 https://github.com/rmm5t/jquery-timeago
(function(d){"function"===typeof define&&define.amd?define(["jquery"],d):d(jQuery)})(function(d){function l(){var a;a=d(this);if(!a.data("timeago")){a.data("timeago",{datetime:e.datetime(a)});var b=d.trim(a.text());e.settings.localeTitle?a.attr("title",a.data("timeago").datetime.toLocaleString()):0<b.length&&(!e.isTime(a)||!a.attr("title"))&&a.attr("title",b)}a=a.data("timeago");b=e.settings;isNaN(a.datetime)||(0==b.cutoff||(new Date).getTime()-a.datetime.getTime()<b.cutoff)&&d(this).text(f(a.datetime));return this}function f(a){return e.inWords((new Date).getTime()-a.getTime())}d.timeago=function(a){return a instanceof Date?f(a):"string"===typeof a?f(d.timeago.parse(a)):"number"===typeof a?f(new Date(a)):f(d.timeago.datetime(a))};var e=d.timeago;d.extend(d.timeago,{settings:{refreshMillis:6E4,allowFuture:!1,localeTitle:!1,cutoff:0,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"about a minute",minutes:"%d minutes",hour:"about an hour",hours:"about %d hours",day:"a day",days:"%d days",month:"about a month",months:"%d months",year:"about a year",years:"%d years",wordSeparator:" ",numbers:[]}},inWords:function(a){function b(b,e){return(d.isFunction(b)?b(e,a):b).replace(/%d/i,c.numbers&&c.numbers[e]||e)}var c=this.settings.strings,e=c.prefixAgo,f=c.suffixAgo;this.settings.allowFuture&&0>a&&(e=c.prefixFromNow,f=c.suffixFromNow);var h=Math.abs(a)/1E3,g=h/60,m=g/60,k=m/24,l=k/365,h=45>h&&b(c.seconds,Math.round(h))||90>h&&b(c.minute,1)||45>g&&b(c.minutes,Math.round(g))||90>g&&b(c.hour,1)||24>m&&b(c.hours,Math.round(m))||42>m&&b(c.day,1)||30>k&&b(c.days,Math.round(k))||45>k&&b(c.month,1)||365>k&&b(c.months,Math.round(k/30))||1.5>l&&b(c.year,1)||b(c.years,Math.round(l)),g=c.wordSeparator||"";void 0===c.wordSeparator&&(g=" ");return d.trim([e,h,f].join(g))},parse:function(a){a=d.trim(a);a=a.replace(/\.\d+/,"");a=a.replace(/-/,"/").replace(/-/,"/");a=a.replace(/T/," ").replace(/Z/," UTC");a=a.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");return new Date(a)},datetime:function(a){a=e.isTime(a)?d(a).attr("datetime"):d(a).attr("title");return e.parse(a)},isTime:function(a){return"time"===d(a).get(0).tagName.toLowerCase()}});var n={init:function(){var a=d.proxy(l,this);a();var b=e.settings;0<b.refreshMillis&&setInterval(a,b.refreshMillis)},update:function(a){d(this).data("timeago",{datetime:e.parse(a)});l.apply(this)}};d.fn.timeago=function(a,b){var c=a?n[a]:n.init;if(!c)throw Error("Unknown function name '"+a+"' for timeago");this.each(function(){c.call(this,b)});return this};document.createElement("abbr");document.createElement("time")});
// Idle Timer - v0.9.2 - 2013-01-06 https://github.com/mikesherov/jquery-idletimer
(function(e){e.idleTimer=function(t,i,d){d=e.extend({startImmediately:!0,idle:!1,enabled:!0,timeout:3e4,events:"mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove"},d),i=i||document;var l=e(i),a=l.data("idleTimerObj")||{},o=function(t){"number"==typeof t&&(t=void 0);var l=e.data(t||i,"idleTimerObj");l.idle=!l.idle;var a=+new Date-l.olddate;if(l.olddate=+new Date,l.idle&&d.timeout>a)return l.idle=!1,clearTimeout(e.idleTimer.tId),d.enabled&&(e.idleTimer.tId=setTimeout(o,d.timeout)),void 0;var m=e.Event(e.data(i,"idleTimer",l.idle?"idle":"active")+".idleTimer");e(i).trigger(m)},m=function(e){var t=e.data("idleTimerObj")||{};t.enabled=!1,clearTimeout(t.tId),e.off(".idleTimer")};if(a.olddate=a.olddate||+new Date,"number"==typeof t)d.timeout=t;else{if("destroy"===t)return m(l),this;if("getElapsedTime"===t)return+new Date-a.olddate}l.on(e.trim((d.events+" ").split(" ").join(".idleTimer ")),function(){var t=e.data(this,"idleTimerObj");clearTimeout(t.tId),t.enabled&&(t.idle&&o(this),t.tId=setTimeout(o,t.timeout))}),a.idle=d.idle,a.enabled=d.enabled,a.timeout=d.timeout,d.startImmediately&&(a.tId=setTimeout(o,a.timeout)),l.data("idleTimer","active"),l.data("idleTimerObj",a)},e.fn.idleTimer=function(t,i){return i||(i={}),this[0]&&e.idleTimer(t,this[0],i),this}})(jQuery);
// Idle Timer - v0.9.3 - 2013-08-04 https://github.com/mikesherov/jquery-idletimer
(function(e){e.idleTimer=function(t,i,d){d=e.extend({startImmediately:!0,idle:!1,enabled:!0,timeout:3e4,events:"mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove"},d),i=i||document;var l=e(i),a=l.data("idleTimerObj")||{},o=function(t){"number"==typeof t&&(t=void 0);var l=e.data(t||i,"idleTimerObj");l.idle=!l.idle;var a=+new Date-l.olddate;if(l.olddate=+new Date,l.idle&&d.timeout>a)return l.idle=!1,clearTimeout(e.idleTimer.tId),d.enabled&&(e.idleTimer.tId=setTimeout(o,d.timeout)),void 0;var m=e.Event(e.data(i,"idleTimer",l.idle?"idle":"active")+".idleTimer");e(i).trigger(m)},m=function(e){var t=e.data("idleTimerObj")||{};t.enabled=!1,clearTimeout(t.tId),e.off(".idleTimer")};if(a.olddate=a.olddate||+new Date,"number"==typeof t)d.timeout=t;else{if("destroy"===t)return m(l),this;if("getElapsedTime"===t)return+new Date-a.olddate}l.on(e.trim((d.events+" ").split(" ").join(".idleTimer ")),function(){var t=e.data(this,"idleTimerObj");clearTimeout(t.tId),t.enabled&&(t.idle&&o(this),t.tId=setTimeout(o,t.timeout))}),a.idle=d.idle,a.enabled=d.enabled,a.timeout=d.timeout,d.startImmediately&&(a.tId=setTimeout(o,a.timeout)),l.data("idleTimer","active"),l.data("idleTimerObj",a)},e.fn.idleTimer=function(t,i){return i||(i={}),this[0]?e.idleTimer(t,this[0],i):this}})(jQuery);

4
todo
View File

@ -42,7 +42,6 @@ the forum search user:mephostophilis returns no result
players world map
account closed accounts in team counts
IE10 no sound toggle http://en.lichess.org/forum/lichess-feedback/notification-of-game-creation#3
show friend games in homepage board
replace plot with watch plot when game starts
team search is broken
liquid UI for large screens http://fr.lichess.org/forum/lichess-feedback/hi-res#5
@ -61,5 +60,6 @@ separate games in spectator chat
real board editor save
clickable mistakes list in analysis
FEN/editor castle/enpassant https://github.com/ornicar/lila/issues/42 white/black to play
stupid team forum slug http://ru.lichess.org/forum/team-4epa250h
user notes, shared among friends
detect cheat using lichess AI
fix AI protocol