Merge branch 'master' into persistentChallenge
* master: (1107 commits) fix typo fix #1525 implement AnalysisRepo.associateToGames upgrade scalachess variant doc style fixes Revert "disable tournament TV for now, it's not quite ready" upgrade scalachess display material score fix some variant doc style left menu dark theme icons on rating stats left menu hover effect on left side menus variants page variant doc style improve variant doc refactor variant documentation stockfish current commit unfuck Prismic.getBookmark fix tournament TV selector disable tournament TV for now, it's not quite ready ...
This commit is contained in:
commit
1b3e61b509
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,5 +17,6 @@ data/
|
|||
dist/
|
||||
node_modules/
|
||||
local/
|
||||
.vagrant
|
||||
|
||||
RUNNING_PID
|
||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -25,3 +25,9 @@
|
|||
[submodule "public/vendor/ChessPursuit"]
|
||||
path = public/vendor/ChessPursuit
|
||||
url = https://github.com/ornicar/ChessPursuit
|
||||
[submodule "public/vendor/multiple-select"]
|
||||
path = public/vendor/multiple-select
|
||||
url = https://github.com/ornicar/multiple-select
|
||||
[submodule "public/vendor/hopscotch"]
|
||||
path = public/vendor/hopscotch
|
||||
url = https://github.com/linkedin/hopscotch
|
||||
|
|
34
.travis.yml
Normal file
34
.travis.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
language: scala
|
||||
|
||||
# https://docs.travis-ci.com/user/notifications/#IRC-notification
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#lichess"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
use_notice: true
|
||||
skip_join: true
|
||||
slack: lichess:sVTqlE0OQNMPq1n6qRnVnfrz
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
# https://docs.travis-ci.com/user/languages/java/#Testing-Against-Multiple-JDKs
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
env:
|
||||
- TRAVIS_NODE_VERSION="4.0.0"
|
||||
|
||||
install:
|
||||
# http://austinpray.com/ops/2015/09/20/change-travis-node-version.html
|
||||
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install "$TRAVIS_NODE_VERSION"
|
||||
- npm install -g gulp
|
||||
|
||||
- git submodule update --init --recursive
|
||||
- ./ui/build
|
||||
- ./bin/build-deps.sh
|
||||
|
||||
script:
|
||||
- sbt compile
|
||||
- sbt test
|
|
@ -1 +1,22 @@
|
|||
See https://github.com/ornicar/lila/wiki/Lichess-Development-Onboarding
|
||||
#### I need help contributing code to Lichess.
|
||||
|
||||
__For setting up your development environment, [read this guide](https://github.com/ornicar/lila/wiki/Lichess-Development-Onboarding).__
|
||||
|
||||
If you experience any issues, __fix them yourself__ or __demonstrate your efforts and make it easy to help__. As stated in the read-me file, I do **not** offer support for your Lichess instance.
|
||||
|
||||
#### I want to report a bug or a problem about Lichess.
|
||||
|
||||
[__Make an issue ticket.__](https://github.com/ornicar/lila/issues/new?title=Submitting a forum thread with the word "thibault" in its title crashes my browser!) However, note that issues that provide little value compared to the required effort may be closed. Before creating an issue, make sure that:
|
||||
|
||||
1. You list the steps to reproduce the problem to show that other users may experience it as well, if the issue is not self-descriptive.
|
||||
2. Search to make sure it isn't a duplicate [The advanced search syntax](https://help.github.com/articles/searching-issues/) may come in handy.
|
||||
3. It is not a trivial problem or demand unrealistic dev time to fix - Pluralization bugs and the such fall under this category.
|
||||
|
||||
#### I want to suggest a feature for Lichess.
|
||||
|
||||
Issue tickets on features that lack potential or effectiveness are not useful and may be closed. Discussions regarding whether a proposed new feature would be useful can be done on [The Lichess Feedback Forum](http://lichess.org/forum/lichess-feedback) to gauge feedback. The developers may also discuss the idea there, and if it is exemplary, a corresponding issue ticket will be made. __When you're ready, [make an issue ticket](https://github.com/ornicar/lila/issues/new?title=Please implement this chess variant idea I came up with)__ and link relevant, constructive comments regarding it in your issue ticket (such as a detailed Reddit post; Linking to an empty forum thread with only your own commentary adds no value). Make sure that the feature you propose:
|
||||
|
||||
1. Is __effective in delivering a goal__. A feature that adds nothing new is purely fancy; Please develop a userscript or userstyle for your personal use instead.
|
||||
2. Doesn't rely on mundane assumptions. Non-technical people have the tendency to measure how difficult / easy a feature is to implement based on their unreliable instincts, and such assumptions wastes everyone's time. __Point out what needs to happen__, not what you think will happen.
|
||||
3. Is __unique, if you're aiming to solve a problem__. Features that can easily be replaced by easier ideas have little value and may not have to be brought up to begin with.
|
||||
4. Is __clear and concise__. If ambiguities exist, define them or propose options.
|
||||
|
|
36
README.md
36
README.md
|
@ -1,4 +1,4 @@
|
|||
[lichess.org](http://lichess.org)
|
||||
[lichess.org](http://lichess.org) [![Build Status](https://travis-ci.org/ornicar/lila.svg?branch=master)](https://travis-ci.org/ornicar/lila)
|
||||
---------------------------------
|
||||
|
||||
<img src="https://raw.githubusercontent.com/ornicar/lila/master/public/images/homepage_light.1200.png" alt="lichess.org" />
|
||||
|
@ -6,7 +6,7 @@
|
|||
It's a free online chess game focused on [realtime](http://lichess.org/games) and ease of use
|
||||
|
||||
It has a [search engine](http://lichess.org/games/search),
|
||||
[computer analysis](http://lichess.org/analyse/ief49lif),
|
||||
[computer analysis](http://lichess.org/ief49lif),
|
||||
[tournaments](http://lichess.org/tournament),
|
||||
[simuls](http://lichess.org/simul),
|
||||
[forums](http://lichess.org/forum),
|
||||
|
@ -165,10 +165,10 @@ $.ajax({
|
|||
});
|
||||
```
|
||||
|
||||
### `GET /api/game` fetch many games
|
||||
### `GET /api/user/<username>/games` fetch user games
|
||||
|
||||
```
|
||||
> curl http://en.lichess.org/api/game?username=thibault&rated=1&nb=10
|
||||
> curl http://en.lichess.org/api/user/thibault/games?nb=50&page=2
|
||||
```
|
||||
|
||||
Games are returned by descendant chronological order.
|
||||
|
@ -176,18 +176,23 @@ All parameters are optional.
|
|||
|
||||
name | type | default | description
|
||||
--- | --- | --- | ---
|
||||
**username** | string | - | filter games by user
|
||||
**rated** | 1 or 0 | - | filter rated or casual games
|
||||
**analysed** | 1 or 0 | - | filter only analysed (or not analysed) games
|
||||
**nb** | int | 10 | maximum number of games to return
|
||||
**nb** | int | 100 | maximum number of games to return per page
|
||||
**page** | int | 1 | for pagination
|
||||
**with_analysis** | 1 or 0 | 0 | include deep analysis data in the result
|
||||
**with_moves** | 1 or 0 | 0 | include a list of PGN moves
|
||||
**with_opening** | 1 or 0 | 0 | include opening informations
|
||||
**token** | string | - | security token (unlocks secret game data)
|
||||
**with_movetimes** | 1 or 0 | 0 | include move time informations
|
||||
**rated** | 1 or 0 | - | filter rated or casual games
|
||||
|
||||
```javascript
|
||||
{
|
||||
"list": [
|
||||
"currentPage": 3,
|
||||
"previousPage": 2,
|
||||
"nextPage": 4,
|
||||
"maxPerPage": 100,
|
||||
"nbPages": 43,
|
||||
"nbResults": 4348,
|
||||
"currentPageResults": [
|
||||
{
|
||||
"id": "39b12Ikl",
|
||||
"variant": "chess960", // standard/chess960/fromPosition/kingOfTheHill/threeCheck
|
||||
|
@ -213,7 +218,9 @@ name | type | default | description
|
|||
"blunder": 1,
|
||||
"inaccuracy": 0,
|
||||
"mistake": 2
|
||||
}
|
||||
},
|
||||
// rounded move times in tenths of seconds
|
||||
"moveTimes":[30,40,10,40,40,100,50,200,400,150,150,40,50,200,80]
|
||||
},
|
||||
"black": ... // other player
|
||||
}
|
||||
|
@ -261,8 +268,9 @@ name | type | default | description
|
|||
--- | --- | --- | ---
|
||||
**with_analysis** | 1 or 0 | 0 | include deep analysis data in the result
|
||||
**with_moves** | 1 or 0 | 0 | include a list of PGN moves
|
||||
**with_movetimes** | 1 or 0 | 0 | include move time informations
|
||||
**with_opening** | 1 or 0 | 0 | include opening informations
|
||||
**with_fens** | 1 or 0 | 0 | include a list of FEN states
|
||||
**token** | string | - | security token (unlocks secret game data)
|
||||
|
||||
```javascript
|
||||
{
|
||||
|
@ -291,7 +299,9 @@ name | type | default | description
|
|||
"blunder": 1,
|
||||
"inaccuracy": 0,
|
||||
"mistake": 2
|
||||
}
|
||||
},
|
||||
// rounded move times in tenths of seconds
|
||||
"moveTimes":[30,40,10,40,40,100,50,200,400,150,150,40,50,200,80]
|
||||
},
|
||||
"black": ... // other player
|
||||
},
|
||||
|
|
50
Vagrantfile
vendored
Normal file
50
Vagrantfile
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "ubuntu/vivid64"
|
||||
|
||||
# Use this script to set up and compile the Lila installation. We set
|
||||
# `privileged` to `false` because otherwise the provisioning script will run
|
||||
# as root. This isn't a problem to install packages globally (`apt-get
|
||||
# install`), but `sbt publish-local` will publish to `root`'s home directory!
|
||||
# Then we would not be able to use those packages when logged in as
|
||||
# `vagrant`.
|
||||
config.vm.provision "shell", path: "bin/provision-vagrant.sh", privileged: false
|
||||
|
||||
# IP address to use to connect to the virtual machine. This should be an
|
||||
# entry in your hosts file. We use a static IP so that the developer doesn't
|
||||
# have to keep adding new entries to their hosts file.
|
||||
config.vm.network "private_network", ip: "192.168.34.34"
|
||||
|
||||
# From https://stefanwrobel.com/how-to-make-vagrant-performance-not-suck. You
|
||||
# may want to set `cpus` and `mem` yourself.
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
host = RbConfig::CONFIG['host_os']
|
||||
|
||||
# Fraction of memory of host OS to allocate to VM. More is better!
|
||||
memory_fraction = 0.5
|
||||
|
||||
# Give VM allocated system memory & access to all cpu cores on the host
|
||||
if host =~ /darwin/
|
||||
cpus = `sysctl -n hw.ncpu`.to_i
|
||||
# sysctl returns Bytes and we need to convert to MB
|
||||
mem = `sysctl -n hw.memsize`.to_i / 1024 / 1024
|
||||
elsif host =~ /linux/
|
||||
cpus = `nproc`.to_i
|
||||
# meminfo shows KB and we need to convert to MB
|
||||
mem = `grep 'MemTotal' /proc/meminfo | sed -e 's/MemTotal://' -e 's/ kB//'`.to_i / 1024
|
||||
else # sorry Windows folks, I can't help you
|
||||
cpus = 2
|
||||
mem = 4096
|
||||
end
|
||||
|
||||
mem *= memory_fraction
|
||||
mem = mem.to_i
|
||||
|
||||
# Needed to use multiple CPUs.
|
||||
v.customize ["modifyvm", :id, "--ioapic", "on"]
|
||||
|
||||
v.customize ["modifyvm", :id, "--cpus", cpus]
|
||||
v.customize ["modifyvm", :id, "--memory", mem]
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@ import com.typesafe.config.Config
|
|||
|
||||
final class Env(
|
||||
config: Config,
|
||||
val scheduler: lila.common.Scheduler,
|
||||
system: ActorSystem,
|
||||
appPath: String) {
|
||||
|
||||
|
@ -22,7 +23,7 @@ final class Env(
|
|||
tourneyWinners = Env.tournament.winners.scheduled,
|
||||
timelineEntries = Env.timeline.entryRepo.userEntries _,
|
||||
dailyPuzzle = Env.puzzle.daily,
|
||||
streamsOnAir = () => Env.tv.streamsOnAir,
|
||||
streamsOnAir = () => Env.tv.streamsOnAir.all,
|
||||
countRounds = Env.round.count,
|
||||
lobbyApi = Env.api.lobbyApi,
|
||||
getPlayban = Env.playban.api.currentBan _,
|
||||
|
@ -40,7 +41,8 @@ final class Env(
|
|||
getRanks = Env.user.cached.ranking.getAll,
|
||||
isDonor = Env.donation.isDonor,
|
||||
isHostingSimul = Env.simul.isHosting,
|
||||
isStreamer = Env.tv.isStreamer.apply) _
|
||||
isStreamer = Env.tv.isStreamer.apply,
|
||||
insightShare = Env.insight.share) _
|
||||
|
||||
system.actorOf(Props(new actor.Renderer), name = RendererName)
|
||||
|
||||
|
@ -75,7 +77,12 @@ final class Env(
|
|||
Env.tv,
|
||||
Env.blog,
|
||||
Env.video,
|
||||
Env.shutup // required to load the actor
|
||||
Env.shutup, // required to load the actor
|
||||
Env.insight, // required to load the actor
|
||||
Env.worldMap, // required to load the actor
|
||||
Env.push, // required to load the actor
|
||||
Env.perfStat, // required to load the actor
|
||||
Env.slack // required to load the actor
|
||||
)
|
||||
play.api.Logger("boot").info("Preloading complete")
|
||||
}
|
||||
|
@ -87,6 +94,7 @@ object Env {
|
|||
|
||||
lazy val current = "app" boot new Env(
|
||||
config = lila.common.PlayApp.loadConfig,
|
||||
scheduler = lila.common.PlayApp.scheduler,
|
||||
system = lila.common.PlayApp.system,
|
||||
appPath = lila.common.PlayApp withApp (_.path.getCanonicalPath))
|
||||
|
||||
|
@ -136,5 +144,8 @@ object Env {
|
|||
def video = lila.video.Env.current
|
||||
def playban = lila.playban.Env.current
|
||||
def shutup = lila.shutup.Env.current
|
||||
def coach = lila.coach.Env.current
|
||||
def insight = lila.insight.Env.current
|
||||
def push = lila.push.Env.current
|
||||
def perfStat = lila.perfStat.Env.current
|
||||
def slack = lila.slack.Env.current
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
|||
|
||||
import play.api.mvc._, Results._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.LilaCookie
|
||||
import lila.db.api.$find
|
||||
|
@ -35,14 +36,19 @@ object Account extends LilaController {
|
|||
me =>
|
||||
negotiate(
|
||||
html = notFound,
|
||||
api = _ => lila.game.GameRepo urgentGames me map { povs =>
|
||||
Env.current.bus.publish(lila.user.User.Active(me), 'userActive)
|
||||
Ok {
|
||||
import play.api.libs.json._
|
||||
Env.user.jsonView(me, extended = true) ++ Json.obj(
|
||||
"nowPlaying" -> JsArray(povs take 20 map Env.api.lobbyApi.nowPlaying))
|
||||
api = _ =>
|
||||
Env.pref.api getPref me flatMap { prefs =>
|
||||
lila.game.GameRepo urgentGames me map { povs =>
|
||||
Env.current.bus.publish(lila.user.User.Active(me), 'userActive)
|
||||
Ok {
|
||||
import play.api.libs.json._
|
||||
import lila.pref.JsonView._
|
||||
Env.user.jsonView(me, extended = true) ++ Json.obj(
|
||||
"prefs" -> prefs,
|
||||
"nowPlaying" -> JsArray(povs take 20 map Env.api.lobbyApi.nowPlaying))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -136,16 +142,23 @@ object Account extends LilaController {
|
|||
(UserRepo toggleKid me) inject Redirect(routes.Account.kid)
|
||||
}
|
||||
|
||||
private def currentSessionId(implicit ctx: Context) =
|
||||
~Env.security.api.reqSessionId(ctx.req)
|
||||
|
||||
def security = Auth { implicit ctx =>
|
||||
me =>
|
||||
Env.security.api.dedup(me.id, ctx.req) >>
|
||||
Env.security.api.locatedOpenSessions(me.id, 50) map { sessions =>
|
||||
Ok(html.account.security(me, sessions, ~Env.security.api.reqSessionId(ctx.req)))
|
||||
Ok(html.account.security(me, sessions, currentSessionId))
|
||||
}
|
||||
}
|
||||
|
||||
def signout(sessionId: String) = Auth { ctx =>
|
||||
def signout(sessionId: String) = Auth { implicit ctx =>
|
||||
me =>
|
||||
lila.security.Store.closeUserAndSessionId(me.id, sessionId)
|
||||
if (sessionId == "all")
|
||||
lila.security.Store.closeUserExceptSessionId(me.id, currentSessionId) inject
|
||||
Redirect(routes.Account.security)
|
||||
else
|
||||
lila.security.Store.closeUserAndSessionId(me.id, sessionId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ object Analyse extends LilaController {
|
|||
def requestAnalysis(id: String) = Auth { implicit ctx =>
|
||||
me =>
|
||||
makeAnalysis(id, me) injectAnyway
|
||||
Ok(html.analyse.computing())
|
||||
Ok(html.analyse.computing(id))
|
||||
}
|
||||
|
||||
private def makeAnalysis(id: String, me: lila.user.User)(implicit ctx: Context) =
|
||||
|
|
|
@ -28,9 +28,9 @@ object Api extends LilaController {
|
|||
)) as JSON
|
||||
}
|
||||
|
||||
def user(username: String) = ApiResult { implicit ctx =>
|
||||
def user(name: String) = ApiResult { implicit ctx =>
|
||||
userApi.one(
|
||||
username = username,
|
||||
username = name,
|
||||
token = get("token"))
|
||||
}
|
||||
|
||||
|
@ -43,17 +43,23 @@ object Api extends LilaController {
|
|||
) map (_.some)
|
||||
}
|
||||
|
||||
def games = ApiResult { implicit ctx =>
|
||||
gameApi.list(
|
||||
username = get("username"),
|
||||
rated = getBoolOpt("rated"),
|
||||
analysed = getBoolOpt("analysed"),
|
||||
withAnalysis = getBool("with_analysis"),
|
||||
withMoves = getBool("with_moves"),
|
||||
withOpening = getBool("with_opening"),
|
||||
token = get("token"),
|
||||
nb = getInt("nb")
|
||||
) map (_.some)
|
||||
def userGames(name: String) = ApiResult { implicit ctx =>
|
||||
lila.user.UserRepo named name flatMap {
|
||||
_ ?? { user =>
|
||||
gameApi.byUser(
|
||||
user = user,
|
||||
rated = getBoolOpt("rated"),
|
||||
analysed = getBoolOpt("analysed"),
|
||||
withAnalysis = getBool("with_analysis"),
|
||||
withMoves = getBool("with_moves"),
|
||||
withOpening = getBool("with_opening"),
|
||||
withMoveTimes = getBool("with_movetimes"),
|
||||
token = get("token"),
|
||||
nb = getInt("nb"),
|
||||
page = getInt("page")
|
||||
) map (_.some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def game(id: String) = ApiResult { implicit ctx =>
|
||||
|
@ -63,6 +69,7 @@ object Api extends LilaController {
|
|||
withMoves = getBool("with_moves"),
|
||||
withOpening = getBool("with_opening"),
|
||||
withFens = getBool("with_fens"),
|
||||
withMoveTimes = getBool("with_movetimes"),
|
||||
token = get("token"))
|
||||
}
|
||||
|
||||
|
|
|
@ -118,8 +118,10 @@ object Auth extends LilaController {
|
|||
UserRepo.create(data.username, data.password, email.some, ctx.blindMode, none)
|
||||
.flatten(s"No user could be created for ${data.username}")
|
||||
.map(_ -> email).flatMap {
|
||||
case (user, email) => env.emailConfirm.send(user, email) inject
|
||||
Redirect(routes.Auth.checkYourEmail(user.username))
|
||||
case (user, email) => env.emailConfirm.send(user, email) >> {
|
||||
if (env.emailConfirm.effective) Redirect(routes.Auth.checkYourEmail(user.username)).fuccess
|
||||
else saveAuthAndRedirect(user)
|
||||
}
|
||||
}
|
||||
}),
|
||||
api = apiVersion => forms.signup.mobile.bindFromRequest.fold(
|
||||
|
@ -143,23 +145,37 @@ object Auth extends LilaController {
|
|||
}
|
||||
|
||||
def signupConfirmEmail(token: String) = Open { implicit ctx =>
|
||||
implicit val req = ctx.req
|
||||
Env.security.emailConfirm.confirm(token) flatMap {
|
||||
case Some(user) => api.saveAuthentication(user.id, ctx.mobileApiVersion) map { sessionId =>
|
||||
Redirect(routes.User.show(user.username)) withCookies LilaCookie.session("sessionId", sessionId)
|
||||
} recoverWith authRecovery
|
||||
case _ => notFound
|
||||
_.fold(notFound)(saveAuthAndRedirect)
|
||||
}
|
||||
}
|
||||
|
||||
private def saveAuthAndRedirect(user: UserModel)(implicit ctx: Context) = {
|
||||
implicit val req = ctx.req
|
||||
api.saveAuthentication(user.id, ctx.mobileApiVersion) map { sessionId =>
|
||||
Redirect(routes.User.show(user.username)) withCookies LilaCookie.session("sessionId", sessionId)
|
||||
} recoverWith authRecovery
|
||||
}
|
||||
|
||||
private def noTorResponse(implicit ctx: Context) = negotiate(
|
||||
html = Unauthorized(html.auth.tor()).fuccess,
|
||||
api = _ => Unauthorized(Json.obj("error" -> "Can't login from TOR, sorry!")).fuccess)
|
||||
|
||||
def setFingerprint(hash: String, ms: Int) = Auth { ctx =>
|
||||
def setFingerprint(fp: String, ms: Int) = Auth { ctx =>
|
||||
me =>
|
||||
// if (ms > 1000) logwarn(s"[Fingerprint] ${me.username} $ms ms / ${~HTTPRequest.userAgent(ctx.req)}")
|
||||
api.setFingerprint(ctx.req, hash) inject Ok
|
||||
api.setFingerprint(ctx.req, fp) flatMap {
|
||||
_ ?? { hash =>
|
||||
!me.lame ?? {
|
||||
api.recentUserIdsByFingerprint(hash).map(_.filter(me.id!=)) flatMap {
|
||||
case otherIds if otherIds.size >= 2 => UserRepo countEngines otherIds flatMap {
|
||||
case nb if nb >= 2 && nb >= otherIds.size / 2 => Env.report.api.autoCheatPrintReport(me.id)
|
||||
case _ => funit
|
||||
}
|
||||
case _ => funit
|
||||
}
|
||||
}
|
||||
}
|
||||
} inject Ok
|
||||
}
|
||||
|
||||
def passwordReset = Open { implicit ctx =>
|
||||
|
@ -175,9 +191,10 @@ object Auth extends LilaController {
|
|||
BadRequest(html.auth.passwordReset(err, captcha, false.some))
|
||||
},
|
||||
data => {
|
||||
UserRepo enabledByEmail data.email flatMap {
|
||||
case Some(user) if env.emailAddress.isValid(data.email) =>
|
||||
Env.security.passwordReset.send(user, data.email) inject Redirect(routes.Auth.passwordResetSent(data.email))
|
||||
val email = env.emailAddress.validate(data.email) | data.email
|
||||
UserRepo enabledByEmail email flatMap {
|
||||
case Some(user) =>
|
||||
Env.security.passwordReset.send(user, email) inject Redirect(routes.Auth.passwordResetSent(data.email))
|
||||
case _ => forms.passwordResetWithCaptcha map {
|
||||
case (form, captcha) => BadRequest(html.auth.passwordReset(form, captcha, false.some))
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import play.api.libs.json.Json
|
||||
import play.api.mvc.Result
|
||||
import views._
|
||||
|
||||
object Coach extends LilaController {
|
||||
|
||||
private def env = Env.coach
|
||||
|
||||
def opening(username: String, colorStr: String) = Open { implicit ctx =>
|
||||
chess.Color(colorStr).fold(notFound) { color =>
|
||||
Accessible(username) { user =>
|
||||
env.statApi.count(user.id) map { nbPeriods =>
|
||||
Ok(html.coach.opening(user, color, nbPeriods))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def openingJson(username: String, colorStr: String) = Open { implicit ctx =>
|
||||
chess.Color(colorStr).fold(notFoundJson(s"No such color: $colorStr")) { color =>
|
||||
AccessibleJson(username) { user =>
|
||||
WithRange { range =>
|
||||
env.statApi.fetchRangeForOpenings(user.id, range) flatMap {
|
||||
_.fold(notFoundJson(s"Data not generated yet")) { period =>
|
||||
env.jsonView.opening(period, color) map { data =>
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def move(username: String) = Open { implicit ctx =>
|
||||
Accessible(username) { user =>
|
||||
env.statApi.count(user.id) map { nbPeriods =>
|
||||
Ok(html.coach.move(user, nbPeriods))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def moveJson(username: String) = Open { implicit ctx =>
|
||||
AccessibleJson(username) { user =>
|
||||
WithRange { range =>
|
||||
env.statApi.fetchRangeForMoves(user.id, range) flatMap {
|
||||
_.fold(notFoundJson(s"Data not generated yet")) { period =>
|
||||
env.jsonView.move(period) map { data =>
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def refresh(username: String) = Open { implicit ctx =>
|
||||
Accessible(username) { user =>
|
||||
env.aggregator(user) inject Ok
|
||||
}
|
||||
}
|
||||
|
||||
private def WithRange(f: Range => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
get("range").flatMap {
|
||||
_.split('-') match {
|
||||
case Array(a, b) => (parseIntOption(a) |@| parseIntOption(b))(Range.apply)
|
||||
case _ => none
|
||||
}
|
||||
}.fold(notFoundJson("No range provided"))(f)
|
||||
|
||||
private def Accessible(username: String)(f: lila.user.User => Fu[Result])(implicit ctx: Context) =
|
||||
lila.user.UserRepo named username flatMap {
|
||||
case None => notFound
|
||||
case Some(u) => env.share.grant(u, ctx.me) flatMap {
|
||||
case true => f(u)
|
||||
case false if isGranted(_.UserSpy) => f(u)
|
||||
case false => fuccess(Forbidden(html.coach.forbidden(u)))
|
||||
}
|
||||
}
|
||||
|
||||
private def AccessibleJson(username: String)(f: lila.user.User => Fu[Result])(implicit ctx: Context) =
|
||||
lila.user.UserRepo named username flatMap {
|
||||
case None => notFoundJson(s"No such user: $username")
|
||||
case Some(u) => env.share.grant(u, ctx.me) flatMap {
|
||||
case true => f(u)
|
||||
case false if isGranted(_.UserSpy) => f(u)
|
||||
case false => fuccess(Forbidden(Json.obj("error" -> s"User $username data is protected")))
|
||||
}
|
||||
} map (_ as JSON)
|
||||
}
|
|
@ -8,9 +8,9 @@ import views._
|
|||
object Donation extends LilaController {
|
||||
|
||||
def index = Open { implicit ctx =>
|
||||
OptionFuOk(Prismic.oneShotBookmark("donate")) {
|
||||
OptionFuOk(Prismic.getBookmark("donate")) {
|
||||
case (doc, resolver) => Env.donation.api.list(100) zip
|
||||
Env.donation.api.top(5) zip
|
||||
Env.donation.api.top(10) zip
|
||||
Env.donation.api.progress map {
|
||||
case ((donations, top), progress) =>
|
||||
views.html.donation.index(doc, resolver, donations, top, progress)
|
||||
|
@ -19,7 +19,7 @@ object Donation extends LilaController {
|
|||
}
|
||||
|
||||
def thanks = Open { implicit ctx =>
|
||||
OptionOk(Prismic.oneShotBookmark("donate-thanks")) {
|
||||
OptionOk(Prismic.getBookmark("donate-thanks")) {
|
||||
case (doc, resolver) => views.html.site.page(doc, resolver)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,10 @@ object Editor extends LilaController {
|
|||
|
||||
def game(id: String) = Open { implicit ctx =>
|
||||
OptionResult(GameRepo game id) { game =>
|
||||
Redirect(routes.Editor.load(
|
||||
get("fen") | (chess.format.Forsyth >> game.toChess)
|
||||
))
|
||||
Redirect {
|
||||
if (game.playable) routes.Round.watcher(game.id, "white")
|
||||
else routes.Editor.load(get("fen") | (chess.format.Forsyth >> game.toChess))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package controllers
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.app._
|
||||
import views._
|
||||
|
||||
object ForumPost extends LilaController with ForumController {
|
||||
|
||||
private val CreateRateLimit = new lila.memo.RateLimit(4, 5 minutes)
|
||||
|
||||
def search(text: String, page: Int) = OpenBody { implicit ctx =>
|
||||
NotForKids {
|
||||
text.trim.isEmpty.fold(
|
||||
|
@ -25,21 +29,23 @@ object ForumPost extends LilaController with ForumController {
|
|||
}
|
||||
|
||||
def create(categSlug: String, slug: String, page: Int) = OpenBody { implicit ctx =>
|
||||
CategGrantWrite(categSlug) {
|
||||
implicit val req = ctx.body
|
||||
OptionFuResult(topicApi.show(categSlug, slug, page, ctx.troll)) {
|
||||
case (categ, topic, posts) =>
|
||||
if (topic.closed) fuccess(BadRequest("This topic is closed"))
|
||||
else forms.post.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha flatMap { captcha =>
|
||||
ctx.userId ?? Env.timeline.status(s"forum:${topic.id}") map { unsub =>
|
||||
BadRequest(html.forum.topic.show(categ, topic, posts, Some(err -> captcha), unsub))
|
||||
CreateRateLimit(ctx.req.remoteAddress) {
|
||||
CategGrantWrite(categSlug) {
|
||||
implicit val req = ctx.body
|
||||
OptionFuResult(topicApi.show(categSlug, slug, page, ctx.troll)) {
|
||||
case (categ, topic, posts) =>
|
||||
if (topic.closed) fuccess(BadRequest("This topic is closed"))
|
||||
else forms.post.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha flatMap { captcha =>
|
||||
ctx.userId ?? Env.timeline.status(s"forum:${topic.id}") map { unsub =>
|
||||
BadRequest(html.forum.topic.show(categ, topic, posts, Some(err -> captcha), unsub))
|
||||
}
|
||||
},
|
||||
data => postApi.makePost(categ, topic, data) map { post =>
|
||||
Redirect(routes.ForumPost.redirect(post.id))
|
||||
}
|
||||
},
|
||||
data => postApi.makePost(categ, topic, data) map { post =>
|
||||
Redirect(routes.ForumPost.redirect(post.id))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package controllers
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.app._
|
||||
import lila.forum.CategRepo
|
||||
import views._
|
||||
|
||||
object ForumTopic extends LilaController with ForumController {
|
||||
|
||||
private val CreateRateLimit = new lila.memo.RateLimit(2, 5 minutes)
|
||||
|
||||
def form(categSlug: String) = Open { implicit ctx =>
|
||||
NotForKids {
|
||||
CategGrantWrite(categSlug) {
|
||||
|
@ -17,17 +21,19 @@ object ForumTopic extends LilaController with ForumController {
|
|||
}
|
||||
|
||||
def create(categSlug: String) = OpenBody { implicit ctx =>
|
||||
CategGrantWrite(categSlug) {
|
||||
implicit val req = ctx.body
|
||||
OptionFuResult(CategRepo bySlug categSlug) { categ =>
|
||||
forms.topic.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha map { captcha =>
|
||||
BadRequest(html.forum.topic.form(categ, err, captcha))
|
||||
},
|
||||
data => topicApi.makeTopic(categ, data) map { topic =>
|
||||
Redirect(routes.ForumTopic.show(categ.slug, topic.slug, 1))
|
||||
}
|
||||
)
|
||||
CreateRateLimit(ctx.req.remoteAddress) {
|
||||
CategGrantWrite(categSlug) {
|
||||
implicit val req = ctx.body
|
||||
OptionFuResult(CategRepo bySlug categSlug) { categ =>
|
||||
forms.topic.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha map { captcha =>
|
||||
BadRequest(html.forum.topic.form(categ, err, captcha))
|
||||
},
|
||||
data => topicApi.makeTopic(categ, data) map { topic =>
|
||||
Redirect(routes.ForumTopic.show(categ.slug, topic.slug, 1))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package controllers
|
||||
|
||||
import play.api.data.Form
|
||||
import play.api.libs.json.Json
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
|
@ -18,18 +19,28 @@ object I18n extends LilaController {
|
|||
implicit val req = ctx.body
|
||||
Form(single("lang" -> text.verifying(env.pool contains _))).bindFromRequest.fold(
|
||||
_ => notFound,
|
||||
lang => (ctx.me ?? { me => lila.user.UserRepo.setLang(me.id, lang) }) inject Redirect {
|
||||
s"${Env.api.Net.Protocol}${lang}.${Env.api.Net.Domain}" + {
|
||||
HTTPRequest.referer(ctx.req).fold(routes.Lobby.home.url) { str =>
|
||||
try {
|
||||
new java.net.URL(str).getPath
|
||||
}
|
||||
catch {
|
||||
case e: java.net.MalformedURLException => routes.Lobby.home.url
|
||||
lang => {
|
||||
ctx.me.filterNot(_.lang contains lang) ?? { me =>
|
||||
lila.user.UserRepo.setLang(me.id, lang)
|
||||
}
|
||||
} >> negotiate(
|
||||
html = Redirect {
|
||||
s"${Env.api.Net.Protocol}${lang}.${Env.api.Net.Domain}" + {
|
||||
HTTPRequest.referer(ctx.req).fold(routes.Lobby.home.url) { str =>
|
||||
try {
|
||||
val pageUrl = new java.net.URL(str);
|
||||
val path = pageUrl.getPath
|
||||
val query = pageUrl.getQuery
|
||||
if (query == null) path
|
||||
else path + "?" + query
|
||||
}
|
||||
catch {
|
||||
case e: java.net.MalformedURLException => routes.Lobby.home.url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.fuccess,
|
||||
api = _ => Ok(Json.obj("lang" -> lang)).fuccess)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ object Importer extends LilaController {
|
|||
failure => fuccess {
|
||||
Ok(html.game.importGame(failure))
|
||||
},
|
||||
data => env.importer(data, ctx.userId, ctx.ip) map { game =>
|
||||
data => env.importer(data, ctx.userId) map { game =>
|
||||
if (game.analysable) Analyse.addCallbacks(game.id) {
|
||||
Env.analyse.analyser.getOrGenerate(
|
||||
game.id,
|
||||
|
|
76
app/controllers/Insight.scala
Normal file
76
app/controllers/Insight.scala
Normal file
|
@ -0,0 +1,76 @@
|
|||
package controllers
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.insight.{ Metric, Dimension }
|
||||
import play.api.libs.json.Json
|
||||
import play.api.mvc._
|
||||
import views._
|
||||
|
||||
object Insight extends LilaController {
|
||||
|
||||
private def env = Env.insight
|
||||
|
||||
def refresh(username: String) = Open { implicit ctx =>
|
||||
Accessible(username) { user =>
|
||||
env.api indexAll user inject Ok
|
||||
}
|
||||
}
|
||||
|
||||
def index(username: String) = path(username,
|
||||
metric = Metric.MeanCpl.key,
|
||||
dimension = Dimension.Perf.key,
|
||||
filters = "")
|
||||
|
||||
def path(username: String, metric: String, dimension: String, filters: String) = Open { implicit ctx =>
|
||||
Accessible(username) { user =>
|
||||
import lila.insight.InsightApi.UserStatus._
|
||||
env.api userStatus user flatMap {
|
||||
case NoGame => Ok(html.insight.noGame(user)).fuccess
|
||||
case Empty => Ok(html.insight.empty(user)).fuccess
|
||||
case s => for {
|
||||
cache <- env.api userCache user
|
||||
prefId <- env.share getPrefId user
|
||||
} yield Ok(html.insight.index(
|
||||
u = user,
|
||||
cache = cache,
|
||||
prefId = prefId,
|
||||
ui = env.jsonView.ui(cache.ecos),
|
||||
question = env.jsonView.question(metric, dimension, filters),
|
||||
stale = s == Stale))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def json(username: String) = OpenBody(BodyParsers.parse.json) { implicit ctx =>
|
||||
import lila.insight.JsonQuestion, JsonQuestion._
|
||||
Accessible(username) { user =>
|
||||
ctx.body.body.validate[JsonQuestion].fold(
|
||||
err => BadRequest(Json.obj("error" -> err.toString)).fuccess,
|
||||
qJson => qJson.question.fold(BadRequest.fuccess) { q =>
|
||||
env.api.ask(q, user) map
|
||||
lila.insight.Chart.fromAnswer(Env.user.lightUser) map
|
||||
env.jsonView.chart.apply map { Ok(_) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def Accessible(username: String)(f: lila.user.User => Fu[Result])(implicit ctx: Context) =
|
||||
lila.user.UserRepo named username flatMap {
|
||||
_.fold(notFound) { u =>
|
||||
env.share.grant(u, ctx.me) flatMap {
|
||||
_.fold(f(u), fuccess(Forbidden(html.insight.forbidden(u))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def AccessibleJson(username: String)(f: lila.user.User => Fu[Result])(implicit ctx: Context) =
|
||||
lila.user.UserRepo named username flatMap {
|
||||
_.fold(notFoundJson(s"No such user: $username")) { u =>
|
||||
env.share.grant(u, ctx.me) flatMap {
|
||||
_.fold(f(u), fuccess(Forbidden(Json.obj("error" -> s"User $username data is protected"))))
|
||||
}
|
||||
}
|
||||
} map (_ as JSON)
|
||||
}
|
|
@ -59,12 +59,6 @@ object Main extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def irc = Open { implicit ctx =>
|
||||
ctx.me ?? Env.team.api.mine map {
|
||||
html.site.irc(_)
|
||||
}
|
||||
}
|
||||
|
||||
def themepicker = Open { implicit ctx =>
|
||||
fuccess {
|
||||
html.base.themepicker()
|
||||
|
@ -78,17 +72,30 @@ object Main extends LilaController {
|
|||
}
|
||||
|
||||
def mobile = Open { implicit ctx =>
|
||||
OptionOk(Prismic oneShotBookmark "mobile-apk") {
|
||||
OptionOk(Prismic getBookmark "mobile-apk") {
|
||||
case (doc, resolver) => html.mobile.home(doc, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
def jslog = Open { ctx =>
|
||||
def mobileRegister(platform: String, deviceId: String) = Auth { implicit ctx =>
|
||||
me =>
|
||||
Env.push.registerDevice(me, platform, deviceId)
|
||||
}
|
||||
|
||||
def mobileUnregister = Auth { implicit ctx =>
|
||||
me =>
|
||||
Env.push.unregisterDevices(me)
|
||||
}
|
||||
|
||||
def jslog(id: String) = Open { ctx =>
|
||||
val referer = HTTPRequest.referer(ctx.req)
|
||||
loginfo(s"[jslog] ${ctx.req.remoteAddress} ${ctx.userId} $referer")
|
||||
ctx.userId.?? {
|
||||
Env.report.api.autoBotReport(_, referer)
|
||||
}
|
||||
lila.game.GameRepo pov id map {
|
||||
_ ?? lila.game.GameRepo.setBorderAlert
|
||||
} inject Ok
|
||||
}
|
||||
|
||||
def notFound(req: RequestHeader): Fu[Result] =
|
||||
|
|
|
@ -9,6 +9,7 @@ import play.twirl.api.Html
|
|||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.user.{ User => UserModel, UserRepo }
|
||||
import lila.security.Granter
|
||||
import views._
|
||||
|
||||
object Message extends LilaController {
|
||||
|
@ -21,7 +22,7 @@ object Message extends LilaController {
|
|||
def inbox(page: Int) = Auth { implicit ctx =>
|
||||
me =>
|
||||
NotForKids {
|
||||
api updateUser me.id
|
||||
api updateUser me
|
||||
api.inbox(me, page) map { html.message.inbox(me, _) }
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +35,11 @@ object Message extends LilaController {
|
|||
implicit me =>
|
||||
NotForKids {
|
||||
OptionFuOk(api.thread(id, me)) { thread =>
|
||||
relationApi.blocks(thread otherUserId me, me.id) map { blocked =>
|
||||
relationApi.fetchBlocks(thread otherUserId me, me.id) map { blocked =>
|
||||
html.message.thread(thread, forms.post, blocked,
|
||||
answerable = !Env.message.LichessSenders.contains(thread.creatorId))
|
||||
}
|
||||
}
|
||||
} map NoCache
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +48,7 @@ object Message extends LilaController {
|
|||
OptionFuResult(api.thread(id, me)) { thread =>
|
||||
implicit val req = ctx.body
|
||||
forms.post.bindFromRequest.fold(
|
||||
err => relationApi.blocks(thread otherUserId me, me.id) map { blocked =>
|
||||
err => relationApi.fetchBlocks(thread otherUserId me, me.id) map { blocked =>
|
||||
BadRequest(html.message.thread(thread, err, blocked,
|
||||
answerable = !Env.message.LichessSenders.contains(thread.creatorId)))
|
||||
},
|
||||
|
@ -78,7 +79,8 @@ object Message extends LilaController {
|
|||
private def renderForm(me: UserModel, title: Option[String], f: Form[_] => Form[_])(implicit ctx: Context): Fu[Html] =
|
||||
get("user") ?? UserRepo.named flatMap { user =>
|
||||
user.fold(fuccess(true))(u => security.canMessage(me.id, u.id)) map { canMessage =>
|
||||
html.message.form(f(forms thread me), user, title, canMessage)
|
||||
html.message.form(f(forms thread me), user, title,
|
||||
canMessage = canMessage || Granter(_.MessageAnyone)(me))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,13 @@ object Mod extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def notifySlack(username: String) = Auth { implicit ctx =>
|
||||
me =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
Env.slack.api.userMod(user = user, mod = me) inject redirect(user.username)
|
||||
}
|
||||
}
|
||||
|
||||
def log = Secure(_.SeeReport) { implicit ctx =>
|
||||
me => modLogApi.recent map { html.mod.log(_) }
|
||||
}
|
||||
|
@ -121,4 +128,28 @@ object Mod extends LilaController {
|
|||
def refreshUserAssess(username: String) = Secure(_.MarkEngine) { implicit ctx =>
|
||||
me => assessApi.refreshAssessByUsername(username) inject redirect(username)
|
||||
}
|
||||
|
||||
def gamify = Secure(_.SeeReport) { implicit ctx =>
|
||||
me =>
|
||||
Env.mod.gamify.leaderboards zip
|
||||
Env.mod.gamify.history(orCompute = true) map {
|
||||
case (leaderboards, history) => Ok(html.mod.gamify.index(leaderboards, history))
|
||||
}
|
||||
}
|
||||
def gamifyPeriod(periodStr: String) = Secure(_.SeeReport) { implicit ctx =>
|
||||
me =>
|
||||
lila.mod.Gamify.Period(periodStr).fold(notFound) { period =>
|
||||
Env.mod.gamify.leaderboards map { leaderboards =>
|
||||
Ok(html.mod.gamify.period(leaderboards, period))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def search = Secure(_.UserSearch) { implicit ctx =>
|
||||
me =>
|
||||
val query = (~get("q")).trim
|
||||
Env.mod.search(query) map { users =>
|
||||
html.mod.search(query, users)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,38 @@ import views._
|
|||
|
||||
object Page extends LilaController {
|
||||
|
||||
private def page(bookmark: String) = Open { implicit ctx =>
|
||||
OptionOk(Prismic oneShotBookmark bookmark) {
|
||||
private def bookmark(name: String) = Open { implicit ctx =>
|
||||
OptionOk(Prismic getBookmark name) {
|
||||
case (doc, resolver) => views.html.site.page(doc, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
def thanks = page("thanks")
|
||||
def thanks = bookmark("thanks")
|
||||
|
||||
def tos = page("tos")
|
||||
def tos = bookmark("tos")
|
||||
|
||||
def helpLichess = page("help")
|
||||
def contribute = bookmark("help")
|
||||
|
||||
def streamHowTo = page("stream-howto")
|
||||
def streamHowTo = bookmark("stream-howto")
|
||||
|
||||
def contact = page("contact")
|
||||
def contact = bookmark("contact")
|
||||
|
||||
def kingOfTheHill = page("king-of-the-hill")
|
||||
def master = bookmark("master")
|
||||
|
||||
def privacy = page("privacy")
|
||||
def privacy = bookmark("privacy")
|
||||
|
||||
def variantHome = Open { implicit ctx =>
|
||||
OptionOk(Prismic getBookmark "variant") {
|
||||
case (doc, resolver) => views.html.site.variantHome(doc, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
def variant(key: String) = Open { implicit ctx =>
|
||||
(for {
|
||||
variant <- chess.variant.Variant.byKey get key
|
||||
perfType <- lila.rating.PerfType byVariant variant
|
||||
} yield OptionOk(Prismic getVariant variant) {
|
||||
case (doc, resolver) => views.html.site.variant(doc, resolver, variant, perfType)
|
||||
}) | notFound
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,6 @@ object Prismic {
|
|||
case _ => routes.Lobby.home.url
|
||||
}
|
||||
|
||||
def getBookmark(name: String): Fu[Option[Document]] = prismicApi flatMap { api =>
|
||||
api.bookmarks.get(name) ?? getDocument
|
||||
}
|
||||
|
||||
def getDocument(id: String): Fu[Option[Document]] = prismicApi flatMap { api =>
|
||||
api.forms("everything")
|
||||
.query(s"""[[:d = at(document.id, "$id")]]""")
|
||||
|
@ -40,9 +36,22 @@ object Prismic {
|
|||
}
|
||||
}
|
||||
|
||||
def oneShotBookmark(name: String) = fetchPrismicApi(true) flatMap { api =>
|
||||
getBookmark(name) map2 { (doc: io.prismic.Document) =>
|
||||
def getBookmark(name: String) = fetchPrismicApi(true) flatMap { api =>
|
||||
api.bookmarks.get(name) ?? getDocument map2 { (doc: io.prismic.Document) =>
|
||||
doc -> makeLinkResolver(api)
|
||||
}
|
||||
} recover {
|
||||
case e: Exception =>
|
||||
play.api.Logger("prismic").error(s"bookmark:$name $e")
|
||||
none
|
||||
}
|
||||
|
||||
def getVariant(variant: chess.variant.Variant) = prismicApi flatMap { api =>
|
||||
api.forms("variant")
|
||||
.query(s"""[[:d = at(my.variant.key, "${variant.key}")]]""")
|
||||
.ref(api.master.ref)
|
||||
.submit() map {
|
||||
_.results.headOption map (_ -> makeLinkResolver(api))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import play.twirl.api.Html
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.paginator.{ Paginator, AdapterLike }
|
||||
import lila.relation.Related
|
||||
import lila.user.{ User => UserModel, UserRepo }
|
||||
import views._
|
||||
|
@ -15,9 +16,9 @@ object Relation extends LilaController {
|
|||
private def env = Env.relation
|
||||
|
||||
private def renderActions(userId: String, mini: Boolean)(implicit ctx: Context) =
|
||||
(ctx.userId ?? { env.api.relation(_, userId) }) zip
|
||||
(ctx.userId ?? { env.api.fetchRelation(_, userId) }) zip
|
||||
(ctx.isAuth ?? { Env.pref.api followable userId }) zip
|
||||
(ctx.userId ?? { env.api.blocks(userId, _) }) flatMap {
|
||||
(ctx.userId ?? { env.api.fetchBlocks(userId, _) }) flatMap {
|
||||
case ((relation, followable), blocked) => negotiate(
|
||||
html = fuccess(Ok(mini.fold(
|
||||
html.relation.mini(userId, blocked = blocked, followable = followable, relation = relation),
|
||||
|
@ -25,8 +26,8 @@ object Relation extends LilaController {
|
|||
))),
|
||||
api = _ => fuccess(Ok(Json.obj(
|
||||
"followable" -> followable,
|
||||
"following" -> relation.exists(true ==),
|
||||
"blocking" -> relation.exists(false ==)
|
||||
"following" -> relation.contains(true),
|
||||
"blocking" -> relation.contains(false)
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
@ -51,70 +52,46 @@ object Relation extends LilaController {
|
|||
env.api.unblock(me.id, userId).nevermind >> renderActions(userId, getBool("mini"))
|
||||
}
|
||||
|
||||
def following(username: String) = Open { implicit ctx =>
|
||||
def following(username: String, page: Int) = Open { implicit ctx =>
|
||||
OptionFuOk(UserRepo named username) { user =>
|
||||
env.api.following(user.id) flatMap followship flatMap { rels =>
|
||||
env.api nbFollowers user.id map { followers =>
|
||||
html.relation.following(user, rels, followers)
|
||||
env.api countFollowers user.id flatMap { nbFollowers =>
|
||||
RelatedPager(env.api.followingPaginatorAdapter(user.id), page) map { pag =>
|
||||
html.relation.following(user, pag, nbFollowers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def followers(username: String) = Open { implicit ctx =>
|
||||
def followers(username: String, page: Int) = Open { implicit ctx =>
|
||||
OptionFuOk(UserRepo named username) { user =>
|
||||
env.api.followers(user.id) flatMap followship flatMap { rels =>
|
||||
env.api nbFollowing user.id map { following =>
|
||||
html.relation.followers(user, rels, following)
|
||||
env.api countFollowing user.id flatMap { nbFollowing =>
|
||||
RelatedPager(env.api.followersPaginatorAdapter(user.id), page) map { pag =>
|
||||
html.relation.followers(user, pag, nbFollowing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def blocks = Auth { implicit ctx =>
|
||||
def blocks(page: Int) = Auth { implicit ctx =>
|
||||
me =>
|
||||
env.api.blocking(me.id) flatMap followship map { rels =>
|
||||
html.relation.blocks(me, rels)
|
||||
RelatedPager(env.api.blockingPaginatorAdapter(me.id), page) map { pag =>
|
||||
html.relation.blocks(me, pag)
|
||||
}
|
||||
}
|
||||
|
||||
private def followship(userIds: Set[String])(implicit ctx: Context): Fu[List[Related]] =
|
||||
private def RelatedPager(adapter: AdapterLike[String], page: Int)(implicit ctx: Context) = Paginator(
|
||||
adapter = adapter mapFutureList followship,
|
||||
currentPage = page,
|
||||
maxPerPage = 30)
|
||||
|
||||
private def followship(userIds: Seq[String])(implicit ctx: Context): Fu[List[Related]] =
|
||||
UserRepo byIds userIds flatMap { users =>
|
||||
(ctx.isAuth ?? { Env.pref.api.followableIds(users map (_.id)) }) flatMap { followables =>
|
||||
users.map { u =>
|
||||
ctx.userId ?? { env.api.relation(_, u.id) } map { rel =>
|
||||
ctx.userId ?? { env.api.fetchRelation(_, u.id) } map { rel =>
|
||||
lila.relation.Related(u, 0, followables(u.id), rel)
|
||||
}
|
||||
}.sequenceFu
|
||||
}
|
||||
}
|
||||
|
||||
def suggest(username: String) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
lila.game.BestOpponents(user.id, 50) flatMap { opponents =>
|
||||
Env.pref.api.followableIds(opponents map (_._1.id)) zip
|
||||
env.api.onlinePopularUsers(20) flatMap {
|
||||
case (followables, popular) =>
|
||||
popular.filterNot(user ==).foldLeft(opponents filter {
|
||||
case (u, _) => followables contains u.id
|
||||
}) {
|
||||
case (xs, x) => xs.exists(_._1 == x).fold(xs, xs :+ (x, 0))
|
||||
}.map {
|
||||
case (u, nb) => env.api.relation(user.id, u.id) map {
|
||||
lila.relation.Related(u, nb, true, _)
|
||||
}
|
||||
}.sequenceFu flatMap { rels =>
|
||||
negotiate(
|
||||
html = fuccess(Ok(html.relation.suggest(user, rels))),
|
||||
api = _ => fuccess {
|
||||
implicit val userWrites = play.api.libs.json.Writes[UserModel] { Env.user.jsonView(_, true) }
|
||||
Ok(Json.obj(
|
||||
"user" -> user,
|
||||
"suggested" -> play.api.libs.json.JsArray(rels.map(_.toJson))))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,15 +43,15 @@ object Report extends LilaController {
|
|||
BadRequest(html.report.form(err, user, captcha))
|
||||
}
|
||||
},
|
||||
data => api.create(data, me) map { thread =>
|
||||
Redirect(routes.Report.thanks)
|
||||
data => api.create(data, me) map { report =>
|
||||
Redirect(routes.Report.thanks(data.user.username))
|
||||
})
|
||||
}
|
||||
|
||||
def thanks = Auth { implicit ctx =>
|
||||
def thanks(reported: String) = Auth { implicit ctx =>
|
||||
implicit me =>
|
||||
fuccess {
|
||||
html.report.thanks()
|
||||
Env.relation.api.fetchBlocks(me.id, reported) map { blocked =>
|
||||
html.report.thanks(reported, blocked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
private def env = Env.setup
|
||||
|
||||
private val FormRateLimit = new lila.memo.RateLimitByKey(500 millis)
|
||||
private val PostRateLimit = new lila.memo.RateLimitByKey(500 millis)
|
||||
private val PostRateLimit = new lila.memo.RateLimit(5, 1 minute)
|
||||
|
||||
def aiForm = Open { implicit ctx =>
|
||||
if (HTTPRequest isXhr ctx.req) {
|
||||
|
@ -62,10 +61,11 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
private def challenge(user: lila.user.User)(implicit ctx: Context): Fu[Option[String]] = ctx.me match {
|
||||
case None => fuccess("Only registered players can send challenges.".some)
|
||||
case Some(me) => Env.relation.api.blocks(user.id, me.id) flatMap {
|
||||
case Some(me) => Env.relation.api.fetchBlocks(user.id, me.id) flatMap {
|
||||
case true => fuccess(s"{{user}} doesn't accept challenges from you.".some)
|
||||
case false => Env.pref.api getPref user zip Env.relation.api.follows(user.id, me.id) map {
|
||||
case (pref, follow) => lila.pref.Pref.Challenge.block(me, user, pref.challenge, follow)
|
||||
case false => Env.pref.api getPref user zip Env.relation.api.fetchFollows(user.id, me.id) map {
|
||||
case (pref, follow) => lila.pref.Pref.Challenge.block(me, user, pref.challenge, follow,
|
||||
fromCheat = me.engine && !user.engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,27 +107,14 @@ object Setup extends LilaController with TheftPrevention {
|
|||
}
|
||||
|
||||
def hookForm = Open { implicit ctx =>
|
||||
if (HTTPRequest isXhr ctx.req) FormRateLimit(ctx.req.remoteAddress) {
|
||||
NoPlaybanOrCurrent {
|
||||
env.forms.hookFilled(timeModeString = get("time")) map { html.setup.hook(_) }
|
||||
}
|
||||
if (HTTPRequest isXhr ctx.req) NoPlaybanOrCurrent {
|
||||
env.forms.hookFilled(timeModeString = get("time")) map { html.setup.hook(_) }
|
||||
}
|
||||
else fuccess {
|
||||
Redirect(routes.Lobby.home + "#hook")
|
||||
}
|
||||
}
|
||||
|
||||
// if request comes from mobile
|
||||
// and the hook is casual,
|
||||
// reuse the saved "membersOnly" value
|
||||
// from the site preferred hook setup
|
||||
private def mobileHookAllowAnon(config: HookConfig)(implicit ctx: Context): Fu[HookConfig] =
|
||||
if (lila.api.Mobile.Api requested ctx.req)
|
||||
env.forms.hookConfig map { saved =>
|
||||
config.copy(allowAnon = saved.allowAnon)
|
||||
}
|
||||
else fuccess(config)
|
||||
|
||||
private def hookResponse(hookId: String) =
|
||||
Ok(Json.obj(
|
||||
"ok" -> true,
|
||||
|
@ -141,13 +128,12 @@ object Setup extends LilaController with TheftPrevention {
|
|||
err => negotiate(
|
||||
html = BadRequest(errorsAsJson(err).toString).fuccess,
|
||||
api = _ => BadRequest(errorsAsJson(err)).fuccess),
|
||||
preConfig => (ctx.userId ?? Env.relation.api.blocking) zip
|
||||
mobileHookAllowAnon(preConfig) flatMap {
|
||||
case (blocking, config) =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
}
|
||||
config => (ctx.userId ?? Env.relation.api.fetchBlocking) flatMap {
|
||||
blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +146,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
GameRepo game gameId map {
|
||||
_.fold(config)(config.updateFrom)
|
||||
} flatMap { config =>
|
||||
(ctx.userId ?? Env.relation.api.blocking) flatMap { blocking =>
|
||||
(ctx.userId ?? Env.relation.api.fetchBlocking) flatMap { blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid ctx.req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import views._
|
|||
object Stat extends LilaController {
|
||||
|
||||
def ratingDistribution(perfKey: lila.rating.Perf.Key) = Open { implicit ctx =>
|
||||
lila.rating.PerfType(perfKey).filter(lila.rating.PerfType.nonPuzzle.contains) match {
|
||||
lila.rating.PerfType(perfKey).filter(lila.rating.PerfType.isGame) match {
|
||||
case Some(perfType) => Env.user.cached.ratingDistribution(perfType.key) map { data =>
|
||||
Ok(html.stat.ratingDistribution(perfType, data))
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ object Team extends LilaController {
|
|||
me => OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
MemberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.kick(team, userIds filterNot (me.id ==))
|
||||
html.team.kick(team, userIds.filterNot(me.id ==).toList.sorted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,15 @@ object Team extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def close(id: String) = Secure(_.CloseTeam) { implicit ctx =>
|
||||
me =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
(api delete team) >>
|
||||
Env.mod.logApi.deleteTeam(me.id, team.name, team.description) inject
|
||||
Redirect(routes.Team all 1)
|
||||
}
|
||||
}
|
||||
|
||||
def form = Auth { implicit ctx =>
|
||||
me =>
|
||||
NotForKids {
|
||||
|
|
|
@ -19,14 +19,25 @@ object Tournament extends LilaController {
|
|||
|
||||
private def tournamentNotFound(implicit ctx: Context) = NotFound(html.tournament.notFound())
|
||||
|
||||
val home = Open { implicit ctx =>
|
||||
env.api.fetchVisibleTournaments zip
|
||||
repo.scheduledDedup zip
|
||||
repo.finished(30) zip
|
||||
UserRepo.allSortToints(10) map {
|
||||
case (((visible, scheduled), finished), leaderboard) =>
|
||||
Ok(html.tournament.home(scheduled, finished, leaderboard, env scheduleJsonView visible))
|
||||
} map NoCache
|
||||
def home(page: Int) = Open { implicit ctx =>
|
||||
negotiate(
|
||||
html = {
|
||||
val finishedPaginator = repo.finishedPaginator(maxPerPage = 30, page = page)
|
||||
if (HTTPRequest isXhr ctx.req) finishedPaginator map { pag =>
|
||||
Ok(html.tournament.finishedPaginator(pag))
|
||||
}
|
||||
else env.api.fetchVisibleTournaments zip
|
||||
repo.scheduledDedup zip
|
||||
finishedPaginator zip
|
||||
UserRepo.allSortToints(10) map {
|
||||
case (((visible, scheduled), finished), leaderboard) =>
|
||||
Ok(html.tournament.home(scheduled, finished, leaderboard, env scheduleJsonView visible))
|
||||
} map NoCache
|
||||
},
|
||||
api = _ => env.api.fetchVisibleTournaments map { tours =>
|
||||
Ok(env scheduleJsonView tours)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def help(sysStr: Option[String]) = Open { implicit ctx =>
|
||||
|
@ -129,6 +140,15 @@ object Tournament extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def terminate(id: String) = Secure(_.TerminateTournament) { implicit ctx =>
|
||||
me =>
|
||||
OptionResult(repo startedById id) { tour =>
|
||||
env.api finish tour
|
||||
Env.mod.logApi.terminateTournament(me.id, tour.fullName)
|
||||
Redirect(routes.Tournament show tour.id)
|
||||
}
|
||||
}
|
||||
|
||||
def form = Auth { implicit ctx =>
|
||||
me =>
|
||||
NoLame {
|
||||
|
|
|
@ -39,8 +39,9 @@ object Tv extends LilaController {
|
|||
Env.api.roundApi.watcher(pov, lila.api.Mobile.Api.currentVersion, tv = onTv.some) zip
|
||||
Env.game.crosstableApi(game) zip
|
||||
Env.tv.tv.getChampions map {
|
||||
case ((data, cross), champions) =>
|
||||
case ((data, cross), champions) => NoCache {
|
||||
Ok(html.tv.index(channel, champions, pov, data, cross, flip))
|
||||
}
|
||||
}
|
||||
},
|
||||
api = apiVersion => Env.api.roundApi.watcher(pov, apiVersion, tv = onTv.some) map { Ok(_) }
|
||||
|
@ -56,20 +57,24 @@ object Tv extends LilaController {
|
|||
private def lichessGames(channel: lila.tv.Tv.Channel)(implicit ctx: Context) =
|
||||
Env.tv.tv.getChampions zip
|
||||
Env.tv.tv.getGames(channel, 9) map {
|
||||
case (champs, games) =>
|
||||
case (champs, games) => NoCache {
|
||||
Ok(html.tv.games(channel, games map lila.game.Pov.first, champs))
|
||||
}
|
||||
}
|
||||
|
||||
def streamIn(id: String) = Open { implicit ctx =>
|
||||
Env.tv.streamsOnAir flatMap { streams =>
|
||||
streams find (_.id == id) match {
|
||||
case None => notFound
|
||||
case Some(s) => fuccess(Ok(html.tv.stream(s, streams filterNot (_.id == id))))
|
||||
OptionFuResult(Env.tv.streamerList find id) { streamer =>
|
||||
Env.tv.streamsOnAir.all flatMap { streams =>
|
||||
val others = streams.filter(_.id != id)
|
||||
streams find (_.id == id) match {
|
||||
case None => fuccess(Ok(html.tv.notStreaming(streamer, others)))
|
||||
case Some(s) => fuccess(Ok(html.tv.stream(s, others)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def streamOut = Action.async {
|
||||
def feed = Action.async {
|
||||
import makeTimeout.short
|
||||
import akka.pattern.ask
|
||||
import lila.round.TvBroadcast
|
||||
|
|
|
@ -42,11 +42,11 @@ object User extends LilaController {
|
|||
def showMini(username: String) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
GameRepo lastPlayedPlaying user zip
|
||||
Env.donation.isDonor(user.id) zip
|
||||
(ctx.userId ?? { relationApi.blocks(user.id, _) }) zip
|
||||
Env.donation.isDonor(user.id) zip
|
||||
(ctx.userId ?? { relationApi.fetchBlocks(user.id, _) }) zip
|
||||
(ctx.userId ?? { Env.game.crosstableApi(user.id, _) }) zip
|
||||
(ctx.isAuth ?? { Env.pref.api.followable(user.id) }) zip
|
||||
(ctx.userId ?? { relationApi.relation(_, user.id) }) map {
|
||||
(ctx.userId ?? { relationApi.fetchRelation(_, user.id) }) map {
|
||||
case (((((pov, donor), blocked), crosstable), followable), relation) =>
|
||||
Ok(html.user.mini(user, pov, blocked, followable, relation, crosstable, donor))
|
||||
.withHeaders(CACHE_CONTROL -> "max-age=5")
|
||||
|
@ -102,12 +102,12 @@ object User extends LilaController {
|
|||
filter = filters.current,
|
||||
me = ctx.me,
|
||||
page = page)(ctx.body)
|
||||
relation <- ctx.userId ?? { relationApi.relation(_, u.id) }
|
||||
relation <- ctx.userId ?? { relationApi.fetchRelation(_, u.id) }
|
||||
notes <- ctx.me ?? { me =>
|
||||
relationApi friends me.id flatMap { env.noteApi.get(u, me, _) }
|
||||
relationApi fetchFriends me.id flatMap { env.noteApi.get(u, me, _) }
|
||||
}
|
||||
followable <- ctx.isAuth ?? { Env.pref.api followable u.id }
|
||||
blocked <- ctx.userId ?? { relationApi.blocks(u.id, _) }
|
||||
blocked <- ctx.userId ?? { relationApi.fetchBlocks(u.id, _) }
|
||||
searchForm = GameFilterMenu.searchForm(userGameSearch, filters.current)(ctx.body)
|
||||
} yield html.user.show(u, info, pag, filters, searchForm, relation, notes, followable, blocked)
|
||||
|
||||
|
@ -130,20 +130,12 @@ object User extends LilaController {
|
|||
def list = Open { implicit ctx =>
|
||||
val nb = 10
|
||||
for {
|
||||
bullet ← env.cached topPerf PerfType.Bullet.key
|
||||
blitz ← env.cached topPerf PerfType.Blitz.key
|
||||
classical ← env.cached topPerf PerfType.Classical.key
|
||||
chess960 ← env.cached topPerf PerfType.Chess960.key
|
||||
kingOfTheHill ← env.cached topPerf PerfType.KingOfTheHill.key
|
||||
threeCheck ← env.cached topPerf PerfType.ThreeCheck.key
|
||||
antichess <- env.cached topPerf PerfType.Antichess.key
|
||||
atomic <- env.cached topPerf PerfType.Atomic.key
|
||||
horde <- env.cached topPerf PerfType.Horde.key
|
||||
nbAllTime ← env.cached topNbGame nb map2 { (user: UserModel) =>
|
||||
user -> user.count.game
|
||||
}
|
||||
nbDay ← Env.game.cached activePlayerUidsDay nb flatMap { pairs =>
|
||||
UserRepo.byOrderedIds(pairs.map(_.userId)) map (_ zip pairs.map(_.nb))
|
||||
leaderboards <- env.cached.leaderboards
|
||||
nbAllTime ← env.cached topNbGame nb
|
||||
nbDay ← Env.game.cached activePlayerUidsDay nb map {
|
||||
_ flatMap { pair =>
|
||||
env lightUser pair.userId map { UserModel.LightCount(_, pair.nb) }
|
||||
}
|
||||
}
|
||||
tourneyWinners ← Env.tournament.winners scheduled nb
|
||||
online ← env.cached topOnline 50
|
||||
|
@ -151,42 +143,51 @@ object User extends LilaController {
|
|||
html = fuccess(Ok(html.user.list(
|
||||
tourneyWinners = tourneyWinners,
|
||||
online = online,
|
||||
bullet = bullet,
|
||||
blitz = blitz,
|
||||
classical = classical,
|
||||
chess960 = chess960,
|
||||
kingOfTheHill = kingOfTheHill,
|
||||
threeCheck = threeCheck,
|
||||
antichess = antichess,
|
||||
atomic = atomic,
|
||||
horde = horde,
|
||||
leaderboards = leaderboards,
|
||||
nbDay = nbDay,
|
||||
nbAllTime = nbAllTime))),
|
||||
api = _ => fuccess {
|
||||
implicit val userWrites = play.api.libs.json.Writes[UserModel] { env.jsonView(_, true) }
|
||||
implicit val lightPerfWrites = play.api.libs.json.Writes[UserModel.LightPerf] { l =>
|
||||
Json.obj(
|
||||
"id" -> l.user.id,
|
||||
"username" -> l.user.name,
|
||||
"title" -> l.user.title,
|
||||
"perfs" -> Json.obj(
|
||||
l.perfKey -> Json.obj("rating" -> l.rating, "progress" -> l.progress)))
|
||||
}
|
||||
Ok(Json.obj(
|
||||
"online" -> online,
|
||||
"bullet" -> bullet,
|
||||
"blitz" -> blitz,
|
||||
"classical" -> classical,
|
||||
"chess960" -> chess960,
|
||||
"kingOfTheHill" -> kingOfTheHill,
|
||||
"threeCheck" -> threeCheck,
|
||||
"antichess" -> antichess,
|
||||
"atomic" -> atomic,
|
||||
"horde" -> horde))
|
||||
"bullet" -> leaderboards.bullet,
|
||||
"blitz" -> leaderboards.blitz,
|
||||
"classical" -> leaderboards.classical,
|
||||
"crazyhouse" -> leaderboards.crazyhouse,
|
||||
"chess960" -> leaderboards.chess960,
|
||||
"kingOfTheHill" -> leaderboards.kingOfTheHill,
|
||||
"threeCheck" -> leaderboards.threeCheck,
|
||||
"antichess" -> leaderboards.antichess,
|
||||
"atomic" -> leaderboards.atomic,
|
||||
"horde" -> leaderboards.horde,
|
||||
"racingKings" -> leaderboards.racingKings))
|
||||
})
|
||||
} yield res
|
||||
}
|
||||
|
||||
def top200(perfKey: String) = Open { implicit ctx =>
|
||||
lila.rating.PerfType(perfKey).fold(notFound) { perfType =>
|
||||
env.cached top200Perf perfType.key map { users =>
|
||||
Ok(html.user.top200(perfType, users))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def mod(username: String) = Secure(_.UserSpy) { implicit ctx =>
|
||||
me => OptionFuOk(UserRepo named username) { user =>
|
||||
(!isGranted(_.SetEmail, user) ?? UserRepo.email(user.id)) zip
|
||||
(Env.security userSpy user.id) zip
|
||||
(Env.mod.assessApi.getPlayerAggregateAssessmentWithGames(user.id)) flatMap {
|
||||
case ((email, spy), playerAggregateAssessment) =>
|
||||
(Env.mod.assessApi.getPlayerAggregateAssessmentWithGames(user.id)) zip
|
||||
Env.mod.logApi.userHistory(user.id) flatMap {
|
||||
case ((((email, spy), playerAggregateAssessment), history)) =>
|
||||
(Env.playban.api bans spy.usersSharingIp.map(_.id)) map { bans =>
|
||||
html.user.mod(user, email, spy, playerAggregateAssessment, bans)
|
||||
html.user.mod(user, email, spy, playerAggregateAssessment, bans, history)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,8 +211,8 @@ object User extends LilaController {
|
|||
fuccess(List.fill(50)(true))
|
||||
) flatMap { followables =>
|
||||
(ops zip followables).map {
|
||||
case ((u, nb), followable) => ctx.userId ?? { myId =>
|
||||
relationApi.relation(myId, u.id)
|
||||
case ((u, nb), followable) => ctx.userId ?? {
|
||||
relationApi.fetchRelation(_, u.id)
|
||||
} map { lila.relation.Related(u, nb, followable, _) }
|
||||
}.sequenceFu map { relateds =>
|
||||
html.user.opponents(user, relateds)
|
||||
|
@ -221,6 +222,25 @@ object User extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def perfStat(username: String, perfKey: String) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { u =>
|
||||
if ((u.disabled || (u.lame && !ctx.is(u))) && !isGranted(_.UserSpy)) notFound
|
||||
else lila.rating.PerfType(perfKey).fold(notFound) { perfType =>
|
||||
for {
|
||||
perfStat <- Env.perfStat.get(u, perfType)
|
||||
ranks <- Env.user.cached.ranking.getAll(u.id)
|
||||
distribution <- u.perfs(perfType).established ?? {
|
||||
Env.user.cached.ratingDistribution(perfType.key) map some
|
||||
}
|
||||
data = Env.perfStat.jsonView(u, perfStat, ranks get perfType.key, distribution)
|
||||
response <- negotiate(
|
||||
html = Ok(html.user.perfStat(u, ranks, perfType, data)).fuccess,
|
||||
api = _ => Ok(data).fuccess)
|
||||
} yield response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def autocomplete = Open { implicit ctx =>
|
||||
get("term", ctx.req).filter(_.nonEmpty).fold(BadRequest("No search term provided").fuccess: Fu[Result]) { term =>
|
||||
JsonOk(UserRepo usernamesLike term)
|
||||
|
|
|
@ -5,9 +5,11 @@ import chess.format.Forsyth.SituationPlus
|
|||
import chess.Situation
|
||||
import play.api.libs.json.Json
|
||||
import play.api.mvc._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.app._
|
||||
import lila.game.{ GameRepo, Pov }
|
||||
import lila.round.Forecast.{ forecastStepJsonFormat, forecastJsonWriter }
|
||||
import views._
|
||||
|
||||
object UserAnalysis extends LilaController with TheftPrevention {
|
||||
|
@ -54,8 +56,6 @@ object UserAnalysis extends LilaController with TheftPrevention {
|
|||
me =>
|
||||
import lila.round.Forecast
|
||||
OptionFuResult(GameRepo pov fullId) { pov =>
|
||||
import lila.round.Forecast.forecastStepJsonFormat
|
||||
import lila.round.Forecast.forecastJsonWriter
|
||||
if (isTheft(pov)) fuccess(theftResponse)
|
||||
else ctx.body.body.validate[Forecast.Steps].fold(
|
||||
err => BadRequest(err.toString).fuccess,
|
||||
|
@ -68,4 +68,24 @@ object UserAnalysis extends LilaController with TheftPrevention {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
def forecastsOnMyTurn(fullId: String, uci: String) = AuthBody(BodyParsers.parse.json) { implicit ctx =>
|
||||
me =>
|
||||
import lila.round.Forecast
|
||||
OptionFuResult(GameRepo pov fullId) { pov =>
|
||||
if (isTheft(pov)) fuccess(theftResponse)
|
||||
else {
|
||||
ctx.body.body.validate[Forecast.Steps].fold(
|
||||
err => BadRequest(err.toString).fuccess,
|
||||
forecasts => {
|
||||
def wait = 50 + (Forecast maxPlies forecasts min 10) * 50
|
||||
Env.round.forecastApi.playAndSave(pov, uci, forecasts) >>
|
||||
Env.current.scheduler.after(wait.millis) {
|
||||
Ok(Json.obj("reload" -> true))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
app/controllers/UserTournament.scala
Normal file
28
app/controllers/UserTournament.scala
Normal file
|
@ -0,0 +1,28 @@
|
|||
package controllers
|
||||
|
||||
import lila.app._
|
||||
import lila.user.UserRepo
|
||||
import play.api.mvc._
|
||||
import views._
|
||||
|
||||
object UserTournament extends LilaController {
|
||||
|
||||
def path(username: String, path: String, page: Int) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
path match {
|
||||
case "recent" =>
|
||||
Env.tournament.leaderboardApi.recentByUser(user, page).map { entries =>
|
||||
Ok(html.userTournament.recent(user, entries))
|
||||
}
|
||||
case "best" =>
|
||||
Env.tournament.leaderboardApi.bestByUser(user, page).map { entries =>
|
||||
Ok(html.userTournament.best(user, entries))
|
||||
}
|
||||
case "chart" => Env.tournament.leaderboardApi.chart(user).map { data =>
|
||||
Ok(html.userTournament.chart(user, data))
|
||||
}
|
||||
case _ => notFound
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package controllers
|
||||
|
||||
import play.api.data._, Forms._
|
||||
import play.api.mvc._
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
|
|
|
@ -13,9 +13,11 @@ object WorldMap extends LilaController {
|
|||
Ok(views.html.site.worldMap())
|
||||
}
|
||||
|
||||
def stream = Action {
|
||||
Ok.chunked(
|
||||
Env.worldMap.stream.producer &> EventSource()
|
||||
) as "text/event-stream"
|
||||
def stream = Action.async {
|
||||
Env.worldMap.getStream map { stream =>
|
||||
Ok.chunked(
|
||||
stream &> EventSource()
|
||||
) as "text/event-stream"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package mashup
|
|||
|
||||
import lila.common.paginator.Paginator
|
||||
import lila.db.api.SortOrder
|
||||
import lila.game.{ Game, Query }
|
||||
import lila.game.{ Game, Query, GameRepo }
|
||||
import lila.user.User
|
||||
|
||||
import play.api.libs.json._
|
||||
|
@ -109,10 +109,12 @@ object GameFilterMenu {
|
|||
case Win => std(Query win user)
|
||||
case Loss => std(Query loss user)
|
||||
case Draw => std(Query draw user)
|
||||
case Playing => pag.apply(
|
||||
case Playing => pag(
|
||||
selector = Query nowPlaying user.id,
|
||||
sort = Seq(),
|
||||
nb = nb)(page)
|
||||
nb = nb)(page) addEffect { p =>
|
||||
p.currentPageResults.filter(_.finishedOrAborted) foreach GameRepo.unsetPlayingUids
|
||||
}
|
||||
case Search => userGameSearch(user, page)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import play.api.libs.json._
|
|||
|
||||
final class Preload(
|
||||
tv: Tv,
|
||||
leaderboard: Boolean => Fu[List[(User, PerfType)]],
|
||||
leaderboard: Boolean => Fu[List[User.LightPerf]],
|
||||
tourneyWinners: Int => Fu[List[Winner]],
|
||||
timelineEntries: String => Fu[List[Entry]],
|
||||
streamsOnAir: () => Fu[List[StreamOnAir]],
|
||||
|
@ -26,7 +26,7 @@ final class Preload(
|
|||
getPlayban: String => Fu[Option[TempBan]],
|
||||
lightUser: String => Option[LightUser]) {
|
||||
|
||||
private type Response = (JsObject, List[Entry], List[MiniForumPost], List[Tournament], List[Simul], Option[Game], List[(User, PerfType)], List[Winner], Option[lila.puzzle.DailyPuzzle], List[StreamOnAir], List[lila.blog.MiniPost], Option[TempBan], Option[Preload.CurrentGame], Int)
|
||||
private type Response = (JsObject, List[Entry], List[MiniForumPost], List[Tournament], List[Simul], Option[Game], List[User.LightPerf], List[Winner], Option[lila.puzzle.DailyPuzzle], List[StreamOnAir], List[lila.blog.MiniPost], Option[TempBan], Option[Preload.CurrentGame], Int)
|
||||
|
||||
def apply(
|
||||
posts: Fu[List[MiniForumPost]],
|
||||
|
|
|
@ -14,7 +14,7 @@ import lila.user.{ User, Trophy, Trophies, TrophyApi }
|
|||
|
||||
case class UserInfo(
|
||||
user: User,
|
||||
ranks: Map[lila.rating.Perf.Key, Int],
|
||||
ranks: lila.rating.UserRankMap,
|
||||
nbUsers: Int,
|
||||
nbPlaying: Int,
|
||||
hasSimul: Boolean,
|
||||
|
@ -29,7 +29,8 @@ case class UserInfo(
|
|||
playTime: User.PlayTime,
|
||||
donor: Boolean,
|
||||
trophies: Trophies,
|
||||
isStreamer: Boolean) {
|
||||
isStreamer: Boolean,
|
||||
insightVisible: Boolean) {
|
||||
|
||||
def nbRated = user.count.rated
|
||||
|
||||
|
@ -65,21 +66,23 @@ object UserInfo {
|
|||
getRanks: String => Fu[Map[String, Int]],
|
||||
isDonor: String => Fu[Boolean],
|
||||
isHostingSimul: String => Fu[Boolean],
|
||||
isStreamer: String => Boolean)(user: User, ctx: Context): Fu[UserInfo] =
|
||||
isStreamer: String => Boolean,
|
||||
insightShare: lila.insight.Share)(user: User, ctx: Context): Fu[UserInfo] =
|
||||
countUsers() zip
|
||||
getRanks(user.id) zip
|
||||
(gameCached nbPlaying user.id) zip
|
||||
gameCached.nbImportedBy(user.id) zip
|
||||
(ctx.me.filter(user!=) ?? { me => crosstableApi(me.id, user.id) }) zip
|
||||
getRatingChart(user) zip
|
||||
relationApi.nbFollowing(user.id) zip
|
||||
relationApi.nbFollowers(user.id) zip
|
||||
(ctx.me ?? Granter(_.UserSpy) ?? { relationApi.nbBlockers(user.id) map (_.some) }) zip
|
||||
relationApi.countFollowing(user.id) zip
|
||||
relationApi.countFollowers(user.id) zip
|
||||
(ctx.me ?? Granter(_.UserSpy) ?? { relationApi.countBlockers(user.id) map (_.some) }) zip
|
||||
postApi.nbByUser(user.id) zip
|
||||
isDonor(user.id) zip
|
||||
trophyApi.findByUser(user) zip
|
||||
(user.count.rated >= 10).??(insightShare.grant(user, ctx.me)) zip
|
||||
PlayTime(user) flatMap {
|
||||
case ((((((((((((nbUsers, ranks), nbPlaying), nbImported), crosstable), ratingChart), nbFollowing), nbFollowers), nbBlockers), nbPosts), isDonor), trophies), playTime) =>
|
||||
case (((((((((((((nbUsers, ranks), nbPlaying), nbImported), crosstable), ratingChart), nbFollowing), nbFollowers), nbBlockers), nbPosts), isDonor), trophies), insightVisible), playTime) =>
|
||||
(nbPlaying > 0) ?? isHostingSimul(user.id) map { hasSimul =>
|
||||
new UserInfo(
|
||||
user = user,
|
||||
|
@ -98,7 +101,8 @@ object UserInfo {
|
|||
playTime = playTime,
|
||||
donor = isDonor,
|
||||
trophies = trophies,
|
||||
isStreamer = isStreamer(user.id))
|
||||
isStreamer = isStreamer(user.id),
|
||||
insightVisible = insightVisible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,11 @@ trait AssetHelper { self: I18nHelper =>
|
|||
test = "window.Highcharts",
|
||||
local = staticUrl("vendor/highcharts4/highcharts.js"))
|
||||
|
||||
val highchartsLatestTag = cdnOrLocal(
|
||||
cdn = "http://code.highcharts.com/4.1/highcharts.js",
|
||||
test = "window.Highcharts",
|
||||
local = staticUrl("vendor/highcharts4/highcharts-4.1.9.js"))
|
||||
|
||||
val highchartsMoreTag = Html {
|
||||
"""<script src="http://code.highcharts.com/4.1.4/highcharts-more.js"></script>"""
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ object Environment
|
|||
with SecurityHelper
|
||||
with TeamHelper
|
||||
with AnalysisHelper
|
||||
with IRCHelper
|
||||
with TournamentHelper
|
||||
with SimulHelper {
|
||||
|
||||
|
@ -68,6 +67,15 @@ object Environment
|
|||
val mod = Html("")
|
||||
}
|
||||
|
||||
val nonPuzzlePerfTypeNameIcons = {
|
||||
import play.api.libs.json.Json
|
||||
Html {
|
||||
Json stringify {
|
||||
Json toJson lila.rating.PerfType.nonPuzzleIconByName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def NotForKids[Html](f: => Html)(implicit ctx: lila.api.Context) =
|
||||
if (ctx.kid) Html("") else f
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
case chess.variant.Antichess => "Lose all your pieces to win"
|
||||
case chess.variant.Atomic => "Explode or mate your opponent's king to win"
|
||||
case chess.variant.Horde => "Destroy the horde to win"
|
||||
case chess.variant.RacingKings => "Race to the eighth rank to win"
|
||||
case chess.variant.Crazyhouse => "Drop captured pieces on the board"
|
||||
case _ => "Variant ending"
|
||||
}
|
||||
case _ => "Game is still being played"
|
||||
|
@ -156,6 +158,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
case S.VariantEnd => game.variant match {
|
||||
case chess.variant.KingOfTheHill => trans.kingInTheCenter()
|
||||
case chess.variant.ThreeCheck => trans.threeChecks()
|
||||
case chess.variant.RacingKings => trans.raceFinished()
|
||||
case _ => trans.variantEnding()
|
||||
}
|
||||
case _ => Html("")
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import scala.util.Random.shuffle
|
||||
|
||||
import controllers._
|
||||
import play.api.i18n.{ Lang, Messages }
|
||||
import play.api.mvc.{ RequestHeader, Call }
|
||||
|
@ -35,11 +33,7 @@ trait I18nHelper {
|
|||
def shortLangName(lang: Lang): Option[String] = shortLangName(lang.language)
|
||||
def shortLangName(lang: String): Option[String] = langName(lang) map (_ takeWhile (','!=))
|
||||
|
||||
def translationCall(implicit ctx: UserContext) =
|
||||
if (ctx.isAnon || ctx.req.cookies.get(hideCallsCookieName).isDefined) None
|
||||
else (~ctx.me.map(_.count.game) >= i18nEnv.CallThreshold) ?? shuffle(
|
||||
(ctx.req.acceptLanguages map transInfos.get).flatten filter (_.nonComplete)
|
||||
).headOption
|
||||
def translationCall(implicit ctx: UserContext) = i18nEnv.call(ctx.me, ctx.req)
|
||||
|
||||
def transValidationPattern(trans: String) =
|
||||
(trans contains "%s") option ".*%s.*"
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import lila.api.Context
|
||||
import lila.team.Team
|
||||
|
||||
trait IRCHelper { self: TeamHelper with SecurityHelper with I18nHelper =>
|
||||
|
||||
private val prompt = "1"
|
||||
private val uio = "OT10cnVlde"
|
||||
|
||||
def myIrcUrl(teams: List[Team])(implicit ctx: Context) =
|
||||
"""http://webchat.freenode.net?nick=%s&channels=%s&prompt=%s&uio=%s""".format(
|
||||
ctx.username | "Anon-.",
|
||||
teamChans(teams) mkString ",",
|
||||
prompt,
|
||||
uio)
|
||||
|
||||
def teamChans(teams: List[Team]) = teams flatMap teamIrcChan
|
||||
|
||||
def teamIrcChan(team: Team) = team.irc option "lichess-team-" + team.id
|
||||
}
|
||||
|
|
@ -37,24 +37,28 @@ trait SetupHelper { self: I18nHelper =>
|
|||
(variant.id.toString, variant.name, variant.title.some)
|
||||
|
||||
def translatedVariantChoices(implicit ctx: Context) = List(
|
||||
(chess.variant.Standard.id.toString, trans.standard.str(), chess.variant.Standard.title.some),
|
||||
variantTuple(chess.variant.Chess960)
|
||||
(chess.variant.Standard.id.toString, trans.standard.str(), chess.variant.Standard.title.some)
|
||||
)
|
||||
|
||||
def translatedVariantChoicesWithVariants(implicit ctx: Context) =
|
||||
translatedVariantChoices(ctx) :+
|
||||
variantTuple(chess.variant.Crazyhouse) :+
|
||||
variantTuple(chess.variant.Chess960) :+
|
||||
variantTuple(chess.variant.KingOfTheHill) :+
|
||||
variantTuple(chess.variant.ThreeCheck) :+
|
||||
variantTuple(chess.variant.Antichess) :+
|
||||
variantTuple(chess.variant.Atomic) :+
|
||||
variantTuple(chess.variant.Horde)
|
||||
variantTuple(chess.variant.Horde) :+
|
||||
variantTuple(chess.variant.RacingKings)
|
||||
|
||||
def translatedVariantChoicesWithFen(implicit ctx: Context) =
|
||||
translatedVariantChoices(ctx) :+
|
||||
variantTuple(chess.variant.Chess960) :+
|
||||
variantTuple(chess.variant.FromPosition)
|
||||
|
||||
def translatedAiVariantChoices(implicit ctx: Context) =
|
||||
translatedVariantChoices(ctx) :+
|
||||
variantTuple(chess.variant.Chess960) :+
|
||||
variantTuple(chess.variant.KingOfTheHill) :+
|
||||
variantTuple(chess.variant.ThreeCheck) :+
|
||||
variantTuple(chess.variant.FromPosition)
|
||||
|
@ -146,4 +150,7 @@ trait SetupHelper { self: I18nHelper =>
|
|||
(Pref.Message.ALWAYS, trans.always.str())
|
||||
)
|
||||
|
||||
def translatedBlindfoldChoices(implicit ctx: Context) = List(
|
||||
Pref.Blindfold.NO -> trans.no.str(),
|
||||
Pref.Blindfold.YES -> trans.yes.str())
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ trait TeamHelper {
|
|||
def myTeam(teamId: String)(implicit ctx: Context): Boolean =
|
||||
ctx.me.??(me => api.belongsTo(teamId, me.id))
|
||||
|
||||
def teamIds(userId: String): List[String] = api teamIds userId
|
||||
def teamIds(userId: String): Set[String] = api teamIds userId
|
||||
|
||||
def teamIdToName(id: String): String = api teamName id getOrElse id
|
||||
|
||||
|
|
|
@ -39,15 +39,14 @@ trait TournamentHelper { self: I18nHelper with DateHelper with UserHelper =>
|
|||
def tournamentIdToName(id: String) = tournamentEnv.cached name id getOrElse "Tournament"
|
||||
|
||||
object scheduledTournamentNameShortHtml {
|
||||
import lila.rating.PerfType._
|
||||
private def icon(c: Char) = s"""<span data-icon="$c"></span>"""
|
||||
private val replacements = List(
|
||||
"Lichess " -> "",
|
||||
"Bullet" -> icon(Bullet.iconChar),
|
||||
"Blitz" -> icon(Blitz.iconChar),
|
||||
"SuperBlitz" -> icon(Blitz.iconChar),
|
||||
"Classical" -> icon(Classical.iconChar)
|
||||
)
|
||||
"Marathon" -> icon('\\'),
|
||||
"SuperBlitz" -> icon(lila.rating.PerfType.Blitz.iconChar)
|
||||
) ::: lila.rating.PerfType.leaderboardable.map { pt =>
|
||||
pt.name -> icon(pt.iconChar)
|
||||
}
|
||||
def apply(name: String) = Html {
|
||||
replacements.foldLeft(name) {
|
||||
case (n, (from, to)) => n.replace(from, to)
|
||||
|
@ -66,10 +65,10 @@ trait TournamentHelper { self: I18nHelper with DateHelper with UserHelper =>
|
|||
|
||||
private def longTournamentDescription(tour: Tournament) =
|
||||
s"${tour.nbPlayers} players compete in the ${showEnglishDate(tour.startsAt)} ${tour.fullName}. " +
|
||||
s"${tour.clock.show} ${tour.mode.name} games are played during ${tour.minutes} minutes. " +
|
||||
tour.winnerId.fold("Winner is not yet decided.") { winnerId =>
|
||||
s"${usernameOrId(winnerId)} takes the prize home!"
|
||||
}
|
||||
s"${tour.clock.show} ${tour.mode.name} games are played during ${tour.minutes} minutes. " +
|
||||
tour.winnerId.fold("Winner is not yet decided.") { winnerId =>
|
||||
s"${usernameOrId(winnerId)} takes the prize home!"
|
||||
}
|
||||
|
||||
def tournamentOpenGraph(tour: Tournament) = lila.app.ui.OpenGraph(
|
||||
title = s"${tour.fullName}: ${tour.variant.name} ${tour.clock.show} ${tour.mode.name} #${tour.id}",
|
||||
|
|
|
@ -33,14 +33,16 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
PerfType.Correspondence,
|
||||
PerfType.Antichess,
|
||||
PerfType.Atomic,
|
||||
PerfType.Horde)
|
||||
PerfType.Horde,
|
||||
PerfType.RacingKings,
|
||||
PerfType.Crazyhouse)
|
||||
|
||||
private def best4Of(u: User, perfTypes: List[PerfType]) =
|
||||
perfTypes.sortBy { pt => -u.perfs(pt).nb } take 4
|
||||
|
||||
def miniViewSortedPerfTypes(u: User): List[PerfType] =
|
||||
best4Of(u, List(PerfType.Bullet, PerfType.Blitz, PerfType.Classical, PerfType.Correspondence)) :::
|
||||
best4Of(u, List(PerfType.Chess960, PerfType.KingOfTheHill, PerfType.ThreeCheck, PerfType.Antichess, PerfType.Atomic, PerfType.Horde))
|
||||
best4Of(u, List(PerfType.Crazyhouse, PerfType.Chess960, PerfType.KingOfTheHill, PerfType.ThreeCheck, PerfType.Antichess, PerfType.Atomic, PerfType.Horde, PerfType.RacingKings))
|
||||
|
||||
def showPerfRating(rating: Int, name: String, nb: Int, provisional: Boolean, icon: Char, klass: String)(implicit ctx: Context) = Html {
|
||||
val title = s"$name rating over ${nb.localize} games"
|
||||
|
@ -65,7 +67,7 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
|
||||
def showRatingDiff(diff: Int) = Html {
|
||||
diff match {
|
||||
case 0 => """<span class="rp null">+0</span>"""
|
||||
case 0 => """<span class="rp null">±0</span>"""
|
||||
case d if d > 0 => s"""<span class="rp up">+$d</span>"""
|
||||
case d => s"""<span class="rp down">$d</span>"""
|
||||
}
|
||||
|
@ -190,11 +192,12 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
userId: String,
|
||||
rating: Option[Int],
|
||||
cssClass: Option[String] = None,
|
||||
withPowerTip: Boolean = true,
|
||||
withTitle: Boolean = false,
|
||||
withOnline: Boolean = true) = {
|
||||
val user = lightUser(userId)
|
||||
val name = user.fold(userId)(_.name)
|
||||
val klass = userClass(userId, cssClass, withOnline)
|
||||
val klass = userClass(userId, cssClass, withOnline, withPowerTip)
|
||||
val href = userHref(name)
|
||||
val content = rating.fold(name)(e => s"$name ($e)")
|
||||
val titleS = titleTag(user.flatMap(_.title) ifTrue withTitle)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
case false => {<span class="is-red" data-icon="L"></span>}
|
||||
}
|
||||
</h1>
|
||||
@form("email").value.map { email =>
|
||||
@form("email").value.filter(_.nonEmpty).map { email =>
|
||||
<p>You have already registered the email: @email</p>
|
||||
}.getOrElse {
|
||||
<p>@trans.emailIsOptional()</p>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<h2>@trans.blindfoldChess()</h2>
|
||||
@base.radios(form("blindfold"), Seq(0 -> trans.no.str(), 1 -> trans.yes.str()))
|
||||
@base.radios(form("blindfold"), translatedBlindfoldChoices)
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
|
@ -100,6 +100,10 @@
|
|||
<h2>Let other players message you</h2>
|
||||
@base.radios(form("message"), translatedMessageChoices)
|
||||
</li>
|
||||
<li>
|
||||
<h2>Share your insights data</h2>
|
||||
@base.radios(form("insightShare"), lila.pref.Pref.InsightShare.choices)
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
<p class="saved text none" data-icon="E">@trans.yourPreferencesHaveBeenSaved()</p>
|
||||
|
|
|
@ -10,6 +10,14 @@
|
|||
This is a list of devices that have logged into your account.
|
||||
Revoke any sessions that you do not recognize.
|
||||
</p>
|
||||
@if(sessions.length > 1){
|
||||
<div class="explanation">
|
||||
Alternatively you can
|
||||
<form class="revoke-all" action="@routes.Account.signout("all")" method="POST">
|
||||
<button type="submit" class="button hint--top thin confirm">revoke all sessions</button>
|
||||
</form>.
|
||||
</div>
|
||||
}
|
||||
<table class="slist">
|
||||
@sessions.map { s =>
|
||||
<tr>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
@()(implicit ctx: Context)
|
||||
@(id: String)(implicit ctx: Context)
|
||||
|
||||
<div class="future_game_analysis progress">
|
||||
@trans.computerAnalysisInProgress()
|
||||
<div class="loader"><span></span></div>
|
||||
<div class="quote">
|
||||
@base.quote(lila.quote.Quote.one(id))
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@ atom = atom.some) {
|
|||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="is color-icon @color"></span>
|
||||
<span class="is color-icon @color.name"></span>
|
||||
</td>
|
||||
<th>
|
||||
@playerLink(pov.game.player(color), withOnline = false)
|
||||
|
@ -76,7 +76,7 @@ atom = atom.some) {
|
|||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td><strong>@lila.analyse.Accuracy(pov.withColor(color), a)</strong></td>
|
||||
<td><strong>@lila.analyse.Accuracy.mean(pov.withColor(color), a)</strong></td>
|
||||
<th>@trans.averageCentipawnLoss()</th>
|
||||
</tr>
|
||||
<tr><td class="spacerlol" colspan=2></td></tr>
|
||||
|
@ -98,7 +98,7 @@ atom = atom.some) {
|
|||
data-max="@lila.analyse.AdvantageChart.max"
|
||||
data-rows="@chart"></div>
|
||||
}.getOrElse {
|
||||
@analyse.computing()
|
||||
@analyse.computing(pov.gameId)
|
||||
}
|
||||
}.getOrElse {
|
||||
@if(analysis.isEmpty) {
|
||||
|
@ -114,6 +114,9 @@ atom = atom.some) {
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="panel quote">
|
||||
@base.quote(lila.quote.Quote.one(pov.gameId))
|
||||
</div>
|
||||
<div class="panel fen_pgn">
|
||||
<p><strong>FEN</strong><input type="input" readonly="true" spellcheck="false" class="copyable fen" /></p>
|
||||
<p><strong>PGN</strong>
|
||||
|
@ -160,6 +163,7 @@ atom = atom.some) {
|
|||
}
|
||||
}
|
||||
<a data-panel="fen_pgn" class="fen_pgn">FEN & PGN</a>
|
||||
<a data-panel="quote" class="quote" data-icon="c" title="Irrelevant chess quote"></a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ chessground = true) {
|
|||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td><strong>@lila.analyse.Accuracy(pov.withColor(color), a)</strong></td>
|
||||
<td><strong>@lila.analyse.Accuracy.mean(pov.withColor(color), a)</strong></td>
|
||||
<th>Average centipawn loss</th>
|
||||
</tr>
|
||||
<tr><td class="spacerlol" colspan=2></td></tr>
|
||||
|
|
|
@ -5,7 +5,7 @@ title = "TOR exit node",
|
|||
zen = true) {
|
||||
<div class="content_box small_box signup">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title text" data-icon="2">Cannot login from your IP</h1>
|
||||
<h1 class="lichess_title text" data-icon="2">Cannot log in from your IP</h1>
|
||||
<p>
|
||||
We have detected that you are using TOR to remain anonymous on the Internet.
|
||||
<br />
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<section>
|
||||
<h2>@trans.play()</h2>
|
||||
<a href="/?any#hook">@trans.createAGame()</a>
|
||||
<a href="@routes.Tournament.home">@trans.tournament()</a>
|
||||
<a href="@routes.Tournament.home()">@trans.tournament()</a>
|
||||
<a href="@routes.Simul.home">@trans.simultaneousExhibitions()</a>
|
||||
<a href="@routes.Tv.index">Lichess TV</a>
|
||||
</section>
|
||||
|
@ -78,6 +78,7 @@
|
|||
<a href="/mobile">@trans.mobileApp()</a> ı
|
||||
}
|
||||
<a href="/blog">@trans.blog()</a> ı
|
||||
<a href="/network">World map</a> ı
|
||||
@NotForKids {
|
||||
<a href="/developers">@trans.webmasters()</a> ı
|
||||
<a href="/help-lichess">@trans.contribute()</a> ı
|
||||
|
@ -87,10 +88,9 @@
|
|||
<a href="/donate">@trans.donate()</a> ı
|
||||
}
|
||||
<a href="/contact">@trans.contact()</a> ı
|
||||
<a href="@routes.Page.tos">@trans.termsOfService()</a> ı
|
||||
<a href="@routes.Page.tos">@trans.termsOfService()</a>
|
||||
@NotForKids {
|
||||
<a href="https://github.com/ornicar/lila" target="_blank">@trans.sourceCode()</a> ı
|
||||
ı <a href="https://github.com/ornicar/lila" target="_blank">@trans.sourceCode()</a>
|
||||
}
|
||||
<a href="/network" target="_blank" title="@trans.realTimeWorldMapOfChessMoves()">@trans.map()</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
@atom.getOrElse {
|
||||
<link href="@routes.Blog.atom()" type="application/atom+xml" rel="alternate" title="Latest blog posts" />
|
||||
}
|
||||
<link rel="mask-icon" href="@staticUrl("favicon.svg")" color="white">
|
||||
<link rel="mask-icon" href="@staticUrl("favicon.svg")" color="black">
|
||||
@if(withLangAnnotations){@langAnnotations}
|
||||
@ctx.transpBgImg.map { img =>
|
||||
<style type="text/css" id="bg-data">body.transp::before{background-image:url('@img');}</style>
|
||||
|
@ -61,6 +61,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
data-piece-set="@ctx.currentPieceSet"
|
||||
data-sound-set="@ctx.currentSoundSet"
|
||||
data-bg="@ctx.currentBg"
|
||||
data-asset-url="@assetBaseUrl"
|
||||
data-accept-languages="@acceptLanguages.mkString(",")">
|
||||
<form id="blind_mode" action="@routes.Main.toggleBlindMode" method="POST">
|
||||
<input type="hidden" name="enable" value="@ctx.blindMode.fold(0,1)" />
|
||||
|
@ -85,6 +86,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
<div class="auth fright">
|
||||
@auth.userDropdown(me, playing)
|
||||
</div>
|
||||
@if(ctx.noKid) {
|
||||
<div id="message_notifications_parent" class="message_notifications fright @if(ctx.nbMessages == 0) {none}">
|
||||
<a id="message_notifications_tag" class="toggle link data-count" data-count="@ctx.nbMessages" data-href="@routes.Message.preview">
|
||||
<span class="hint--bottom-left" data-hint="@trans.inbox()">
|
||||
|
@ -96,6 +98,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
<div class="title"><a href="@routes.Message.inbox(page=1)">@trans.inbox() »</a></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="challenge_notifications fright">
|
||||
<a id="challenge_notifications_tag" class="toggle link none data-count" data-count="0">
|
||||
<span class="hint--bottom-left" data-hint="Challenges">
|
||||
|
@ -175,7 +178,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
<div class="content list"></div>
|
||||
<div class="nobody">
|
||||
<span>@trans.noFriendsOnline()</span>
|
||||
<a class="find button" href="@routes.Relation.suggest(me.username)">
|
||||
<a class="find button" href="@routes.User.opponents(me.username)">
|
||||
<span class="is3 text" data-icon="h">@trans.findFriends()</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
6
app/views/base/quote.scala.html
Normal file
6
app/views/base/quote.scala.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
@(quote: lila.quote.Quote)
|
||||
|
||||
<blockquote class="pull-quote@if(quote.text.size > 330){ long}">
|
||||
<p>@quote.text</p>
|
||||
<footer>@quote.author</footer>
|
||||
</blockquote>
|
|
@ -5,7 +5,7 @@
|
|||
<a href="/">@trans.play()</a>
|
||||
<div>
|
||||
<a href="/?any#hook">@trans.createAGame()</a>
|
||||
<a href="@routes.Tournament.home">@trans.tournament()</a>
|
||||
<a href="@routes.Tournament.home()">@trans.tournament()</a>
|
||||
<a href="@routes.Simul.home">@trans.simultaneousExhibitions()</a>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<a href="@routes.Tv.index">@trans.watch()</a>
|
||||
<div>
|
||||
<a href="@routes.Tv.index">Lichess TV</a>
|
||||
<a href="@routes.Tv.games">Games in progress</a>
|
||||
<a href="@routes.Tv.games">@trans.gamesBeingPlayedRightNow()</a>
|
||||
<a href="@routes.Video.index">@trans.videoLibrary()</a>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -29,7 +29,7 @@
|
|||
<a href="@routes.User.list">@trans.community()</a>
|
||||
<div>
|
||||
<a href="@routes.User.list">@trans.players()</a>
|
||||
<a href="@routes.Stat.ratingDistribution("blitz")">Rating stats</a>
|
||||
<a href="@routes.Stat.ratingDistribution("blitz")">@trans.ratingStats()</a>
|
||||
@NotForKids {
|
||||
<a href="@routes.Team.home()">@trans.teams()</a>
|
||||
<a href="@routes.ForumCateg.index">@trans.forum()</a>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
@(u: User)(implicit ctx: Context)
|
||||
|
||||
@coach.layout(u, title = s"${u.username} coach data is protected", withSide = false) {
|
||||
|
||||
You cannot see @userLink(u) coach data!
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
@(u: User, title: String, robots: Boolean = true, evenMoreJs: Html = Html(""), evenMoreCss: Html = Html(""), openGraph: Option[lila.app.ui.OpenGraph] = None, chessground: Boolean = true, withSide: Boolean = true)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("coach.css")
|
||||
@evenMoreCss
|
||||
}
|
||||
|
||||
@moreJs = {
|
||||
@jsTag("coach.js")
|
||||
@evenMoreJs
|
||||
}
|
||||
|
||||
@sideSection = {
|
||||
@if(withSide) {
|
||||
<div id="coach_side">
|
||||
<div class="navigation">
|
||||
<a href="@routes.Coach.opening(u.username, "white")">Openings as White</a>
|
||||
<a href="@routes.Coach.opening(u.username, "black")">Openings as Black</a>
|
||||
@* <a href="@routes.Coach.move(u.username)">Moves</a> *@
|
||||
</div>
|
||||
@if(true) {
|
||||
<div data-icon="E">This data is fresh</div>
|
||||
}
|
||||
@if(u.count.rated > 0) {
|
||||
<form class="refresh" method="post" action="@routes.Coach.refresh(u.username)">
|
||||
<button class="button" type="submit">Refresh @u.username stats</button>
|
||||
</form>
|
||||
}
|
||||
<br />
|
||||
<div class="refreshing none">
|
||||
Hang on while we crunch data<br />
|
||||
from @u.username @if(u.count.rated > 5000) {
|
||||
@{5000.localize} last
|
||||
} else {
|
||||
@u.count.rated.localize
|
||||
} rated games!
|
||||
<br /><br />
|
||||
It shouldn't take long.
|
||||
<br /><br /><br />
|
||||
<iframe src='http://en.lichess.org/tv/frame' class='lichess-tv-iframe' allowtransparency='true' frameBorder='0' style='width: 224px; height: 264px;' title='Lichess free online chess'>
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@base.layout(
|
||||
title = title,
|
||||
side = sideSection.some,
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
robots = robots,
|
||||
chessground = chessground,
|
||||
openGraph = openGraph)(body)
|
|
@ -1,21 +0,0 @@
|
|||
@(u: User, nbPeriods: Int)(implicit ctx: Context)
|
||||
|
||||
@moreJs = {
|
||||
@highchartsTag
|
||||
@jsAt(s"compiled/lichess.coach.move${isProd??(".min")}.js")
|
||||
@embedJs {
|
||||
LichessCoachMove(document.getElementById('coach_move'), {
|
||||
nbPeriods: @nbPeriods,
|
||||
user: @Html(J.stringify(J.obj("id" -> u.id, "name" -> u.username)))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@coach.layout(u,
|
||||
title = s"${u.username} moves",
|
||||
evenMoreJs = moreJs,
|
||||
evenMoreCss = cssTag("coachMove.css"),
|
||||
chessground = false) {
|
||||
|
||||
<div id="coach_move" class="content_box no_padding coach_main"></div>
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
@(u: User, color: chess.Color, nbPeriods: Int)(implicit ctx: Context)
|
||||
|
||||
@moreJs = {
|
||||
@highchartsTag
|
||||
@jsAt(s"compiled/lichess.coach.opening${isProd??(".min")}.js")
|
||||
@embedJs {
|
||||
LichessCoachOpening(document.getElementById('coach_opening'), {
|
||||
nbPeriods: @nbPeriods,
|
||||
user: @Html(J.stringify(J.obj("id" -> u.id, "name" -> u.username))),
|
||||
color: "@color.name"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@coach.layout(u,
|
||||
title = s"${u.username} openings as ${color.name}",
|
||||
evenMoreJs = moreJs,
|
||||
evenMoreCss = cssTag("coachOpening.css"),
|
||||
chessground = false) {
|
||||
|
||||
<div id="coach_opening" class="content_box no_padding coach_main"></div>
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
@(sections: lila.coach.GameSections)(implicit ctx: Context)
|
||||
|
||||
@sectionTable(section: lila.coach.GameSections.Section) = {
|
||||
@if(section.nb != 0) {
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Moves played</th>
|
||||
<td>@section.moves.avg.map(_.localize)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Centipawn loss</th>
|
||||
<td>@section.acpl.avg.fold("N/A")(_.localize.toString)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
}
|
||||
<tr>
|
||||
<th>Overall</th>
|
||||
<td>@sectionTable(sections.all)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Opening</th>
|
||||
<td>@sectionTable(sections.opening)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Middlegame</th>
|
||||
<td>@sectionTable(sections.middle)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Endgame</th>
|
||||
<td>@sectionTable(sections.end)</td>
|
||||
</tr>
|
|
@ -1,39 +0,0 @@
|
|||
@(r: lila.coach.PerfResults, title: Option[String])(implicit ctx: Context)
|
||||
<table>
|
||||
@title.map { t =>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan=2>@t</th>
|
||||
</tr>
|
||||
</thead>
|
||||
}
|
||||
<tbody>
|
||||
@List("Win" -> r.outcomeStatuses.win, "Loss" -> r.outcomeStatuses.loss).map {
|
||||
case (name, statuses) => {
|
||||
<tr>
|
||||
<th>@name statuses</th>
|
||||
<td>
|
||||
<table>
|
||||
<tbody>
|
||||
@statuses.sorted.map {
|
||||
case (status, nb) => {
|
||||
<tr>
|
||||
<th>@status.name</th>
|
||||
<td>@nb.localize (@(nb * 100 / statuses.sum)%)</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
<tr>
|
||||
<th>Best Rating</th>
|
||||
<td>@r.bestRating.map { br =>
|
||||
<strong>@br.rating</strong> after <a href="@routes.Round.watcher(br.id, "white")">playing</a> @userIdLink(br.userId.some)
|
||||
}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,53 +0,0 @@
|
|||
@(r: lila.coach.Results, title: Option[String])(implicit ctx: Context)
|
||||
<table>
|
||||
@title.map { t =>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan=2>@t</th>
|
||||
</tr>
|
||||
</thead>
|
||||
}
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Games</th>
|
||||
<td>@r.nbGames.localize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Analysed games</th>
|
||||
<td>@r.nbAnalysis.localize (@((r.nbAnalysis * 100 / r.nbGames).localize)%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Wins</th>
|
||||
<td>@r.nbWin.localize (@((r.nbWin * 100 / r.nbGames).localize)%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Losses</th>
|
||||
<td>@r.nbLoss.localize (@((r.nbLoss * 100 / r.nbGames).localize)%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Draws</th>
|
||||
<td>@r.nbDraw.localize (@((r.nbDraw * 100 / r.nbGames).localize)%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Rating diff</th>
|
||||
<td>@r.ratingDiffAvg.map { rd =>
|
||||
@showProgress(rd)
|
||||
}</td>
|
||||
</tr>
|
||||
@gameSectionsTable(r.gameSections)
|
||||
<tr>
|
||||
<th>Best Win</th>
|
||||
<td>@r.bestWin.map { bw =>
|
||||
<a href="@routes.Round.watcher(bw.id, "white")">@userIdSpanMini(bw.userId, withOnline = true) <strong>@bw.rating</strong></a>
|
||||
}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Opponent average rating</th>
|
||||
<td>@r.opponentRatingAvg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last played</th>
|
||||
<td>@r.lastPlayed.map(momentFromNow)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,4 +1,4 @@
|
|||
@(doc: io.prismic.Document, resolver: io.prismic.DocumentLinkResolver, donations: List[lila.donation.Donation], top: List[lila.donation.Donation], progress: lila.donation.Progress)(implicit ctx: Context)
|
||||
@(doc: io.prismic.Document, resolver: io.prismic.DocumentLinkResolver, donations: List[lila.donation.Donation], top: List[User.ID], progress: lila.donation.Progress)(implicit ctx: Context)
|
||||
|
||||
@payPalVars = {
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
|
@ -19,8 +19,8 @@
|
|||
</div>
|
||||
<h2>Top Donors</h2>
|
||||
<div class="donors">
|
||||
@top.map { donation =>
|
||||
<div>@userIdLink(donation.userId)</div>
|
||||
@top.map { userId =>
|
||||
<div>@userIdLink(userId.some)</div>
|
||||
}
|
||||
</div>
|
||||
<h2>Recent Donors</h2>
|
||||
|
@ -56,7 +56,7 @@ description = "Your donations help pay for the web servers, show that lichess is
|
|||
<! -- $5 -->
|
||||
@payPalVars
|
||||
<input type="hidden" name="hosted_button_id" value="JBSUYNPRCMT9U">
|
||||
<strong>$5</strong> = <strong>9 hours</strong> of lichess costs:
|
||||
<strong>$5</strong> = <strong>6 hours</strong> of lichess costs:
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
</form>
|
||||
|
||||
|
@ -64,7 +64,7 @@ description = "Your donations help pay for the web servers, show that lichess is
|
|||
<! -- $10 -->
|
||||
@payPalVars
|
||||
<input type="hidden" name="hosted_button_id" value="ZZ96PGW3UNRPQ">
|
||||
<strong>$10</strong> = <strong>18 hours</strong> of lichess costs:
|
||||
<strong>$10</strong> = <strong>12 hours</strong> of lichess costs:
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
</form>
|
||||
|
||||
|
@ -72,7 +72,7 @@ description = "Your donations help pay for the web servers, show that lichess is
|
|||
<! -- $20 -->
|
||||
@payPalVars
|
||||
<input type="hidden" name="hosted_button_id" value="VTKN4LNQZ2FNL">
|
||||
<strong>$20</strong> = <strong>one day and a half</strong> of lichess costs:
|
||||
<strong>$20</strong> = <strong>one day</strong> of lichess costs:
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
</form>
|
||||
|
||||
|
@ -80,7 +80,7 @@ description = "Your donations help pay for the web servers, show that lichess is
|
|||
<! -- $50 -->
|
||||
@payPalVars
|
||||
<input type="hidden" name="hosted_button_id" value="XXYXZXY7GJQYJ">
|
||||
<strong>$50</strong> = <strong>half a week</strong> of lichess costs:
|
||||
<strong>$50</strong> = <strong>2.5 days</strong> of lichess costs:
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
</form>
|
||||
|
||||
|
@ -102,10 +102,10 @@ description = "Your donations help pay for the web servers, show that lichess is
|
|||
<input type="hidden" name="hosted_button_id" value="36KH4D465UB9G">
|
||||
<input type="hidden" name="on0" value="Monthly donation">Monthly donation
|
||||
<select name="os0">
|
||||
<option value="9 hours">9 hours: $5.00 USD - monthly</option>
|
||||
<option value="18 hours">18 hours: $10.00 USD - monthly</option>
|
||||
<option value="one day and a half">one day and a half: $20.00 USD - monthly</option>
|
||||
<option value="half a week">half a week: $50.00 USD - monthly</option>
|
||||
<option value="$5">6 hours: $5.00 USD - monthly</option>
|
||||
<option value="$10">12 hours: $10.00 USD - monthly</option>
|
||||
<option value="$20">one day: $20.00 USD - monthly</option>
|
||||
<option value="$50">2.5 days: $50.00 USD - monthly</option>
|
||||
</select>
|
||||
<input type="hidden" name="currency_code" value="USD">
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribe_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@(posts: List[lila.forum.MiniForumPost])(implicit ctx: Context)
|
||||
@posts.map { p =>
|
||||
<li>
|
||||
<a @if(p.isTeam) { data-icon="f" } class="post_link" href="@routes.ForumPost.redirect(p.postId)"> @shorten(p.topicName, 30)</a>
|
||||
<a @if(p.isTeam) { data-icon="f" } class="post_link" href="@routes.ForumPost.redirect(p.postId)" title="@Html(escapeEvenDoubleQuotes(p.topicName))"> @shorten(p.topicName, 30)</a>
|
||||
@userIdLink(p.userId, withOnline = false)
|
||||
<span class="extract">@shorten(p.text, 70)</span>
|
||||
</li>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<li><h1>@categ.name</h1></li>
|
||||
</ol>
|
||||
<div class="warning">
|
||||
<h1 data-icon="!">Important</h1>
|
||||
<h1 data-icon="!"> Important</h1>
|
||||
<p>
|
||||
To report a user for cheating or bad behaviour,<br />
|
||||
<strong><a href="@routes.Report.form">use the report form</a></strong>.
|
||||
|
|
|
@ -2,14 +2,8 @@
|
|||
@url = {
|
||||
@variant match {
|
||||
case chess.variant.Standard => {https://en.wikipedia.org/wiki/Chess}
|
||||
case chess.variant.Chess960 => {https://en.wikipedia.org/wiki/Chess960}
|
||||
case chess.variant.KingOfTheHill => {@routes.Page.kingOfTheHill}
|
||||
case chess.variant.ThreeCheck => {http://en.wikipedia.org/wiki/Three-check_chess}
|
||||
case chess.variant.Antichess => {http://en.wikipedia.org/wiki/Losing_chess}
|
||||
case chess.variant.FromPosition => {@routes.Editor.index?fen=@initialFen.map(_.replace(" ", "_"))}
|
||||
case chess.variant.Atomic => {http://www.freechess.org/Help/HelpFiles/atomic.html}
|
||||
case chess.variant.Horde => {http://en.wikipedia.org/wiki/Dunsany%27s_chess#Horde_variant}
|
||||
case _ => {}
|
||||
case v => {@routes.Page.variant(v.key)}
|
||||
}
|
||||
}
|
||||
<a href="@url" rel="nofollow" target="_blank" @if(hintAsTitle){title}else{data-hint}="@variant.title" class="@cssClass">@name</a>
|
||||
<a href="@url" rel="nofollow" target="_blank" @if(hintAsTitle){title}else{data-hint}="@variant.title" class="@cssClass variant-link">@name</a>
|
||||
|
|
|
@ -62,7 +62,7 @@ No need to submit a complete translation. You can just translate some sentences,
|
|||
value="@form("comment").value"
|
||||
name="@form("comment").name"
|
||||
id="@form("comment").id"
|
||||
placeholder="Briefly describe your changes, in english" />
|
||||
placeholder="Briefly describe your changes, in English" />
|
||||
@errMsg(form("comment"))
|
||||
</div>
|
||||
<br />
|
||||
|
|
16
app/views/insight/empty.scala.html
Normal file
16
app/views/insight/empty.scala.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
@(u: User)(implicit ctx: Context)
|
||||
|
||||
@insight.layout(u,
|
||||
title = s"${u.username}'s chess insights",
|
||||
moreJs = jsTag("insight-refresh.js")) {
|
||||
|
||||
<div class="content_box small_box">
|
||||
<div class="head">
|
||||
<h1 class="text" data-icon="7">@u.username chess insights</h1>
|
||||
</div>
|
||||
<br /><br /><br />
|
||||
<p>@userLink(u) has no chess insights yet!</p>
|
||||
<br /><br />
|
||||
@refreshForm(u, s"Generate ${u.username}'s chess insights")
|
||||
</div>
|
||||
}
|
16
app/views/insight/forbidden.scala.html
Normal file
16
app/views/insight/forbidden.scala.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
@(u: User)(implicit ctx: Context)
|
||||
|
||||
@site.message(
|
||||
title = s"${u.username}'s chess insights are protected",
|
||||
back = true,
|
||||
icon = Html("7").some) {
|
||||
|
||||
Sorry, you cannot see @userLink(u)'s chess insights.
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
Maybe ask them to change their <a class="button" href="@routes.Pref.form("privacy")">privacy settings</a> ?
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
}
|
52
app/views/insight/index.scala.html
Normal file
52
app/views/insight/index.scala.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
@(u: User, cache: lila.insight.UserCache, prefId: Int, ui: play.api.libs.json.JsObject, question: play.api.libs.json.JsObject, stale: Boolean)(implicit ctx: Context)
|
||||
|
||||
@moreJs = {
|
||||
@highchartsLatestTag
|
||||
@jsAt("vendor/multiple-select/multiple-select.js")
|
||||
@jsAt(s"compiled/lichess.insight${isProd??(".min")}.js")
|
||||
@jsTag("insight-refresh.js")
|
||||
@jsTag("insight-tour.js")
|
||||
@embedJs {
|
||||
$(function() {
|
||||
lichess = lichess || {};
|
||||
lichess.insight = LichessInsight(document.getElementById('insight'), {
|
||||
ui: @Html(toJson(ui)),
|
||||
initialQuestion: @Html(toJson(question)),
|
||||
i18n: {},
|
||||
myUserId: @Html(ctx.userId.fold("null")(id => s""""$id"""")),
|
||||
user: {
|
||||
id: "@u.id",
|
||||
name: "@u.username",
|
||||
nbGames: @cache.count,
|
||||
stale: @stale,
|
||||
shareId: @prefId
|
||||
},
|
||||
pageUrl: "@routes.Insight.index(u.username)",
|
||||
postUrl: "@routes.Insight.json(u.username)"
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("insight.css")
|
||||
@cssVendorTag("multiple-select/multiple-select.css")
|
||||
@ctx.currentBg match {
|
||||
case "dark" => { @cssTag("insight.dark.css") }
|
||||
case "transp" => { @cssTag("insight.dark.css")@cssTag("insight.transp.css") }
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@insight.layout(u,
|
||||
title = s"${u.username}'s chess insights",
|
||||
moreJs = moreJs,
|
||||
moreCss = moreCss) {
|
||||
<div id="insight"></div>
|
||||
@if(stale) {
|
||||
<div class="insight-stale none">
|
||||
<p>There are new games to learn from!</p>
|
||||
@refreshForm(u, "Update insights")
|
||||
</div>
|
||||
}
|
||||
}
|
8
app/views/insight/layout.scala.html
Normal file
8
app/views/insight/layout.scala.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
@(u: User, title: String, moreJs: Html = Html(""), moreCss: Html = Html(""), openGraph: Option[lila.app.ui.OpenGraph] = None, chessground: Boolean = true)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@base.layout(
|
||||
title = title,
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
chessground = chessground,
|
||||
openGraph = openGraph)(body)
|
12
app/views/insight/noGame.scala.html
Normal file
12
app/views/insight/noGame.scala.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
@(u: User)(implicit ctx: Context)
|
||||
|
||||
@site.message(
|
||||
title = s"${u.username} has not played a rated game yet!",
|
||||
back = true,
|
||||
icon = Html("7").some) {
|
||||
|
||||
Before using chess insights, @userLink(u) has to play at least one rated game.
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
}
|
18
app/views/insight/refreshForm.scala.html
Normal file
18
app/views/insight/refreshForm.scala.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
@(u: User, action: String)(implicit ctx: Context)
|
||||
|
||||
<form class="insight-refresh" method="post" action="@routes.Insight.refresh(u.username)">
|
||||
<button data-icon="E" class="button text">@action</button>
|
||||
<div class="crunching none">
|
||||
<span class="square-spin"></span>
|
||||
<br />
|
||||
<strong>Now crunching data just for you!</strong>
|
||||
<br />
|
||||
<br />
|
||||
<p>Would you like to watch<br />a live game while you wait?</p>
|
||||
<br />
|
||||
<script src="http://en.lichess.org/tv/embed?theme=brown&bg=light"></script>
|
||||
<br />
|
||||
<br />
|
||||
<p>This can take a while,<br />maybe reload this page later!</p>
|
||||
</div>
|
||||
</form>
|
|
@ -1,4 +1,4 @@
|
|||
@(data: play.api.libs.json.JsObject, userTimeline: List[lila.timeline.Entry], forumRecent: List[lila.forum.MiniForumPost], tours: List[Tournament], simuls: List[lila.simul.Simul], featured: Option[Game], leaderboard: List[(User, lila.rating.PerfType)], tournamentWinners: List[lila.tournament.Winner], puzzle: Option[lila.puzzle.DailyPuzzle], streams: List[lila.tv.StreamOnAir], lastPost: List[lila.blog.MiniPost], playban: Option[lila.playban.TempBan], currentGame: Option[lila.app.mashup.Preload.CurrentGame], nbRounds: Int)(implicit ctx: Context)
|
||||
@(data: play.api.libs.json.JsObject, userTimeline: List[lila.timeline.Entry], forumRecent: List[lila.forum.MiniForumPost], tours: List[Tournament], simuls: List[lila.simul.Simul], featured: Option[Game], leaderboard: List[User.LightPerf], tournamentWinners: List[lila.tournament.Winner], puzzle: Option[lila.puzzle.DailyPuzzle], streams: List[lila.tv.StreamOnAir], lastPost: List[lila.blog.MiniPost], playban: Option[lila.playban.TempBan], currentGame: Option[lila.app.mashup.Preload.CurrentGame], nbRounds: Int)(implicit ctx: Context)
|
||||
|
||||
@underchat = {
|
||||
<div id="featured_game">
|
||||
|
@ -62,6 +62,7 @@
|
|||
@embedJs {
|
||||
lichess = lichess || {};
|
||||
lichess.lobby = {
|
||||
perfIcons: @nonPuzzlePerfTypeNameIcons,
|
||||
data: @Html(J.stringify(data)),
|
||||
playban: @playban.fold(Html("null")){ pb =>
|
||||
@Html(J.stringify(J.obj("minutes" -> pb.mins, "remainingSeconds" -> (pb.remainingSeconds + 3))))
|
||||
|
@ -113,7 +114,7 @@ description = trans.freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrati
|
|||
}
|
||||
<div class="undertable">
|
||||
<div class="undertable_top">
|
||||
<a class="more hint--bottom" data-hint="@trans.seeAllTournaments()" href="@routes.Tournament.home">@trans.more() »</a>
|
||||
<a class="more hint--bottom" data-hint="@trans.seeAllTournaments()" href="@routes.Tournament.home()">@trans.more() »</a>
|
||||
<span class="title text" data-icon="g">@trans.openTournaments()</span>
|
||||
</div>
|
||||
<div id="enterable_tournaments" class="enterable_list undertable_inner scroll-shadow-hard">
|
||||
|
@ -139,15 +140,15 @@ description = trans.freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrati
|
|||
<div class="undertable_inner scroll-shadow-hard">
|
||||
<table>
|
||||
<tbody>
|
||||
@leaderboard.map {
|
||||
case (u, pt) => {
|
||||
@leaderboard.map { l =>
|
||||
<tr>
|
||||
<td>@userLink(u)</td>
|
||||
<td>@showPerfRating(u, pt, klass = "title")</td>
|
||||
<td>@showProgress(u.perfs(pt).progress, withTitle = false)</td>
|
||||
<td>@lightUserLink(l.user)</td>
|
||||
@lila.rating.PerfType(l.perfKey).map { pt =>
|
||||
<td data-icon="@pt.iconChar">@l.rating</td>
|
||||
}
|
||||
<td>@showProgress(l.progress, withTitle = false)</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
block them using the block button on their profile page.</p>
|
||||
<h2>What now?</h2>
|
||||
<p>Just wait until you can create/join games again!</p>
|
||||
<p>Maybe <a href="@routes.Tournament.home">join a tournament</a>,<br />
|
||||
<p>Maybe <a href="@routes.Tournament.home()">join a tournament</a>,<br />
|
||||
or play some <a href="@routes.Puzzle.home">training puzzles</a>,<br />
|
||||
or watch <a href="@routes.Video.index">chess videos</a>!
|
||||
</p>
|
||||
|
|
26
app/views/mod/gamify/champion.scala.html
Normal file
26
app/views/mod/gamify/champion.scala.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
@(champ: Option[lila.mod.Gamify.ModMixed], img: String, period: lila.mod.Gamify.Period)(implicit ctx: Context)
|
||||
|
||||
<div class="champ">
|
||||
<img src="@staticUrl(s"images/mod/$img.png")" />
|
||||
<h2>Mod of the @period.name</h2>
|
||||
@champ.map { m =>
|
||||
<h3>@userIdLink(m.modId.some, withOnline = false)</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Total score</th>
|
||||
<td>@m.score</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Actions taken</th>
|
||||
<td>@m.action</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Reports closed</th>
|
||||
<td>@m.report</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}.getOrElse { Nobody! }
|
||||
<a class="current button" href="@routes.Mod.gamifyPeriod(period.name)">View @period.name leaderboard</a>
|
||||
</div>
|
57
app/views/mod/gamify/index.scala.html
Normal file
57
app/views/mod/gamify/index.scala.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
@(leaderboards: lila.mod.Gamify.Leaderboards, history: List[lila.mod.Gamify.HistoryMonth])(implicit ctx: Context)
|
||||
|
||||
@import lila.mod.Gamify.Period
|
||||
|
||||
@title = @{ "Moderator hall of fame" }
|
||||
|
||||
@yearHeader(year: Int) = @{
|
||||
Html(s"""<tr class="year">
|
||||
<th>$year</th>
|
||||
<th>Champions of the past</th>
|
||||
<th>Score</th>
|
||||
<th>Actions taken</th>
|
||||
<th>Reports closed</th>
|
||||
</tr>""")
|
||||
}
|
||||
|
||||
@mod.layout(
|
||||
title = title,
|
||||
active = "gamify",
|
||||
moreCss = cssTag("mod-gamify.css")) {
|
||||
|
||||
<div id="mod-gamify" class="content_box no_padding index">
|
||||
<h1>@title</h1>
|
||||
<div class="champs clearfix">
|
||||
<div class="third">
|
||||
@champion(leaderboards.daily.headOption, "reward1", Period.Day)
|
||||
</div>
|
||||
<div class="third">
|
||||
@champion(leaderboards.weekly.headOption, "reward2", Period.Week)
|
||||
</div>
|
||||
<div class="third">
|
||||
@champion(leaderboards.monthly.headOption, "reward3", Period.Month)
|
||||
</div>
|
||||
</div>
|
||||
<div class="history">
|
||||
<table class="slist">
|
||||
<tbody>
|
||||
@history.headOption.filterNot(_.date.getMonthOfYear == 12).map { h =>
|
||||
@yearHeader(h.date.getYear)
|
||||
}
|
||||
@history.map { h =>
|
||||
@if(h.date.getMonthOfYear == 12) {
|
||||
@yearHeader(h.date.getYear)
|
||||
}
|
||||
<tr>
|
||||
<th>@h.date.monthOfYear.getAsText</th>
|
||||
<th>@userIdLink(h.champion.modId.some, withOnline = false)</th>
|
||||
<td class="score">@h.champion.score.localize</td>
|
||||
<td>@h.champion.action.localize</td>
|
||||
<td>@h.champion.report.localize</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
40
app/views/mod/gamify/period.scala.html
Normal file
40
app/views/mod/gamify/period.scala.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
@(leaderboards: lila.mod.Gamify.Leaderboards, period: lila.mod.Gamify.Period)(implicit ctx: Context)
|
||||
|
||||
@title = @{ s"Moderators of the ${period.name}" }
|
||||
|
||||
@mod.layout(
|
||||
title = title,
|
||||
active = "gamify",
|
||||
moreCss = cssTag("mod-gamify.css")) {
|
||||
|
||||
<div id="mod-gamify" class="content_box no_padding">
|
||||
<h1>
|
||||
<a href="@routes.Mod.gamify" data-icon="I" class="text">@title</a>
|
||||
</h1>
|
||||
<div class="period">
|
||||
<table class="slist">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2"></th>
|
||||
<th>Actions</th>
|
||||
<th>Reports</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@leaderboards(period).zipWithIndex.map {
|
||||
case (m, i) => {
|
||||
<tr>
|
||||
<th>@(i + 1)</th>
|
||||
<th>@userIdLink(m.modId.some, withOnline = false)</th>
|
||||
<td>@m.action.localize</td>
|
||||
<td>@m.report.localize</td>
|
||||
<td class="score">@m.score.localize</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -4,6 +4,12 @@
|
|||
@if(isGranted(_.SeeReport)) {
|
||||
<a class="@active.active("report")" href="@routes.Report.list">Reports</a>
|
||||
}
|
||||
@if(isGranted(_.SeeReport)) {
|
||||
<a class="@active.active("gamify")" href="@routes.Mod.gamify">Hall of fame</a>
|
||||
}
|
||||
@if(isGranted(_.UserSearch)) {
|
||||
<a class="@active.active("search")" href="@routes.Mod.search">Search users</a>
|
||||
}
|
||||
@if(isGranted(_.StreamConfig)) {
|
||||
<a class="@active.active("stream")" href="@routes.Tv.streamConfig">Streams</a>
|
||||
}
|
||||
|
|
58
app/views/mod/search.scala.html
Normal file
58
app/views/mod/search.scala.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
@(query: String, users: List[User])(implicit ctx: Context)
|
||||
|
||||
@title = @{ "Search users" }
|
||||
|
||||
@layout(
|
||||
title = title,
|
||||
active = "search") {
|
||||
|
||||
<style type="text/css">
|
||||
#mod-search form {
|
||||
margin: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
#mod-search form input {
|
||||
padding: 15px 25px;
|
||||
font-size: 1.2em;
|
||||
width: 400px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="mod-search" class="content_box">
|
||||
<h1 data-icon="y" class="text">@title</h1>
|
||||
<form class="search" action="@routes.Mod.search" method="GET">
|
||||
<input name="q" placeholder="Search by IP, email, or username" value="@query" />
|
||||
</form>
|
||||
@if(users.nonEmpty) {
|
||||
<table class="slist">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Games</th>
|
||||
<th>Marks</th>
|
||||
<th>IPban</th>
|
||||
<th>Closed</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@users.map { u =>
|
||||
<tr>
|
||||
<td>@userLink(u, withBestRating = true, params = "?mod")</td>
|
||||
<td>@u.count.game.localize</td>
|
||||
<td>
|
||||
@if(u.engine){ENGINE}
|
||||
@if(u.booster){BOOSTER}
|
||||
@if(u.troll){TROLL}
|
||||
</td>
|
||||
<td>@if(u.ipBan){IPBAN}</td>
|
||||
<td>@if(u.disabled){CLOSED}</td>
|
||||
<td>@momentFromNow(u.createdAt)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
}
|
||||
</div>
|
||||
}
|
|
@ -49,6 +49,6 @@ url = s"$netBaseUrl${routes.Opening.show(opening.id).url}",
|
|||
description = s"Opening training #${opening.id}: " + opening.color.fold(
|
||||
trans.findTheBestMoveForWhite,
|
||||
trans.findTheBestMoveForBlack
|
||||
).str() + s". Played by ${opening.attempts} players.").some) {
|
||||
).str() + s" Played by ${opening.attempts} players.").some) {
|
||||
<div class="round cg-512">@miniBoardContent</div>
|
||||
}
|
||||
|
|
|
@ -64,6 +64,6 @@ url = s"$netBaseUrl${routes.Puzzle.show(puzzle.id).url}",
|
|||
description = s"Tactic puzzle #${puzzle.id}: " + puzzle.color.fold(
|
||||
trans.findTheBestMoveForWhite,
|
||||
trans.findTheBestMoveForBlack
|
||||
).str() + s". Played by ${puzzle.attempts} players.").some) {
|
||||
).str() + s" Played by ${puzzle.attempts} players.").some) {
|
||||
<div class="round cg-512">@miniBoardContent</div>
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
@sideSection = {
|
||||
<div class="side">
|
||||
@searchForm()
|
||||
@NotForKids {
|
||||
<a class="ask" href="@routes.QaQuestion.ask">Ask a question</a>
|
||||
}
|
||||
@side
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
@(u: User, users: List[lila.relation.Related])(implicit ctx: Context)
|
||||
@(u: User, pag: Paginator[lila.relation.Related])(implicit ctx: Context)
|
||||
|
||||
@user.layout(title = u.username + " - " + trans.blocks(users.size)) {
|
||||
@user.layout(title = u.username + " - " + trans.blocks(pag.nbResults)) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>
|
||||
@userLink(u, withOnline = false)
|
||||
@trans.blocks(users.size)
|
||||
@trans.blocks(pag.nbResults)
|
||||
</h1>
|
||||
@user.simpleTable(users)
|
||||
@user.simpleTable(pag, routes.Relation.blocks())
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
@(u: User, users: List[lila.relation.Related], following: Int)(implicit ctx: Context)
|
||||
@(u: User, pag: Paginator[lila.relation.Related], nbFollowing: Int)(implicit ctx: Context)
|
||||
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowers(users.size)) {
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowers(pag.nbResults)) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>
|
||||
@userLink(u, withOnline = false)
|
||||
@trans.nbFollowers(users.size)
|
||||
@trans.nbFollowers(pag.nbResults)
|
||||
&
|
||||
<a href="@routes.Relation.following(u.username)">@trans.nbFollowing(following)</a>
|
||||
<a href="@routes.Relation.following(u.username)">@trans.nbFollowing(nbFollowing)</a>
|
||||
</h1>
|
||||
@user.simpleTable(users)
|
||||
@user.simpleTable(pag, routes.Relation.followers(u.username))
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
@(u: User, users: List[lila.relation.Related], followers: Int)(implicit ctx: Context)
|
||||
@(u: User, pag: Paginator[lila.relation.Related], nbFollowers: Int)(implicit ctx: Context)
|
||||
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowing(users.size)) {
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowing(pag.nbResults)) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>
|
||||
@userLink(u, withOnline = false)
|
||||
@trans.nbFollowing(users.size)
|
||||
@trans.nbFollowing(pag.nbResults)
|
||||
&
|
||||
<a href="@routes.Relation.followers(u.username)">@trans.nbFollowers(followers)</a>
|
||||
<a href="@routes.Relation.followers(u.username)">@trans.nbFollowers(nbFollowers)</a>
|
||||
</h1>
|
||||
@user.simpleTable(users)
|
||||
@user.simpleTable(pag, routes.Relation.following(u.username))
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
@(u: User, sugs: List[lila.relation.Related])(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s - %s".format(u.username, trans.findFriends()) }
|
||||
|
||||
@user.layout(title = title) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>@userLink(u, withOnline = false) @trans.findFriends()</h1>
|
||||
@user.relatedTable(u, sugs)
|
||||
</div>
|
||||
}
|
||||
|
|
@ -45,7 +45,6 @@
|
|||
@errMsg(form("text"))
|
||||
</div>
|
||||
@base.captcha(form("move"), form("gameId"), captcha)
|
||||
@errMsg(form)
|
||||
<div class="actions">
|
||||
<input class="send button" type="submit" value="@trans.send()" />
|
||||
<a class="cancel" href="@routes.Lobby.home()">@trans.cancel()</a>
|
||||
|
|
|
@ -1,16 +1,39 @@
|
|||
@()(implicit ctx: Context)
|
||||
@(userId: String, blocked: Boolean)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "Thanks for the report" }
|
||||
|
||||
@site.layout(title = title, moreCss = cssTag("report.css")) {
|
||||
@moreJs = {
|
||||
@embedJs {
|
||||
$('button.report-block').one('click', function() {
|
||||
var $button = $(this);
|
||||
$button.find('span').text('Blocking...');
|
||||
$.ajax({
|
||||
url:$button.data('action'),
|
||||
method:'post',
|
||||
success: function() {
|
||||
$button.find('span').text('Blocked!');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@site.layout(title = title, moreCss = cssTag("report.css"), moreJs = moreJs) {
|
||||
|
||||
<div class="content_box small_box">
|
||||
<h1 class="lichess_title">@title</h1>
|
||||
<p>The moderators will review it very soon, and take appropriate action.</p>
|
||||
<br /><br /><br />
|
||||
@if(!blocked) {
|
||||
In the mean time, you can block this user:
|
||||
<button data-action="@routes.Relation.block(userId)" class="report-block icon button hint--top inline" type="submit" data-hint="@trans.block()">
|
||||
<span class="text" data-icon="k">Block @usernameOrId(userId)</span>
|
||||
</button>
|
||||
<br /><br /><br />
|
||||
}
|
||||
<p>
|
||||
<br />
|
||||
<br />
|
||||
<a href="@routes.Lobby.home">Return to lichess homepage</a>
|
||||
<a href="@routes.Lobby.home">Return to lichess homepage</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
@game.players.map { p =>
|
||||
@if(game.playerBlurPercent(p.color) > 30) {
|
||||
<br />
|
||||
<span class="mod blurs">
|
||||
@playerLink(p, cssClass = s"is color-icon ${p.color.name}".some, withOnline = false, mod = true) @p.blurs/@game.playerMoves(p.color) blurs
|
||||
<strong>@game.playerBlurPercent(p.color)%</strong>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
@game.players.map { p =>
|
||||
@p.holdAlert.map { h =>
|
||||
<br />
|
||||
<span class="mod hold">
|
||||
@playerLink(p, cssClass = s"is color-icon ${p.color.name}".some, mod = true, withOnline = false) hold alert<br />
|
||||
(ply: @h.ply, mean: @h.mean ms, SD: @h.sd)<br />
|
||||
(ply: @h.ply, mean: @h.mean ms, SD: @h.sd)
|
||||
</span><br />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<h2>@trans.blindfoldChess()</h2>
|
||||
@base.radios(form("blindfold"), lila.pref.Pref.Blindfold.choices, container = true, prefix = "igp_")
|
||||
@base.radios(form("blindfold"), translatedBlindfoldChoices, container = true, prefix = "igp_")
|
||||
</div>
|
||||
</form>
|
||||
<a target="_blank" class="prefs button text" data-icon="%" href="@routes.Pref.form("game-display")">@trans.preferences()</a>
|
||||
|
|
|
@ -55,6 +55,7 @@ side = Html("")) {
|
|||
@trans.mode(): @modeName(c.mode)
|
||||
</p>
|
||||
}
|
||||
@base.quote(lila.quote.Quote.one(pov.gameId))
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -8,10 +8,7 @@ title = trans.hostANewSimul.str()) {
|
|||
<div class="content_box small_box simul_box">
|
||||
<h1>@trans.hostANewSimul()</h1>
|
||||
<form class="plain create content_box_content" action="@routes.Simul.create" method="POST">
|
||||
<p class="help">
|
||||
@trans.whenCreateSimul()<br />
|
||||
<a href="@routes.Simul.home">@trans.joinExistingSimul()</a>
|
||||
</p>
|
||||
<p class="help">@trans.whenCreateSimul()</p>
|
||||
@form.globalError.map { error =>
|
||||
<p class="error">@error.message</p>
|
||||
}
|
||||
|
|
|
@ -21,9 +21,15 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
@title = @{ trans.simultaneousExhibitions.str() }
|
||||
|
||||
@simul.layout(
|
||||
title = trans.simultaneousExhibitions.str(),
|
||||
side = side.some) {
|
||||
title = title,
|
||||
side = side.some,
|
||||
openGraph = lila.app.ui.OpenGraph(
|
||||
title = title,
|
||||
url = s"$netBaseUrl${routes.Simul.home}",
|
||||
description = trans.aboutSimul.str()).some) {
|
||||
<div id="simul_list" data-href="@routes.Simul.homeReload()">
|
||||
@simul.homeInner(opens, starteds, finisheds)
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(title: String, moreJs: Html = Html(""), side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, chessground: Boolean = true)(body: Html)(implicit ctx: Context)
|
||||
@(title: String, moreJs: Html = Html(""), side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, chessground: Boolean = true, openGraph: Option[lila.app.ui.OpenGraph] = None)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("simul.css")
|
||||
|
@ -11,6 +11,7 @@ moreCss = moreCss,
|
|||
side = side,
|
||||
chat = chat,
|
||||
underchat = underchat,
|
||||
chessground = chessground) {
|
||||
chessground = chessground,
|
||||
openGraph = openGraph) {
|
||||
@body
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue