Merge branch 'v2'
* v2: (1324 commits)
email-confirm head bar z-index
fix email change tokens after 5c94ebc99a
fix public tournament chat not available
tweak coord position
render analysis gauge after the board so it goes over the rank coords
refactor coords
include all coords CSS in board pages
implement menuHover in board editor
board editor code tweaks
implement menuHover in puzzles & analysis
implement menuHover in round
specific lichess topnav hoverIntent implementation
fix insight max board size
fix top bar on long following pages
don't reply to lichess messages, nobody reads that
send a message when the max follow limit is reached
fix max-follow bug
responsive coach editor
fix account pages height on mobile
fix round moves list index width
...
pull/5042/head
commit
0725765d20
|
@ -9,6 +9,7 @@ public/vendor/stockfish.wasm
|
|||
public/vendor/stockfish-mv.wasm
|
||||
public/vendor/stockfish.pexe
|
||||
public/vendor/stockfish.js
|
||||
public/css/
|
||||
target
|
||||
bin/.translate_version
|
||||
data/
|
||||
|
@ -17,6 +18,7 @@ node_modules/
|
|||
local/
|
||||
ui/*/npm-debug.log
|
||||
hs_*.log
|
||||
yarn-error.log
|
||||
|
||||
RUNNING_PID
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ Exceptions (free)
|
|||
Files | Author(s) | License
|
||||
--- | --- | ---
|
||||
public/font70 | [Dave Gandy](http://fontawesome.io/), [GitHub](https://github.com/primer/octicons), [Webalys](http://www.webalys.com/), [Zurb](http://zurb.com/playground/foundation-icon-fonts-3), [Daniel Bruce](http://www.entypo.com/), [Shapemade](http://steadysets.com/), [Sergey Shmidt](http://designmodo.com/linecons-free/) and the lichess authors | [OFL](http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), [MIT](https://github.com/primer/octicons/blob/master/LICENSE), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/), AGPLv3+
|
||||
ChessSansPiratf in public/fonts | the [pgn4web](http://pgn4web.casaschi.net/home.html) authors | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt)
|
||||
ChessSansPiratf in public/font | the [pgn4web](http://pgn4web.casaschi.net/home.html) authors | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt)
|
||||
public/images/staunton | [James Clarke](https://github.com/clarkerubber/Staunton-Pieces) | [MIT](https://github.com/clarkerubber/Staunton-Pieces/blob/master/LICENSE)
|
||||
public/images/staunton/piece/CubesAndPi | CubesAndPi | AGPLv3+
|
||||
public/images/trophy | [James Clarke](https://github.com/clarkerubber/Staunton-Pieces/tree/master/Trophies) | [MIT](https://github.com/clarkerubber/Staunton-Pieces/blob/master/LICENSE)
|
||||
|
|
|
@ -2,7 +2,6 @@ package lila.app
|
|||
package actor
|
||||
|
||||
import akka.actor._
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.game.Pov
|
||||
import views.{ html => V }
|
||||
|
@ -12,10 +11,10 @@ private[app] final class Renderer extends Actor {
|
|||
def receive = {
|
||||
|
||||
case lila.tv.actorApi.RenderFeaturedJs(game) =>
|
||||
sender ! V.game.bits.featuredJs(Pov first game).body
|
||||
sender ! V.game.bits.featuredJs(Pov first game).render
|
||||
|
||||
case lila.tournament.actorApi.TournamentTable(tours) =>
|
||||
sender ! V.tournament.enterable(tours).render
|
||||
sender ! V.tournament.bits.enterable(tours).render
|
||||
|
||||
case lila.simul.actorApi.SimulTable(simuls) =>
|
||||
sender ! V.simul.bits.allCreated(simuls).render
|
||||
|
|
|
@ -68,16 +68,16 @@ object Analyse extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def embed(gameId: String, color: String) = Open { implicit ctx =>
|
||||
def embed(gameId: String, color: String) = Action.async { implicit req =>
|
||||
GameRepo.gameWithInitialFen(gameId) flatMap {
|
||||
case Some((game, initialFen)) =>
|
||||
val pov = Pov(game, chess.Color(color == "white"))
|
||||
Env.api.roundApi.review(pov, lila.api.Mobile.Api.currentVersion,
|
||||
Env.api.roundApi.embed(pov, lila.api.Mobile.Api.currentVersion,
|
||||
initialFenO = initialFen.some,
|
||||
withFlags = WithFlags(opening = true)) map { data =>
|
||||
Ok(html.analyse.embed(pov, data))
|
||||
}
|
||||
case _ => fuccess(NotFound(html.analyse.embed.notFound()))
|
||||
case _ => fuccess(NotFound(html.analyse.embed.notFound))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ object Api extends LilaController {
|
|||
}
|
||||
|
||||
def index = Action {
|
||||
Ok(views.html.site.api())
|
||||
Ok(views.html.site.bits.api)
|
||||
}
|
||||
|
||||
def user(name: String) = CookieBasedApiRequest { ctx =>
|
||||
|
|
|
@ -42,13 +42,12 @@ object Auth extends LilaController {
|
|||
}
|
||||
|
||||
def authenticateUser(u: UserModel, result: Option[String => Result] = None)(implicit ctx: Context): Fu[Result] = {
|
||||
implicit val req = ctx.req
|
||||
if (u.ipBan) fuccess(Redirect(routes.Lobby.home))
|
||||
else api.saveAuthentication(u.id, ctx.mobileApiVersion) flatMap { sessionId =>
|
||||
negotiate(
|
||||
html = fuccess {
|
||||
val redirectTo = get("referrer").filter(goodReferrer) orElse
|
||||
req.session.get(api.AccessUri) getOrElse
|
||||
ctxReq.session.get(api.AccessUri) getOrElse
|
||||
routes.Lobby.home.url
|
||||
result.fold(Redirect(redirectTo))(_(redirectTo))
|
||||
},
|
||||
|
@ -118,21 +117,19 @@ object Auth extends LilaController {
|
|||
}
|
||||
|
||||
def logout = Open { implicit ctx =>
|
||||
implicit val req = ctx.req
|
||||
req.session get "sessionId" foreach lila.security.Store.delete
|
||||
ctxReq.session get "sessionId" foreach lila.security.Store.delete
|
||||
negotiate(
|
||||
html = Redirect(routes.Main.mobile).fuccess,
|
||||
html = Redirect(routes.Auth.login).fuccess,
|
||||
api = _ => Ok(Json.obj("ok" -> true)).fuccess
|
||||
) map (_ withCookies LilaCookie.newSession)
|
||||
}
|
||||
|
||||
// mobile app BC logout with GET
|
||||
def logoutGet = Open { implicit ctx =>
|
||||
implicit val req = ctx.req
|
||||
negotiate(
|
||||
html = notFound,
|
||||
api = _ => {
|
||||
req.session get "sessionId" foreach lila.security.Store.delete
|
||||
ctxReq.session get "sessionId" foreach lila.security.Store.delete
|
||||
Ok(Json.obj("ok" -> true)).withCookies(LilaCookie.newSession).fuccess
|
||||
}
|
||||
)
|
||||
|
@ -248,7 +245,7 @@ object Auth extends LilaController {
|
|||
|
||||
private def welcome(user: UserModel, email: EmailAddress)(implicit ctx: Context) = {
|
||||
garbageCollect(user, email)
|
||||
env.welcomeEmail(user, email)
|
||||
env.automaticEmail.welcome(user, email)
|
||||
}
|
||||
|
||||
private def garbageCollect(user: UserModel, email: EmailAddress)(implicit ctx: Context) =
|
||||
|
@ -317,7 +314,6 @@ object Auth extends LilaController {
|
|||
}
|
||||
|
||||
private def redirectNewUser(user: UserModel)(implicit ctx: Context) = {
|
||||
implicit val req = ctx.req
|
||||
api.saveAuthentication(user.id, ctx.mobileApiVersion) flatMap { sessionId =>
|
||||
negotiate(
|
||||
html = Redirect(routes.User.show(user.username)).fuccess,
|
||||
|
@ -344,7 +340,7 @@ object Auth extends LilaController {
|
|||
|
||||
def passwordReset = Open { implicit ctx =>
|
||||
forms.passwordResetWithCaptcha map {
|
||||
case (form, captcha) => Ok(html.auth.passwordReset(form, captcha))
|
||||
case (form, captcha) => Ok(html.auth.bits.passwordReset(form, captcha))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,7 +348,7 @@ object Auth extends LilaController {
|
|||
implicit val req = ctx.body
|
||||
forms.passwordReset.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha map { captcha =>
|
||||
BadRequest(html.auth.passwordReset(err, captcha, false.some))
|
||||
BadRequest(html.auth.bits.passwordReset(err, captcha, false.some))
|
||||
},
|
||||
data => {
|
||||
UserRepo.enabledWithEmail(data.realEmail.normalize) flatMap {
|
||||
|
@ -363,7 +359,7 @@ object Auth extends LilaController {
|
|||
case _ => {
|
||||
lila.mon.user.auth.passwordResetRequest("no_email")()
|
||||
forms.passwordResetWithCaptcha map {
|
||||
case (form, captcha) => BadRequest(html.auth.passwordReset(form, captcha, false.some))
|
||||
case (form, captcha) => BadRequest(html.auth.bits.passwordReset(form, captcha, false.some))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,7 +369,7 @@ object Auth extends LilaController {
|
|||
|
||||
def passwordResetSent(email: String) = Open { implicit ctx =>
|
||||
fuccess {
|
||||
Ok(html.auth.passwordResetSent(email))
|
||||
Ok(html.auth.bits.passwordResetSent(email))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,7 +382,7 @@ object Auth extends LilaController {
|
|||
case Some(user) => {
|
||||
authLog(user.username, "Reset password")
|
||||
lila.mon.user.auth.passwordResetConfirm("token_ok")()
|
||||
fuccess(html.auth.passwordResetConfirm(user, token, forms.passwdReset, none))
|
||||
fuccess(html.auth.bits.passwordResetConfirm(user, token, forms.passwdReset, none))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +396,7 @@ object Auth extends LilaController {
|
|||
case Some(user) =>
|
||||
implicit val req = ctx.body
|
||||
FormFuResult(forms.passwdReset) { err =>
|
||||
fuccess(html.auth.passwordResetConfirm(user, token, err, false.some))
|
||||
fuccess(html.auth.bits.passwordResetConfirm(user, token, err, false.some))
|
||||
} { data =>
|
||||
HasherRateLimit(user.username, ctx.req) { _ =>
|
||||
Env.user.authenticator.setPassword(user.id, ClearPassword(data.newPasswd1)) >>
|
||||
|
|
|
@ -49,7 +49,7 @@ object Blog extends LilaController {
|
|||
blogApi context req flatMap { implicit prismic =>
|
||||
blogApi.recent(prismic.api, none, 1, lila.common.MaxPerPage(50)) map {
|
||||
_ ?? { docs =>
|
||||
Ok(views.xml.blog.atom(docs)) as XML
|
||||
Ok(views.html.blog.atom(docs)) as XML
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,13 +41,16 @@ object Challenge extends LilaController {
|
|||
else none
|
||||
val json = env.jsonView.show(c, version, direction)
|
||||
negotiate(
|
||||
html = fuccess {
|
||||
if (mine) error match {
|
||||
case Some(e) => BadRequest(html.challenge.mine.apply(c, json, e.some))
|
||||
case None => Ok(html.challenge.mine.apply(c, json, none))
|
||||
html =
|
||||
if (mine) fuccess {
|
||||
error match {
|
||||
case Some(e) => BadRequest(html.challenge.mine(c, json, e.some))
|
||||
case None => Ok(html.challenge.mine(c, json, none))
|
||||
}
|
||||
}
|
||||
else Ok(html.challenge.theirs.apply(c, json))
|
||||
},
|
||||
else (c.challengerUserId ?? UserRepo.named) map { user =>
|
||||
Ok(html.challenge.theirs(c, json, user))
|
||||
},
|
||||
api = _ => Ok(json).fuccess
|
||||
) flatMap withChallengeAnonCookie(mine && c.challengerIsAnon, c, true)
|
||||
}
|
||||
|
@ -90,7 +93,6 @@ object Challenge extends LilaController {
|
|||
cond ?? {
|
||||
GameRepo.game(c.id).map {
|
||||
_ map { game =>
|
||||
implicit val req = ctx.req
|
||||
LilaCookie.cookie(
|
||||
AnonCookie.name,
|
||||
game.player(if (owner) c.finalColor else !c.finalColor).id,
|
||||
|
|
|
@ -30,8 +30,7 @@ object Coordinate extends LilaController {
|
|||
err => fuccess(BadRequest),
|
||||
value => Env.pref.api.setPref(
|
||||
me,
|
||||
(p: lila.pref.Pref) => p.copy(coordColor = value),
|
||||
notifyChange = false
|
||||
(p: lila.pref.Pref) => p.copy(coordColor = value)
|
||||
) inject Ok(())
|
||||
)
|
||||
}
|
||||
|
|
|
@ -70,8 +70,7 @@ object Dasher extends LilaController {
|
|||
"image" -> ctx.pref.bgImgOrDefault
|
||||
),
|
||||
"board" -> Json.obj(
|
||||
"is3d" -> ctx.pref.is3d,
|
||||
"zoom" -> ctx.zoom
|
||||
"is3d" -> ctx.pref.is3d
|
||||
),
|
||||
"theme" -> Json.obj(
|
||||
"d2" -> Json.obj(
|
||||
|
|
|
@ -3,7 +3,6 @@ package controllers
|
|||
import chess.format.Forsyth
|
||||
import chess.Situation
|
||||
import play.api.libs.json._
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.app._
|
||||
import lila.game.GameRepo
|
||||
|
|
|
@ -7,12 +7,6 @@ object Event extends LilaController {
|
|||
|
||||
private def api = Env.event.api
|
||||
|
||||
def index = Open { implicit ctx =>
|
||||
api.recentEnabled.map { events =>
|
||||
Ok(html.event.index(events))
|
||||
}
|
||||
}
|
||||
|
||||
def show(id: String) = Open { implicit ctx =>
|
||||
OptionOk(api oneEnabled id) { event =>
|
||||
html.event.show(event)
|
||||
|
|
|
@ -46,7 +46,7 @@ object ForumTopic extends LilaController with ForumController {
|
|||
case (categ, topic, posts) => for {
|
||||
unsub <- ctx.userId ?? Env.timeline.status(s"forum:${topic.id}")
|
||||
canWrite <- isGrantedWrite(categSlug)
|
||||
form <- (!posts.hasNextPage && canWrite && topic.open) ?? forms.postWithCaptcha.map(_.some)
|
||||
form <- (!posts.hasNextPage && canWrite && topic.open && !topic.isOld) ?? forms.postWithCaptcha.map(_.some)
|
||||
canModCateg <- isGrantedMod(categ.slug)
|
||||
_ <- Env.user.lightUserApi preloadMany posts.currentPageResults.flatMap(_.userId)
|
||||
} yield html.forum.topic.show(categ, topic, posts, form, unsub, canModCateg = canModCateg)
|
||||
|
|
|
@ -6,8 +6,7 @@ import play.api.http._
|
|||
import play.api.libs.json.{ Json, JsObject, JsArray, JsString, Writes }
|
||||
import play.api.mvc._
|
||||
import play.api.mvc.BodyParsers.parse
|
||||
import play.twirl.api.Html
|
||||
import scalatags.Text.{ TypedTag, Frag }
|
||||
import scalatags.Text.Frag
|
||||
|
||||
import lila.api.{ PageData, Context, HeaderContext, BodyContext }
|
||||
import lila.app._
|
||||
|
@ -28,22 +27,11 @@ private[controllers] trait LilaController
|
|||
|
||||
protected implicit val LilaResultZero = Zero.instance[Result](Results.NotFound)
|
||||
|
||||
protected implicit val LilaHtmlMonoid = lila.app.templating.Environment.LilaHtmlMonoid
|
||||
|
||||
protected implicit final class LilaPimpedResult(result: Result) {
|
||||
def fuccess = scala.concurrent.Future successful result
|
||||
}
|
||||
|
||||
protected implicit def LilaHtmlToResult(content: Html): Result = Ok(content)
|
||||
|
||||
protected implicit def contentTypeOfFrag(implicit codec: Codec): ContentTypeOf[Frag] =
|
||||
ContentTypeOf[Frag](Some(ContentTypes.HTML))
|
||||
protected implicit def writeableOfFrag(implicit codec: Codec): Writeable[Frag] =
|
||||
Writeable(frag => codec.encode(frag.render))
|
||||
|
||||
protected implicit def LilaScalatagsToHtml(tags: scalatags.Text.TypedTag[String]): Html = Html(tags.render)
|
||||
|
||||
protected implicit def LilaFragToResult(content: Frag): Result = Ok(content)
|
||||
protected implicit def LilaFragToResult(frag: Frag): Result = Ok(frag)
|
||||
|
||||
protected implicit def makeApiVersion(v: Int) = ApiVersion(v)
|
||||
|
||||
|
@ -56,7 +44,9 @@ private[controllers] trait LilaController
|
|||
api = _ => fuccess(jsonOkResult)
|
||||
)
|
||||
|
||||
implicit def lang(implicit ctx: Context) = ctx.lang
|
||||
implicit def ctxLang(implicit ctx: Context) = ctx.lang
|
||||
implicit def ctxReq(implicit ctx: Context) = ctx.req
|
||||
implicit def reqConfig(implicit req: RequestHeader) = ui.EmbedConfig(req)
|
||||
|
||||
protected def NoCache(res: Result): Result = res.withHeaders(
|
||||
CACHE_CONTROL -> "no-cache, no-store, must-revalidate", EXPIRES -> "0"
|
||||
|
@ -213,7 +203,7 @@ private[controllers] trait LilaController
|
|||
|
||||
protected def NoTor(res: => Fu[Result])(implicit ctx: Context) =
|
||||
if (Env.security.tor isExitNode HTTPRequest.lastRemoteAddress(ctx.req))
|
||||
Unauthorized(views.html.auth.tor()).fuccess
|
||||
Unauthorized(views.html.auth.bits.tor()).fuccess
|
||||
else res
|
||||
|
||||
protected def NoEngine[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
|
||||
|
@ -347,8 +337,7 @@ private[controllers] trait LilaController
|
|||
protected def authenticationFailed(implicit ctx: Context): Fu[Result] =
|
||||
negotiate(
|
||||
html = fuccess {
|
||||
implicit val req = ctx.req
|
||||
Redirect(routes.Auth.signup) withCookies LilaCookie.session(Env.security.api.AccessUri, req.uri)
|
||||
Redirect(routes.Auth.signup) withCookies LilaCookie.session(Env.security.api.AccessUri, ctx.req.uri)
|
||||
},
|
||||
api = _ => ensureSessionId(ctx.req) {
|
||||
Unauthorized(jsonError("Login required"))
|
||||
|
@ -361,7 +350,7 @@ private[controllers] trait LilaController
|
|||
html =
|
||||
if (HTTPRequest isSynchronousHttp ctx.req) fuccess {
|
||||
lila.mon.http.response.code403()
|
||||
Forbidden(views.html.base.authFailed())
|
||||
Forbidden(views.html.site.message.authFailed)
|
||||
}
|
||||
else fuccess(Results.Forbidden("Authorization failed")),
|
||||
api = _ => fuccess(forbiddenJsonResult)
|
||||
|
@ -371,8 +360,8 @@ private[controllers] trait LilaController
|
|||
if (req.session.data.contains(LilaCookie.sessionId)) res
|
||||
else res withCookies LilaCookie.makeSessionId(req)
|
||||
|
||||
protected def negotiate(html: => Fu[Result], api: ApiVersion => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
lila.api.Mobile.Api.requestVersion(ctx.req).fold(html) { v =>
|
||||
protected def negotiate(html: => Fu[Result], api: ApiVersion => Fu[Result])(implicit req: RequestHeader): Fu[Result] =
|
||||
lila.api.Mobile.Api.requestVersion(req).fold(html) { v =>
|
||||
api(v) dmap (_ as JSON)
|
||||
}.dmap(_.withHeaders("Vary" -> "Accept"))
|
||||
|
||||
|
@ -430,7 +419,9 @@ private[controllers] trait LilaController
|
|||
}
|
||||
} dmap {
|
||||
case Some(d) if !lila.common.PlayApp.isProd =>
|
||||
Some(d.copy(user = d.user.addRole(lila.security.Permission.Beta.name)))
|
||||
d.copy(user = d.user
|
||||
.addRole(lila.security.Permission.Beta.name)
|
||||
.addRole(lila.security.Permission.Prismic.name)).some
|
||||
case d => d
|
||||
} flatMap {
|
||||
case None => fuccess(None -> None)
|
||||
|
|
|
@ -69,7 +69,7 @@ object Main extends LilaController {
|
|||
def mobile = Open { implicit ctx =>
|
||||
pageHit
|
||||
OptionOk(Prismic getBookmark "mobile-apk") {
|
||||
case (doc, resolver) => html.mobile.home(doc, resolver)
|
||||
case (doc, resolver) => html.mobile(doc, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,10 +135,6 @@ Disallow: /games/export
|
|||
}
|
||||
}
|
||||
|
||||
val freeJs = Open { implicit ctx =>
|
||||
Ok(html.site.freeJs(ctx)).fuccess
|
||||
}
|
||||
|
||||
def renderNotFound(req: RequestHeader): Fu[Result] =
|
||||
reqToCtx(req) map renderNotFound
|
||||
|
||||
|
@ -147,12 +143,8 @@ Disallow: /games/export
|
|||
NotFound(html.base.notFound()(ctx))
|
||||
}
|
||||
|
||||
def fpmenu = Open { implicit ctx =>
|
||||
Ok(html.base.fpmenu()).fuccess
|
||||
}
|
||||
|
||||
def getFishnet = Open { implicit ctx =>
|
||||
Ok(html.site.getFishnet()).fuccess
|
||||
Ok(html.site.bits.getFishnet()).fuccess
|
||||
}
|
||||
|
||||
def costs = Open { implicit ctx =>
|
||||
|
@ -181,6 +173,7 @@ Disallow: /games/export
|
|||
case 87 => routes.Stat.ratingDistribution("blitz").url
|
||||
case 110 => s"$faq#name"
|
||||
case 29 => s"$faq#titles"
|
||||
case 4811 => s"$faq#lm"
|
||||
case 216 => routes.Main.mobile.url
|
||||
case 340 => s"$faq#trophies"
|
||||
case 6 => s"$faq#ratings"
|
||||
|
|
|
@ -3,8 +3,8 @@ package controllers
|
|||
import play.api.data.Form
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc.Result
|
||||
import play.twirl.api.Html
|
||||
import scala.concurrent.duration._
|
||||
import scalatags.Text.Frag
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
|
@ -47,7 +47,7 @@ object Message extends LilaController {
|
|||
negotiate(
|
||||
html = OptionFuOk(api.thread(id, me)) { thread =>
|
||||
relationApi.fetchBlocks(thread otherUserId me, me.id) map { blocked =>
|
||||
val form = !thread.isTooBig option forms.post
|
||||
val form = thread.isReplyable option forms.post
|
||||
html.message.thread(thread, form, blocked)
|
||||
}
|
||||
} map NoCache,
|
||||
|
@ -131,7 +131,7 @@ object Message extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private def renderForm(me: UserModel, title: Option[String], f: Form[_] => Form[_])(implicit ctx: Context): Fu[Html] =
|
||||
private def renderForm(me: UserModel, title: Option[String], f: Form[_] => Form[_])(implicit ctx: Context): Fu[Frag] =
|
||||
get("user") ?? UserRepo.named flatMap { user =>
|
||||
user.fold(fuTrue)(u => security.canMessage(me.id, u.id)) map { canMessage =>
|
||||
html.message.form(
|
||||
|
|
|
@ -7,6 +7,7 @@ import lila.common.{ IpAddress, EmailAddress, HTTPRequest }
|
|||
import lila.report.{ Suspect, Mod => AsMod, SuspectId }
|
||||
import lila.security.Permission
|
||||
import lila.user.{ UserRepo, User => UserModel, Title }
|
||||
import lila.mod.UserSearch
|
||||
import ornicar.scalalib.Zero
|
||||
import views._
|
||||
|
||||
|
@ -236,13 +237,18 @@ object Mod extends LilaController {
|
|||
|
||||
def search = SecureBody(_.UserSearch) { implicit ctx => me =>
|
||||
implicit def req = ctx.body
|
||||
val f = lila.mod.UserSearch.form
|
||||
val f = UserSearch.form
|
||||
f.bindFromRequest.fold(
|
||||
err => BadRequest(html.mod.search(err, Nil)).fuccess,
|
||||
query => Env.mod.search(query) map { html.mod.search(f.fill(query), _) }
|
||||
)
|
||||
}
|
||||
|
||||
protected[controllers] def searchTerm(q: String)(implicit ctx: Context) = {
|
||||
val query = UserSearch exact q
|
||||
Env.mod.search(query) map { users => Ok(html.mod.search(UserSearch.form fill query, users)) }
|
||||
}
|
||||
|
||||
def chatUser(username: String) = Secure(_.ChatTimeout) { implicit ctx => me =>
|
||||
implicit val lightUser = Env.user.lightUserSync _
|
||||
JsonOptionOk {
|
||||
|
@ -267,7 +273,7 @@ object Mod extends LilaController {
|
|||
)).bindFromRequest.fold(
|
||||
err => BadRequest(html.mod.permissions(user)).fuccess,
|
||||
permissions =>
|
||||
modApi.setPermissions(me.id, user.username, Permission(permissions)) >> {
|
||||
modApi.setPermissions(AsMod(me), user.username, Permission(permissions)) >> {
|
||||
(Permission(permissions) diff Permission(user.roles) contains Permission.Coach) ??
|
||||
Env.security.automaticEmail.onBecomeCoach(user)
|
||||
} >> {
|
||||
|
@ -285,7 +291,7 @@ object Mod extends LilaController {
|
|||
val email = query.headOption.map(EmailAddress.apply) flatMap Env.security.emailAddressValidator.validate
|
||||
val username = query lift 1
|
||||
def tryWith(setEmail: EmailAddress, q: String): Fu[Option[Result]] =
|
||||
Env.mod.search(lila.mod.UserSearch.exact(q)) flatMap {
|
||||
Env.mod.search(UserSearch.exact(q)) flatMap {
|
||||
case List(UserModel.WithEmails(user, _)) => (!user.everLoggedIn).?? {
|
||||
lila.mon.user.register.modConfirmEmail()
|
||||
modApi.setEmail(me.id, user.id, setEmail)
|
||||
|
|
|
@ -19,13 +19,13 @@ object OAuthApp extends LilaController {
|
|||
}
|
||||
|
||||
def create = Auth { implicit ctx => me =>
|
||||
Ok(html.oAuth.app.create(env.forms.app.create)).fuccess
|
||||
Ok(html.oAuth.app.form.create(env.forms.app.create)).fuccess
|
||||
}
|
||||
|
||||
def createApply = AuthBody { implicit ctx => me =>
|
||||
implicit val req = ctx.body
|
||||
env.forms.app.create.bindFromRequest.fold(
|
||||
err => BadRequest(html.oAuth.app.create(err)).fuccess,
|
||||
err => BadRequest(html.oAuth.app.form.create(err)).fuccess,
|
||||
setup => {
|
||||
val app = setup make me
|
||||
env.appApi.create(app) inject Redirect(routes.OAuthApp.edit(app.clientId.value))
|
||||
|
@ -35,7 +35,7 @@ object OAuthApp extends LilaController {
|
|||
|
||||
def edit(id: String) = Auth { implicit ctx => me =>
|
||||
OptionFuResult(env.appApi.findBy(App.Id(id), me)) { app =>
|
||||
Ok(html.oAuth.app.edit(app, env.forms.app.edit(app))).fuccess
|
||||
Ok(html.oAuth.app.form.edit(app, env.forms.app.edit(app))).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ object OAuthApp extends LilaController {
|
|||
OptionFuResult(env.appApi.findBy(App.Id(id), me)) { app =>
|
||||
implicit val req = ctx.body
|
||||
env.forms.app.edit(app).bindFromRequest.fold(
|
||||
err => BadRequest(html.oAuth.app.edit(app, err)).fuccess,
|
||||
err => BadRequest(html.oAuth.app.form.edit(app, err)).fuccess,
|
||||
data => env.appApi.update(app) { data.update(_) } map { r => Redirect(routes.OAuthApp.edit(app.clientId.value)) }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,13 @@ object Page extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def source = Open { implicit ctx =>
|
||||
pageHit
|
||||
OptionOk(Prismic getBookmark "source") {
|
||||
case (doc, resolver) => views.html.site.help.source(doc, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
def swag = Open { implicit ctx =>
|
||||
pageHit
|
||||
OptionOk(Prismic getBookmark "swag") {
|
||||
|
|
|
@ -31,8 +31,7 @@ object Pref extends LilaController {
|
|||
}
|
||||
|
||||
def formApply = AuthBody { implicit ctx => me =>
|
||||
def onSuccess(data: lila.pref.DataForm.PrefData) =
|
||||
api.setPref(data(ctx.pref), notifyChange = true) inject Ok("saved")
|
||||
def onSuccess(data: lila.pref.DataForm.PrefData) = api.setPref(data(ctx.pref)) inject Ok("saved")
|
||||
implicit val req = ctx.body
|
||||
forms.pref.bindFromRequest.fold(
|
||||
err => forms.pref.bindFromRequest(lila.pref.FormCompatLayer(ctx.body)).fold(
|
||||
|
@ -43,16 +42,16 @@ object Pref extends LilaController {
|
|||
)
|
||||
}
|
||||
|
||||
def setZoom = Action { implicit req =>
|
||||
val zoom = getInt("v", req) | 100
|
||||
Ok(()).withCookies(LilaCookie.session("zoom", zoom.toString))
|
||||
}
|
||||
|
||||
def set(name: String) = OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
(setters get name) ?? {
|
||||
case (form, fn) => FormResult(form) { v =>
|
||||
fn(v, ctx) map { cookie => Ok(()).withCookies(cookie) }
|
||||
if (name == "zoom") {
|
||||
Ok.withCookies(LilaCookie.session("zoom", (getInt("v") | 180).toString)).fuccess
|
||||
} else {
|
||||
implicit val req = ctx.body
|
||||
(setters get name) ?? {
|
||||
case (form, fn) => FormResult(form) { v =>
|
||||
fn(v, ctx) map { cookie => Ok(()).withCookies(cookie) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +74,6 @@ object Pref extends LilaController {
|
|||
|
||||
private def save(name: String)(value: String, ctx: Context): Fu[Cookie] =
|
||||
ctx.me ?? {
|
||||
api.setPrefString(_, name, value, notifyChange = false)
|
||||
api.setPrefString(_, name, value)
|
||||
} inject LilaCookie.session(name, value)(ctx.req)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import play.api.mvc._
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.{ HTTPRequest, IpAddress, MaxPerSecond }
|
||||
import lila.game.PgnDump
|
||||
import lila.puzzle.{ PuzzleId, Result, Puzzle => PuzzleModel, UserInfos }
|
||||
import lila.user.UserRepo
|
||||
|
@ -215,18 +216,37 @@ object Puzzle extends LilaController {
|
|||
)
|
||||
}
|
||||
|
||||
/* For BC */
|
||||
def embed = Action { req =>
|
||||
Ok {
|
||||
val bg = get("bg", req) | "light"
|
||||
val theme = get("theme", req) | "brown"
|
||||
val url = s"""${req.domain + routes.Puzzle.frame}?bg=$bg&theme=$theme"""
|
||||
s"""document.write("<iframe src='https://$url&embed=" + document.domain + "' class='lichess-training-iframe' allowtransparency='true' frameBorder='0' style='width: 224px; height: 264px;' title='Lichess free online chess'></iframe>");"""
|
||||
s"""document.write("<iframe src='https://$url&embed=" + document.domain + "' class='lichess-training-iframe' allowtransparency='true' frameborder='0' style='width: 224px; height: 264px;' title='Lichess free online chess'></iframe>");"""
|
||||
} as JAVASCRIPT withHeaders (CACHE_CONTROL -> "max-age=86400")
|
||||
}
|
||||
|
||||
def frame = Open { implicit ctx =>
|
||||
OptionOk(env.daily.get) { daily =>
|
||||
html.puzzle.embed(daily)
|
||||
def frame = Action.async { implicit req =>
|
||||
env.daily.get map {
|
||||
case None => NotFound
|
||||
case Some(daily) => html.puzzle.embed(daily)
|
||||
}
|
||||
}
|
||||
|
||||
def activity = Scoped(_.Puzzle.Read) { req => me =>
|
||||
Api.GlobalLinearLimitPerIP(HTTPRequest lastRemoteAddress req) {
|
||||
Api.GlobalLinearLimitPerUserOption(me.some) {
|
||||
val config = lila.puzzle.PuzzleActivity.Config(
|
||||
user = me,
|
||||
max = getInt("max", req) map (_ atLeast 1),
|
||||
perSecond = MaxPerSecond(20)
|
||||
)
|
||||
Ok.chunked(env.activity.stream(config)).withHeaders(
|
||||
noProxyBufferHeader,
|
||||
CONTENT_TYPE -> ndJsonContentType
|
||||
).fuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,7 +35,13 @@ object Relation extends LilaController {
|
|||
}
|
||||
|
||||
def follow(userId: String) = Auth { implicit ctx => me =>
|
||||
env.api.follow(me.id, UserModel normalize userId).nevermind >> renderActions(userId, getBool("mini"))
|
||||
env.api.reachedMaxFollowing(me.id) flatMap {
|
||||
case true => Env.message.api.sendPresetFromLichess(
|
||||
me,
|
||||
lila.message.ModPreset.maxFollow(me.username, Env.relation.MaxFollow)
|
||||
).void
|
||||
case _ => env.api.follow(me.id, UserModel normalize userId).nevermind >> renderActions(userId, getBool("mini"))
|
||||
}
|
||||
}
|
||||
|
||||
def unfollow(userId: String) = Auth { implicit ctx => me =>
|
||||
|
@ -56,7 +62,7 @@ object Relation extends LilaController {
|
|||
RelatedPager(env.api.followingPaginatorAdapter(user.id), page) flatMap { pag =>
|
||||
negotiate(
|
||||
html = env.api countFollowers user.id map { nbFollowers =>
|
||||
Ok(html.relation.following(user, pag, nbFollowers))
|
||||
Ok(html.relation.bits.following(user, pag, nbFollowers))
|
||||
},
|
||||
api = _ => Ok(jsonRelatedPaginator(pag)).fuccess
|
||||
)
|
||||
|
@ -71,7 +77,7 @@ object Relation extends LilaController {
|
|||
RelatedPager(env.api.followersPaginatorAdapter(user.id), page) flatMap { pag =>
|
||||
negotiate(
|
||||
html = env.api countFollowing user.id map { nbFollowing =>
|
||||
Ok(html.relation.followers(user, pag, nbFollowing))
|
||||
Ok(html.relation.bits.followers(user, pag, nbFollowing))
|
||||
},
|
||||
api = _ => Ok(jsonRelatedPaginator(pag)).fuccess
|
||||
)
|
||||
|
@ -112,7 +118,7 @@ object Relation extends LilaController {
|
|||
def blocks(page: Int) = Auth { implicit ctx => me =>
|
||||
Reasonable(page, 20) {
|
||||
RelatedPager(env.api.blockingPaginatorAdapter(me.id), page) map { pag =>
|
||||
html.relation.blocks(me, pag)
|
||||
html.relation.bits.blocks(me, pag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,14 @@ object Relay extends LilaController {
|
|||
|
||||
def form = Auth { implicit ctx => me =>
|
||||
NoLame {
|
||||
Ok(html.relay.create(env.forms.create)).fuccess
|
||||
Ok(html.relay.form.create(env.forms.create)).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
def create = AuthBody { implicit ctx => me =>
|
||||
implicit val req = ctx.body
|
||||
env.forms.create.bindFromRequest.fold(
|
||||
err => BadRequest(html.relay.create(err)).fuccess,
|
||||
err => BadRequest(html.relay.form.create(err)).fuccess,
|
||||
setup => env.api.create(setup, me) map { relay =>
|
||||
Redirect(showRoute(relay))
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ object Relay extends LilaController {
|
|||
|
||||
def edit(slug: String, id: String) = Auth { implicit ctx => me =>
|
||||
OptionFuResult(env.api.byIdAndContributor(id, me)) { relay =>
|
||||
Ok(html.relay.edit(relay, env.forms.edit(relay))).fuccess
|
||||
Ok(html.relay.form.edit(relay, env.forms.edit(relay))).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ object Relay extends LilaController {
|
|||
OptionFuResult(env.api.byIdAndContributor(id, me)) { relay =>
|
||||
implicit val req = ctx.body
|
||||
env.forms.edit(relay).bindFromRequest.fold(
|
||||
err => BadRequest(html.relay.edit(relay, err)).fuccess,
|
||||
err => BadRequest(html.relay.form.edit(relay, err)).fuccess,
|
||||
data => env.api.update(relay) { data.update(_, me) } map { r =>
|
||||
Redirect(showRoute(r))
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ object Round extends LilaController with TheftPrevention {
|
|||
simul foreach Env.simul.api.onPlayerConnection(pov.game, ctx.me)
|
||||
Ok(html.round.player(pov, data,
|
||||
tour = tour,
|
||||
simul = simul.filter(_ isHost ctx.me),
|
||||
simul = simul,
|
||||
cross = crosstable,
|
||||
playing = playing,
|
||||
chatOption = chatOption,
|
||||
|
@ -192,7 +192,7 @@ object Round extends LilaController with TheftPrevention {
|
|||
else for { // web crawlers don't need the full thing
|
||||
initialFen <- GameRepo.initialFen(pov.gameId)
|
||||
pgn <- Env.api.pgnDump(pov.game, initialFen, none, PgnDump.WithFlags(clocks = false))
|
||||
} yield Ok(html.round.watcherBot(pov, initialFen, pgn))
|
||||
} yield Ok(html.round.watcher.crawler(pov, initialFen, pgn))
|
||||
}.mon(_.http.response.watcher.website),
|
||||
api = apiVersion => for {
|
||||
data <- Env.api.roundApi.watcher(pov, apiVersion, tv = none)
|
||||
|
@ -210,7 +210,7 @@ object Round extends LilaController with TheftPrevention {
|
|||
tourId ?? { Env.tournament.api.miniView(_, withTop) }
|
||||
|
||||
private[controllers] def getWatcherChat(game: GameModel)(implicit ctx: Context): Fu[Option[lila.chat.UserChat.Mine]] = {
|
||||
ctx.noKid && ctx.me.exists(Env.chat.panic.allowed) && {
|
||||
ctx.noKid && ctx.me.fold(true)(Env.chat.panic.allowed) && {
|
||||
game.finishedOrAborted || !ctx.userId.exists(game.userIds.contains)
|
||||
}
|
||||
} ?? {
|
||||
|
@ -222,7 +222,7 @@ object Round extends LilaController with TheftPrevention {
|
|||
private[controllers] def getPlayerChat(game: GameModel, tour: Option[Tour])(implicit ctx: Context): Fu[Option[Chat.GameOrEvent]] = ctx.noKid ?? {
|
||||
(game.tournamentId, game.simulId) match {
|
||||
case (Some(tid), _) => {
|
||||
ctx.isAuth && tour.fold(true)(Tournament.canHaveChat)
|
||||
ctx.isAuth && tour.fold(true)(Tournament.canHaveChat(_, none))
|
||||
} ??
|
||||
Env.chat.api.userChat.cached.findMine(Chat.Id(tid), ctx.me).map { chat =>
|
||||
Chat.GameOrEvent(Right(chat truncate 50)).some
|
||||
|
@ -294,14 +294,10 @@ object Round extends LilaController with TheftPrevention {
|
|||
}
|
||||
|
||||
def mini(gameId: String, color: String) = Open { implicit ctx =>
|
||||
OptionOk(GameRepo.pov(gameId, color)) { pov =>
|
||||
html.game.bits.mini(pov)
|
||||
}
|
||||
OptionOk(GameRepo.pov(gameId, color))(html.game.bits.mini)
|
||||
}
|
||||
|
||||
def miniFullId(fullId: String) = Open { implicit ctx =>
|
||||
OptionOk(GameRepo pov fullId) { pov =>
|
||||
html.game.bits.mini(pov)
|
||||
}
|
||||
OptionOk(GameRepo pov fullId)(html.game.bits.mini)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -218,7 +218,6 @@ object Setup extends LilaController with TheftPrevention {
|
|||
}
|
||||
|
||||
private[controllers] def redirectPov(pov: Pov)(implicit ctx: Context) = {
|
||||
implicit val req = ctx.req
|
||||
val redir = Redirect(routes.Round.watcher(pov.gameId, "white"))
|
||||
if (ctx.isAuth) redir
|
||||
else redir withCookies LilaCookie.cookie(
|
||||
|
|
|
@ -5,9 +5,9 @@ import play.api.mvc._
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.chat.Chat
|
||||
import lila.common.HTTPRequest
|
||||
import lila.simul.{ Simul => Sim }
|
||||
import lila.chat.Chat
|
||||
import views._
|
||||
|
||||
object Simul extends LilaController {
|
||||
|
@ -48,36 +48,50 @@ object Simul extends LilaController {
|
|||
} map NoCache
|
||||
}
|
||||
|
||||
private[controllers] def canHaveChat(implicit ctx: Context): Boolean = ctx.me ?? { u =>
|
||||
if (ctx.kid) false
|
||||
else Env.chat.panic allowed u
|
||||
}
|
||||
private[controllers] def canHaveChat(implicit ctx: Context): Boolean =
|
||||
!ctx.kid && // no public chats for kids
|
||||
ctx.me.fold(true) { // anon can see public chats
|
||||
Env.chat.panic.allowed
|
||||
}
|
||||
|
||||
def start(simulId: String) = Open { implicit ctx =>
|
||||
AsHost(simulId) { simul =>
|
||||
env.api start simul.id
|
||||
Ok(Json.obj("ok" -> true)) as JSON
|
||||
jsonOkResult
|
||||
}
|
||||
}
|
||||
|
||||
def abort(simulId: String) = Open { implicit ctx =>
|
||||
AsHost(simulId) { simul =>
|
||||
env.api abort simul.id
|
||||
Ok(Json.obj("ok" -> true)) as JSON
|
||||
jsonOkResult
|
||||
}
|
||||
}
|
||||
|
||||
def accept(simulId: String, userId: String) = Open { implicit ctx =>
|
||||
AsHost(simulId) { simul =>
|
||||
env.api.accept(simul.id, userId, true)
|
||||
Ok(Json.obj("ok" -> true)) as JSON
|
||||
jsonOkResult
|
||||
}
|
||||
}
|
||||
|
||||
def reject(simulId: String, userId: String) = Open { implicit ctx =>
|
||||
AsHost(simulId) { simul =>
|
||||
env.api.accept(simul.id, userId, false)
|
||||
Ok(Json.obj("ok" -> true)) as JSON
|
||||
jsonOkResult
|
||||
}
|
||||
}
|
||||
|
||||
def setText(simulId: String) = OpenBody { implicit ctx =>
|
||||
AsHost(simulId) { simul =>
|
||||
implicit val req = ctx.body
|
||||
env.forms.setText.bindFromRequest.fold(
|
||||
err => BadRequest,
|
||||
text => {
|
||||
env.api.setText(simul.id, text)
|
||||
jsonOkResult
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ object Streamer extends LilaController {
|
|||
def create = AuthBody { implicit ctx => me =>
|
||||
NoLame {
|
||||
NoShadowban {
|
||||
api.find(me) flatMap {
|
||||
api find me flatMap {
|
||||
case None => api.create(me) inject Redirect(routes.Streamer.edit)
|
||||
case _ => Redirect(routes.Streamer.edit).fuccess
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ object Streamer extends LilaController {
|
|||
private def AsStreamer(f: StreamerModel.WithUser => Fu[Result])(implicit ctx: Context) =
|
||||
ctx.me.fold(notFound) { me =>
|
||||
api.find(get("u").ifTrue(isGranted(_.Streamers)) | me.id) flatMap {
|
||||
_.fold(Ok(html.streamer.create(me)).fuccess)(f)
|
||||
_.fold(Ok(html.streamer.bits.create(me)).fuccess)(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ object Study extends LilaController {
|
|||
}
|
||||
else Env.studySearch(ctx.me)(text, page) flatMap { pag =>
|
||||
negotiate(
|
||||
html = Ok(html.study.search(pag, text)).fuccess,
|
||||
html = Ok(html.study.list.search(pag, text)).fuccess,
|
||||
api = _ => apiStudies(pag)
|
||||
)
|
||||
}
|
||||
|
@ -209,10 +209,12 @@ object Study extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
private[controllers] def chatOf(study: lila.study.Study)(implicit ctx: Context) =
|
||||
(ctx.noKid && ctx.me.exists { me =>
|
||||
study.isMember(me.id) || Env.chat.panic.allowed(me)
|
||||
}) ?? Env.chat.api.userChat.findMine(Chat.Id(study.id.value), ctx.me).map(some)
|
||||
private[controllers] def chatOf(study: lila.study.Study)(implicit ctx: Context) = {
|
||||
!ctx.kid && // no public chats for kids
|
||||
ctx.me.fold(true) { // anon can see public chats
|
||||
Env.chat.panic.allowed
|
||||
}
|
||||
} ?? Env.chat.api.userChat.findMine(Chat.Id(study.id.value), ctx.me).map(some)
|
||||
|
||||
def websocket(id: String, apiVersion: Int) = SocketOption[JsValue] { implicit ctx =>
|
||||
get("sri") ?? { uid =>
|
||||
|
@ -278,17 +280,17 @@ object Study extends LilaController {
|
|||
)
|
||||
}
|
||||
|
||||
def embed(id: String, chapterId: String) = Open { implicit ctx =>
|
||||
env.api.byIdWithChapter(id, chapterId) flatMap {
|
||||
def embed(id: String, chapterId: String) = Action.async { implicit req =>
|
||||
env.api.byIdWithChapter(id, chapterId).map(_.filterNot(_.study.isPrivate)) flatMap {
|
||||
_.fold(embedNotFound) {
|
||||
case WithChapter(study, chapter) => CanViewResult(study) {
|
||||
case WithChapter(study, chapter) =>
|
||||
env.jsonView(study.copy(
|
||||
members = lila.study.StudyMembers(Map.empty) // don't need no members
|
||||
), List(chapter.metadata), chapter, ctx.me) flatMap { studyJson =>
|
||||
), List(chapter.metadata), chapter, none) flatMap { studyJson =>
|
||||
val setup = chapter.setup
|
||||
val initialFen = chapter.root.fen.some
|
||||
val pov = UserAnalysis.makePov(initialFen, setup.variant)
|
||||
val baseData = Env.round.jsonView.userAnalysisJson(pov, ctx.pref, initialFen, setup.orientation, owner = false, me = ctx.me)
|
||||
val baseData = Env.round.jsonView.userAnalysisJson(pov, lila.pref.Pref.default, initialFen, setup.orientation, owner = false, me = none)
|
||||
val analysis = baseData ++ Json.obj(
|
||||
"treeParts" -> partitionTreeJsonWriter.writes {
|
||||
lila.study.TreeBuilder.makeRoot(chapter.root, setup.variant)
|
||||
|
@ -303,13 +305,12 @@ object Study extends LilaController {
|
|||
api = _ => Ok(Json.obj("study" -> data.study, "analysis" -> data.analysis)).fuccess
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} map NoCache
|
||||
}
|
||||
|
||||
private def embedNotFound(implicit ctx: Context): Fu[Result] =
|
||||
fuccess(NotFound(html.study.embed.notFound()))
|
||||
private def embedNotFound(implicit req: RequestHeader): Fu[Result] =
|
||||
fuccess(NotFound(html.study.embed.notFound))
|
||||
|
||||
def cloneStudy(id: String) = Auth { implicit ctx => me =>
|
||||
OptionFuResult(env.api.byId(id)) { study =>
|
||||
|
|
|
@ -19,7 +19,7 @@ object Team extends LilaController {
|
|||
|
||||
def all(page: Int) = Open { implicit ctx =>
|
||||
NotForKids {
|
||||
paginator popularTeams page map { html.team.all(_) }
|
||||
paginator popularTeams page map { html.team.list.all(_) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,8 +40,8 @@ object Team extends LilaController {
|
|||
|
||||
def search(text: String, page: Int) = OpenBody { implicit ctx =>
|
||||
NotForKids {
|
||||
if (text.trim.isEmpty) paginator popularTeams page map { html.team.all(_) }
|
||||
else Env.teamSearch(text, page) map { html.team.search(text, _) }
|
||||
if (text.trim.isEmpty) paginator popularTeams page map { html.team.list.all(_) }
|
||||
else Env.teamSearch(text, page) map { html.team.list.search(text, _) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ object Team extends LilaController {
|
|||
|
||||
def edit(id: String) = Auth { implicit ctx => me =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) { fuccess(html.team.edit(team, forms edit team)) }
|
||||
Owner(team) { fuccess(html.team.form.edit(team, forms edit team)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ object Team extends LilaController {
|
|||
Owner(team) {
|
||||
implicit val req = ctx.body
|
||||
forms.edit(team).bindFromRequest.fold(
|
||||
err => BadRequest(html.team.edit(team, err)).fuccess,
|
||||
err => BadRequest(html.team.form.edit(team, err)).fuccess,
|
||||
data => api.update(team, data, me) inject Redirect(routes.Team.show(team.id))
|
||||
)
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ object Team extends LilaController {
|
|||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
MemberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.kick(team, userIds - me.id)
|
||||
html.team.admin.kick(team, userIds - me.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ object Team extends LilaController {
|
|||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
implicit val req = ctx.body
|
||||
forms.selectMember.bindFromRequest.value ?? { api.kick(team, _, me) } inject Redirect(routes.Team.show(team.id))
|
||||
forms.selectMember.bindFromRequest.value.pp ?? { api.kick(team, _, me) } inject Redirect(routes.Team.show(team.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ object Team extends LilaController {
|
|||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
MemberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.changeOwner(team, userIds - team.createdBy)
|
||||
html.team.admin.changeOwner(team, userIds - team.createdBy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ object Team extends LilaController {
|
|||
NotForKids {
|
||||
OnePerWeek(me) {
|
||||
forms.anyCaptcha map { captcha =>
|
||||
Ok(html.team.form(forms.create, captcha))
|
||||
Ok(html.team.form.create(forms.create, captcha))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ object Team extends LilaController {
|
|||
implicit val req = ctx.body
|
||||
forms.create.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha map { captcha =>
|
||||
BadRequest(html.team.form(err, captcha))
|
||||
BadRequest(html.team.form.create(err, captcha))
|
||||
},
|
||||
data => api.create(data, me) ?? {
|
||||
_ map { team => Redirect(routes.Team.show(team.id)): Result }
|
||||
|
@ -155,7 +155,7 @@ object Team extends LilaController {
|
|||
}
|
||||
|
||||
def mine = Auth { implicit ctx => me =>
|
||||
api mine me map { html.team.mine(_) }
|
||||
api mine me map { html.team.list.mine(_) }
|
||||
}
|
||||
|
||||
def join(id: String) = Auth { implicit ctx => implicit me =>
|
||||
|
@ -168,12 +168,12 @@ object Team extends LilaController {
|
|||
|
||||
def requests = Auth { implicit ctx => me =>
|
||||
Env.team.cached.nbRequests invalidate me.id
|
||||
api requestsWithUsers me map { html.team.allRequests(_) }
|
||||
api requestsWithUsers me map { html.team.request.all(_) }
|
||||
}
|
||||
|
||||
def requestForm(id: String) = Auth { implicit ctx => me =>
|
||||
OptionFuOk(api.requestable(id, me)) { team =>
|
||||
forms.anyCaptcha map { html.team.requestForm(team, forms.request, _) }
|
||||
forms.anyCaptcha map { html.team.request.requestForm(team, forms.request, _) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ object Team extends LilaController {
|
|||
implicit val req = ctx.body
|
||||
forms.request.bindFromRequest.fold(
|
||||
err => forms.anyCaptcha map { captcha =>
|
||||
BadRequest(html.team.requestForm(team, err, captcha))
|
||||
BadRequest(html.team.request.requestForm(team, err, captcha))
|
||||
},
|
||||
setup => api.createRequest(team, setup, me) inject Redirect(routes.Team.show(team.id))
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ object Tournament extends LilaController {
|
|||
private def env = Env.tournament
|
||||
private def repo = TournamentRepo
|
||||
|
||||
private def tournamentNotFound(implicit ctx: Context) = NotFound(html.tournament.notFound())
|
||||
private def tournamentNotFound(implicit ctx: Context) = NotFound(html.tournament.bits.notFound())
|
||||
|
||||
private[controllers] val upcomingCache = Env.memo.asyncCache.single[(VisibleTournaments, List[Tour])](
|
||||
name = "tournament.home",
|
||||
|
@ -64,7 +64,7 @@ object Tournament extends LilaController {
|
|||
case "arena" => System.Arena.some
|
||||
case _ => none
|
||||
}
|
||||
Ok(html.tournament.faqPage(system)).fuccess
|
||||
Ok(html.tournament.faq.page(system)).fuccess
|
||||
}
|
||||
|
||||
def leaderboard = Open { implicit ctx =>
|
||||
|
@ -74,11 +74,14 @@ object Tournament extends LilaController {
|
|||
} yield Ok(html.tournament.leaderboard(winners))
|
||||
}
|
||||
|
||||
private[controllers] def canHaveChat(tour: Tour)(implicit ctx: Context): Boolean = ctx.me ?? { u =>
|
||||
if (ctx.kid) false
|
||||
else if (tour.isPrivate) true
|
||||
else Env.chat.panic.allowed(u, tighter = tour.variant == chess.variant.Antichess)
|
||||
}
|
||||
private[controllers] def canHaveChat(tour: Tour, json: Option[JsObject])(implicit ctx: Context): Boolean =
|
||||
!ctx.kid && // no public chats for kids
|
||||
ctx.me.fold(!tour.isPrivate) { u => // anon can see public chats, except for private tournaments
|
||||
(!tour.isPrivate || json.fold(true)(jsonHasMe)) && // private tournament that I joined
|
||||
Env.chat.panic.allowed(u, tighter = tour.variant == chess.variant.Antichess)
|
||||
}
|
||||
|
||||
private def jsonHasMe(js: JsObject): Boolean = (js \ "me").toOption.isDefined
|
||||
|
||||
def show(id: String) = Open { implicit ctx =>
|
||||
val page = getInt("page")
|
||||
|
@ -88,8 +91,8 @@ object Tournament extends LilaController {
|
|||
(for {
|
||||
verdicts <- env.api.verdicts(tour, ctx.me, getUserTeamIds)
|
||||
version <- env.version(tour.id)
|
||||
chat <- canHaveChat(tour) ?? Env.chat.api.userChat.cached.findMine(Chat.Id(tour.id), ctx.me).map(some)
|
||||
json <- env.jsonView(tour, page, ctx.me, getUserTeamIds, none, version.some, partial = false, ctx.lang)
|
||||
chat <- canHaveChat(tour, json.some) ?? Env.chat.api.userChat.cached.findMine(Chat.Id(tour.id), ctx.me).map(some)
|
||||
_ <- chat ?? { c => Env.user.lightUserApi.preloadMany(c.chat.userIds) }
|
||||
streamers <- streamerCache get tour.id
|
||||
shieldOwner <- env.shieldApi currentOwner tour
|
||||
|
@ -284,11 +287,19 @@ object Tournament extends LilaController {
|
|||
|
||||
def shields = Open { implicit ctx =>
|
||||
for {
|
||||
history <- env.shieldApi.history
|
||||
history <- env.shieldApi.history(5.some)
|
||||
_ <- Env.user.lightUserApi preloadMany history.userIds
|
||||
} yield html.tournament.shields(history)
|
||||
}
|
||||
|
||||
def categShields(k: String) = Open { implicit ctx =>
|
||||
OptionFuOk(env.shieldApi.byCategKey(k)) {
|
||||
case (categ, awards) =>
|
||||
Env.user.lightUserApi preloadMany awards.map(_.owner.value) inject
|
||||
html.tournament.shields.byCateg(categ, awards)
|
||||
}
|
||||
}
|
||||
|
||||
def calendar = Open { implicit ctx =>
|
||||
env.api.calendar map { tours =>
|
||||
Ok(html.tournament.calendar(env.scheduleJsonView calendar tours))
|
||||
|
|
|
@ -15,16 +15,11 @@ object Tv extends LilaController {
|
|||
(lila.tv.Tv.Channel.byKey get chanKey).fold(notFound)(lichessTv)
|
||||
}
|
||||
|
||||
def sides(chanKey: String, gameId: String, color: String) = Open { implicit ctx =>
|
||||
lila.tv.Tv.Channel.byKey get chanKey match {
|
||||
case None => notFound
|
||||
case Some(channel) =>
|
||||
OptionFuResult(GameRepo.pov(gameId, color)) { pov =>
|
||||
Env.tv.tv.getChampions zip
|
||||
Env.game.crosstableApi.withMatchup(pov.game) map {
|
||||
case (champions, crosstable) => Ok(html.tv.side.sides(channel, champions, pov, crosstable))
|
||||
}
|
||||
}
|
||||
def sides(gameId: String, color: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo.pov(gameId, color)) { pov =>
|
||||
Env.game.crosstableApi.withMatchup(pov.game) map { ct =>
|
||||
Ok(html.tv.side.sides(pov, ct))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +57,7 @@ object Tv extends LilaController {
|
|||
|
||||
def gamesChannel(chanKey: String) = Open { implicit ctx =>
|
||||
(lila.tv.Tv.Channel.byKey get chanKey) ?? { channel =>
|
||||
Env.tv.tv.getChampions zip Env.tv.tv.getGames(channel, 9) map {
|
||||
Env.tv.tv.getChampions zip Env.tv.tv.getGames(channel, 12) map {
|
||||
case (champs, games) => NoCache {
|
||||
Ok(html.tv.games(channel, games map lila.game.Pov.first, champs))
|
||||
}
|
||||
|
@ -81,23 +76,19 @@ object Tv extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
/* for BC */
|
||||
def embed = Action { req =>
|
||||
Ok {
|
||||
val bg = get("bg", req) | "light"
|
||||
val theme = get("theme", req) | "brown"
|
||||
val url = s"""${req.domain + routes.Tv.frame}?bg=$bg&theme=$theme"""
|
||||
s"""document.write("<iframe src='https://$url&embed=" + document.domain + "' class='lichess-tv-iframe' allowtransparency='true' frameBorder='0' style='width: 224px; height: 264px;' title='Lichess free online chess'></iframe>");"""
|
||||
val config = ui.EmbedConfig(req)
|
||||
val url = s"""${req.domain + routes.Tv.frame}?bg=${config.bg}&theme=${config.board}"""
|
||||
s"""document.write("<iframe src='https://$url&embed=" + document.domain + "' class='lichess-tv-iframe' allowtransparency='true' frameborder='0' style='width: 224px; height: 264px;' title='Lichess free online chess'></iframe>");"""
|
||||
} as JAVASCRIPT withHeaders (CACHE_CONTROL -> "max-age=86400")
|
||||
}
|
||||
|
||||
def frame = Action.async { implicit req =>
|
||||
Env.tv.tv.getBestGame map {
|
||||
case None => NotFound
|
||||
case Some(game) => Ok(views.html.tv.embed(
|
||||
Pov first game,
|
||||
get("bg", req) | "light",
|
||||
lila.pref.Theme(~get("theme", req)).cssClass
|
||||
))
|
||||
case Some(game) => Ok(views.html.tv.embed(Pov first game))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import play.api.data.Form
|
|||
import play.api.libs.iteratee._
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc._
|
||||
import play.twirl.api.Html
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.api.{ Context, BodyContext }
|
||||
|
@ -68,7 +67,7 @@ object User extends LilaController {
|
|||
nbs ← Env.current.userNbGames(u, ctx)
|
||||
info ← Env.current.userInfo(u, nbs, ctx)
|
||||
social ← Env.current.socialInfo(u, ctx)
|
||||
} yield status(html.user.show.activity(u, as, info, social))
|
||||
} yield status(html.user.show.page.activity(u, as, info, social))
|
||||
}.mon(_.http.response.user.show.website)
|
||||
else Env.activity.read.recent(u) map { as =>
|
||||
status(html.activity(u, as))
|
||||
|
@ -98,7 +97,7 @@ object User extends LilaController {
|
|||
_ <- Env.team.cached.nameCache preloadMany info.teamIds
|
||||
social ← Env.current.socialInfo(u, ctx)
|
||||
searchForm = (filters.current == GameFilter.Search) option GameFilterMenu.searchForm(userGameSearch, filters.current)(ctx.body)
|
||||
} yield html.user.show.games(u, info, pag, filters, searchForm, social)
|
||||
} yield html.user.show.page.games(u, info, pag, filters, searchForm, social)
|
||||
else fuccess(html.user.show.gamesContent(u, nbs, pag, filters, filter))
|
||||
} yield res,
|
||||
api = _ => apiGames(u, filter, page)
|
||||
|
@ -108,12 +107,14 @@ object User extends LilaController {
|
|||
}
|
||||
|
||||
private def EnabledUser(username: String)(f: UserModel => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
OptionFuResult(UserRepo named username) { u =>
|
||||
if (u.enabled || isGranted(_.UserSpy)) f(u)
|
||||
else negotiate(
|
||||
UserRepo named username flatMap {
|
||||
case None if isGranted(_.UserSpy) => Mod.searchTerm(username.trim)
|
||||
case None => notFound
|
||||
case Some(u) if (u.enabled || isGranted(_.UserSpy)) => f(u)
|
||||
case Some(u) => negotiate(
|
||||
html = UserRepo isErased u flatMap { erased =>
|
||||
if (erased.value) notFound
|
||||
else NotFound(html.user.disabled(u)).fuccess
|
||||
else NotFound(html.user.show.page.disabled(u)).fuccess
|
||||
},
|
||||
api = _ => fuccess(NotFound(jsonError("No such user, or account closed")))
|
||||
)
|
||||
|
@ -145,12 +146,12 @@ object User extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def online = Open { implicit req =>
|
||||
def online = Action.async { implicit req =>
|
||||
val max = 50
|
||||
negotiate(
|
||||
html = notFound,
|
||||
html = notFoundJson(),
|
||||
api = _ => env.cached.getTop50Online map { list =>
|
||||
Ok(Json.toJson(list.take(getInt("nb").fold(10)(_ min max)).map(env.jsonView(_))))
|
||||
Ok(Json.toJson(list.take(getInt("nb", req).fold(10)(_ min max)).map(env.jsonView(_))))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -291,7 +292,7 @@ object User extends LilaController {
|
|||
}
|
||||
val irwin = Env.irwin.api.reports.withPovs(user) map {
|
||||
_ ?? { reps =>
|
||||
html.irwin.irwinReport(reps).some
|
||||
html.irwin.report(reps).some
|
||||
}
|
||||
}
|
||||
val assess = Env.mod.assessApi.getPlayerAggregateAssessmentWithGames(user.id) flatMap {
|
||||
|
@ -300,7 +301,7 @@ object User extends LilaController {
|
|||
}
|
||||
}
|
||||
import play.api.libs.EventSource
|
||||
implicit val extractor = EventSource.EventDataExtractor[Html](_.toString)
|
||||
implicit val extractor = EventSource.EventDataExtractor[scalatags.Text.Frag](_.render)
|
||||
Ok.chunked {
|
||||
(Enumerator(html.user.mod.menu(user)) interleave
|
||||
futureToEnumerator(parts.logTimeIfGt(s"$username parts", 2 seconds)) interleave
|
||||
|
|
|
@ -12,11 +12,11 @@ object UserTournament extends LilaController {
|
|||
path match {
|
||||
case "recent" =>
|
||||
Env.tournament.leaderboardApi.recentByUser(user, page).map { entries =>
|
||||
Ok(html.userTournament.recent(user, entries))
|
||||
Ok(html.userTournament.bits.recent(user, entries))
|
||||
}
|
||||
case "best" =>
|
||||
Env.tournament.leaderboardApi.bestByUser(user, page).map { entries =>
|
||||
Ok(html.userTournament.best(user, entries))
|
||||
Ok(html.userTournament.bits.best(user, entries))
|
||||
}
|
||||
case "chart" => Env.tournament.leaderboardApi.chart(user).map { data =>
|
||||
Ok(html.userTournament.chart(user, data))
|
||||
|
|
|
@ -41,7 +41,7 @@ object Video extends LilaController {
|
|||
def show(id: String) = Open { implicit ctx =>
|
||||
WithUserControl { control =>
|
||||
env.api.video.find(id) flatMap {
|
||||
case None => fuccess(NotFound(html.video.notFound(control)))
|
||||
case None => fuccess(NotFound(html.video.bits.notFound(control)))
|
||||
case Some(video) => env.api.video.similar(ctx.me, video, 9) zip
|
||||
ctx.userId.?? { userId =>
|
||||
env.api.view.add(View.make(videoId = video.id, userId = userId))
|
||||
|
@ -56,7 +56,7 @@ object Video extends LilaController {
|
|||
def author(author: String) = Open { implicit ctx =>
|
||||
WithUserControl { control =>
|
||||
env.api.video.byAuthor(ctx.me, author, getInt("page") | 1) map { videos =>
|
||||
Ok(html.video.author(author, videos, control))
|
||||
Ok(html.video.bits.author(author, videos, control))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ object Video extends LilaController {
|
|||
def tags = Open { implicit ctx =>
|
||||
WithUserControl { control =>
|
||||
env.api.tag.allPopular map { tags =>
|
||||
Ok(html.video.tags(tags, control))
|
||||
Ok(html.video.bits.tags(tags, control))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ final class Preload(
|
|||
lightUserApi: LightUserApi
|
||||
) {
|
||||
|
||||
private type Response = (JsObject, Vector[Entry], List[MiniForumPost], List[Tournament], List[Event], List[Simul], Option[Game], List[User.LightPerf], List[Winner], Option[lila.puzzle.DailyPuzzle], LiveStreams.WithTitles, List[lila.blog.MiniPost], Option[TempBan], Option[Preload.CurrentGame], Int)
|
||||
private type Response = (JsObject, Vector[Entry], List[MiniForumPost], List[Tournament], List[Event], List[Simul], Option[Game], List[User.LightPerf], List[Winner], Option[lila.puzzle.DailyPuzzle], LiveStreams.WithTitles, List[lila.blog.MiniPost], Option[TempBan], Option[Preload.CurrentGame], Int, List[Pov])
|
||||
|
||||
def apply(
|
||||
posts: Fu[List[MiniForumPost]],
|
||||
|
@ -46,16 +46,17 @@ final class Preload(
|
|||
leaderboard(()) zip
|
||||
tourneyWinners zip
|
||||
(ctx.noBot ?? dailyPuzzle()) zip
|
||||
liveStreams().dmap(_.autoFeatured.withTitles(lightUserApi)) zip
|
||||
(ctx.userId ?? getPlayban) flatMap {
|
||||
case (data, povs) ~ posts ~ tours ~ events ~ simuls ~ feat ~ entries ~ lead ~ tWinners ~ puzzle ~ streams ~ playban =>
|
||||
liveStreams().dmap(_.autoFeatured withTitles lightUserApi) zip
|
||||
(ctx.userId ?? getPlayban) zip
|
||||
(ctx.blind ?? ctx.me ?? GameRepo.urgentGames) flatMap {
|
||||
case (data, povs) ~ posts ~ tours ~ events ~ simuls ~ feat ~ entries ~ lead ~ tWinners ~ puzzle ~ streams ~ playban ~ blindGames =>
|
||||
val currentGame = ctx.me ?? Preload.currentGameMyTurn(povs, lightUserApi.sync) _
|
||||
lightUserApi.preloadMany {
|
||||
tWinners.map(_.userId) :::
|
||||
posts.flatMap(_.userId) :::
|
||||
entries.flatMap(_.userIds).toList
|
||||
} inject
|
||||
(data, entries, posts, tours, events, simuls, feat, lead, tWinners, puzzle, streams, Env.blog.lastPostCache.apply, playban, currentGame, countRounds())
|
||||
(data, entries, posts, tours, events, simuls, feat, lead, tWinners, puzzle, streams, Env.blog.lastPostCache.apply, playban, currentGame, countRounds(), blindGames)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
package lila
|
||||
|
||||
package object app extends PackageObject with socket.WithSocket
|
||||
import play.api.http._
|
||||
import play.api.mvc.Codec
|
||||
import scalatags.Text.Frag
|
||||
|
||||
package object app extends PackageObject with socket.WithSocket {
|
||||
|
||||
implicit def contentTypeOfFrag(implicit codec: Codec): ContentTypeOf[Frag] =
|
||||
ContentTypeOf[Frag](Some(ContentTypes.HTML))
|
||||
|
||||
implicit def writeableOfFrag(implicit codec: Codec): Writeable[Frag] =
|
||||
Writeable(frag => codec.encode(frag.render))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.user.UserContext
|
||||
|
||||
trait AiHelper { self: I18nHelper =>
|
||||
|
@ -15,8 +14,8 @@ trait AiHelper { self: I18nHelper =>
|
|||
s"$name$rating"
|
||||
}
|
||||
|
||||
def aiNameHtml(level: Int, withRating: Boolean = true)(implicit ctx: UserContext) =
|
||||
Html(aiName(level, withRating).replace(" ", " "))
|
||||
def aiNameFrag(level: Int, withRating: Boolean = true)(implicit ctx: UserContext) =
|
||||
raw(aiName(level, withRating).replace(" ", " "))
|
||||
|
||||
def aiRating(level: Int): Option[Int] = Env.fishnet.aiPerfApi.intRatings get level
|
||||
}
|
||||
|
|
|
@ -3,18 +3,21 @@ package templating
|
|||
|
||||
import controllers.routes
|
||||
import play.api.mvc.RequestHeader
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
import lila.common.{ AssetVersion, ContentSecurityPolicy }
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.{ Nonce, AssetVersion, ContentSecurityPolicy }
|
||||
|
||||
trait AssetHelper { self: I18nHelper with SecurityHelper =>
|
||||
|
||||
def isProd: Boolean
|
||||
|
||||
val siteDomain = lila.api.Env.current.Net.Domain
|
||||
val assetDomain = lila.api.Env.current.Net.AssetDomain
|
||||
val socketDomain = lila.api.Env.current.Net.SocketDomain
|
||||
|
||||
val sameAssetDomain = siteDomain == assetDomain
|
||||
|
||||
val assetBaseUrl = s"//$assetDomain"
|
||||
|
||||
def assetVersion = AssetVersion.current
|
||||
|
@ -26,75 +29,72 @@ trait AssetHelper { self: I18nHelper with SecurityHelper =>
|
|||
|
||||
def dbImageUrl(path: String) = s"$assetBaseUrl/image/$path"
|
||||
|
||||
def cssTag(name: String): Html = cssAt("stylesheets/" + name)
|
||||
def cssTag(name: String)(implicit ctx: Context): Frag =
|
||||
cssTagWithTheme(name, ctx.currentBg)
|
||||
|
||||
def cssTags(names: String*): Html = Html {
|
||||
names.map { name =>
|
||||
cssTag(name).body
|
||||
} mkString ""
|
||||
}
|
||||
def cssTags(names: List[(String, Boolean)]): Html =
|
||||
cssTags(names.collect { case (k, true) => k }: _*)
|
||||
def cssTagWithTheme(name: String, theme: String): Frag =
|
||||
cssAt(s"css/$name.$theme.${if (isProd) "min" else "dev"}.css")
|
||||
|
||||
def cssVendorTag(name: String) = cssAt("vendor/" + name)
|
||||
def cssTagNoTheme(name: String)(implicit ctx: Context): Frag =
|
||||
cssAt(s"css/$name.${if (isProd) "min" else "dev"}.css")
|
||||
|
||||
def cssAt(path: String): Html = Html {
|
||||
s"""<link href="${assetUrl(path)}" type="text/css" rel="stylesheet"/>"""
|
||||
}
|
||||
private def cssAt(path: String): Frag =
|
||||
link(href := assetUrl(path), tpe := "text/css", rel := "stylesheet")
|
||||
|
||||
def jsTag(name: String, async: Boolean = false) =
|
||||
jsAt("javascripts/" + name, async = async)
|
||||
def jsTag(name: String, defer: Boolean = false): Frag =
|
||||
jsAt("javascripts/" + name, defer = defer)
|
||||
|
||||
def jsAt(path: String, async: Boolean = false): Html = Html {
|
||||
val src = assetUrl(path)
|
||||
s"""<script${if (async) " async defer" else ""} src="$src"></script>"""
|
||||
}
|
||||
/* about async & defer, see https://flaviocopes.com/javascript-async-defer/
|
||||
* we want defer only, to ensure scripts are executed in order of declaration,
|
||||
* so that round.js doesn't run before site.js */
|
||||
def jsAt(path: String, defer: Boolean = false): Frag = script(
|
||||
defer option deferAttr,
|
||||
src := assetUrl(path)
|
||||
)
|
||||
|
||||
val jQueryTag = Html {
|
||||
val jQueryTag = raw {
|
||||
s"""<script src="${staticUrl("javascripts/vendor/jquery.min.js")}"></script>"""
|
||||
}
|
||||
|
||||
def roundTag = jsAt(s"compiled/lichess.round${isProd ?? (".min")}.js", async = true)
|
||||
def roundTag = jsAt(s"compiled/lichess.round${isProd ?? (".min")}.js", defer = true)
|
||||
def roundNvuiTag(implicit ctx: Context) = ctx.blind option
|
||||
jsAt(s"compiled/lichess.round.nvui.min.js", async = true)
|
||||
jsAt(s"compiled/lichess.round.nvui.min.js", defer = true)
|
||||
|
||||
def analyseTag = jsAt(s"compiled/lichess.analyse${isProd ?? (".min")}.js")
|
||||
def analyseNvuiTag(implicit ctx: Context) = ctx.blind option
|
||||
jsAt(s"compiled/lichess.analyse.nvui.min.js")
|
||||
|
||||
val highchartsLatestTag = Html {
|
||||
def captchaTag = jsAt(s"compiled/captcha.js")
|
||||
|
||||
val highchartsLatestTag = raw {
|
||||
s"""<script src="${staticUrl("vendor/highcharts-4.2.5/highcharts.js")}"></script>"""
|
||||
}
|
||||
|
||||
val highchartsMoreTag = Html {
|
||||
val highchartsMoreTag = raw {
|
||||
s"""<script src="${staticUrl("vendor/highcharts-4.2.5/highcharts-more.js")}"></script>"""
|
||||
}
|
||||
|
||||
val typeaheadTag = Html {
|
||||
s"""<script src="${staticUrl("javascripts/vendor/typeahead.bundle.min.js")}"></script>"""
|
||||
val fingerprintTag = raw {
|
||||
s"""<script async src="${staticUrl("javascripts/vendor/fp2.min.js")}"></script>"""
|
||||
}
|
||||
|
||||
val fingerprintTag = Html {
|
||||
s"""<script async defer src="${staticUrl("javascripts/vendor/fp2.min.js")}"></script>"""
|
||||
}
|
||||
|
||||
val flatpickrTag = Html {
|
||||
s"""<script async defer src="${staticUrl("javascripts/vendor/flatpickr.min.js")}"></script>"""
|
||||
}
|
||||
|
||||
val nonAsyncFlatpickrTag = Html {
|
||||
val flatpickrTag = raw {
|
||||
s"""<script defer src="${staticUrl("javascripts/vendor/flatpickr.min.js")}"></script>"""
|
||||
}
|
||||
|
||||
def delayFlatpickrStart(implicit ctx: Context) = embedJs {
|
||||
val nonAsyncFlatpickrTag = raw {
|
||||
s"""<script defer src="${staticUrl("javascripts/vendor/flatpickr.min.js")}"></script>"""
|
||||
}
|
||||
|
||||
def delayFlatpickrStart(implicit ctx: Context) = embedJsUnsafe {
|
||||
"""$(function() { setTimeout(function() { $(".flatpickr").flatpickr(); }, 2000) });"""
|
||||
}
|
||||
|
||||
val infiniteScrollTag = jsTag("vendor/jquery.infinitescroll.min.js")
|
||||
|
||||
def prismicJs(implicit ctx: Context) = Html {
|
||||
def prismicJs(implicit ctx: Context): Frag = raw {
|
||||
isGranted(_.Prismic) ?? {
|
||||
embedJsUnsafe("""window.prismic={endpoint:'https://lichess.prismic.io/api/v2'}""").body ++
|
||||
embedJsUnsafe("""window.prismic={endpoint:'https://lichess.prismic.io/api/v2'}""").render ++
|
||||
"""<script type="text/javascript" src="//static.cdn.prismic.io/prismic.min.js"></script>"""
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ trait AssetHelper { self: I18nHelper with SecurityHelper =>
|
|||
ContentSecurityPolicy(
|
||||
defaultSrc = List("'self'", assets),
|
||||
connectSrc = List("'self'", assets, socket, lila.api.Env.current.ExplorerEndpoint, lila.api.Env.current.TablebaseEndpoint),
|
||||
styleSrc = List("'self'", "'unsafe-inline'", assets, "https://fonts.googleapis.com"),
|
||||
styleSrc = List("'self'", "'unsafe-inline'", assets),
|
||||
fontSrc = List("'self'", assetDomain, "https://fonts.gstatic.com"),
|
||||
frameSrc = List("'self'", assets, "https://www.youtube.com"),
|
||||
workerSrc = List("'self'", assets),
|
||||
|
@ -120,15 +120,12 @@ trait AssetHelper { self: I18nHelper with SecurityHelper =>
|
|||
ctx.nonce.fold(csp)(csp.withNonce(_))
|
||||
}
|
||||
|
||||
def embedJsUnsafe(js: String)(implicit ctx: Context): Html = Html {
|
||||
val nonce = ctx.nonce ?? { nonce => s""" nonce="$nonce"""" }
|
||||
s"""<script$nonce>$js</script>"""
|
||||
}
|
||||
def embedJsUnsafe(js: scalatags.Text.RawFrag)(implicit ctx: Context): scalatags.Text.RawFrag = scalatags.Text.all.raw {
|
||||
def embedJsUnsafe(js: String)(implicit ctx: Context): Frag = raw {
|
||||
val nonce = ctx.nonce ?? { nonce => s""" nonce="$nonce"""" }
|
||||
s"""<script$nonce>$js</script>"""
|
||||
}
|
||||
|
||||
def embedJs(js: Html)(implicit ctx: Context): Html = embedJsUnsafe(js.body)
|
||||
def embedJs(js: String)(implicit ctx: Context): Html = embedJsUnsafe(js)
|
||||
def embedJsUnsafe(js: String, nonce: Nonce): Frag = raw {
|
||||
s"""<script nonce="$nonce">$js</script>"""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,32 +3,34 @@ package templating
|
|||
|
||||
import chess.{ Color, Board, Pos }
|
||||
import lila.api.Context
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.game.Pov
|
||||
|
||||
trait ChessgroundHelper {
|
||||
|
||||
def chessground(board: Board, orient: Color, lastMove: List[Pos] = Nil)(implicit ctx: Context): Html = wrap {
|
||||
if (ctx.pref.is3d) ""
|
||||
else {
|
||||
def top(p: Pos) = orient.fold(8 - p.y, p.y - 1) * 12.5
|
||||
def left(p: Pos) = orient.fold(p.x - 1, 8 - p.x) * 12.5
|
||||
val highlights = ctx.pref.highlight ?? lastMove.distinct.map { pos =>
|
||||
s"""<square class="last-move" style="top:${top(pos)}%;left:${left(pos)}%"></square>"""
|
||||
} mkString ""
|
||||
val pieces =
|
||||
if (ctx.pref.isBlindfold) ""
|
||||
else board.pieces.map {
|
||||
case (pos, piece) =>
|
||||
val klass = s"${piece.color.name} ${piece.role.name}"
|
||||
s"""<piece class="$klass" style="top:${top(pos)}%;left:${left(pos)}%"></piece>"""
|
||||
def chessground(board: Board, orient: Color, lastMove: List[Pos] = Nil)(implicit ctx: Context): Frag = wrap {
|
||||
raw {
|
||||
if (ctx.pref.is3d) ""
|
||||
else {
|
||||
def top(p: Pos) = orient.fold(8 - p.y, p.y - 1) * 12.5
|
||||
def left(p: Pos) = orient.fold(p.x - 1, 8 - p.x) * 12.5
|
||||
val highlights = ctx.pref.highlight ?? lastMove.distinct.map { pos =>
|
||||
s"""<square class="last-move" style="top:${top(pos)}%;left:${left(pos)}%"></square>"""
|
||||
} mkString ""
|
||||
s"$highlights$pieces"
|
||||
val pieces =
|
||||
if (ctx.pref.isBlindfold) ""
|
||||
else board.pieces.map {
|
||||
case (pos, piece) =>
|
||||
val klass = s"${piece.color.name} ${piece.role.name}"
|
||||
s"""<piece class="$klass" style="top:${top(pos)}%;left:${left(pos)}%"></piece>"""
|
||||
} mkString ""
|
||||
s"$highlights$pieces"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def chessground(pov: Pov)(implicit ctx: Context): Html = chessground(
|
||||
def chessground(pov: Pov)(implicit ctx: Context): Frag = chessground(
|
||||
board = pov.game.board,
|
||||
orient = pov.color,
|
||||
lastMove = pov.game.history.lastMove.map(_.origDest) ?? {
|
||||
|
@ -36,9 +38,11 @@ trait ChessgroundHelper {
|
|||
}
|
||||
)
|
||||
|
||||
private def wrap(content: String) = Html {
|
||||
s"""<div class="cg-board-wrap"><div class="cg-board">$content</div></div>"""
|
||||
}
|
||||
private def wrap(content: Frag): Frag = div(cls := "cg-board-wrap")(
|
||||
div(cls := "cg-board")(content)
|
||||
)
|
||||
|
||||
lazy val miniBoardContent = wrap("")
|
||||
|
||||
lazy val chessgroundSvg = wrap(raw("<svg></svg>"))
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import scala.collection.mutable.AnyRefMap
|
|||
import org.joda.time.format._
|
||||
import org.joda.time.format.ISODateTimeFormat
|
||||
import org.joda.time.{ Period, PeriodType, DurationFieldType, DateTime, DateTimeZone }
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
trait DateHelper { self: I18nHelper =>
|
||||
|
||||
|
@ -61,9 +61,8 @@ trait DateHelper { self: I18nHelper =>
|
|||
def showEnglishDate(date: DateTime): String =
|
||||
englishDateFormatter print date
|
||||
|
||||
def semanticDate(date: DateTime)(implicit ctx: Context) = Html {
|
||||
s"""<time datetime="${isoDate(date)}">${showDate(date)}</time>"""
|
||||
}
|
||||
def semanticDate(date: DateTime)(implicit ctx: Context): Frag =
|
||||
timeTag(datetimeAttr := isoDate(date))(showDate(date))
|
||||
|
||||
def showPeriod(period: Period)(implicit ctx: Context): String =
|
||||
periodFormatter(ctx) print period.normalizedStandard(periodType)
|
||||
|
@ -74,13 +73,15 @@ trait DateHelper { self: I18nHelper =>
|
|||
def isoDate(date: DateTime): String = isoFormatter print date
|
||||
|
||||
private val oneDayMillis = 1000 * 60 * 60 * 24
|
||||
def momentFromNow(date: DateTime, alwaysRelative: Boolean = false, once: Boolean = false) = Html {
|
||||
|
||||
def momentFromNow(date: DateTime, alwaysRelative: Boolean = false, once: Boolean = false): Frag = {
|
||||
if (!alwaysRelative && (date.getMillis - nowMillis) > oneDayMillis) absClientDateTime(date)
|
||||
s"""<time class="timeago${if (once) " once" else ""}" datetime="${isoDate(date)}"></time>"""
|
||||
}
|
||||
def absClientDateTime(date: DateTime) = Html {
|
||||
s"""<time class="timeago abs" datetime="${isoDate(date)}"></time>"""
|
||||
else timeTag(cls := s"timeago${once ?? " once"}", datetimeAttr := isoDate(date))
|
||||
}
|
||||
|
||||
def absClientDateTime(date: DateTime): Frag =
|
||||
timeTag(cls := "timeago abs", datetimeAttr := isoDate(date))("-")
|
||||
|
||||
def momentFromNowOnce(date: DateTime) = momentFromNow(date, once = true)
|
||||
|
||||
def secondsFromNow(seconds: Int, alwaysRelative: Boolean = false)(implicit ctx: Context) =
|
||||
|
|
|
@ -3,17 +3,13 @@ package templating
|
|||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Env.{ current => apiEnv }
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
object Environment
|
||||
extends lila.Lilaisms
|
||||
with StringHelper
|
||||
with HtmlHelper
|
||||
with JsonHelper
|
||||
with AssetHelper
|
||||
with RequestHelper
|
||||
with DateHelper
|
||||
with NumberHelper
|
||||
with PaginatorHelper
|
||||
|
@ -27,9 +23,7 @@ object Environment
|
|||
with SecurityHelper
|
||||
with TeamHelper
|
||||
with TournamentHelper
|
||||
with SimulHelper
|
||||
with ChessgroundHelper
|
||||
with ui.ScalatagsTwirl {
|
||||
with ChessgroundHelper {
|
||||
|
||||
type FormWithCaptcha = (play.api.data.Form[_], lila.common.Captcha)
|
||||
|
||||
|
@ -48,7 +42,7 @@ object Environment
|
|||
|
||||
def contactEmail = apiEnv.Net.Email
|
||||
|
||||
def contactEmailLink = Html(s"""<a href="mailto:$contactEmail">$contactEmail</a>""")
|
||||
def contactEmailLink = a(href := s"mailto:$contactEmail")(contactEmail)
|
||||
|
||||
def cspEnabled = apiEnv.cspEnabledSetting.get _
|
||||
|
||||
|
@ -58,5 +52,7 @@ object Environment
|
|||
def reportNbOpen: Int =
|
||||
lila.report.Env.current.api.nbOpen.awaitOrElse(10.millis, 0)
|
||||
|
||||
def NotForKids(f: => Html)(implicit ctx: lila.api.Context) = if (ctx.kid) emptyHtml else f
|
||||
def NotForKids(f: => Frag)(implicit ctx: lila.api.Context) = if (ctx.kid) emptyFrag else f
|
||||
|
||||
val spinner: Frag = raw("""<div class="spinner"><svg viewBox="0 0 40 40"><circle cx=20 cy=20 r=18 fill="none"></circle></svg></div>""")
|
||||
}
|
||||
|
|
|
@ -2,33 +2,29 @@ package lila.app
|
|||
package templating
|
||||
|
||||
import play.api.data._
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.i18n.I18nDb
|
||||
|
||||
trait FormHelper { self: I18nHelper =>
|
||||
|
||||
def errMsg(form: Field)(implicit ctx: Context): Html = errMsg(form.errors)
|
||||
def errMsg(form: Field)(implicit ctx: Context): Frag = errMsg(form.errors)
|
||||
|
||||
def errMsg(form: Form[_])(implicit ctx: Context): Html = errMsg(form.errors)
|
||||
def errMsg(form: Form[_])(implicit ctx: Context): Frag = errMsg(form.errors)
|
||||
|
||||
def errMsg(error: FormError)(implicit ctx: Context): Html = Html {
|
||||
s"""<p class="error">${transKey(error.message, I18nDb.Site, error.args)}</p>"""
|
||||
}
|
||||
def errMsg(error: FormError)(implicit ctx: Context): Frag =
|
||||
p(cls := "error")(transKey(error.message, I18nDb.Site, error.args))
|
||||
|
||||
def errMsg(errors: Seq[FormError])(implicit ctx: Context): Html = Html {
|
||||
def errMsg(errors: Seq[FormError])(implicit ctx: Context): Frag =
|
||||
errors map errMsg mkString
|
||||
}
|
||||
|
||||
def globalError(form: Form[_])(implicit ctx: Context): Option[Html] =
|
||||
def globalError(form: Form[_])(implicit ctx: Context): Option[Frag] =
|
||||
form.globalError map errMsg
|
||||
|
||||
val booleanChoices = Seq("true" -> "✓ Yes", "false" -> "✗ No")
|
||||
|
||||
object form3 extends ui.ScalatagsPlay {
|
||||
|
||||
import ui.ScalatagsTemplate._
|
||||
object form3 {
|
||||
|
||||
private val idPrefix = "form3"
|
||||
|
||||
|
@ -47,20 +43,14 @@ trait FormHelper { self: I18nHelper =>
|
|||
* such as `optional(nonEmptyText)`.
|
||||
* And we can't tell from the Field whether it's optional or not :(
|
||||
*/
|
||||
// case ("constraint.required", _) => required := true
|
||||
// case ("constraint.required", _) => required
|
||||
case ("constraint.minLength", Seq(m: Int)) => minlength := m
|
||||
case ("constraint.maxLength", Seq(m: Int)) => maxlength := m
|
||||
case ("constraint.min", Seq(m: Int)) => min := m
|
||||
case ("constraint.max", Seq(m: Int)) => max := m
|
||||
}
|
||||
|
||||
/* All public methods must return HTML
|
||||
* because twirl just calls toString on scalatags frags
|
||||
* and that escapes the content :( */
|
||||
|
||||
def split(html: Html): Html = div(cls := "form-split")(html)
|
||||
|
||||
def split(frags: Frag*): Html = div(cls := "form-split")(frags)
|
||||
val split = div(cls := "form-split")
|
||||
|
||||
def group(
|
||||
field: Field,
|
||||
|
@ -68,94 +58,84 @@ trait FormHelper { self: I18nHelper =>
|
|||
klass: String = "",
|
||||
half: Boolean = false,
|
||||
help: Option[Frag] = None
|
||||
)(content: Field => Frag)(implicit ctx: Context): Html =
|
||||
div(cls := List(
|
||||
"form-group" -> true,
|
||||
"is-invalid" -> field.hasErrors,
|
||||
"form-half" -> half,
|
||||
klass -> klass.nonEmpty
|
||||
))(
|
||||
groupLabel(field)(labelContent),
|
||||
content(field),
|
||||
errors(field),
|
||||
help map { helper(_) }
|
||||
)
|
||||
)(content: Field => Frag)(implicit ctx: Context): Frag = div(cls := List(
|
||||
"form-group" -> true,
|
||||
"is-invalid" -> field.hasErrors,
|
||||
"form-half" -> half,
|
||||
klass -> klass.nonEmpty
|
||||
))(
|
||||
groupLabel(field)(labelContent),
|
||||
content(field),
|
||||
errors(field),
|
||||
help map { helper(_) }
|
||||
)
|
||||
|
||||
def input(field: Field, typ: String = "", klass: String = ""): BaseTagType =
|
||||
st.input(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
value := field.value,
|
||||
`type` := typ.nonEmpty.option(typ),
|
||||
tpe := typ.nonEmpty.option(typ),
|
||||
cls := List("form-control" -> true, klass -> klass.nonEmpty)
|
||||
)(validationModifiers(field))
|
||||
|
||||
def inputHtml(field: Field, typ: String = "", klass: String = "")(modifiers: Modifier*): Html =
|
||||
input(field, typ, klass)(modifiers)
|
||||
|
||||
def checkbox(
|
||||
field: Field,
|
||||
labelContent: Frag,
|
||||
half: Boolean = false,
|
||||
help: Option[Frag] = None,
|
||||
disabled: Boolean = false
|
||||
): Html =
|
||||
div(cls := List(
|
||||
"form-check form-group" -> true,
|
||||
"form-half" -> half
|
||||
))(
|
||||
div(
|
||||
span(cls := "form-check-input")(
|
||||
st.input(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
value := "true",
|
||||
`type` := "checkbox",
|
||||
cls := "form-control cmn-toggle",
|
||||
checked := field.value.has("true").option(true),
|
||||
st.disabled := disabled.option(true)
|
||||
),
|
||||
label(`for` := id(field))
|
||||
): Frag = div(cls := List(
|
||||
"form-check form-group" -> true,
|
||||
"form-half" -> half
|
||||
))(
|
||||
div(
|
||||
span(cls := "form-check-input")(
|
||||
st.input(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
value := "true",
|
||||
tpe := "checkbox",
|
||||
cls := "form-control cmn-toggle",
|
||||
field.value.has("true") option checked,
|
||||
disabled option st.disabled
|
||||
),
|
||||
groupLabel(field)(labelContent)
|
||||
label(`for` := id(field))
|
||||
),
|
||||
help map { helper(_) }
|
||||
)
|
||||
groupLabel(field)(labelContent)
|
||||
),
|
||||
help map { helper(_) }
|
||||
)
|
||||
|
||||
def select(
|
||||
field: Field,
|
||||
options: Iterable[(Any, String)],
|
||||
default: Option[String] = None
|
||||
): Html =
|
||||
st.select(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
cls := "form-control"
|
||||
)(validationModifiers(field))(
|
||||
default map { option(value := "")(_) },
|
||||
options.toSeq map {
|
||||
case (value, name) => option(
|
||||
st.value := value.toString,
|
||||
selected := field.value.has(value.toString).option(true)
|
||||
)(name)
|
||||
}
|
||||
)
|
||||
): Frag = st.select(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
cls := "form-control"
|
||||
)(validationModifiers(field))(
|
||||
default map { option(value := "")(_) },
|
||||
options.toSeq map {
|
||||
case (value, name) => option(
|
||||
st.value := value.toString,
|
||||
field.value.has(value.toString) option selected
|
||||
)(name)
|
||||
}
|
||||
)
|
||||
|
||||
def textarea(
|
||||
field: Field,
|
||||
klass: String = ""
|
||||
)(modifiers: Modifier*): Html =
|
||||
st.textarea(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
cls := List("form-control" -> true, klass -> klass.nonEmpty)
|
||||
)(validationModifiers(field))(modifiers)(~field.value)
|
||||
)(modifiers: Modifier*): Frag = st.textarea(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
cls := List("form-control" -> true, klass -> klass.nonEmpty)
|
||||
)(validationModifiers(field))(modifiers)(~field.value)
|
||||
|
||||
val actions = div(cls := "form-actions")
|
||||
def actionsHtml(html: Frag): Html = actions(html)
|
||||
|
||||
val action = div(cls := "form-actions single")
|
||||
def actionHtml(html: Frag): Html = div(cls := "form-actions single")(html)
|
||||
|
||||
def submit(
|
||||
content: Frag,
|
||||
|
@ -163,8 +143,8 @@ trait FormHelper { self: I18nHelper =>
|
|||
nameValue: Option[(String, String)] = None,
|
||||
klass: String = "",
|
||||
confirm: Option[String] = None
|
||||
): Html = button(
|
||||
`type` := "submit",
|
||||
): Frag = button(
|
||||
tpe := "submit",
|
||||
dataIcon := icon,
|
||||
name := nameValue.map(_._1),
|
||||
value := nameValue.map(_._2),
|
||||
|
@ -177,34 +157,33 @@ trait FormHelper { self: I18nHelper =>
|
|||
title := confirm
|
||||
)(content)
|
||||
|
||||
def hidden(field: Field, value: Option[String] = None): Html =
|
||||
st.input(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
st.value := value.orElse(field.value),
|
||||
`type` := "hidden"
|
||||
)
|
||||
def hidden(field: Field, value: Option[String] = None): Frag = st.input(
|
||||
st.id := id(field),
|
||||
name := field.name,
|
||||
st.value := value.orElse(field.value),
|
||||
tpe := "hidden"
|
||||
)
|
||||
|
||||
def password(field: Field, content: Html)(implicit ctx: Context): Frag =
|
||||
group(field, content)(input(_, typ = "password")(required := true))
|
||||
def password(field: Field, content: Frag)(implicit ctx: Context): Frag =
|
||||
group(field, content)(input(_, typ = "password")(required))
|
||||
|
||||
def passwordNoAutocomplete(field: Field, content: Html)(implicit ctx: Context): Frag =
|
||||
group(field, content)(input(_, typ = "password")(autocomplete := "off")(required := true))
|
||||
def passwordModified(field: Field, content: Frag)(modifiers: Modifier*)(implicit ctx: Context): Frag =
|
||||
group(field, content)(input(_, typ = "password")(required)(modifiers))
|
||||
|
||||
def globalError(form: Form[_])(implicit ctx: Context): Option[Html] =
|
||||
def globalError(form: Form[_])(implicit ctx: Context): Option[Frag] =
|
||||
form.globalError map { err =>
|
||||
div(cls := "form-group is-invalid")(error(err))
|
||||
}
|
||||
|
||||
def flatpickr(field: Field, withTime: Boolean = true): Html =
|
||||
def flatpickr(field: Field, withTime: Boolean = true): Frag =
|
||||
input(field, klass = "flatpickr")(
|
||||
dataEnableTime := withTime,
|
||||
datatime24h := withTime
|
||||
)
|
||||
|
||||
object file {
|
||||
def image(name: String): Html = st.input(`type` := "file", st.name := name, accept := "image/*")
|
||||
def pgn(name: String): Html = st.input(`type` := "file", st.name := name, accept := ".pgn")
|
||||
def image(name: String): Frag = st.input(tpe := "file", st.name := name, accept := "image/*")
|
||||
def pgn(name: String): Frag = st.input(tpe := "file", st.name := name, accept := ".pgn")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.forum.Post
|
||||
|
||||
trait ForumHelper { self: UserHelper with StringHelper =>
|
||||
|
@ -22,7 +21,7 @@ trait ForumHelper { self: UserHelper with StringHelper =>
|
|||
|
||||
def authorName(post: Post) = post.userId match {
|
||||
case Some(userId) => userIdSpanMini(userId, withOnline = true)
|
||||
case None => Html(lila.user.User.anonymous)
|
||||
case None => frag(lila.user.User.anonymous)
|
||||
}
|
||||
|
||||
def authorLink(
|
||||
|
@ -30,9 +29,9 @@ trait ForumHelper { self: UserHelper with StringHelper =>
|
|||
cssClass: Option[String] = None,
|
||||
withOnline: Boolean = true,
|
||||
modIcon: Boolean = false
|
||||
) =
|
||||
if (post.erased) Html(s"""<span class="author">${lila.common.String.erasedHtml}</span>""")
|
||||
else post.userId.fold(Html(lila.user.User.anonymous)) { userId =>
|
||||
): Frag =
|
||||
if (post.erased) span(cls := "author")("<erased>")
|
||||
else post.userId.fold(frag(lila.user.User.anonymous)) { userId =>
|
||||
userIdLink(userId.some, cssClass = cssClass, withOnline = withOnline, modIcon = modIcon)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,18 @@ package templating
|
|||
import chess.format.Forsyth
|
||||
import chess.{ Status => S, Color, Clock, Mode }
|
||||
import controllers.routes
|
||||
import play.twirl.api.Html
|
||||
import scalatags.Text.Frag
|
||||
|
||||
import lila.common.String.html.escapeHtml
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.game.{ Game, Player, Namer, Pov }
|
||||
import lila.i18n.{ I18nKeys, enLang }
|
||||
import lila.user.{ User, UserContext }
|
||||
import lila.i18n.{ I18nKeys => trans, enLang }
|
||||
import lila.user.{ User, UserContext, Title }
|
||||
|
||||
trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHelper with HtmlHelper with ChessgroundHelper =>
|
||||
trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHelper with ChessgroundHelper =>
|
||||
|
||||
private val dataLive = attr("data-live")
|
||||
private val dataColor = attr("data-color")
|
||||
private val dataFen = attr("data-fen")
|
||||
private val dataLastmove = attr("data-lastmove")
|
||||
|
||||
def netBaseUrl: String
|
||||
def cdnUrl(path: String): String
|
||||
|
@ -67,34 +70,49 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
}
|
||||
|
||||
def variantName(variant: chess.variant.Variant)(implicit ctx: UserContext) = variant match {
|
||||
case chess.variant.Standard => I18nKeys.standard.txt()
|
||||
case chess.variant.FromPosition => I18nKeys.fromPosition.txt()
|
||||
case chess.variant.Standard => trans.standard.txt()
|
||||
case chess.variant.FromPosition => trans.fromPosition.txt()
|
||||
case v => v.name
|
||||
}
|
||||
|
||||
def variantNameNoCtx(variant: chess.variant.Variant) = variant match {
|
||||
case chess.variant.Standard => I18nKeys.standard.literalTxtTo(enLang)
|
||||
case chess.variant.FromPosition => I18nKeys.fromPosition.literalTxtTo(enLang)
|
||||
case chess.variant.Standard => trans.standard.literalTxtTo(enLang)
|
||||
case chess.variant.FromPosition => trans.fromPosition.literalTxtTo(enLang)
|
||||
case v => v.name
|
||||
}
|
||||
|
||||
def shortClockName(clock: Option[Clock.Config])(implicit ctx: UserContext): Html =
|
||||
clock.fold(I18nKeys.unlimited())(shortClockName)
|
||||
def shortClockName(clock: Option[Clock.Config])(implicit ctx: UserContext): Frag =
|
||||
clock.fold[Frag](trans.unlimited())(shortClockName)
|
||||
|
||||
def shortClockName(clock: Clock.Config): Html = Html(clock.show)
|
||||
def shortClockName(clock: Clock.Config): Frag = raw(clock.show)
|
||||
|
||||
def modeName(mode: Mode)(implicit ctx: UserContext): String = mode match {
|
||||
case Mode.Casual => I18nKeys.casual.txt()
|
||||
case Mode.Rated => I18nKeys.rated.txt()
|
||||
case Mode.Casual => trans.casual.txt()
|
||||
case Mode.Rated => trans.rated.txt()
|
||||
}
|
||||
|
||||
def modeNameNoCtx(mode: Mode): String = mode match {
|
||||
case Mode.Casual => I18nKeys.casual.literalTxtTo(enLang)
|
||||
case Mode.Rated => I18nKeys.rated.literalTxtTo(enLang)
|
||||
case Mode.Casual => trans.casual.literalTxtTo(enLang)
|
||||
case Mode.Rated => trans.rated.literalTxtTo(enLang)
|
||||
}
|
||||
|
||||
def playerUsername(player: Player, withRating: Boolean = true, withTitle: Boolean = true) =
|
||||
Namer.player(player, withRating, withTitle)(lightUser)
|
||||
def playerUsername(player: Player, withRating: Boolean = true, withTitle: Boolean = true): Frag =
|
||||
player.aiLevel.fold[Frag](
|
||||
player.userId.flatMap(lightUser).fold[Frag](lila.user.User.anonymous) { user =>
|
||||
val title = user.title ifTrue withTitle map { t =>
|
||||
frag(
|
||||
span(
|
||||
cls := "title",
|
||||
(Title(t) == Title.BOT) option dataBotAttr,
|
||||
st.title := Title titleName Title(t)
|
||||
)(t),
|
||||
" "
|
||||
)
|
||||
}
|
||||
if (withRating) frag(title, user.name, " ", "(", lila.game.Namer ratingString player, ")")
|
||||
else frag(title, user.name)
|
||||
}
|
||||
) { level => raw(s"A.I. level $level") }
|
||||
|
||||
def playerText(player: Player, withRating: Boolean = false) =
|
||||
Namer.playerText(player, withRating)(lightUser)
|
||||
|
@ -102,8 +120,8 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
def gameVsText(game: Game, withRatings: Boolean = false): String =
|
||||
Namer.gameVsText(game, withRatings)(lightUser)
|
||||
|
||||
val berserkIconSpan = """<span data-icon="`"></span>"""
|
||||
val statusIconSpan = """<span class="status"></span>"""
|
||||
val berserkIconSpan = iconTag("`")
|
||||
val statusIconSpan = i(cls := "status")
|
||||
|
||||
def playerLink(
|
||||
player: Player,
|
||||
|
@ -116,59 +134,63 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
withBerserk: Boolean = false,
|
||||
mod: Boolean = false,
|
||||
link: Boolean = true
|
||||
)(implicit ctx: UserContext) = Html {
|
||||
)(implicit ctx: UserContext): Frag = {
|
||||
val statusIcon =
|
||||
if (withStatus) statusIconSpan
|
||||
else if (withBerserk && player.berserk) berserkIconSpan
|
||||
else ""
|
||||
if (withStatus) statusIconSpan.some
|
||||
else if (withBerserk && player.berserk) berserkIconSpan.some
|
||||
else none
|
||||
player.userId.flatMap(lightUser) match {
|
||||
case None =>
|
||||
val klass = cssClass.??(" " + _)
|
||||
val content = (player.aiLevel, player.name) match {
|
||||
case (Some(level), _) => aiNameHtml(level, withRating).body
|
||||
case (_, Some(name)) => escapeHtml(name).body
|
||||
case _ => User.anonymous
|
||||
}
|
||||
s"""<span class="user_link$klass">$content$statusIcon</span>"""
|
||||
case Some(user) =>
|
||||
val klass = userClass(user.id, cssClass, withOnline)
|
||||
val href = s"${routes.User show user.name}${if (mod) "?mod" else ""}"
|
||||
val content = playerUsername(player, withRating)
|
||||
val diff = (player.ratingDiff ifTrue withDiff) ?? showRatingDiff
|
||||
val mark = engine ?? s"""<span class="engine_mark" title="${I18nKeys.thisPlayerUsesChessComputerAssistance()}"></span>"""
|
||||
val icon = withOnline ?? lineIcon(user)
|
||||
val space = if (withOnline) " " else ""
|
||||
val tag = if (link) "a" else "span"
|
||||
s"""<$tag $klass href="$href">$icon$space$content$diff$mark</$tag>$statusIcon"""
|
||||
span(cls := s"user-link$klass")(
|
||||
(player.aiLevel, player.name) match {
|
||||
case (Some(level), _) => aiNameFrag(level, withRating)
|
||||
case (_, Some(name)) => name
|
||||
case _ => User.anonymous
|
||||
},
|
||||
statusIcon
|
||||
)
|
||||
case Some(user) => frag(
|
||||
(if (link) a else span)(
|
||||
cls := userClass(user.id, cssClass, withOnline),
|
||||
href := s"${routes.User show user.name}${if (mod) "?mod" else ""}"
|
||||
)(
|
||||
withOnline option frag(lineIcon(user), " "),
|
||||
playerUsername(player, withRating),
|
||||
(player.ratingDiff ifTrue withDiff) map { d => frag(" ", showRatingDiff(d)) },
|
||||
engine option span(cls := "engine_mark", title := trans.thisPlayerUsesChessComputerAssistance.txt())
|
||||
),
|
||||
statusIcon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def gameEndStatus(game: Game)(implicit ctx: UserContext): String = game.status match {
|
||||
case S.Aborted => I18nKeys.gameAborted.txt()
|
||||
case S.Mate => I18nKeys.checkmate.txt()
|
||||
case S.Aborted => trans.gameAborted.txt()
|
||||
case S.Mate => trans.checkmate.txt()
|
||||
case S.Resign => game.loser match {
|
||||
case Some(p) if p.color.white => I18nKeys.whiteResigned.txt()
|
||||
case _ => I18nKeys.blackResigned.txt()
|
||||
case Some(p) if p.color.white => trans.whiteResigned.txt()
|
||||
case _ => trans.blackResigned.txt()
|
||||
}
|
||||
case S.UnknownFinish => I18nKeys.finished.txt()
|
||||
case S.Stalemate => I18nKeys.stalemate.txt()
|
||||
case S.UnknownFinish => trans.finished.txt()
|
||||
case S.Stalemate => trans.stalemate.txt()
|
||||
case S.Timeout => game.loser match {
|
||||
case Some(p) if p.color.white => I18nKeys.whiteLeftTheGame.txt()
|
||||
case Some(_) => I18nKeys.blackLeftTheGame.txt()
|
||||
case None => I18nKeys.draw.txt()
|
||||
case Some(p) if p.color.white => trans.whiteLeftTheGame.txt()
|
||||
case Some(_) => trans.blackLeftTheGame.txt()
|
||||
case None => trans.draw.txt()
|
||||
}
|
||||
case S.Draw => I18nKeys.draw.txt()
|
||||
case S.Outoftime => I18nKeys.timeOut.txt()
|
||||
case S.Draw => trans.draw.txt()
|
||||
case S.Outoftime => trans.timeOut.txt()
|
||||
case S.NoStart => {
|
||||
val color = game.loser.fold(Color.white)(_.color).name.capitalize
|
||||
s"$color didn't move"
|
||||
}
|
||||
case S.Cheat => "Cheat detected"
|
||||
case S.VariantEnd => game.variant match {
|
||||
case chess.variant.KingOfTheHill => I18nKeys.kingInTheCenter.txt()
|
||||
case chess.variant.ThreeCheck => I18nKeys.threeChecks.txt()
|
||||
case chess.variant.RacingKings => I18nKeys.raceFinished.txt()
|
||||
case _ => I18nKeys.variantEnding.txt()
|
||||
case chess.variant.KingOfTheHill => trans.kingInTheCenter.txt()
|
||||
case chess.variant.ThreeCheck => trans.threeChecks.txt()
|
||||
case chess.variant.RacingKings => trans.raceFinished.txt()
|
||||
case _ => trans.variantEnding.txt()
|
||||
}
|
||||
case _ => ""
|
||||
}
|
||||
|
@ -203,6 +225,10 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
}
|
||||
}.toString
|
||||
|
||||
def gameLink(pov: Pov)(implicit ctx: UserContext): String = gameLink(pov.game, pov.color)
|
||||
|
||||
private val cgBoard = div(cls := "cg-board")
|
||||
|
||||
def gameFen(
|
||||
pov: Pov,
|
||||
ownerLink: Boolean = false,
|
||||
|
@ -210,34 +236,40 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
withTitle: Boolean = true,
|
||||
withLink: Boolean = true,
|
||||
withLive: Boolean = true
|
||||
)(implicit ctx: UserContext) = Html {
|
||||
)(implicit ctx: UserContext): Frag = {
|
||||
val game = pov.game
|
||||
val isLive = withLive && game.isBeingPlayed
|
||||
val href = withLink ?? s"""href="${gameLink(game, pov.color, ownerLink, tv)}""""
|
||||
val title = withTitle ?? s"""title="${gameTitle(game, pov.color)}""""
|
||||
val cssClass = isLive ?? ("live live_" + game.id)
|
||||
val live = isLive ?? game.id
|
||||
val fen = Forsyth exportBoard game.board
|
||||
val lastMove = ~game.lastMoveKeys
|
||||
val cssClass = isLive ?? ("live mini-board-" + game.id)
|
||||
val variant = game.variant.key
|
||||
val tag = if (withLink) "a" else "span"
|
||||
s"""<$tag $href $title class="mini_board mini_board_${game.id} parse_fen is2d $cssClass $variant" data-live="$live" data-color="${pov.color.name}" data-fen="$fen" data-lastmove="$lastMove">$miniBoardContent</$tag>"""
|
||||
val tag = if (withLink) a else span
|
||||
val classes = s"mini-board mini-board-${game.id} cg-board-wrap parse-fen is2d $cssClass $variant"
|
||||
tag(
|
||||
href := withLink.option(gameLink(game, pov.color, ownerLink, tv)),
|
||||
title := withTitle.option(gameTitle(game, pov.color)),
|
||||
cls := s"mini-board mini-board-${game.id} cg-board-wrap parse-fen is2d $cssClass $variant",
|
||||
dataLive := isLive.option(game.id),
|
||||
dataColor := pov.color.name,
|
||||
dataFen := Forsyth.exportBoard(game.board),
|
||||
dataLastmove := ~game.lastMoveKeys
|
||||
)(cgBoard)
|
||||
}
|
||||
|
||||
def gameFenNoCtx(pov: Pov, tv: Boolean = false, blank: Boolean = false) = Html {
|
||||
def gameFenNoCtx(pov: Pov, tv: Boolean = false, blank: Boolean = false): Frag = {
|
||||
val isLive = pov.game.isBeingPlayed
|
||||
val variant = pov.game.variant.key
|
||||
s"""<a href="%s%s" title="%s" class="mini_board mini_board_${pov.gameId} parse_fen is2d %s $variant" data-live="%s" data-color="%s" data-fen="%s" data-lastmove="%s"%s>$miniBoardContent</a>""".format(
|
||||
blank ?? netBaseUrl,
|
||||
if (tv) routes.Tv.index else routes.Round.watcher(pov.gameId, pov.color.name),
|
||||
gameTitle(pov.game, pov.color),
|
||||
isLive ?? ("live live_" + pov.gameId),
|
||||
isLive ?? pov.gameId,
|
||||
pov.color.name,
|
||||
Forsyth exportBoard pov.game.board,
|
||||
~pov.game.lastMoveKeys,
|
||||
blank ?? """ target="_blank""""
|
||||
)
|
||||
a(
|
||||
href := (if (tv) routes.Tv.index() else routes.Round.watcher(pov.gameId, pov.color.name)),
|
||||
title := gameTitle(pov.game, pov.color),
|
||||
cls := List(
|
||||
s"mini-board mini-board-${pov.gameId} cg-board-wrap parse-fen is2d $variant" -> true,
|
||||
s"live mini-board-${pov.gameId}" -> isLive
|
||||
),
|
||||
dataLive := isLive.option(pov.gameId),
|
||||
dataColor := pov.color.name,
|
||||
dataFen := Forsyth.exportBoard(pov.game.board),
|
||||
dataLastmove := ~pov.game.lastMoveKeys,
|
||||
target := blank.option("_blank")
|
||||
)(cgBoard)
|
||||
}
|
||||
|
||||
def challengeTitle(c: lila.challenge.Challenge)(implicit ctx: UserContext) = {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import ornicar.scalalib.Zero
|
||||
import play.twirl.api.Html
|
||||
|
||||
trait HtmlHelper {
|
||||
|
||||
val emptyHtml = Html("")
|
||||
|
||||
implicit val LilaHtmlZero: Zero[Html] = Zero.instance(emptyHtml)
|
||||
|
||||
implicit val LilaHtmlMonoid = scalaz.Monoid.instance[Html](
|
||||
(a, b) => Html(a.body + b.body),
|
||||
LilaHtmlZero.zero
|
||||
)
|
||||
|
||||
val spinner = Html("""<div class="spinner"><svg viewBox="0 0 40 40"><circle cx=20 cy=20 r=18 fill="none"></circle></svg></div>""")
|
||||
|
||||
@inline implicit def toPimpedHtml(html: Html) = new PimpedHtml(html)
|
||||
}
|
||||
|
||||
final class PimpedHtml(private val self: Html) extends AnyVal {
|
||||
def ++(other: Html): Html = Html(s"${self.body}${other.body}")
|
||||
def ++(other: String): Html = Html(s"${self.body}${other}")
|
||||
}
|
|
@ -2,9 +2,9 @@ package lila.app
|
|||
package templating
|
||||
|
||||
import play.api.libs.json.JsObject
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.common.Lang
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.i18n.{ LangList, I18nKey, Translator, JsQuantity, I18nDb, JsDump, TimeagoLocales }
|
||||
import lila.user.UserContext
|
||||
|
||||
|
@ -12,8 +12,8 @@ trait I18nHelper {
|
|||
|
||||
implicit def ctxLang(implicit ctx: UserContext): Lang = ctx.lang
|
||||
|
||||
def transKey(key: String, db: I18nDb.Ref, args: Seq[Any] = Nil)(implicit lang: Lang): Html =
|
||||
Translator.html.literal(key, db, args, lang)
|
||||
def transKey(key: String, db: I18nDb.Ref, args: Seq[Any] = Nil)(implicit lang: Lang): Frag =
|
||||
Translator.frag.literal(key, db, args, lang)
|
||||
|
||||
def i18nJsObject(keys: Seq[I18nKey])(implicit lang: Lang): JsObject =
|
||||
JsDump.keysToObject(keys, I18nDb.Site, lang)
|
||||
|
@ -24,11 +24,10 @@ trait I18nHelper {
|
|||
def i18nFullDbJsObject(db: I18nDb.Ref)(implicit lang: Lang): JsObject =
|
||||
JsDump.dbToObject(db, lang)
|
||||
|
||||
private val defaultTimeagoLocale = TimeagoLocales.js.get("en") err "Missing en TimeagoLocales"
|
||||
def timeagoLocaleScript(implicit ctx: lila.api.Context): String = {
|
||||
TimeagoLocales.js.get(ctx.lang.code) orElse
|
||||
TimeagoLocales.js.get(ctx.lang.language) getOrElse
|
||||
defaultTimeagoLocale
|
||||
~TimeagoLocales.js.get("en")
|
||||
}
|
||||
|
||||
def langName = LangList.nameByStr _
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import play.api.libs.json._
|
||||
import play.twirl.api.Html
|
||||
import scalatags.Text.{ Frag, RawFrag }
|
||||
|
||||
import lila.api.Context
|
||||
|
||||
trait JsonHelper {
|
||||
|
||||
def toJsonHtml[A: Writes](map: Map[Int, A]): Html = toJsonHtml {
|
||||
map mapKeys (_.toString)
|
||||
}
|
||||
|
||||
def toJsonHtml[A: Writes](a: A): Html = lila.common.String.html.safeJsonHtml(Json toJson a)
|
||||
def toJsonFrag[A: Writes](a: A): Frag = RawFrag(lila.common.String.html.safeJsonValue(Json toJson a))
|
||||
|
||||
def htmlOrNull[A, B](a: Option[A])(f: A => Html) = a.fold(Html("null"))(f)
|
||||
|
||||
def jsOrNull[A: Writes](a: Option[A]) = a.fold(Html("null"))(x => toJsonHtml(x))
|
||||
|
||||
def jsUserIdString(implicit ctx: Context) = ctx.userId.fold("null")(id => s""""$id"""")
|
||||
def jsUserId(implicit ctx: Context) = Html { jsUserIdString(ctx) }
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import lila.api.Context
|
||||
|
||||
trait RequestHelper {
|
||||
|
||||
def currentPath(implicit ctx: Context) = ctx.req.path
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.security.{ Permission, Granter }
|
||||
import lila.user.{ User, UserContext }
|
||||
|
||||
|
@ -20,13 +19,11 @@ trait SecurityHelper {
|
|||
def isGranted(permission: Permission, user: User): Boolean =
|
||||
Granter(permission)(user)
|
||||
|
||||
def canGrant = Granter.canGrant _
|
||||
|
||||
def canViewRoles(user: User)(implicit ctx: UserContext): Boolean =
|
||||
isGranted(_.ChangePermission) || (isGranted(_.Admin) && user.roles.nonEmpty)
|
||||
|
||||
def reportScore(score: lila.report.Report.Score) = Html {
|
||||
s"""<div class="score ${score.color}" title="Report score">${score.value.toInt}</div>"""
|
||||
}
|
||||
// def reportScore(score: lila.report.Report.Score) = Html {
|
||||
// s"""<div class="score"><i>Score</i><strong>${score.value.toInt}</strong></div>"""
|
||||
// }
|
||||
def reportScore(score: lila.report.Report.Score): Frag =
|
||||
div(cls := s"score ${score.color}", title := "Report score")(score.value.toInt)
|
||||
}
|
||||
|
|
|
@ -211,6 +211,12 @@ trait SetupHelper { self: I18nHelper =>
|
|||
(Pref.InsightShare.EVERYBODY, trans.withEverybody.txt())
|
||||
)
|
||||
|
||||
def translatedBoardResizeHandleChoices(implicit ctx: Context) = List(
|
||||
(Pref.ResizeHandle.NEVER, trans.never.txt()),
|
||||
(Pref.ResizeHandle.INITIAL, "Only on initial position"),
|
||||
(Pref.ResizeHandle.ALWAYS, trans.always.txt())
|
||||
)
|
||||
|
||||
def translatedBlindfoldChoices(implicit ctx: Context) = List(
|
||||
Pref.Blindfold.NO -> trans.no.txt(),
|
||||
Pref.Blindfold.YES -> trans.yes.txt()
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import controllers.routes
|
||||
import lila.simul.Simul
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
trait SimulHelper { self: I18nHelper =>
|
||||
|
||||
def simulLink(simulId: Simul.ID): Html = Html {
|
||||
val url = routes.Simul.show(simulId)
|
||||
s"""<a class="text" data-icon="|" href="$url">Simultaneous exhibition</a>"""
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.user.UserContext
|
||||
import ui.ScalatagsTemplate._
|
||||
|
||||
trait StringHelper { self: NumberHelper =>
|
||||
|
||||
|
@ -15,10 +14,6 @@ trait StringHelper { self: NumberHelper =>
|
|||
|
||||
def pluralize(s: String, n: Int) = s"$n $s${if (n > 1) "s" else ""}"
|
||||
|
||||
def repositionTooltipUnsafe(link: Html, position: String) = Html {
|
||||
link.body.replace("<a ", s"""<a data-pt-pos="$position" """)
|
||||
}
|
||||
|
||||
def showNumber(n: Int): String = if (n > 0) s"+$n" else n.toString
|
||||
|
||||
implicit def lilaRichString(str: String) = new {
|
||||
|
@ -30,23 +25,32 @@ trait StringHelper { self: NumberHelper =>
|
|||
|
||||
private val NumberFirstRegex = """(\d++)\s(.+)""".r
|
||||
private val NumberLastRegex = """\s(\d++)$""".r.unanchored
|
||||
def splitNumberUnsafe(s: String)(implicit ctx: UserContext): Html = Html {
|
||||
s match {
|
||||
case NumberFirstRegex(number, text) =>
|
||||
s"<strong>${(~parseIntOption(number)).localize}</strong><br />$text"
|
||||
case NumberLastRegex(n) if s.length > n.length + 1 =>
|
||||
s"${s.dropRight(n.length + 1)}<br /><strong>${(~parseIntOption(n)).localize}</strong>"
|
||||
case h => h.replaceIf('\n', "<br />")
|
||||
|
||||
def splitNumber(s: Frag)(implicit ctx: UserContext): Frag = {
|
||||
val rendered = s.render
|
||||
rendered match {
|
||||
case NumberFirstRegex(number, html) => frag(
|
||||
strong((~parseIntOption(number)).localize),
|
||||
br,
|
||||
raw(html)
|
||||
)
|
||||
case NumberLastRegex(n) if rendered.length > n.length + 1 => frag(
|
||||
raw(rendered.dropRight(n.length + 1)),
|
||||
br,
|
||||
strong((~parseIntOption(n)).localize)
|
||||
)
|
||||
case h => raw(h.replaceIf('\n', "<br>"))
|
||||
}
|
||||
}
|
||||
def splitNumber(s: Html)(implicit ctx: UserContext): Html = splitNumberUnsafe(s.body)
|
||||
|
||||
def encodeFen(fen: String) = lila.common.String.base64.encode(fen).reverse
|
||||
|
||||
def addQueryParameter(url: String, key: String, value: Any) =
|
||||
if (url contains "?") s"$url&$key=$value" else s"$url?$key=$value"
|
||||
|
||||
def htmlList(htmls: List[Html], separator: String = ", ") = Html {
|
||||
htmls mkString separator
|
||||
def fragList(frags: List[Frag], separator: String = ", "): Frag = frags match {
|
||||
case Nil => emptyFrag
|
||||
case one :: Nil => one
|
||||
case first :: rest => first :: rest.map { f => frag(separator, f) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@ package lila.app
|
|||
package templating
|
||||
|
||||
import controllers.routes
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.team.Env.{ current => teamEnv }
|
||||
import lila.common.String.html.escapeHtml
|
||||
|
||||
trait TeamHelper {
|
||||
|
||||
|
@ -15,15 +14,12 @@ trait TeamHelper {
|
|||
def myTeam(teamId: String)(implicit ctx: Context): Boolean =
|
||||
ctx.me.??(me => api.syncBelongsTo(teamId, me.id))
|
||||
|
||||
def teamIdToName(id: String): Html = escapeHtml(api teamName id getOrElse id)
|
||||
def teamIdToName(id: String): Frag = StringFrag(api.teamName(id).getOrElse(id))
|
||||
|
||||
def teamLink(id: String, withIcon: Boolean = true): Html = Html {
|
||||
val href = routes.Team.show(id)
|
||||
val content = teamIdToName(id)
|
||||
val icon = if (withIcon) """ data-icon="f"""" else ""
|
||||
val space = if (withIcon) " " else ""
|
||||
s"""<a$icon href="$href">$space$content</a>"""
|
||||
}
|
||||
def teamLink(id: String, withIcon: Boolean = true): Frag = a(
|
||||
href := routes.Team.show(id),
|
||||
dataIcon := withIcon.option("f")
|
||||
)(withIcon option nbsp, teamIdToName(id))
|
||||
|
||||
def teamForumUrl(id: String) = routes.ForumCateg.show("team-" + id)
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ package lila.app
|
|||
package templating
|
||||
|
||||
import controllers.routes
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.tournament.Env.{ current => tournamentEnv }
|
||||
import lila.tournament.{ Tournament, System, Schedule }
|
||||
import lila.user.{ User, UserContext }
|
||||
|
||||
import play.api.libs.json.Json
|
||||
import play.twirl.api.Html
|
||||
|
||||
trait TournamentHelper { self: I18nHelper with DateHelper with UserHelper =>
|
||||
|
||||
|
@ -24,16 +24,17 @@ trait TournamentHelper { self: I18nHelper with DateHelper with UserHelper =>
|
|||
}
|
||||
}
|
||||
|
||||
def tournamentLink(tour: Tournament): Html = Html {
|
||||
val cssClass = if (tour.isScheduled) "text is-gold" else "text"
|
||||
val url = routes.Tournament.show(tour.id)
|
||||
s"""<a data-icon="g" class="$cssClass" href="$url">${tour.fullName}</a>"""
|
||||
}
|
||||
def tournamentLink(tour: Tournament): Frag = a(
|
||||
dataIcon := "g",
|
||||
cls := (if (tour.isScheduled) "text is-gold" else "text"),
|
||||
href := routes.Tournament.show(tour.id).url
|
||||
)(tour.fullName)
|
||||
|
||||
def tournamentLink(tourId: String): Html = Html {
|
||||
val url = routes.Tournament.show(tourId)
|
||||
s"""<a class="text" data-icon="g" href="$url">${tournamentIdToName(tourId)}</a>"""
|
||||
}
|
||||
def tournamentLink(tourId: String): Frag = a(
|
||||
dataIcon := "g",
|
||||
cls := "text",
|
||||
href := routes.Tournament.show(tourId).url
|
||||
)(tournamentIdToName(tourId))
|
||||
|
||||
def tournamentIdToName(id: String) = tournamentEnv.cached name id getOrElse "Tournament"
|
||||
|
||||
|
@ -47,7 +48,7 @@ trait TournamentHelper { self: I18nHelper with DateHelper with UserHelper =>
|
|||
) ::: lila.rating.PerfType.leaderboardable.map { pt =>
|
||||
pt.name -> icon(pt.iconChar)
|
||||
}
|
||||
def apply(name: String) = Html {
|
||||
def apply(name: String): Frag = raw {
|
||||
replacements.foldLeft(name) {
|
||||
case (n, (from, to)) => n.replace(from, to)
|
||||
}
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
package lila.app
|
||||
package templating
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import controllers.routes
|
||||
import mashup._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.LightUser
|
||||
import lila.i18n.I18nKeys
|
||||
import lila.i18n.{ I18nKeys => trans }
|
||||
import lila.rating.{ PerfType, Perf }
|
||||
import lila.user.{ User, Title, UserContext }
|
||||
|
||||
trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with NumberHelper =>
|
||||
trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
||||
|
||||
def showProgress(progress: Int, withTitle: Boolean = true) = Html {
|
||||
val span = progress match {
|
||||
case 0 => ""
|
||||
case p if p > 0 => s"""<span class="positive" data-icon="N">$p</span>"""
|
||||
case p if p < 0 => s"""<span class="negative" data-icon="M">${math.abs(p)}</span>"""
|
||||
}
|
||||
val title = if (withTitle) """ data-hint="Rating progression over the last twelve games"""" else ""
|
||||
val klass = if (withTitle) "progress hint--bottom" else "progress"
|
||||
s"""<span$title class="$klass">$span</span>"""
|
||||
}
|
||||
def ratingProgress(progress: Int) =
|
||||
if (progress > 0) goodTag(cls := "rp")(progress)
|
||||
else if (progress < 0) badTag(cls := "rp")(math.abs(progress))
|
||||
else emptyFrag
|
||||
|
||||
val topBarSortedPerfTypes: List[PerfType] = List(
|
||||
PerfType.Bullet,
|
||||
|
@ -40,38 +33,37 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
PerfType.Crazyhouse
|
||||
)
|
||||
|
||||
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"
|
||||
val attr = if (klass == "title") "title" else "data-hint"
|
||||
val number = if (nb > 0) s"$rating${if (provisional) "?" else ""}"
|
||||
else " -"
|
||||
s"""<span $attr="$title" class="$klass"><span data-icon="$icon">$number</span></span>"""
|
||||
}
|
||||
def showPerfRating(rating: Int, name: String, nb: Int, provisional: Boolean, icon: Char)(implicit ctx: Context): Frag =
|
||||
span(
|
||||
title := s"$name rating over ${nb.localize} games",
|
||||
dataIcon := icon,
|
||||
cls := "text"
|
||||
)(
|
||||
if (nb > 0) frag(rating, provisional option "?")
|
||||
else frag(nbsp, nbsp, nbsp, "-")
|
||||
)
|
||||
|
||||
def showPerfRating(perfType: PerfType, perf: Perf, klass: String)(implicit ctx: Context): Html =
|
||||
showPerfRating(perf.intRating, perfType.name, perf.nb, perf.provisional, perfType.iconChar, klass)
|
||||
def showPerfRating(perfType: PerfType, perf: Perf)(implicit ctx: Context): Frag =
|
||||
showPerfRating(perf.intRating, perfType.name, perf.nb, perf.provisional, perfType.iconChar)
|
||||
|
||||
def showPerfRating(u: User, perfType: PerfType, klass: String = "hint--bottom")(implicit ctx: Context): Html =
|
||||
showPerfRating(perfType, u perfs perfType, klass)
|
||||
def showPerfRating(u: User, perfType: PerfType)(implicit ctx: Context): Frag =
|
||||
showPerfRating(perfType, u perfs perfType)
|
||||
|
||||
def showPerfRating(u: User, perfKey: String)(implicit ctx: Context): Option[Html] =
|
||||
def showPerfRating(u: User, perfKey: String)(implicit ctx: Context): Option[Frag] =
|
||||
PerfType(perfKey) map { showPerfRating(u, _) }
|
||||
|
||||
def showBestPerf(u: User)(implicit ctx: Context): Option[Html] = u.perfs.bestPerf map {
|
||||
case (pt, perf) => showPerfRating(pt, perf, klass = "hint--bottom")
|
||||
def showBestPerf(u: User)(implicit ctx: Context): Option[Frag] = u.perfs.bestPerf map {
|
||||
case (pt, perf) => showPerfRating(pt, perf)
|
||||
}
|
||||
def showBestPerfs(u: User, nb: Int)(implicit ctx: Context): Html = Html {
|
||||
def showBestPerfs(u: User, nb: Int)(implicit ctx: Context): List[Frag] =
|
||||
u.perfs.bestPerfs(nb) map {
|
||||
case (pt, perf) => showPerfRating(pt, perf, klass = "hint--bottom").body
|
||||
} mkString " "
|
||||
}
|
||||
|
||||
def showRatingDiff(diff: Int) = Html {
|
||||
diff match {
|
||||
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>"""
|
||||
case (pt, perf) => showPerfRating(pt, perf)
|
||||
}
|
||||
|
||||
def showRatingDiff(diff: Int): Frag = diff match {
|
||||
case 0 => span("±0")
|
||||
case d if d > 0 => goodTag(s"+$d")
|
||||
case d => badTag(s"−${-d}")
|
||||
}
|
||||
|
||||
def lightUser(userId: String): Option[LightUser] = Env.user lightUserSync userId
|
||||
|
@ -94,8 +86,8 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
truncate: Option[Int] = None,
|
||||
params: String = "",
|
||||
modIcon: Boolean = false
|
||||
): Html = Html {
|
||||
userIdOption.flatMap(lightUser).fold(User.anonymous) { user =>
|
||||
): Frag =
|
||||
userIdOption.flatMap(lightUser).fold[Frag](User.anonymous) { user =>
|
||||
userIdNameLink(
|
||||
userId = user.id,
|
||||
username = user.name,
|
||||
|
@ -108,7 +100,6 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
modIcon = modIcon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def lightUserLink(
|
||||
user: LightUser,
|
||||
|
@ -117,31 +108,33 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
withTitle: Boolean = true,
|
||||
truncate: Option[Int] = None,
|
||||
params: String = ""
|
||||
): Html = Html {
|
||||
userIdNameLink(
|
||||
userId = user.id,
|
||||
username = user.name,
|
||||
isPatron = user.isPatron,
|
||||
title = withTitle ?? user.title map Title.apply,
|
||||
cssClass = cssClass,
|
||||
withOnline = withOnline,
|
||||
truncate = truncate,
|
||||
params = params,
|
||||
modIcon = false
|
||||
)
|
||||
}
|
||||
): Frag = userIdNameLink(
|
||||
userId = user.id,
|
||||
username = user.name,
|
||||
isPatron = user.isPatron,
|
||||
title = withTitle ?? user.title map Title.apply,
|
||||
cssClass = cssClass,
|
||||
withOnline = withOnline,
|
||||
truncate = truncate,
|
||||
params = params,
|
||||
modIcon = false
|
||||
)
|
||||
|
||||
def userIdLink(
|
||||
userId: String,
|
||||
cssClass: Option[String]
|
||||
): Html = userIdLink(userId.some, cssClass)
|
||||
): Frag = userIdLink(userId.some, cssClass)
|
||||
|
||||
def titleTag(title: Option[Title]) = Html {
|
||||
title.fold("") { t =>
|
||||
s"""<span class="title"${(t == Title.BOT) ?? " data-bot"} title="${Title titleName t}">$t</span> """
|
||||
}
|
||||
def titleTag(title: Option[Title]): Option[Frag] = title map { t =>
|
||||
frag(
|
||||
span(
|
||||
cls := s"title${(t == Title.BOT) ?? " data-bot"}",
|
||||
st.title := Title.titleName(t)
|
||||
)(t),
|
||||
nbsp
|
||||
)
|
||||
}
|
||||
def titleTag(lu: LightUser): Html = titleTag(lu.title map Title.apply)
|
||||
def titleTag(lu: LightUser): Frag = titleTag(lu.title map Title.apply)
|
||||
|
||||
private def userIdNameLink(
|
||||
userId: String,
|
||||
|
@ -153,14 +146,14 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
title: Option[Title],
|
||||
params: String,
|
||||
modIcon: Boolean
|
||||
): String = {
|
||||
val klass = userClass(userId, cssClass, withOnline)
|
||||
val href = userHref(username, params = params)
|
||||
val content = truncate.fold(username)(username.take)
|
||||
val titleS = titleTag(title).body
|
||||
val icon = withOnline ?? (if (modIcon) moderatorIcon else lineIcon(isPatron))
|
||||
s"""<a $klass $href>$icon$titleS$content</a>"""
|
||||
}
|
||||
): Frag = a(
|
||||
cls := userClass(userId, cssClass, withOnline),
|
||||
href := userUrl(username, params = params)
|
||||
)(
|
||||
withOnline ?? (if (modIcon) moderatorIcon else lineIcon(isPatron)),
|
||||
titleTag(title),
|
||||
truncate.fold(username)(username.take)
|
||||
)
|
||||
|
||||
def userLink(
|
||||
user: User,
|
||||
|
@ -172,33 +165,15 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
withPerfRating: Option[PerfType] = None,
|
||||
text: Option[String] = None,
|
||||
params: String = ""
|
||||
): Html = Html {
|
||||
val klass = userClass(user.id, cssClass, withOnline, withPowerTip)
|
||||
val href = userHref(user.username, params)
|
||||
val content = text | user.username
|
||||
val titleS = if (withTitle) titleTag(user.title).body else ""
|
||||
val rating = userRating(user, withPerfRating, withBestRating)
|
||||
val icon = withOnline ?? lineIcon(user)
|
||||
s"""<a $klass $href>$icon$titleS$content$rating</a>"""
|
||||
}
|
||||
|
||||
def userInfosLink(
|
||||
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, withPowerTip)
|
||||
val href = userHref(name)
|
||||
val rat = rating ?? { r => s" ($r)" }
|
||||
val titleS = withTitle ?? user ?? (u => titleTag(u).body)
|
||||
val icon = withOnline ?? lineIcon(user)
|
||||
Html(s"""<a $klass $href>$icon$titleS$name$rat</a>""")
|
||||
}
|
||||
): Frag = a(
|
||||
cls := userClass(user.id, cssClass, withOnline, withPowerTip),
|
||||
href := userUrl(user.username, params)
|
||||
)(
|
||||
withOnline ?? lineIcon(user),
|
||||
withTitle option titleTag(user.title),
|
||||
text | user.username,
|
||||
userRating(user, withPerfRating, withBestRating)
|
||||
)
|
||||
|
||||
def userSpan(
|
||||
user: User,
|
||||
|
@ -209,31 +184,37 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
withBestRating: Boolean = false,
|
||||
withPerfRating: Option[PerfType] = None,
|
||||
text: Option[String] = None
|
||||
) = Html {
|
||||
val klass = userClass(user.id, cssClass, withOnline, withPowerTip)
|
||||
val href = s"data-${userHref(user.username)}"
|
||||
val content = text | user.username
|
||||
val titleS = if (withTitle) titleTag(user.title).body else ""
|
||||
val rating = userRating(user, withPerfRating, withBestRating)
|
||||
val icon = withOnline ?? lineIcon(user)
|
||||
s"""<span $klass $href>$icon$titleS$content$rating</span>"""
|
||||
}
|
||||
): Frag = span(
|
||||
cls := userClass(user.id, cssClass, withOnline, withPowerTip),
|
||||
dataHref := userUrl(user.username)
|
||||
)(
|
||||
withOnline ?? lineIcon(user),
|
||||
withTitle option titleTag(user.title),
|
||||
text | user.username,
|
||||
userRating(user, withPerfRating, withBestRating)
|
||||
)
|
||||
|
||||
def userIdSpanMini(userId: String, withOnline: Boolean = false) = Html {
|
||||
def userIdSpanMini(userId: String, withOnline: Boolean = false): Frag = {
|
||||
val user = lightUser(userId)
|
||||
val name = user.fold(userId)(_.name)
|
||||
val content = user.fold(userId)(_.name)
|
||||
val titleS = user.??(u => titleTag(u.title map Title.apply).body)
|
||||
val klass = userClass(userId, none, withOnline)
|
||||
val href = s"data-${userHref(name)}"
|
||||
val icon = withOnline ?? lineIcon(user)
|
||||
s"""<span $klass $href>$icon$titleS$content</span>"""
|
||||
span(
|
||||
cls := userClass(userId, none, withOnline),
|
||||
dataHref := userUrl(name)
|
||||
)(
|
||||
withOnline ?? lineIcon(user),
|
||||
user.??(u => titleTag(u.title map Title.apply)),
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
private def renderRating(perf: Perf) =
|
||||
s" (${perf.intRating}${if (perf.provisional) "?" else ""})"
|
||||
private def renderRating(perf: Perf): Frag = frag(
|
||||
" (",
|
||||
perf.intRating,
|
||||
perf.provisional option "?",
|
||||
")"
|
||||
)
|
||||
|
||||
private def userRating(user: User, withPerfRating: Option[PerfType], withBestRating: Boolean) =
|
||||
private def userRating(user: User, withPerfRating: Option[PerfType], withBestRating: Boolean): Frag =
|
||||
withPerfRating match {
|
||||
case Some(perfType) => renderRating(user.perfs(perfType))
|
||||
case _ if withBestRating => user.perfs.bestPerf ?? {
|
||||
|
@ -242,37 +223,36 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
case _ => ""
|
||||
}
|
||||
|
||||
private def userHref(username: String, params: String = "") =
|
||||
s"""href="${routes.User.show(username)}$params""""
|
||||
|
||||
private def addClass(cls: Option[String]) = cls.fold("")(" " + _)
|
||||
private def userUrl(username: String, params: String = "") =
|
||||
s"""${routes.User.show(username)}$params"""
|
||||
|
||||
protected def userClass(
|
||||
userId: String,
|
||||
cssClass: Option[String],
|
||||
withOnline: Boolean,
|
||||
withPowerTip: Boolean = true
|
||||
): String = {
|
||||
val online = if (withOnline) {
|
||||
if (isOnline(userId)) " online" else " offline"
|
||||
} else ""
|
||||
s"""class="user_link${addClass(cssClass)}${addClass(withPowerTip option "ulpt")}$online""""
|
||||
}
|
||||
): List[(String, Boolean)] =
|
||||
(withOnline ?? List((if (isOnline(userId)) "online" else "offline") -> true)) ::: List(
|
||||
"user-link" -> true,
|
||||
~cssClass -> cssClass.isDefined,
|
||||
"ulpt" -> withPowerTip
|
||||
)
|
||||
|
||||
def userGameFilterTitle(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext) =
|
||||
splitNumber(userGameFilterTitleNoTag(u, nbs, filter))
|
||||
def userGameFilterTitle(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext): Frag =
|
||||
if (filter == GameFilter.Search) frag(br, trans.advancedSearch())
|
||||
else splitNumber(userGameFilterTitleNoTag(u, nbs, filter))
|
||||
|
||||
def userGameFilterTitleNoTag(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext): Html = (filter match {
|
||||
case GameFilter.All => I18nKeys.nbGames.pluralSame(u.count.game)
|
||||
case GameFilter.Me => nbs.withMe ?? I18nKeys.nbGamesWithYou.pluralSame
|
||||
case GameFilter.Rated => I18nKeys.nbRated.pluralSame(u.count.rated)
|
||||
case GameFilter.Win => I18nKeys.nbWins.pluralSame(u.count.win)
|
||||
case GameFilter.Loss => I18nKeys.nbLosses.pluralSame(u.count.loss)
|
||||
case GameFilter.Draw => I18nKeys.nbDraws.pluralSame(u.count.draw)
|
||||
case GameFilter.Playing => I18nKeys.nbPlaying.pluralSame(nbs.playing)
|
||||
case GameFilter.Bookmark => I18nKeys.nbBookmarks.pluralSame(nbs.bookmark)
|
||||
case GameFilter.Imported => I18nKeys.nbImportedGames.pluralSame(nbs.imported)
|
||||
case GameFilter.Search => I18nKeys.advancedSearch()
|
||||
def userGameFilterTitleNoTag(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext): String = (filter match {
|
||||
case GameFilter.All => trans.nbGames.pluralSameTxt(u.count.game)
|
||||
case GameFilter.Me => nbs.withMe ?? trans.nbGamesWithYou.pluralSameTxt
|
||||
case GameFilter.Rated => trans.nbRated.pluralSameTxt(u.count.rated)
|
||||
case GameFilter.Win => trans.nbWins.pluralSameTxt(u.count.win)
|
||||
case GameFilter.Loss => trans.nbLosses.pluralSameTxt(u.count.loss)
|
||||
case GameFilter.Draw => trans.nbDraws.pluralSameTxt(u.count.draw)
|
||||
case GameFilter.Playing => trans.nbPlaying.pluralSameTxt(nbs.playing)
|
||||
case GameFilter.Bookmark => trans.nbBookmarks.pluralSameTxt(nbs.bookmark)
|
||||
case GameFilter.Imported => trans.nbImportedGames.pluralSameTxt(nbs.imported)
|
||||
case GameFilter.Search => trans.advancedSearch.txt()
|
||||
})
|
||||
|
||||
def describeUser(user: User) = {
|
||||
|
@ -288,12 +268,12 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
|
|||
val patronIconChar = ""
|
||||
val lineIconChar = ""
|
||||
|
||||
val lineIcon: String = """<i class="line"></i>"""
|
||||
val patronIcon: String = """<i class="line patron" title="lichess Patron"></i>"""
|
||||
val moderatorIcon: String = """<i class="line moderator" title="lichess Moderator"></i>"""
|
||||
private def lineIcon(patron: Boolean): String = if (patron) patronIcon else lineIcon
|
||||
private def lineIcon(user: Option[LightUser]): String = lineIcon(user.??(_.isPatron))
|
||||
def lineIcon(user: LightUser): String = lineIcon(user.isPatron)
|
||||
def lineIcon(user: User): String = lineIcon(user.isPatron)
|
||||
def lineIconChar(user: User): String = if (user.isPatron) patronIconChar else lineIconChar
|
||||
val lineIcon: Frag = i(cls := "line")
|
||||
val patronIcon: Frag = i(cls := "line patron", title := "Lichess Patron")
|
||||
val moderatorIcon: Frag = i(cls := "line moderator", title := "Lichess Mod")
|
||||
private def lineIcon(patron: Boolean): Frag = if (patron) patronIcon else lineIcon
|
||||
private def lineIcon(user: Option[LightUser]): Frag = lineIcon(user.??(_.isPatron))
|
||||
def lineIcon(user: LightUser): Frag = lineIcon(user.isPatron)
|
||||
def lineIcon(user: User): Frag = lineIcon(user.isPatron)
|
||||
def lineIconChar(user: User): Frag = if (user.isPatron) patronIconChar else lineIconChar
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package lila.app
|
||||
package ui
|
||||
|
||||
import play.api.mvc.RequestHeader
|
||||
|
||||
import lila.common.{ Nonce, Lang }
|
||||
|
||||
case class EmbedConfig(bg: String, board: String, lang: Lang, req: RequestHeader, nonce: Nonce)
|
||||
|
||||
object EmbedConfig {
|
||||
|
||||
object implicits {
|
||||
implicit def configLang(implicit config: EmbedConfig): Lang = config.lang
|
||||
implicit def configReq(implicit config: EmbedConfig): RequestHeader = config.req
|
||||
}
|
||||
|
||||
def apply(req: RequestHeader): EmbedConfig = EmbedConfig(
|
||||
bg = get("bg", req).filterNot("auto" ==) | "light",
|
||||
board = lila.pref.Theme(~get("theme", req)).cssClass,
|
||||
lang = lila.i18n.I18nLangPicker(req, none),
|
||||
req = req,
|
||||
nonce = Nonce.random
|
||||
)
|
||||
|
||||
private def get(name: String, req: RequestHeader): Option[String] =
|
||||
req.queryString get name flatMap (_.headOption) filter (_.nonEmpty)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package lila.app
|
||||
package ui
|
||||
|
||||
import lila.common.String.html.escapeHtml
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
case class OpenGraph(
|
||||
title: String,
|
||||
|
@ -13,39 +13,45 @@ case class OpenGraph(
|
|||
more: List[(String, String)] = Nil
|
||||
) {
|
||||
|
||||
def frag = scalatags.Text.RawFrag(s"${og.str}${twitter.str}")
|
||||
def frags: List[Frag] = og.frags ::: twitter.frags
|
||||
|
||||
object og {
|
||||
|
||||
private def tag(name: String, value: String) =
|
||||
s"""<meta property="og:$name" content="${escapeHtml(value)}"/>"""
|
||||
private val property = attr("property")
|
||||
|
||||
private def tag(name: String, value: String) = meta(
|
||||
property := s"og:$name",
|
||||
content := value
|
||||
)
|
||||
|
||||
private val tupledTag = (tag _).tupled
|
||||
|
||||
def str = List(
|
||||
def frags: List[Frag] = List(
|
||||
"title" -> title,
|
||||
"description" -> description,
|
||||
"url" -> url,
|
||||
"type" -> `type`,
|
||||
"site_name" -> siteName
|
||||
).map(tupledTag).mkString +
|
||||
image.?? { tag("image", _) } +
|
||||
more.map(tupledTag).mkString
|
||||
).map(tupledTag) :::
|
||||
image.map { tag("image", _) }.toList :::
|
||||
more.map(tupledTag)
|
||||
}
|
||||
|
||||
object twitter {
|
||||
|
||||
private def tag(name: String, value: String) =
|
||||
s"""<meta name="twitter:$name" content="${escapeHtml(value)}"/>"""
|
||||
private def tag(name: String, value: String) = meta(
|
||||
st.name := s"twitter:$name",
|
||||
content := value
|
||||
)
|
||||
|
||||
private val tupledTag = (tag _).tupled
|
||||
|
||||
def str = List(
|
||||
def frags: List[Frag] = List(
|
||||
"card" -> "summary",
|
||||
"title" -> title,
|
||||
"description" -> description
|
||||
).map(tupledTag).mkString +
|
||||
image.?? { tag("image", _) } +
|
||||
more.map(tupledTag).mkString
|
||||
).map(tupledTag) :::
|
||||
image.map { tag("image", _) }.toList :::
|
||||
more.map(tupledTag)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,24 +3,30 @@ package ui
|
|||
|
||||
import ornicar.scalalib.Zero
|
||||
|
||||
import play.twirl.api.Html
|
||||
import scalatags.Text.all.{ genericAttr, attr, StringFrag }
|
||||
import scalatags.text.Builder
|
||||
import scalatags.Text.{ Frag, RawFrag, Attr, AttrValue, Modifier, Cap, Aggregate, Attrs, Styles }
|
||||
import scalatags.Text.{ Aggregate, Cap }
|
||||
import scalatags.Text.all._
|
||||
|
||||
// collection of lila attrs
|
||||
trait ScalatagsAttrs {
|
||||
lazy val minlength = attr("minlength") // missing from scalatags atm
|
||||
lazy val dataTag = attr("data-tag")
|
||||
lazy val dataIcon = attr("data-icon")
|
||||
lazy val dataHint = attr("data-hint")
|
||||
lazy val dataHref = attr("data-href")
|
||||
lazy val dataCount = attr("data-count")
|
||||
lazy val dataEnableTime = attr("data-enable-time")
|
||||
lazy val datatime24h = attr("data-time_24h")
|
||||
lazy val dataColor = attr("data-color")
|
||||
lazy val dataFen = attr("data-fen")
|
||||
lazy val novalidate = attr("novalidate")
|
||||
val minlength = attr("minlength") // missing from scalatags atm
|
||||
val dataTag = attr("data-tag")
|
||||
val dataIcon = attr("data-icon")
|
||||
val dataHref = attr("data-href")
|
||||
val dataCount = attr("data-count")
|
||||
val dataEnableTime = attr("data-enable-time")
|
||||
val datatime24h = attr("data-time_24h")
|
||||
val dataColor = attr("data-color")
|
||||
val dataFen = attr("data-fen")
|
||||
val dataRel = attr("data-rel")
|
||||
val novalidate = attr("novalidate").empty
|
||||
val datetimeAttr = attr("datetime")
|
||||
val dataBotAttr = attr("data-bot").empty
|
||||
val deferAttr = attr("defer").empty
|
||||
object frame {
|
||||
val scrolling = attr("scrolling")
|
||||
val allowfullscreen = attr("allowfullscreen").empty
|
||||
}
|
||||
}
|
||||
|
||||
// collection of lila snippets
|
||||
|
@ -30,15 +36,30 @@ trait ScalatagsSnippets extends Cap {
|
|||
import scalatags.Text.all._
|
||||
|
||||
val nbsp = raw(" ")
|
||||
val amp = raw("&")
|
||||
def iconTag(icon: Char): Tag = iconTag(icon.toString)
|
||||
def iconTag(icon: String): Tag = i(dataIcon := icon)
|
||||
def iconTag(icon: Char, text: Frag): Tag = iconTag(icon.toString, text)
|
||||
def iconTag(icon: String, text: Frag): Tag = i(dataIcon := icon, cls := "text")(text)
|
||||
|
||||
lazy val dataBotAttr = attr("data-bot").empty
|
||||
val styleTag = tag("style")(tpe := "text/css")
|
||||
val ratingTag = tag("rating")
|
||||
val countTag = tag("count")
|
||||
val goodTag = tag("good")
|
||||
val badTag = tag("bad")
|
||||
val timeTag = tag("time")
|
||||
|
||||
def dataBot(title: lila.user.Title): Modifier =
|
||||
if (title == lila.user.Title.BOT) dataBotAttr
|
||||
else emptyModifier
|
||||
|
||||
def pagerNext(pager: lila.common.paginator.Paginator[_], url: Int => String): Option[Frag] =
|
||||
pager.nextPage.map { np =>
|
||||
div(cls := "pager none")(a(rel := "next", href := url(np))("Next"))
|
||||
}
|
||||
def pagerNextTable(pager: lila.common.paginator.Paginator[_], url: Int => String): Option[Frag] =
|
||||
pager.nextPage.map { np =>
|
||||
tr(th(cls := "pager none")(a(rel := "next", href := url(np))("Next")))
|
||||
}
|
||||
}
|
||||
|
||||
// basic imports from scalatags
|
||||
|
@ -51,9 +72,16 @@ trait ScalatagsBundle extends Cap
|
|||
// short prefix
|
||||
trait ScalatagsPrefix {
|
||||
object st extends Cap with Attrs with scalatags.text.Tags {
|
||||
lazy val group = tag("group")
|
||||
}
|
||||
val group = tag("group")
|
||||
val headTitle = tag("title")
|
||||
val nav = tag("nav")
|
||||
val section = tag("section")
|
||||
val article = tag("article")
|
||||
val aside = tag("aside")
|
||||
val rating = tag("rating")
|
||||
|
||||
val frameborder = attr("frameborder")
|
||||
}
|
||||
}
|
||||
|
||||
// what to import in a pure scalatags template
|
||||
|
@ -65,42 +93,24 @@ trait ScalatagsTemplate extends Styles
|
|||
with ScalatagsPrefix {
|
||||
|
||||
val trans = lila.i18n.I18nKeys
|
||||
def main = scalatags.Text.tags2.main
|
||||
|
||||
/* Convert play URLs to scalatags attributes with toString */
|
||||
implicit val playCallAttr = genericAttr[play.api.mvc.Call]
|
||||
}
|
||||
|
||||
object ScalatagsTemplate extends ScalatagsTemplate
|
||||
|
||||
// what to import in all twirl templates
|
||||
trait ScalatagsTwirl extends ScalatagsPlay
|
||||
|
||||
// what to import in twirl templates containing scalatags forms
|
||||
// Allows `*.rows := 5`
|
||||
trait ScalatagsTwirlForm extends ScalatagsPlay with Cap with Aggregate {
|
||||
object * extends Cap with Attrs with ScalatagsAttrs
|
||||
}
|
||||
object ScalatagsTwirlForm extends ScalatagsTwirlForm
|
||||
|
||||
// interop with play
|
||||
trait ScalatagsPlay {
|
||||
|
||||
/* Feed frags back to twirl by converting them to rendered Html */
|
||||
implicit def fragToPlayHtml(frag: Frag): Html = Html(frag.render)
|
||||
|
||||
/* Use play Html inside tags without double-encoding */
|
||||
implicit def playHtmlToFrag(html: Html): Frag = RawFrag(html.body)
|
||||
|
||||
/* Convert play URLs to scalatags attributes with toString */
|
||||
implicit val playCallAttr = genericAttr[play.api.mvc.Call]
|
||||
|
||||
@inline implicit def fragToHtml(frag: Frag) = new FragToHtml(frag)
|
||||
}
|
||||
|
||||
final class FragToHtml(private val self: Frag) extends AnyVal {
|
||||
def toHtml: Html = Html(self.render)
|
||||
}
|
||||
|
||||
// generic extensions
|
||||
trait ScalatagsExtensions {
|
||||
|
||||
implicit def stringValueFrag(sv: StringValue): Frag = new StringFrag(sv.value)
|
||||
|
||||
implicit val stringValueAttr = new AttrValue[StringValue] {
|
||||
def apply(t: scalatags.text.Builder, a: Attr, v: StringValue): Unit =
|
||||
t.setAttr(a.name, scalatags.text.Builder.GenericAttrValueSource(v.value))
|
||||
}
|
||||
|
||||
implicit val charAttr = genericAttr[Char]
|
||||
|
||||
implicit val optionStringAttr = new AttrValue[Option[String]] {
|
||||
|
@ -111,11 +121,6 @@ trait ScalatagsExtensions {
|
|||
}
|
||||
}
|
||||
|
||||
implicit val optionBooleanAttr = new AttrValue[Option[Boolean]] {
|
||||
def apply(t: scalatags.text.Builder, a: Attr, v: Option[Boolean]): Unit =
|
||||
if (~v) t.setAttr(a.name, scalatags.text.Builder.GenericAttrValueSource("true"))
|
||||
}
|
||||
|
||||
/* for class maps such as List("foo" -> true, "active" -> isActive) */
|
||||
implicit val classesAttr = new AttrValue[List[(String, Boolean)]] {
|
||||
def apply(t: scalatags.text.Builder, a: Attr, m: List[(String, Boolean)]): Unit = {
|
||||
|
|
|
@ -8,10 +8,10 @@ import lila.pref.PrefCateg
|
|||
|
||||
object bits {
|
||||
|
||||
def categName(categ: lila.pref.PrefCateg)(implicit ctx: Context) = categ match {
|
||||
case PrefCateg.GameDisplay => trans.gameDisplay()
|
||||
case PrefCateg.ChessClock => trans.chessClock()
|
||||
case PrefCateg.GameBehavior => trans.gameBehavior()
|
||||
case PrefCateg.Privacy => trans.privacy()
|
||||
def categName(categ: lila.pref.PrefCateg)(implicit ctx: Context): String = categ match {
|
||||
case PrefCateg.GameDisplay => trans.gameDisplay.txt()
|
||||
case PrefCateg.ChessClock => trans.chessClock.txt()
|
||||
case PrefCateg.GameBehavior => trans.gameBehavior.txt()
|
||||
case PrefCateg.Privacy => trans.privacy.txt()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,26 +11,24 @@ object close {
|
|||
|
||||
def apply(u: lila.user.User, form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = s"${u.username} - ${trans.closeAccount.txt()}",
|
||||
active = "close",
|
||||
evenMoreCss = cssTag("form3.css")
|
||||
active = "close"
|
||||
) {
|
||||
div(cls := "content_box small_box")(
|
||||
div(cls := "signup_box")(
|
||||
h1(dataIcon := "j", cls := "lichess_title text")(trans.closeAccount.frag()),
|
||||
st.form(cls := "form3", action := routes.Account.closeConfirm, method := "POST")(
|
||||
div(cls := "form-group")(trans.closeAccountExplanation.frag()),
|
||||
div(cls := "form-group")("You will not be allowed to open a new account with the same name, even if the case if different."),
|
||||
form3.passwordNoAutocomplete(form("passwd"), trans.password.frag()),
|
||||
form3.actions(frag(
|
||||
a(href := routes.User.show(u.username))(trans.changedMindDoNotCloseAccount.frag()),
|
||||
form3.submit(
|
||||
trans.closeAccount.frag(),
|
||||
icon = "j".some,
|
||||
confirm = "Closing is definitive. There is no going back. Are you sure?".some
|
||||
)
|
||||
))
|
||||
div(cls := "account box box-pad")(
|
||||
h1(dataIcon := "j", cls := "text")(trans.closeAccount()),
|
||||
st.form(cls := "form3", action := routes.Account.closeConfirm, method := "POST")(
|
||||
div(cls := "form-group")(trans.closeAccountExplanation()),
|
||||
div(cls := "form-group")("You will not be allowed to open a new account with the same name, even if the case if different."),
|
||||
form3.passwordModified(form("passwd"), trans.password())(autocomplete := "off"),
|
||||
form3.actions(frag(
|
||||
a(href := routes.User.show(u.username))(trans.changedMindDoNotCloseAccount()),
|
||||
form3.submit(
|
||||
trans.closeAccount(),
|
||||
icon = "j".some,
|
||||
confirm = "Closing is definitive. There is no going back. Are you sure?".some,
|
||||
klass = "button-red"
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package views.html
|
||||
package account
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object email {
|
||||
|
||||
def apply(u: lila.user.User, form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = trans.changeEmail.txt(),
|
||||
active = "email"
|
||||
) {
|
||||
div(cls := "account box box-pad")(
|
||||
h1(
|
||||
trans.changeEmail(),
|
||||
ctx.req.queryString.contains("ok") option
|
||||
frag(" ", i(cls := "is-green", dataIcon := "E"))
|
||||
),
|
||||
st.form(cls := "form3", action := routes.Account.emailApply, method := "POST")(
|
||||
form3.password(form("passwd"), trans.password()),
|
||||
form3.group(form("email"), trans.email())(form3.input(_, typ = "email")(required)),
|
||||
form3.action(form3.submit(trans.apply()))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
@(u: User, form: Form[_])(implicit ctx: Context)
|
||||
@import lila.app.ui.ScalatagsTwirlForm._
|
||||
|
||||
@account.layout(title = s"${u.username} - ${trans.changeEmail.txt()}", active = "email", evenMoreCss = cssTag("form3.css")) {
|
||||
<div class="content_box small_box">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">
|
||||
@trans.changeEmail()
|
||||
@if(ctx.req.queryString.contains("ok")) { <span class="is-green" data-icon="E"></span>}
|
||||
</h1>
|
||||
<form class="form3" action="@routes.Account.emailApply" method="POST">
|
||||
@form3.password(form("passwd"), trans.password.frag()).toHtml
|
||||
@form3.group(form("email"), trans.email.frag())(form3.input(_, typ = "email"))
|
||||
@form3.actionHtml(form3.submit(trans.apply.frag()))
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}.toHtml
|
|
@ -16,23 +16,23 @@ object emailConfirmHelp {
|
|||
|
||||
def apply(form: Form[_], status: Option[Status])(implicit ctx: Context) = views.html.base.layout(
|
||||
title = title,
|
||||
moreCss = cssTags("form3.css", "emailConfirmHelp.css"),
|
||||
moreCss = cssTag("email-confirm"),
|
||||
moreJs = jsTag("emailConfirmHelp.js")
|
||||
)(frag(
|
||||
div(cls := "content_box small_box emailConfirmHelp")(
|
||||
h1(cls := "lichess_title")(title),
|
||||
main(cls := "page-small box box-pad email-confirm-help")(
|
||||
h1(title),
|
||||
p("You signed up, but didn't receive your confirmation email?"),
|
||||
st.form(cls := "form3", action := routes.Account.emailConfirmHelp, method := "get")(
|
||||
form3.split(
|
||||
form3.group(
|
||||
form("username"),
|
||||
trans.username.frag(),
|
||||
trans.username(),
|
||||
help = raw("What username did you create?").some
|
||||
) { f =>
|
||||
form3.input(f)(pattern := lila.user.User.newUsernameRegex.regex)
|
||||
},
|
||||
div(cls := "form-group")(
|
||||
form3.submit(trans.apply.frag())
|
||||
form3.submit(trans.apply())
|
||||
)
|
||||
)
|
||||
),
|
||||
|
|
|
@ -13,20 +13,25 @@ object kid {
|
|||
title = s"${u.username} - ${trans.kidMode.txt()}",
|
||||
active = "kid"
|
||||
) {
|
||||
div(cls := "content_box small_box high")(
|
||||
div(cls := "signup_box")(
|
||||
h1(cls := "lichess_title")(trans.kidMode.frag()),
|
||||
p(cls := "explanation")(trans.kidModeExplanation.frag()),
|
||||
br,
|
||||
br,
|
||||
br,
|
||||
st.form(action := s"${routes.Account.kidPost}?v=${!u.kid}", method := "POST")(
|
||||
input(tpe := "submit", cls := "submit button", value := (if (u.kid) { trans.disableKidMode.txt() } else { trans.enableKidMode.txt() }))
|
||||
),
|
||||
br,
|
||||
br,
|
||||
p(trans.inKidModeTheLichessLogoGetsIconX.frag(raw(s"""<span title="${trans.kidMode()}" class="kiddo">😊</span>""")))
|
||||
)
|
||||
div(cls := "account box box-pad")(
|
||||
h1(trans.kidMode()),
|
||||
p(trans.kidModeExplanation()),
|
||||
br,
|
||||
br,
|
||||
br,
|
||||
st.form(action := s"${routes.Account.kidPost}?v=${!u.kid}", method := "POST")(
|
||||
input(
|
||||
tpe := "submit",
|
||||
cls := List(
|
||||
"button" -> true,
|
||||
"button-red" -> u.kid
|
||||
),
|
||||
value := (if (u.kid) { trans.disableKidMode.txt() } else { trans.enableKidMode.txt() })
|
||||
)
|
||||
),
|
||||
br,
|
||||
br,
|
||||
p(trans.inKidModeTheLichessLogoGetsIconX(span(cls := "kiddo", title := trans.kidMode.txt())(":)")))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
package views.html
|
||||
package account
|
||||
|
||||
import play.twirl.api.Html
|
||||
package views.html.account
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
|
@ -14,53 +11,58 @@ object layout {
|
|||
def apply(
|
||||
title: String,
|
||||
active: String,
|
||||
evenMoreCss: Html = emptyHtml,
|
||||
evenMoreJs: Html = emptyHtml
|
||||
)(body: Html)(implicit ctx: Context) = views.html.base.layout(
|
||||
evenMoreCss: Frag = emptyFrag,
|
||||
evenMoreJs: Frag = emptyFrag
|
||||
)(body: Frag)(implicit ctx: Context): Frag = views.html.base.layout(
|
||||
title = title,
|
||||
menu = Some(frag(
|
||||
lila.pref.PrefCateg.all.map { categ =>
|
||||
a(cls := active.activeO(categ.slug), href := routes.Pref.form(categ.slug))(
|
||||
bits.categName(categ)
|
||||
)
|
||||
},
|
||||
a(cls := active.activeO("kid"), href := routes.Account.kid())(
|
||||
trans.kidMode.frag()
|
||||
),
|
||||
div(cls := "sep"),
|
||||
a(cls := active.activeO("editProfile"), href := routes.Account.profile())(
|
||||
trans.editProfile.frag()
|
||||
),
|
||||
isGranted(_.Coach) option a(href := routes.Coach.edit)("Coach profile"),
|
||||
div(cls := "sep"),
|
||||
a(cls := active.activeO("username"), href := routes.Account.username())(
|
||||
trans.changeUsername.frag()
|
||||
),
|
||||
a(cls := active.activeO("password"), href := routes.Account.passwd())(
|
||||
trans.changePassword.frag()
|
||||
),
|
||||
a(cls := active.activeO("email"), href := routes.Account.email())(
|
||||
trans.changeEmail.frag()
|
||||
),
|
||||
a(cls := active.activeO("twofactor"), href := routes.Account.twoFactor())(
|
||||
"Two-factor authentication"
|
||||
),
|
||||
a(cls := active.activeO("security"), href := routes.Account.security())(
|
||||
trans.security.frag()
|
||||
),
|
||||
div(cls := "sep"),
|
||||
a(href := routes.Plan.index, style := "color: #d59120; font-weight: bold;")("Patron"),
|
||||
div(cls := "sep"),
|
||||
a(cls := active.activeO("oauth.token"), href := routes.OAuthToken.index)(
|
||||
"API Access tokens"
|
||||
),
|
||||
ctx.noBot option a(cls := active.activeO("oauth.app"), href := routes.OAuthApp.index)("OAuth Apps"),
|
||||
div(cls := "sep"),
|
||||
a(cls := active.activeO("close"), href := routes.Account.close())(
|
||||
trans.closeAccount.frag()
|
||||
)
|
||||
)),
|
||||
moreCss = frag(cssTag("account.css"), evenMoreCss),
|
||||
moreCss = frag(cssTag("account"), evenMoreCss),
|
||||
moreJs = frag(jsTag("account.js"), evenMoreJs)
|
||||
)(body)
|
||||
) {
|
||||
def activeCls(c: String) = cls := active.activeO(c)
|
||||
main(cls := "account page-menu")(
|
||||
st.nav(cls := "page-menu__menu subnav")(
|
||||
lila.pref.PrefCateg.all.map { categ =>
|
||||
a(activeCls(categ.slug), href := routes.Pref.form(categ.slug))(
|
||||
bits.categName(categ)
|
||||
)
|
||||
},
|
||||
a(activeCls("kid"), href := routes.Account.kid())(
|
||||
trans.kidMode()
|
||||
),
|
||||
div(cls := "sep"),
|
||||
a(activeCls("editProfile"), href := routes.Account.profile())(
|
||||
trans.editProfile()
|
||||
),
|
||||
isGranted(_.Coach) option a(activeCls("coach"), href := routes.Coach.edit)("Coach profile"),
|
||||
div(cls := "sep"),
|
||||
a(activeCls("password"), href := routes.Account.passwd())(
|
||||
trans.changePassword()
|
||||
),
|
||||
a(activeCls("email"), href := routes.Account.email())(
|
||||
trans.changeEmail()
|
||||
),
|
||||
a(activeCls("username"), href := routes.Account.username())(
|
||||
trans.changeUsername()
|
||||
),
|
||||
a(activeCls("twofactor"), href := routes.Account.twoFactor())(
|
||||
"Two-factor authentication"
|
||||
),
|
||||
a(activeCls("security"), href := routes.Account.security())(
|
||||
trans.security()
|
||||
),
|
||||
div(cls := "sep"),
|
||||
a(href := routes.Plan.index)("Patron"),
|
||||
div(cls := "sep"),
|
||||
a(activeCls("oauth.token"), href := routes.OAuthToken.index)(
|
||||
"API Access tokens"
|
||||
),
|
||||
ctx.noBot option a(activeCls("oauth.app"), href := routes.OAuthApp.index)("OAuth Apps"),
|
||||
div(cls := "sep"),
|
||||
a(activeCls("close"), href := routes.Account.close())(
|
||||
trans.closeAccount()
|
||||
)
|
||||
),
|
||||
div(cls := "page-menu__content")(body)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,21 +11,19 @@ object passwd {
|
|||
|
||||
def apply(form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = trans.changePassword.txt(),
|
||||
active = "password",
|
||||
evenMoreCss = cssTag("form3.css")
|
||||
active = "password"
|
||||
) {
|
||||
div(cls := "content_box small_box")(
|
||||
div(cls := "signup_box")(
|
||||
h1(cls := "lichess_title")(
|
||||
trans.changePassword.frag(),
|
||||
raw(ctx.req.queryString.contains("ok") ?? """ <span class="is-green" data-icon="E"></span>""")
|
||||
),
|
||||
st.form(cls := "form3", action := routes.Account.passwdApply, method := "POST")(
|
||||
form3.password(form("oldPasswd"), trans.currentPassword.frag()),
|
||||
form3.password(form("newPasswd1"), trans.newPassword.frag()),
|
||||
form3.password(form("newPasswd2"), trans.newPasswordAgain.frag()),
|
||||
form3.actionHtml(form3.submit(trans.apply.frag()))
|
||||
)
|
||||
div(cls := "account box box-pad")(
|
||||
h1(
|
||||
trans.changePassword(),
|
||||
ctx.req.queryString.contains("ok") option
|
||||
frag(" ", i(cls := "is-green", dataIcon := "E"))
|
||||
),
|
||||
st.form(cls := "form3", action := routes.Account.passwdApply, method := "POST")(
|
||||
form3.password(form("oldPasswd"), trans.currentPassword()),
|
||||
form3.password(form("newPasswd1"), trans.newPassword()),
|
||||
form3.password(form("newPasswd2"), trans.newPasswordAgain()),
|
||||
form3.action(form3.submit(trans.apply()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ import controllers.routes
|
|||
|
||||
object pref {
|
||||
|
||||
private def categFieldset(categ: lila.pref.PrefCateg, active: lila.pref.PrefCateg)(body: Frag) =
|
||||
div(cls := List("none" -> (categ != active)))(body)
|
||||
private def categFieldset(categ: lila.pref.PrefCateg, active: lila.pref.PrefCateg) =
|
||||
div(cls := List("none" -> (categ != active)))
|
||||
|
||||
private def setting(name: Frag, body: Frag) = li(h2(name), body)
|
||||
private def setting(name: Frag, body: Frag) = st.section(h2(name), body)
|
||||
|
||||
private def radios(field: play.api.data.Field, options: Iterable[(Any, String)], prefix: String = "ir") =
|
||||
st.group(cls := "radio")(
|
||||
|
@ -23,7 +23,7 @@ object pref {
|
|||
div(
|
||||
input(
|
||||
st.id := s"$prefix$id",
|
||||
st.checked := checked option true,
|
||||
checked option st.checked,
|
||||
cls := checked option "active",
|
||||
`type` := "radio",
|
||||
value := v._1.toString,
|
||||
|
@ -36,137 +36,130 @@ object pref {
|
|||
|
||||
def apply(u: lila.user.User, form: play.api.data.Form[_], categ: lila.pref.PrefCateg)(implicit ctx: Context) = account.layout(
|
||||
title = s"${bits.categName(categ)} - ${u.username} - ${trans.preferences.txt()}",
|
||||
active = categ.slug,
|
||||
evenMoreCss = cssTag("pref.css")
|
||||
active = categ.slug
|
||||
) {
|
||||
val booleanChoices = Seq(0 -> trans.no.txt(), 1 -> trans.yes.txt())
|
||||
div(cls := "content_box small_box prefs")(
|
||||
div(cls := "signup_box")(
|
||||
h1(cls := "lichess_title text", dataIcon := "%")(bits.categName(categ)),
|
||||
st.form(cls := "autosubmit", action := routes.Pref.formApply, method := "POST")(
|
||||
categFieldset(PrefCateg.GameDisplay, categ) {
|
||||
ul(
|
||||
setting(
|
||||
trans.pieceAnimation.frag(),
|
||||
radios(form("display.animation"), translatedAnimationChoices)
|
||||
),
|
||||
setting(
|
||||
trans.materialDifference.frag(),
|
||||
radios(form("display.captured"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.boardHighlights.frag(),
|
||||
radios(form("display.highlight"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.pieceDestinations.frag(),
|
||||
radios(form("display.destination"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.boardCoordinates.frag(),
|
||||
radios(form("display.coords"), translatedBoardCoordinateChoices)
|
||||
),
|
||||
setting(
|
||||
trans.moveListWhilePlaying.frag(),
|
||||
radios(form("display.replay"), translatedMoveListWhilePlayingChoices)
|
||||
),
|
||||
setting(
|
||||
trans.pgnPieceNotation.frag(),
|
||||
radios(form("display.pieceNotation"), translatedPieceNotationChoices)
|
||||
),
|
||||
setting(
|
||||
trans.zenMode.frag(),
|
||||
radios(form("display.zen"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.blindfoldChess.frag(),
|
||||
radios(form("display.blindfold"), translatedBlindfoldChoices)
|
||||
)
|
||||
)
|
||||
},
|
||||
categFieldset(PrefCateg.ChessClock, categ) {
|
||||
ul(
|
||||
setting(
|
||||
trans.tenthsOfSeconds.frag(),
|
||||
radios(form("clockTenths"), translatedClockTenthsChoices)
|
||||
),
|
||||
setting(
|
||||
trans.horizontalGreenProgressBars.frag(),
|
||||
radios(form("clockBar"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.soundWhenTimeGetsCritical.frag(),
|
||||
radios(form("clockSound"), booleanChoices)
|
||||
)
|
||||
)
|
||||
},
|
||||
categFieldset(PrefCateg.GameBehavior, categ) {
|
||||
ul(
|
||||
setting(
|
||||
trans.howDoYouMovePieces.frag(),
|
||||
radios(form("behavior.moveEvent"), translatedMoveEventChoices)
|
||||
),
|
||||
setting(
|
||||
trans.premovesPlayingDuringOpponentTurn.frag(),
|
||||
radios(form("behavior.premove"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.takebacksWithOpponentApproval.frag(),
|
||||
radios(form("behavior.takeback"), translatedTakebackChoices)
|
||||
),
|
||||
setting(
|
||||
trans.promoteToQueenAutomatically.frag(),
|
||||
radios(form("behavior.autoQueen"), translatedAutoQueenChoices)
|
||||
),
|
||||
setting(
|
||||
trans.claimDrawOnThreefoldRepetitionAutomatically.frag(),
|
||||
radios(form("behavior.autoThreefold"), translatedAutoThreefoldChoices)
|
||||
),
|
||||
setting(
|
||||
trans.moveConfirmation.frag(),
|
||||
radios(form("behavior.submitMove"), submitMoveChoices)
|
||||
),
|
||||
setting(
|
||||
trans.confirmResignationAndDrawOffers.frag(),
|
||||
radios(form("behavior.confirmResign"), confirmResignChoices)
|
||||
),
|
||||
setting(
|
||||
trans.inputMovesWithTheKeyboard.frag(),
|
||||
radios(form("behavior.keyboardMove"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.castleByMovingTheKingTwoSquaresOrOntoTheRook.frag(),
|
||||
radios(form("behavior.rookCastle"), translatedRookCastleChoices)
|
||||
)
|
||||
)
|
||||
},
|
||||
categFieldset(PrefCateg.Privacy, categ) {
|
||||
ul(
|
||||
setting(
|
||||
trans.letOtherPlayersFollowYou.frag(),
|
||||
radios(form("follow"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.letOtherPlayersChallengeYou.frag(),
|
||||
radios(form("challenge"), translatedChallengeChoices)
|
||||
),
|
||||
setting(
|
||||
trans.letOtherPlayersMessageYou.frag(),
|
||||
radios(form("message"), translatedMessageChoices)
|
||||
),
|
||||
setting(
|
||||
trans.letOtherPlayersInviteYouToStudy.frag(),
|
||||
radios(form("studyInvite"), translatedStudyInviteChoices)
|
||||
),
|
||||
setting(
|
||||
trans.shareYourInsightsData.frag(),
|
||||
radios(form("insightShare"), translatedInsightSquareChoices)
|
||||
)
|
||||
)
|
||||
},
|
||||
p(cls := "saved text none", dataIcon := "E")(trans.yourPreferencesHaveBeenSaved.frag())
|
||||
val booleanChoices = Seq(0 -> trans.no.txt(), 1 -> trans.yes.txt())
|
||||
div(cls := "account box box-pad")(
|
||||
h1(bits.categName(categ)),
|
||||
st.form(cls := "autosubmit", action := routes.Pref.formApply, method := "POST")(
|
||||
categFieldset(PrefCateg.GameDisplay, categ)(
|
||||
setting(
|
||||
trans.pieceAnimation(),
|
||||
radios(form("display.animation"), translatedAnimationChoices)
|
||||
),
|
||||
setting(
|
||||
trans.materialDifference(),
|
||||
radios(form("display.captured"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.boardHighlights(),
|
||||
radios(form("display.highlight"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.pieceDestinations(),
|
||||
radios(form("display.destination"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.boardCoordinates(),
|
||||
radios(form("display.coords"), translatedBoardCoordinateChoices)
|
||||
),
|
||||
setting(
|
||||
trans.moveListWhilePlaying(),
|
||||
radios(form("display.replay"), translatedMoveListWhilePlayingChoices)
|
||||
),
|
||||
setting(
|
||||
trans.pgnPieceNotation(),
|
||||
radios(form("display.pieceNotation"), translatedPieceNotationChoices)
|
||||
),
|
||||
setting(
|
||||
trans.zenMode(),
|
||||
radios(form("display.zen"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
"Display board resize handle",
|
||||
radios(form("display.resizeHandle"), translatedBoardResizeHandleChoices)
|
||||
),
|
||||
setting(
|
||||
trans.blindfoldChess(),
|
||||
radios(form("display.blindfold"), translatedBlindfoldChoices)
|
||||
)
|
||||
)
|
||||
),
|
||||
categFieldset(PrefCateg.ChessClock, categ)(
|
||||
setting(
|
||||
trans.tenthsOfSeconds(),
|
||||
radios(form("clockTenths"), translatedClockTenthsChoices)
|
||||
),
|
||||
setting(
|
||||
trans.horizontalGreenProgressBars(),
|
||||
radios(form("clockBar"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.soundWhenTimeGetsCritical(),
|
||||
radios(form("clockSound"), booleanChoices)
|
||||
)
|
||||
),
|
||||
categFieldset(PrefCateg.GameBehavior, categ)(
|
||||
setting(
|
||||
trans.howDoYouMovePieces(),
|
||||
radios(form("behavior.moveEvent"), translatedMoveEventChoices)
|
||||
),
|
||||
setting(
|
||||
trans.premovesPlayingDuringOpponentTurn(),
|
||||
radios(form("behavior.premove"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.takebacksWithOpponentApproval(),
|
||||
radios(form("behavior.takeback"), translatedTakebackChoices)
|
||||
),
|
||||
setting(
|
||||
trans.promoteToQueenAutomatically(),
|
||||
radios(form("behavior.autoQueen"), translatedAutoQueenChoices)
|
||||
),
|
||||
setting(
|
||||
trans.claimDrawOnThreefoldRepetitionAutomatically(),
|
||||
radios(form("behavior.autoThreefold"), translatedAutoThreefoldChoices)
|
||||
),
|
||||
setting(
|
||||
trans.moveConfirmation(),
|
||||
radios(form("behavior.submitMove"), submitMoveChoices)
|
||||
),
|
||||
setting(
|
||||
trans.confirmResignationAndDrawOffers(),
|
||||
radios(form("behavior.confirmResign"), confirmResignChoices)
|
||||
),
|
||||
setting(
|
||||
trans.castleByMovingTheKingTwoSquaresOrOntoTheRook(),
|
||||
radios(form("behavior.rookCastle"), translatedRookCastleChoices)
|
||||
),
|
||||
setting(
|
||||
trans.inputMovesWithTheKeyboard(),
|
||||
radios(form("behavior.keyboardMove"), booleanChoices)
|
||||
)
|
||||
),
|
||||
categFieldset(PrefCateg.Privacy, categ)(
|
||||
setting(
|
||||
trans.letOtherPlayersFollowYou(),
|
||||
radios(form("follow"), booleanChoices)
|
||||
),
|
||||
setting(
|
||||
trans.letOtherPlayersChallengeYou(),
|
||||
radios(form("challenge"), translatedChallengeChoices)
|
||||
),
|
||||
setting(
|
||||
trans.letOtherPlayersMessageYou(),
|
||||
radios(form("message"), translatedMessageChoices)
|
||||
),
|
||||
setting(
|
||||
trans.letOtherPlayersInviteYouToStudy(),
|
||||
radios(form("studyInvite"), translatedStudyInviteChoices)
|
||||
),
|
||||
setting(
|
||||
trans.shareYourInsightsData(),
|
||||
radios(form("insightShare"), translatedInsightSquareChoices)
|
||||
)
|
||||
),
|
||||
p(cls := "saved text none", dataIcon := "E")(trans.yourPreferencesHaveBeenSaved())
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,38 +17,37 @@ object profile {
|
|||
|
||||
def apply(u: lila.user.User, form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = s"${u.username} - ${trans.editProfile.txt()}",
|
||||
active = "editProfile",
|
||||
evenMoreCss = cssTag("form3.css")
|
||||
active = "editProfile"
|
||||
) {
|
||||
div(cls := "content_box small_box")(
|
||||
h1(cls := "lichess_title text", dataIcon := "*")(trans.editProfile()),
|
||||
st.form(cls := "form3", action := routes.Account.profileApply, method := "POST")(
|
||||
div(cls := "form-group")(trans.allInformationIsPublicAndOptional()),
|
||||
form3.split(
|
||||
form3.group(form("country"), trans.country.frag(), half = true) { f =>
|
||||
form3.select(f, lila.user.Countries.allPairs, default = "".some)
|
||||
},
|
||||
form3.group(form("location"), trans.location.frag(), half = true)(form3.input(_))
|
||||
),
|
||||
NotForKids {
|
||||
form3.group(form("bio"), trans.biography.frag(), help = trans.biographyDescription.frag().some) { f =>
|
||||
form3.textarea(f)(rows := 5)
|
||||
}
|
||||
div(cls := "account box box-pad")(
|
||||
h1(trans.editProfile()),
|
||||
st.form(cls := "form3", action := routes.Account.profileApply, method := "POST")(
|
||||
div(cls := "form-group")(trans.allInformationIsPublicAndOptional()),
|
||||
form3.split(
|
||||
form3.group(form("country"), trans.country(), half = true) { f =>
|
||||
form3.select(f, lila.user.Countries.allPairs, default = "".some)
|
||||
},
|
||||
form3.split(
|
||||
form3.group(form("firstName"), trans.firstName.frag(), half = true)(form3.input(_)),
|
||||
form3.group(form("lastName"), trans.lastName.frag(), half = true)(form3.input(_))
|
||||
),
|
||||
form3.split(
|
||||
List("fide", "uscf", "ecf").map { rn =>
|
||||
form3.group(form(s"${rn}Rating"), trans.xRating.frag(rn.toUpperCase), help = trans.ifNoneLeaveEmpty.frag().some, klass = "form-third")(form3.input(_, typ = "number"))
|
||||
}
|
||||
),
|
||||
form3.group(form("links"), raw("Social media links "), help = Some(linksHelp)) { f =>
|
||||
form3.group(form("location"), trans.location(), half = true)(form3.input(_))
|
||||
),
|
||||
NotForKids {
|
||||
form3.group(form("bio"), trans.biography(), help = trans.biographyDescription().some) { f =>
|
||||
form3.textarea(f)(rows := 5)
|
||||
},
|
||||
form3.actionHtml(form3.submit(trans.apply.frag()))
|
||||
)
|
||||
}
|
||||
},
|
||||
form3.split(
|
||||
form3.group(form("firstName"), trans.firstName(), half = true)(form3.input(_)),
|
||||
form3.group(form("lastName"), trans.lastName(), half = true)(form3.input(_))
|
||||
),
|
||||
form3.split(
|
||||
List("fide", "uscf", "ecf").map { rn =>
|
||||
form3.group(form(s"${rn}Rating"), trans.xRating(rn.toUpperCase), help = trans.ifNoneLeaveEmpty().some, klass = "form-third")(form3.input(_, typ = "number"))
|
||||
}
|
||||
),
|
||||
form3.group(form("links"), raw("Social media links "), help = Some(linksHelp)) { f =>
|
||||
form3.textarea(f)(rows := 5)
|
||||
},
|
||||
form3.action(form3.submit(trans.apply()))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package views.html
|
||||
package account
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object security {
|
||||
|
||||
def apply(u: lila.user.User, sessions: List[lila.security.LocatedSession], curSessionId: String)(implicit ctx: Context) =
|
||||
account.layout(title = s"${u.username} - ${trans.security.txt()}", active = "security") {
|
||||
div(cls := "account security box")(
|
||||
h1(trans.security()),
|
||||
div(cls := "box__pad")(
|
||||
p(trans.thisIsAListOfDevicesThatHaveLoggedIntoYourAccount()),
|
||||
sessions.length > 1 option div(
|
||||
trans.alternativelyYouCanX {
|
||||
form(cls := "revoke-all", action := routes.Account.signout("all"), method := "POST")(
|
||||
button(tpe := "submit", cls := "button button-empty button-red confirm")(
|
||||
trans.revokeAllSessions()
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
table(cls := "slist slist-pad")(
|
||||
sessions.map { s =>
|
||||
tr(
|
||||
td(cls := "icon")(
|
||||
span(
|
||||
cls := s"is-${if (s.session.id == curSessionId) "gold" else "green"}",
|
||||
dataIcon := (if (s.session.isMobile) "" else "")
|
||||
)
|
||||
),
|
||||
td(cls := "info")(
|
||||
span(cls := "ip")(s.session.ip),
|
||||
" ",
|
||||
span(cls := "location")(s.location.map(_.toString)),
|
||||
p(cls := "ua")(s.session.ua),
|
||||
s.session.date.map { date =>
|
||||
p(cls := "date")(
|
||||
momentFromNow(date),
|
||||
s.session.id == curSessionId option span(cls := "current")("[CURRENT]")
|
||||
)
|
||||
}
|
||||
),
|
||||
td(
|
||||
s.session.id != curSessionId option
|
||||
form(action := routes.Account.signout(s.session.id), method := "POST")(
|
||||
button(tpe := "submit", cls := "button button-red", title := trans.logOut.txt(), dataIcon := "L")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
@(u: User, sessions: List[lila.security.LocatedSession], curSessionId: String)(implicit ctx: Context)
|
||||
|
||||
@title = @{ s"${u.username} - ${trans.security.txt()}" }
|
||||
|
||||
@account.layout(title = title, active = "security") {
|
||||
<div class="content_box no_padding high security">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">@trans.security()</h1>
|
||||
<p class="explanation">@trans.thisIsAListOfDevicesThatHaveLoggedIntoYourAccount()</p>
|
||||
@if(sessions.length > 1) {
|
||||
<div class="explanation">
|
||||
@trans.alternativelyYouCanX {
|
||||
<form class="revoke-all" action="@routes.Account.signout("all")" method="POST">
|
||||
<button type="submit" class="button hint--top thin confirm">@trans.revokeAllSessions()</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<table class="slist">
|
||||
@sessions.map { s =>
|
||||
<tr>
|
||||
<td class="icon">
|
||||
<span class="is-@if(s.session.id == curSessionId){gold}else{green}" data-icon="@if(s.session.isMobile){}else{}"></span>
|
||||
</td>
|
||||
<td class="info">
|
||||
<span class="ip">@s.session.ip</span>
|
||||
<span class="location">@s.location</span>
|
||||
<p class="ua">@s.session.ua</p>
|
||||
@s.session.date.map { date =>
|
||||
<p class="date">
|
||||
@momentFromNow(date)
|
||||
@if(s.session.id == curSessionId) { <span class="current">[CURRENT]</span> }
|
||||
</p>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if(s.session.id != curSessionId) {
|
||||
<form action="@routes.Account.signout(s.session.id)" method="POST">
|
||||
<button type="submit" class="button text hint--top" data-hint="@trans.logOut()">
|
||||
<span data-icon="L"></span>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}.toHtml
|
|
@ -9,19 +9,18 @@ import controllers.routes
|
|||
|
||||
object twoFactor {
|
||||
|
||||
private val qrCode = raw("""<div style="width: 276px; height: 275px; padding: 10px; background: white; margin: 2em auto;"><div id="qrcode" style="width: 256px; height: 256px;"></div></div>""")
|
||||
private val qrCode = raw("""<div style="width: 276px; height: 276px; padding: 10px; background: white; margin: 2em auto;"><div id="qrcode" style="width: 256px; height: 256px;"></div></div>""")
|
||||
|
||||
def setup(u: lila.user.User, form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = s"${u.username} - Two-factor authentication",
|
||||
active = "twofactor",
|
||||
evenMoreCss = cssTag("form3.css"),
|
||||
evenMoreJs = frag(
|
||||
jsAt("javascripts/vendor/qrcode.min.js"),
|
||||
jsTag("twofactor.form.js")
|
||||
)
|
||||
) {
|
||||
div(cls := "content_box small_box high twofactor")(
|
||||
h1(cls := "lichess_title")("Setup two-factor authentication"),
|
||||
div(cls := "account twofactor box box-pad")(
|
||||
h1("Setup two-factor authentication"),
|
||||
st.form(cls := "form3", action := routes.Account.setupTwoFactor, method := "POST")(
|
||||
div(cls := "form-group")("Two-factor authentication adds another layer of security to your account."),
|
||||
div(cls := "form-group")(
|
||||
|
@ -31,35 +30,34 @@ object twoFactor {
|
|||
qrCode,
|
||||
div(cls := "form-group explanation")("Enter your password and the authentication code generated by the app to complete the setup. You will need an authentication code every time you log in."),
|
||||
form3.hidden(form("secret")),
|
||||
form3.password(form("passwd"), trans.password.frag()),
|
||||
form3.group(form("token"), raw("Authentication code"))(form3.input(_)(pattern := "[0-9]{6}", autocomplete := "off", required := "")),
|
||||
form3.password(form("passwd"), trans.password()),
|
||||
form3.group(form("token"), raw("Authentication code"))(form3.input(_)(pattern := "[0-9]{6}", autocomplete := "off", required)),
|
||||
form3.globalError(form),
|
||||
div(cls := "form-group")("Note: If you lose access to your two-factor authentication codes, you can do a password reset via email."),
|
||||
form3.actionHtml(form3.submit(raw("Enable two-factor authentication")))
|
||||
form3.action(form3.submit(raw("Enable two-factor authentication")))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def disable(u: lila.user.User, form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = s"${u.username} - Two-factor authentication",
|
||||
active = "twofactor",
|
||||
evenMoreCss = cssTag("form3.css")
|
||||
active = "twofactor"
|
||||
) {
|
||||
div(cls := "content_box small_box high twofactor")(
|
||||
h1(cls := "lichess_title")(
|
||||
raw("""<i data-icon="E" class="is-green"></i> """),
|
||||
"Two-factor authentication enabled"
|
||||
div(cls := "account twofactor box box-pad")(
|
||||
h1(
|
||||
i(cls := "is-green", dataIcon := "E"),
|
||||
" Two-factor authentication enabled"
|
||||
),
|
||||
p("Your account is protected with two-factor authentication."),
|
||||
st.form(cls := "form3", action := routes.Account.disableTwoFactor, method := "POST")(
|
||||
p(
|
||||
"You need your password and an authentication code from your authenticator app to disable two-factor authentication. ",
|
||||
"If you lost access to your authentication codes, you can also do a password reset via email."
|
||||
),
|
||||
p(cls := "explanation")("Your account is protected with two-factor authentication."),
|
||||
st.form(cls := "form3", action := routes.Account.disableTwoFactor, method := "POST")(
|
||||
p(cls := "explanation")(
|
||||
"You need your password and an authentication code from your authenticator app to disable two-factor authentication. ",
|
||||
"If you lost access to your authentication codes, you can also do a password reset via email."
|
||||
),
|
||||
form3.password(form("passwd"), trans.password.frag()),
|
||||
form3.group(form("token"), raw("Authentication code"))(form3.input(_)(pattern := "[0-9]{6}", autocomplete := "off", required := "")),
|
||||
form3.actionHtml(form3.submit(raw("Disable two-factor authentication"), icon = None))
|
||||
)
|
||||
form3.password(form("passwd"), trans.password()),
|
||||
form3.group(form("token"), raw("Authentication code"))(form3.input(_)(pattern := "[0-9]{6}", autocomplete := "off", required)),
|
||||
form3.action(form3.submit(raw("Disable two-factor authentication"), icon = None))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,16 +11,15 @@ object username {
|
|||
|
||||
def apply(u: lila.user.User, form: play.api.data.Form[_])(implicit ctx: Context) = account.layout(
|
||||
title = s"${u.username} - ${trans.editProfile.txt()}",
|
||||
active = "editUsername",
|
||||
evenMoreCss = cssTag("form3.css")
|
||||
active = "username"
|
||||
) {
|
||||
div(cls := "content_box small_box")(
|
||||
h1(cls := "lichess_title text", dataIcon := "*")(trans.editProfile()),
|
||||
st.form(cls := "form3", action := routes.Account.usernameApply, method := "POST")(
|
||||
form3.globalError(form),
|
||||
form3.group(form("userName"), trans.username.frag(), half = true, help = trans.changeUsernameDescription.frag().some)(form3.input(_)),
|
||||
form3.actionHtml(form3.submit(trans.apply.frag()))
|
||||
)
|
||||
div(cls := "account box box-pad")(
|
||||
h1(cls := "text", dataIcon := "*")(trans.changeUsername()),
|
||||
st.form(cls := "form3", action := routes.Account.usernameApply, method := "POST")(
|
||||
form3.globalError(form),
|
||||
form3.group(form("username"), trans.username(), help = trans.changeUsernameDescription().some)(form3.input(_)(required)),
|
||||
form3.action(form3.submit(trans.apply()))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package views.html
|
||||
|
||||
import play.twirl.api.Html
|
||||
import scalatags.Text.tags2.section
|
||||
|
||||
import lila.activity.activities._
|
||||
import lila.activity.model._
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.String.html.escapeHtml
|
||||
import lila.user.User
|
||||
|
||||
import controllers.routes
|
||||
|
@ -18,7 +14,7 @@ object activity {
|
|||
def apply(u: User, as: Iterable[lila.activity.ActivityView])(implicit ctx: Context) =
|
||||
div(cls := "activity")(
|
||||
as.toSeq map { a =>
|
||||
section(
|
||||
st.section(
|
||||
h2(semanticDate(a.interval.getStart)),
|
||||
div(cls := "entries")(
|
||||
a.patron map renderPatron,
|
||||
|
@ -49,7 +45,9 @@ object activity {
|
|||
private def renderPatron(p: Patron)(implicit ctx: Context) =
|
||||
div(cls := "entry plan")(
|
||||
iconTag(""),
|
||||
div(trans.activity.supportedNbMonths.plural(p.months, p.months, Html(s"""<a href="${routes.Plan.index}">Patron</a>""")))
|
||||
div(
|
||||
trans.activity.supportedNbMonths.plural(p.months, p.months, a(href := routes.Plan.index)("Patron"))
|
||||
)
|
||||
)
|
||||
|
||||
private def renderPractice(p: Map[lila.practice.PracticeStudy, Int])(implicit ctx: Context) = {
|
||||
|
@ -70,7 +68,7 @@ object activity {
|
|||
case (study, nb) =>
|
||||
val href = routes.Practice.show("-", study.slug, study.id.value)
|
||||
frag(
|
||||
trans.activity.practicedNbPositions.plural(nb, nb, Html(s"""<a href="$href">${study.name}</a>""")),
|
||||
trans.activity.practicedNbPositions.plural(nb, nb, a(st.href := href)(study.name)),
|
||||
br
|
||||
)
|
||||
}
|
||||
|
@ -105,9 +103,8 @@ object activity {
|
|||
posts.toSeq.map {
|
||||
case (topic, posts) =>
|
||||
val url = routes.ForumTopic.show(topic.categId, topic.slug)
|
||||
val content = escapeHtml(shorten(topic.name, 70))
|
||||
frag(
|
||||
trans.activity.postedNbMessages.plural(posts.size, posts.size, Html(s"""<a href="$url">$content</a>""")),
|
||||
trans.activity.postedNbMessages.plural(posts.size, posts.size, a(href := url)(shorten(topic.name, 70))),
|
||||
subTag(
|
||||
posts.map { post =>
|
||||
div(cls := "line")(a(href := routes.ForumPost.redirect(post.id))(shorten(post.text, 120)))
|
||||
|
@ -173,7 +170,7 @@ object activity {
|
|||
if (in) trans.activity.gainedNbFollowers.pluralSame(f.actualNb)
|
||||
else trans.activity.followedNbPlayers.pluralSame(f.actualNb),
|
||||
subTag(
|
||||
htmlList(f.ids.map(id => userIdLink(id.some))),
|
||||
fragList(f.ids.map(id => userIdLink(id.some))),
|
||||
f.nb.map { nb =>
|
||||
frag(" and ", nb - maxSubEntries, " more")
|
||||
}
|
||||
|
@ -185,7 +182,7 @@ object activity {
|
|||
|
||||
private def renderSimuls(u: User)(simuls: List[lila.simul.Simul])(implicit ctx: Context) =
|
||||
entryTag(
|
||||
iconTag("|"),
|
||||
iconTag("f"),
|
||||
div(
|
||||
simuls.groupBy(_.isHost(u.some)).toSeq.map {
|
||||
case (isHost, simuls) => frag(
|
||||
|
@ -226,7 +223,7 @@ object activity {
|
|||
iconTag("f"),
|
||||
div(
|
||||
trans.activity.joinedNbTeams.pluralSame(teams.value.size),
|
||||
subTag(htmlList(teams.value.map(id => teamLink(id))))
|
||||
subTag(fragList(teams.value.map(id => teamLink(id))))
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -245,7 +242,7 @@ object activity {
|
|||
),
|
||||
dataIcon := (t.rank <= 3).option("g")
|
||||
)(
|
||||
trans.activity.rankedInTournament.plural(t.nbGames, Html(s"""<strong>${t.rank}</strong>"""), (t.rankRatio.value * 100).toInt atLeast 1, t.nbGames, link),
|
||||
trans.activity.rankedInTournament.plural(t.nbGames, strong(t.rank), (t.rankRatio.value * 100).toInt atLeast 1, t.nbGames, link),
|
||||
br
|
||||
)
|
||||
}
|
||||
|
@ -273,10 +270,10 @@ object activity {
|
|||
s"""<score>${scoreStr("win", s.win, trans.nbWins)}${scoreStr("draw", s.draw, trans.nbDraws)}${scoreStr("loss", s.loss, trans.nbLosses)}</score>"""
|
||||
}
|
||||
|
||||
private def ratingProgFrag(r: RatingProg)(implicit ctx: Context) = raw {
|
||||
val prog = showProgress(r.diff, withTitle = false)
|
||||
s"""<rating>${r.after.value}$prog</rating>"""
|
||||
}
|
||||
private def ratingProgFrag(r: RatingProg)(implicit ctx: Context) = ratingTag(
|
||||
r.after.value,
|
||||
ratingProgress(r.diff)
|
||||
)
|
||||
|
||||
private def scoreStr(tag: String, p: Int, name: lila.i18n.I18nKey)(implicit ctx: Context) =
|
||||
if (p == 0) ""
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package views.html.analyse
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.analyse.Advice.Judgement
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.analyse.Advice.Judgement
|
||||
|
||||
object bits {
|
||||
|
||||
|
@ -20,18 +18,12 @@ object bits {
|
|||
|
||||
def layout(
|
||||
title: String,
|
||||
side: Option[Frag] = None,
|
||||
chat: Option[Frag] = None,
|
||||
underchat: Option[Frag] = None,
|
||||
moreCss: Html = emptyHtml,
|
||||
moreJs: Html = emptyHtml,
|
||||
moreCss: Frag = emptyFrag,
|
||||
moreJs: Frag = emptyFrag,
|
||||
openGraph: Option[lila.app.ui.OpenGraph] = None
|
||||
)(body: Html)(implicit ctx: Context): Frag =
|
||||
)(body: Frag)(implicit ctx: Context): Frag =
|
||||
views.html.base.layout(
|
||||
title = title,
|
||||
side = side.map(_.toHtml),
|
||||
chat = chat,
|
||||
underchat = underchat,
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
openGraph = openGraph,
|
||||
|
|
|
@ -1,44 +1,36 @@
|
|||
package views.html.analyse
|
||||
|
||||
import play.api.libs.json.Json
|
||||
import scalatags.Text.tags2.{ title => titleTag }
|
||||
import play.api.libs.json.{ Json, JsObject }
|
||||
import play.api.mvc.RequestHeader
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.EmbedConfig
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.String.html.safeJsonValue
|
||||
import views.html.base.layout.{ bits => layout }
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object embed {
|
||||
|
||||
import views.html.base.layout.bits._
|
||||
import EmbedConfig.implicits._
|
||||
|
||||
private def bodyClass(implicit ctx: Context) = List(
|
||||
"base" -> true,
|
||||
ctx.currentTheme.cssClass -> true,
|
||||
(if (ctx.currentBg == "transp") "dark transp" else ctx.currentBg) -> true
|
||||
)
|
||||
|
||||
def apply(pov: lila.game.Pov, data: play.api.libs.json.JsObject)(implicit ctx: Context) = frag(
|
||||
doctype,
|
||||
htmlTag(ctx)(
|
||||
topComment,
|
||||
def apply(pov: lila.game.Pov, data: JsObject)(implicit config: EmbedConfig) = frag(
|
||||
layout.doctype,
|
||||
layout.htmlTag(config.lang)(
|
||||
head(
|
||||
charset,
|
||||
metaCsp(none),
|
||||
titleTag(s"${playerText(pov.game.whitePlayer)} vs ${playerText(pov.game.blackPlayer)} in ${pov.gameId} : ${pov.game.opening.fold(trans.analysis.txt())(_.opening.ecoName)}"),
|
||||
fontStylesheets,
|
||||
currentBgCss,
|
||||
cssTags("common.css", "board.css", "analyse.css", "analyse-embed.css"),
|
||||
pieceSprite
|
||||
layout.charset,
|
||||
layout.viewport,
|
||||
layout.metaCsp(basicCsp withNonce config.nonce),
|
||||
st.headTitle(replay titleOf pov),
|
||||
layout.pieceSprite(lila.pref.PieceSet.default),
|
||||
cssTagWithTheme("analyse.embed", config.bg)
|
||||
),
|
||||
body(cls := bodyClass ::: List(
|
||||
"highlight" -> true,
|
||||
"piece_letter" -> ctx.pref.pieceNotationIsLetter
|
||||
body(cls := List(
|
||||
s"highlight ${config.bg} ${config.board}" -> true
|
||||
))(
|
||||
div(cls := "is2d")(
|
||||
div(cls := "embedded_analyse analyse cg-512")(miniBoardContent)
|
||||
main(cls := "analyse")
|
||||
),
|
||||
footer {
|
||||
val url = routes.Round.watcher(pov.gameId, pov.color.name)
|
||||
|
@ -55,32 +47,31 @@ object embed {
|
|||
jsTag("vendor/mousetrap.js"),
|
||||
jsAt("compiled/util.js"),
|
||||
jsAt("compiled/trans.js"),
|
||||
jsAt("compiled/embed-analyse.js"),
|
||||
analyseTag,
|
||||
jsTag("embed-analyse.js"),
|
||||
embedJs(s"""lichess.startEmbeddedAnalyse({
|
||||
element: document.querySelector('.embedded_analyse'),
|
||||
data: ${safeJsonValue(data)},
|
||||
embed: true,
|
||||
i18n: ${views.html.board.userAnalysisI18n(withCeval = false, withExplorer = false)}
|
||||
});""")
|
||||
embedJsUnsafe(s"""lichess.startEmbeddedAnalyse(${
|
||||
safeJsonValue(Json.obj(
|
||||
"data" -> data,
|
||||
"embed" -> true,
|
||||
"i18n" -> views.html.board.userAnalysisI18n(withCeval = false, withExplorer = false)
|
||||
))
|
||||
})""", config.nonce)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def notFound()(implicit ctx: Context) = frag(
|
||||
doctype,
|
||||
htmlTag(ctx)(
|
||||
topComment,
|
||||
def notFound(implicit config: EmbedConfig) = frag(
|
||||
layout.doctype,
|
||||
layout.htmlTag(config.lang)(
|
||||
head(
|
||||
charset,
|
||||
metaCsp(none),
|
||||
titleTag("404 - Game not found"),
|
||||
fontStylesheets,
|
||||
currentBgCss,
|
||||
cssTags("common.css", "analyse-embed.css")
|
||||
layout.charset,
|
||||
layout.viewport,
|
||||
layout.metaCsp(basicCsp),
|
||||
st.headTitle("404 - Game not found"),
|
||||
cssTagWithTheme("analyse.round.embed", "dark")
|
||||
),
|
||||
body(cls := bodyClass)(
|
||||
div(cls := "not_found")(
|
||||
body(cls := "dark")(
|
||||
div(cls := "not-found")(
|
||||
h1("Game not found")
|
||||
)
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ object help {
|
|||
private def k(str: String) = raw(s"""<kbd>$str</kbd>""")
|
||||
|
||||
def apply(isStudy: Boolean)(implicit ctx: Context) = frag(
|
||||
h2(trans.keyboardShortcuts.frag()),
|
||||
h2(trans.keyboardShortcuts()),
|
||||
table(
|
||||
tbody(
|
||||
header("Navigate the move tree"),
|
||||
|
@ -37,7 +37,7 @@ object help {
|
|||
row(frag(k("x")), "Show threat"),
|
||||
row(frag(k("e")), "Opening/endgame explorer"),
|
||||
row(frag(k("f")), trans.flipBoard.txt()),
|
||||
row(frag(k("/")), "Focus chat"),
|
||||
row(frag(k("c")), "Focus chat"),
|
||||
row(frag(k("shift"), k("C")), trans.keyShowOrHideComments.txt()),
|
||||
row(frag(k("?")), "Show this help dialog"),
|
||||
isStudy option frag(
|
||||
|
@ -49,8 +49,8 @@ object help {
|
|||
tr(
|
||||
td(cls := "mouse", colspan := 2)(
|
||||
ul(
|
||||
li(trans.youCanAlsoScrollOverTheBoardToMoveInTheGame.frag()),
|
||||
li(trans.analysisShapesHowTo.frag())
|
||||
li(trans.youCanAlsoScrollOverTheBoardToMoveInTheGame()),
|
||||
li(trans.analysisShapesHowTo())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -2,12 +2,11 @@ package views.html.analyse
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.common.String.html.safeJsonValue
|
||||
import lila.i18n.{ I18nKeys => trans }
|
||||
|
||||
private object jsI18n {
|
||||
|
||||
def apply()(implicit ctx: Context) = safeJsonValue(i18nJsObject(translations))
|
||||
def apply()(implicit ctx: Context) = i18nJsObject(translations)
|
||||
|
||||
private val translations = List(
|
||||
trans.flipBoard,
|
||||
|
@ -51,7 +50,6 @@ private object jsI18n {
|
|||
trans.toggleLocalEvaluation,
|
||||
// action menu
|
||||
trans.menu,
|
||||
trans.preferences,
|
||||
trans.inlineNotation,
|
||||
trans.computerAnalysis,
|
||||
trans.enable,
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package views.html.analyse
|
||||
|
||||
import play.twirl.api.Html
|
||||
import play.api.libs.json.Json
|
||||
|
||||
import chess.variant.Crazyhouse
|
||||
|
||||
import bits.dataPanel
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.Lang
|
||||
import lila.common.String.html.safeJsonValue
|
||||
import lila.game.Pov
|
||||
|
||||
|
@ -13,6 +16,9 @@ import controllers.routes
|
|||
|
||||
object replay {
|
||||
|
||||
private[analyse] def titleOf(pov: Pov)(implicit lang: Lang) =
|
||||
s"${playerText(pov.game.whitePlayer)} vs ${playerText(pov.game.blackPlayer)}: ${pov.game.opening.fold(trans.analysis.txt())(_.opening.ecoName)}"
|
||||
|
||||
def apply(
|
||||
pov: Pov,
|
||||
data: play.api.libs.json.JsObject,
|
||||
|
@ -33,82 +39,101 @@ object replay {
|
|||
views.html.chat.json(c.chat, name = trans.spectatorRoom.txt(), timeout = c.timeout, withNote = ctx.isAuth, public = true)
|
||||
}
|
||||
val pgnLinks = div(
|
||||
a(dataIcon := "x", cls := "text", rel := "nofollow", href := s"${routes.Game.exportOne(game.id)}?literate=1")(trans.downloadAnnotated()),
|
||||
a(dataIcon := "x", cls := "text", rel := "nofollow", href := s"${routes.Game.exportOne(game.id)}?evals=0&clocks=0")(trans.downloadRaw()),
|
||||
game.isPgnImport option a(dataIcon := "x", cls := "text", rel := "nofollow", href := s"${routes.Game.exportOne(game.id)}?imported=1")(trans.downloadImported()),
|
||||
ctx.noBlind option a(dataIcon := "=", cls := "text embed_howto", target := "_blank")(trans.embedInYourWebsite())
|
||||
a(dataIcon := "x", cls := "text", href := s"${routes.Game.exportOne(game.id)}?literate=1")(trans.downloadAnnotated()),
|
||||
a(dataIcon := "x", cls := "text", href := s"${routes.Game.exportOne(game.id)}?evals=0&clocks=0")(trans.downloadRaw()),
|
||||
game.isPgnImport option a(dataIcon := "x", cls := "text", href := s"${routes.Game.exportOne(game.id)}?imported=1")(trans.downloadImported()),
|
||||
ctx.noBlind option a(dataIcon := "=", cls := "text embed-howto", target := "_blank")(trans.embedInYourWebsite())
|
||||
)
|
||||
|
||||
bits.layout(
|
||||
title = s"${playerText(pov.game.whitePlayer)} vs ${playerText(pov.game.blackPlayer)}: ${game.opening.fold(trans.analysis.txt())(_.opening.ecoName)}",
|
||||
side = views.html.game.side(pov, initialFen, none, simul = simul, userTv = userTv, bookmarked = bookmarked),
|
||||
chat = views.html.chat.frag.some,
|
||||
underchat = Some(views.html.round.bits underchat pov.game),
|
||||
moreCss = cssTags("analyse.css", "chat.css"),
|
||||
title = titleOf(pov),
|
||||
moreCss = frag(
|
||||
cssTag("analyse.round"),
|
||||
pov.game.variant == Crazyhouse option cssTag("analyse.zh"),
|
||||
ctx.blind option cssTag("round.nvui")
|
||||
),
|
||||
moreJs = frag(
|
||||
analyseTag,
|
||||
analyseNvuiTag,
|
||||
embedJs(s"""lichess=lichess||{};
|
||||
lichess.analyse={data:${safeJsonValue(data)},i18n:${jsI18n()},userId:$jsUserId,chat:${jsOrNull(chatJson)},
|
||||
explorer:{endpoint:"$explorerEndpoint",tablebaseEndpoint:"$tablebaseEndpoint"}}""")
|
||||
embedJsUnsafe(s"""lichess=lichess||{};lichess.analyse=${
|
||||
safeJsonValue(Json.obj(
|
||||
"data" -> data,
|
||||
"i18n" -> jsI18n(),
|
||||
"userId" -> ctx.userId,
|
||||
"chat" -> chatJson,
|
||||
"explorer" -> Json.obj(
|
||||
"endpoint" -> explorerEndpoint,
|
||||
"tablebaseEndpoint" -> tablebaseEndpoint
|
||||
)
|
||||
))
|
||||
}""")
|
||||
),
|
||||
openGraph = povOpenGraph(pov).some
|
||||
)(frag(
|
||||
div(cls := "analyse cg-512")(
|
||||
views.html.board.bits.domPreload(none)
|
||||
main(cls := "analyse")(
|
||||
st.aside(cls := "analyse__side")(
|
||||
views.html.game.side(pov, initialFen, none, simul = simul, userTv = userTv, bookmarked = bookmarked)
|
||||
),
|
||||
chatOption.map(_ => views.html.chat.frag),
|
||||
div(cls := "analyse__board main-board")(chessgroundSvg),
|
||||
div(cls := "analyse__tools")(div(cls := "ceval")),
|
||||
div(cls := "analyse__controls"),
|
||||
!ctx.blind option frag(
|
||||
div(cls := "analyse__underboard")(
|
||||
div(cls := "analyse__underboard__panels")(
|
||||
div(cls := "active"),
|
||||
game.analysable option div(cls := "computer-analysis")(
|
||||
if (analysis.isDefined || analysisStarted) div(id := "adv-chart")
|
||||
else form(
|
||||
cls := s"future-game-analysis${ctx.isAnon ?? " must-login"}",
|
||||
action := routes.Analyse.requestAnalysis(gameId),
|
||||
method := "post"
|
||||
)(
|
||||
button(`type` := "submit", cls := "button text")(
|
||||
span(cls := "is3 text", dataIcon := "")(trans.requestAComputerAnalysis())
|
||||
)
|
||||
)
|
||||
),
|
||||
div(cls := "fen-pgn")(
|
||||
div(
|
||||
strong("FEN"),
|
||||
input(readonly, spellcheck := false, cls := "copyable autoselect analyse__underboard__fen")
|
||||
),
|
||||
div(cls := "pgn-options")(
|
||||
strong("PGN"),
|
||||
pgnLinks
|
||||
),
|
||||
div(cls := "pgn")(pgn)
|
||||
),
|
||||
div(cls := "move-times")(
|
||||
game.turns > 1 option div(id := "movetimes-chart")
|
||||
),
|
||||
cross.map { c =>
|
||||
div(cls := "ctable")(
|
||||
views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov), pov.gameId.some)
|
||||
)
|
||||
}
|
||||
),
|
||||
div(cls := "analyse__underboard__menu")(
|
||||
game.analysable option
|
||||
span(
|
||||
cls := "computer-analysis",
|
||||
dataPanel := "computer-analysis",
|
||||
title := analysis.map { a => s"Provided by ${usernameOrId(a.providedBy)}" }
|
||||
)(trans.computerAnalysis()),
|
||||
!game.isPgnImport option frag(
|
||||
game.turns > 1 option span(dataPanel := "move-times")(trans.moveTimes()),
|
||||
cross.isDefined option span(dataPanel := "ctable")(trans.crosstable())
|
||||
),
|
||||
span(dataPanel := "fen-pgn")(raw("FEN & PGN"))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
if (ctx.blind) div(cls := "blind_content none")(
|
||||
if (ctx.blind) div(cls := "blind-content none")(
|
||||
h2("PGN downloads"),
|
||||
pgnLinks
|
||||
)
|
||||
else div(cls := "underboard_content none")(
|
||||
div(cls := "analysis_panels")(
|
||||
game.analysable option div(cls := "panel computer_analysis")(
|
||||
if (analysis.isDefined || analysisStarted) div(id := "adv_chart")
|
||||
else form(
|
||||
cls := s"future_game_analysis${ctx.isAnon ?? " must_login"}",
|
||||
action := routes.Analyse.requestAnalysis(gameId),
|
||||
method := "post"
|
||||
)(
|
||||
button(`type` := "submit", cls := "button text")(
|
||||
span(cls := "is3 text", dataIcon := "")(trans.requestAComputerAnalysis())
|
||||
)
|
||||
)
|
||||
),
|
||||
div(cls := "panel fen_pgn")(
|
||||
div(
|
||||
strong("FEN"),
|
||||
input(readonly := true, spellcheck := false, cls := "copyable autoselect fen")
|
||||
),
|
||||
div(cls := "pgn_options")(
|
||||
strong("PGN"),
|
||||
pgnLinks
|
||||
),
|
||||
div(cls := "pgn")(pgn)
|
||||
),
|
||||
div(cls := "panel move_times")(
|
||||
game.turns > 1 option div(id := "movetimes_chart")
|
||||
),
|
||||
cross.map { c =>
|
||||
div(cls := "panel crosstable")(
|
||||
views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov), pov.gameId.some)
|
||||
)
|
||||
}
|
||||
),
|
||||
div(cls := "analysis_menu")(
|
||||
game.analysable option
|
||||
a(
|
||||
dataPanel := "computer_analysis",
|
||||
cls := "computer_analysis",
|
||||
title := analysis.map { a => s"Provided by ${usernameOrId(a.providedBy)}" }
|
||||
)(trans.computerAnalysis()),
|
||||
!game.isPgnImport option frag(
|
||||
game.turns > 1 option a(dataPanel := "move_times", cls := "move_times")(trans.moveTimes()),
|
||||
cross.isDefined option a(dataPanel := "crosstable", cls := "crosstable")(trans.crosstable())
|
||||
),
|
||||
a(dataPanel := "fen_pgn", cls := "fen_pgn")(raw("FEN & PGN"))
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package views.html.analyse
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import bits.dataPanel
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
|
@ -22,65 +20,31 @@ object replayBot {
|
|||
import pov._
|
||||
|
||||
views.html.analyse.bits.layout(
|
||||
title = s"${playerText(pov.player)} vs ${playerText(pov.opponent)} in $gameId : ${game.opening.fold(trans.analysis.txt())(_.opening.ecoName)}",
|
||||
side = views.html.game.side(pov, initialFen, none, simul = simul, bookmarked = false),
|
||||
chat = none,
|
||||
underchat = Some(views.html.game.bits.watchers),
|
||||
moreCss = cssTag("analyse.css"),
|
||||
title = replay titleOf pov,
|
||||
moreCss = cssTag("analyse.round"),
|
||||
openGraph = povOpenGraph(pov).some
|
||||
) {
|
||||
frag(
|
||||
div(cls := "analyse cg-512")(
|
||||
views.html.board.bits.domPreload(pov.some),
|
||||
div(cls := "lichess_ground for_bot")(
|
||||
h1(titleGame(pov.game)),
|
||||
p(describePov(pov)),
|
||||
p(pov.game.opening.map(_.opening.ecoName))
|
||||
)
|
||||
main(cls := "analyse")(
|
||||
st.aside(cls := "analyse__side")(
|
||||
views.html.game.side(pov, initialFen, none, simul = simul, bookmarked = false)
|
||||
),
|
||||
analysis.map { a =>
|
||||
div(cls := "advice_summary")(
|
||||
table(
|
||||
a.summary map {
|
||||
case (color, pairs) => frag(
|
||||
thead(
|
||||
tr(
|
||||
td(span(cls := s"is color-icon $color"))
|
||||
),
|
||||
th(playerLink(pov.game.player(color), withOnline = false))
|
||||
),
|
||||
tbody(
|
||||
pairs map {
|
||||
case (judgment, nb) => tr(
|
||||
td(strong(nb)),
|
||||
th(bits.judgmentName(judgment))
|
||||
)
|
||||
},
|
||||
tr(
|
||||
td(strong(lila.analyse.Accuracy.mean(pov.withColor(color), a))),
|
||||
th(trans.averageCentipawnLoss())
|
||||
),
|
||||
tr(td(cls := "spacerlol", colspan := 2))
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
div(cls := "underboard_content")(
|
||||
div(cls := "analysis_panels")(
|
||||
div(cls := "panel fen_pgn")(
|
||||
div(cls := "analyse__board main-board")(chessgroundSvg),
|
||||
div(cls := "analyse__tools")(div(cls := "ceval")),
|
||||
div(cls := "analyse__controls"),
|
||||
div(cls := "analyse__underboard")(
|
||||
div(cls := "analyse__underboard__panels")(
|
||||
div(cls := "fen-pgn active")(
|
||||
div(
|
||||
strong("FEN"),
|
||||
input(readonly, spellcheck := false, cls := "copyable autoselect analyse__underboard__fen")
|
||||
),
|
||||
div(cls := "pgn")(pgn)
|
||||
),
|
||||
cross.map { c =>
|
||||
div(cls := "panel crosstable")(
|
||||
div(cls := "ctable active")(
|
||||
views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov), pov.gameId.some)
|
||||
)
|
||||
}
|
||||
),
|
||||
div(cls := "analysis_menu")(
|
||||
cross.isDefined option a(dataPanel := "crosstable", cls := "crosstable")(trans.crosstable()),
|
||||
a(dataPanel := "fen_pgn", cls := "fen_pgn")(raw("FEN & PGN"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
package views.html
|
||||
package auth
|
||||
|
||||
import play.api.data.{ Form, Field }
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.user.User
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object bits {
|
||||
|
||||
def formFields(username: Field, password: Field, emailOption: Option[Field], register: Boolean)(implicit ctx: Context) = frag(
|
||||
form3.group(username, if (register) trans.username() else trans.usernameOrEmail()) { f =>
|
||||
frag(
|
||||
form3.input(f)(autofocus, required),
|
||||
p(cls := "error exists none")(trans.usernameAlreadyUsed())
|
||||
)
|
||||
},
|
||||
form3.password(password, trans.password()),
|
||||
emailOption.map { email =>
|
||||
form3.group(email, trans.email(), help = frag("We will only use it for password reset.").some)(form3.input(_, typ = "email")(required))
|
||||
}
|
||||
)
|
||||
|
||||
def passwordReset(form: Form[_], captcha: lila.common.Captcha, ok: Option[Boolean] = None)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = trans.passwordReset.txt(),
|
||||
moreCss = cssTag("auth"),
|
||||
moreJs = captchaTag
|
||||
) {
|
||||
main(cls := "auth auth-signup box box-pad")(
|
||||
h1(
|
||||
ok.map { r =>
|
||||
span(cls := (if (r) "is-green" else "is-red"), dataIcon := (if (r) "E" else "L"))
|
||||
},
|
||||
trans.passwordReset()
|
||||
),
|
||||
st.form(
|
||||
cls := "form3",
|
||||
action := routes.Auth.passwordResetApply,
|
||||
method := "post"
|
||||
)(
|
||||
form3.group(form("email"), trans.email())(form3.input(_, typ = "email")(autofocus)),
|
||||
views.html.base.captcha(form, captcha),
|
||||
form3.action(form3.submit(trans.emailMeALink()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def passwordResetSent(email: String)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = trans.passwordReset.txt()
|
||||
) {
|
||||
main(cls := "page-small box box-pad")(
|
||||
h1(cls := "is-green text", dataIcon := "E")(trans.checkYourEmail()),
|
||||
p(trans.weHaveSentYouAnEmailTo(email)),
|
||||
p(trans.ifYouDoNotSeeTheEmailCheckOtherPlaces())
|
||||
)
|
||||
}
|
||||
|
||||
def passwordResetConfirm(u: User, token: String, form: Form[_], ok: Option[Boolean] = None)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = s"${u.username} - ${trans.changePassword.txt()}",
|
||||
moreCss = cssTag("form3")
|
||||
) {
|
||||
main(cls := "page-small box box-pad")(
|
||||
(ok match {
|
||||
case Some(true) => h1(cls := "is-green text", dataIcon := "E")
|
||||
case Some(false) => h1(cls := "is-red text", dataIcon := "L")
|
||||
case _ => h1
|
||||
})(
|
||||
userLink(u, withOnline = false),
|
||||
" - ",
|
||||
trans.changePassword()
|
||||
),
|
||||
st.form(cls := "form3", action := routes.Auth.passwordResetConfirmApply(token), method := "POST")(
|
||||
form3.hidden(form("token")),
|
||||
form3.passwordModified(form("newPasswd1"), trans.newPassword())(autofocus),
|
||||
form3.password(form("newPasswd2"), trans.newPasswordAgain()),
|
||||
form3.globalError(form),
|
||||
form3.action(form3.submit(trans.changePassword()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def checkYourEmailBanner(userEmail: lila.security.EmailConfirm.UserEmail) = frag(
|
||||
styleTag("""
|
||||
body { margin-top: 45px; }
|
||||
#email-confirm {
|
||||
height: 40px;
|
||||
background: #3893E8;
|
||||
color: #fff!important;
|
||||
font-size: 1.3em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #666;
|
||||
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.3);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 107;
|
||||
}
|
||||
#email-confirm a {
|
||||
color: #fff!important;
|
||||
text-decoration: underline;
|
||||
margin-left: 1em;
|
||||
}
|
||||
"""),
|
||||
div(id := "email-confirm")(
|
||||
s"Almost there, ${userEmail.username}! Now check your email (${userEmail.email.conceal}) for signup confirmation.",
|
||||
a(href := routes.Auth.checkYourEmail)("Click here for help")
|
||||
)
|
||||
)
|
||||
|
||||
def tor()(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = "Tor exit node"
|
||||
) {
|
||||
main(cls := "page-small box box-pad")(
|
||||
h1(cls := "text", dataIcon := "2")("Ooops"),
|
||||
p("Sorry, you can't signup to lichess through TOR!"),
|
||||
p("As an Anonymous user, you can play, train, and use all lichess features.")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package views.html.auth
|
||||
|
||||
import play.api.data.Form
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object checkYourEmail {
|
||||
|
||||
def apply(
|
||||
userEmail: Option[lila.security.EmailConfirm.UserEmail],
|
||||
form: Option[Form[_]] = None
|
||||
)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = "Check your email",
|
||||
moreCss = cssTag("email-confirm")
|
||||
) {
|
||||
main(cls := s"page-small box box-pad email-confirm ${if (form.exists(_.hasErrors)) "error" else "anim"}")(
|
||||
h1(cls := "is-green text", dataIcon := "E")(trans.checkYourEmail()),
|
||||
p(trans.weHaveSentYouAnEmailClickTheLink()),
|
||||
h2("Not receiving it?"),
|
||||
ol(
|
||||
li(h3(trans.ifYouDoNotSeeTheEmailCheckOtherPlaces())),
|
||||
userEmail.map(_.email).map { email =>
|
||||
li(
|
||||
h3("Make sure your email address is correct:"),
|
||||
br, br,
|
||||
st.form(action := routes.Auth.fixEmail, method := "POST")(
|
||||
input(
|
||||
id := "new-email",
|
||||
tpe := "email",
|
||||
required,
|
||||
name := "email",
|
||||
value := form.flatMap(_("email").value).getOrElse(email.value),
|
||||
pattern := s"^((?!^${email.value}$$).)*$$"
|
||||
),
|
||||
embedJsUnsafe("""
|
||||
var email = document.getElementById("new-email");
|
||||
var currentError = "This is already your current email.";
|
||||
email.setCustomValidity(currentError);
|
||||
email.addEventListener("input", function() {
|
||||
email.setCustomValidity(email.validity.patternMismatch ? currentError : "");
|
||||
});"""),
|
||||
button(tpe := "submit", cls := "button")("Change it"),
|
||||
form.map { f =>
|
||||
errMsg(f("email"))
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
li(
|
||||
h3("Wait up to 5 minutes."), br,
|
||||
"Depending on your email provider, it can take a while to arrive."
|
||||
),
|
||||
li(
|
||||
h3("Still not getting it?"), br,
|
||||
"Did you make sure your email address is correct?", br,
|
||||
"Did you wait 5 minutes?", br,
|
||||
"If so, ",
|
||||
a(href := routes.Account.emailConfirmHelp)("proceed to this page to solve the issue"), "."
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
@(userEmail: Option[lila.security.EmailConfirm.UserEmail], form: Option[Form[_]] = None)(implicit ctx: Context)
|
||||
|
||||
@auth.layout(title = "Check your email") {
|
||||
<div class="content_box small_box signup email_confirm @if(form.exists(_.hasErrors)){error}else{anim}">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title is-green text" data-icon="E">@trans.checkYourEmail()</h1>
|
||||
<p>@trans.weHaveSentYouAnEmailClickTheLink()</p>
|
||||
<h2>Not receiving it?</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<h3>@trans.ifYouDoNotSeeTheEmailCheckOtherPlaces()</h3>
|
||||
</li>
|
||||
@userEmail.map(_.email).map { email =>
|
||||
<li>
|
||||
<h3>Make sure your email address is correct:</h3><br />
|
||||
<form action="@routes.Auth.fixEmail" method="POST">
|
||||
<input
|
||||
id="new_email"
|
||||
type="email"
|
||||
required="required"
|
||||
name="email"
|
||||
value="@form.flatMap(_("email").value).getOrElse(email.value)"
|
||||
pattern="^((?!^@{email.value}$).)*$" />
|
||||
<script>
|
||||
var email = document.getElementById("new_email");
|
||||
var currentError = "This is already your current email.";
|
||||
email.setCustomValidity(currentError);
|
||||
email.addEventListener("input", function() {
|
||||
email.setCustomValidity(email.validity.patternMismatch ? currentError : "");
|
||||
});
|
||||
</script>
|
||||
<button type="submit">Change it</button>
|
||||
@form.map { f =>
|
||||
@errMsg(f("email"))
|
||||
}
|
||||
</form>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<h3>Wait up to 10 minutes.</h3><br />
|
||||
Depending on your email provider, it can take a while to arrive.
|
||||
</li>
|
||||
<li>
|
||||
<h3>Still not getting it?</h3><br />
|
||||
Did you make sure your email address is correct?<br />
|
||||
Did you wait 10 minutes?<br />
|
||||
If so, <a class="blue", href="@routes.Account.emailConfirmHelp">proceed to this page to solve the issue</a>.
|
||||
</li>
|
||||
</ol>
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
@(userEmail: lila.security.EmailConfirm.UserEmail)
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
#email_confirm {
|
||||
height: 40px;
|
||||
background: #3893E8;
|
||||
color: #fff!important;
|
||||
font-size: 1.3em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #666;
|
||||
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.3);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
#email_confirm a {
|
||||
color: #fff!important;
|
||||
text-decoration: underline;
|
||||
margin-left: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="email_confirm">
|
||||
Almost there, @userEmail.username! Now check your email (@userEmail.email.conceal) for signup confirmation.
|
||||
<a href="@routes.Auth.checkYourEmail">Click here for help</a>
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
@(username: Field, password: Field, emailOption: Option[Field], register: Boolean)(implicit ctx: Context)
|
||||
@import lila.app.ui.ScalatagsTwirlForm._
|
||||
|
||||
@form3.group(username, if(register) trans.username.frag() else trans.usernameOrEmail.frag()) { f =>
|
||||
@form3.inputHtml(f)(*.autofocus := true)
|
||||
<p class="error exists none">@trans.usernameAlreadyUsed()</p>
|
||||
}
|
||||
@form3.group(password, trans.password.frag())(form3.input(_, typ = "password"))
|
||||
@emailOption.map { email =>
|
||||
@form3.group(email, trans.email.frag())(form3.input(_, typ = "email"))
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
@(title: String, moreJs: Html = emptyHtml, formCss: Boolean = false, csp: Option[lila.common.ContentSecurityPolicy] = None)(body: Html)(implicit ctx: Context)
|
||||
@base.layout(
|
||||
title = title,
|
||||
moreCss = cssTags(List("user-signup.css" -> true, "form3.css" -> formCss)),
|
||||
moreJs = moreJs,
|
||||
csp = csp)(body).toHtml
|
|
@ -0,0 +1,47 @@
|
|||
package views.html
|
||||
package auth
|
||||
|
||||
import play.api.data.Form
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object login {
|
||||
|
||||
val twoFactorHelp = span(dataIcon := "")(
|
||||
"Open the two-factor authentication app on your device to view your authentication code and verify your identity."
|
||||
)
|
||||
|
||||
def apply(form: Form[_], referrer: Option[String])(implicit ctx: Context) = views.html.base.layout(
|
||||
title = trans.signIn.txt(),
|
||||
moreJs = jsTag("login.js"),
|
||||
moreCss = cssTag("auth")
|
||||
) {
|
||||
main(cls := "auth auth-login box box-pad")(
|
||||
h1(trans.signIn()),
|
||||
st.form(
|
||||
cls := "form3",
|
||||
action := s"${routes.Auth.authenticate}${referrer.?? { ref => s"?referrer=${java.net.URLEncoder.encode(ref, "US-ASCII")}" }}",
|
||||
method := "post"
|
||||
)(
|
||||
div(cls := "one-factor")(
|
||||
form3.globalError(form),
|
||||
auth.bits.formFields(form("username"), form("password"), none, register = false),
|
||||
form3.submit(trans.signIn(), icon = none)
|
||||
),
|
||||
div(cls := "two-factor none")(
|
||||
form3.group(form("token"), raw("Authentication code"), help = Some(twoFactorHelp))(form3.input(_)(autocomplete := "off", pattern := "[0-9]{6}")),
|
||||
p(cls := "error none")("Invalid code."),
|
||||
form3.submit(trans.signIn(), icon = none)
|
||||
)
|
||||
),
|
||||
div(cls := "alternative")(
|
||||
a(href := routes.Auth.signup())(trans.signUp()),
|
||||
a(href := routes.Auth.passwordReset())(trans.passwordReset())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
@(form: Form[_], referrer: Option[String])(implicit ctx: Context)
|
||||
@import lila.app.ui.ScalatagsTwirlForm._
|
||||
|
||||
@twoFactorHelp = {
|
||||
<span data-icon="">Open the two-factor authentication app on your device to view your authentication code and verify your identity.</span>
|
||||
}
|
||||
|
||||
@auth.layout(
|
||||
title = trans.signIn.txt(),
|
||||
moreJs = jsTag("login.js"),
|
||||
formCss = true) {
|
||||
<div class="content_box login">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">@trans.signIn()</h1>
|
||||
<form class="form3 login" action="@routes.Auth.authenticate@referrer.map { ref =>?referrer=@{java.net.URLEncoder.encode(ref, "US-ASCII")}}" method="POST">
|
||||
<div class="one-factor">
|
||||
@form3.globalError(form)
|
||||
@auth.formFields(form("username"), form("password"), none, register = false)
|
||||
@form3.actionHtml(form3.submit(trans.signIn.frag(), icon = "F".some))
|
||||
</div>
|
||||
<div class="two-factor none">
|
||||
@form3.group(form("token"), raw("Authentication code"), help = Some(twoFactorHelp))(form3.input(_)(*.autocomplete := "off", *.pattern := "[0-9]{6}"))
|
||||
<p class="error none">Invalid code.</p>
|
||||
@form3.actionHtml(form3.submit(trans.signIn.frag(), icon = "F".some))
|
||||
</form>
|
||||
</div>
|
||||
<div class="alternative">
|
||||
@trans.newToLichess()
|
||||
<br />
|
||||
<br />
|
||||
<a href="@routes.Auth.signup()" class="button" data-icon="F"> @trans.signUp()</a>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
@trans.forgotPassword()
|
||||
<br />
|
||||
<br />
|
||||
<a href="@routes.Auth.passwordReset()" class="button" data-icon="F"> @trans.passwordReset()</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
@(form: Form[_], captcha: lila.common.Captcha, ok: Option[Boolean] = None)(implicit ctx: Context)
|
||||
|
||||
@auth.layout(
|
||||
title = trans.passwordReset.txt(),
|
||||
formCss= true) {
|
||||
<div class="content_box small_box signup">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title text">
|
||||
@ok.map {
|
||||
case true => {<span class="is-green" data-icon="E"></span>}
|
||||
case false => {<span class="is-red" data-icon="L"></span>}
|
||||
}
|
||||
@trans.passwordReset()
|
||||
</h1>
|
||||
<form class="form3" action="@routes.Auth.passwordResetApply" method="POST">
|
||||
@form3.group(form("email"), trans.email.frag())(form3.input(_, typ = "email"))
|
||||
@base.captcha(form, captcha).toHtml
|
||||
@form3.actionHtml(form3.submit(trans.emailMeALink.frag(), icon = "F".some))
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
@(u: User, token: String, form: Form[_], ok: Option[Boolean] = None)(implicit ctx: Context)
|
||||
|
||||
@title = @{ s"${u.username} - ${trans.changePassword.txt()}" }
|
||||
|
||||
@auth.layout(title = title, formCss = true) {
|
||||
<div class="content_box small_box">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title text">
|
||||
@userLink(u, withOnline = false) - @trans.changePassword()
|
||||
@ok.map {
|
||||
case true => {<span class="is-green" data-icon="E"></span>}
|
||||
case false => {<span class="is-red" data-icon="L"></span>}
|
||||
}
|
||||
</h1>
|
||||
<form class="form3" action="@routes.Auth.passwordResetConfirmApply(token)" method="POST">
|
||||
@form3.hidden(form("token"))
|
||||
@form3.password(form("newPasswd1"), trans.newPassword.frag()).toHtml
|
||||
@form3.password(form("newPasswd2"), trans.newPasswordAgain.frag()).toHtml
|
||||
@form3.globalError(form)
|
||||
@form3.actionHtml(form3.submit(trans.changePassword.frag()))
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
@(email: String)(implicit ctx: Context)
|
||||
|
||||
@auth.layout(
|
||||
title = trans.passwordReset.txt()) {
|
||||
<div class="content_box small_box signup">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title is-green text" data-icon="E">@trans.checkYourEmail()</h1>
|
||||
<p>@trans.weHaveSentYouAnEmailTo(email)</p>
|
||||
<p>@trans.ifYouDoNotSeeTheEmailCheckOtherPlaces()</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package views.html
|
||||
package auth
|
||||
|
||||
import play.api.data.Form
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object signup {
|
||||
|
||||
private val recaptchaScript = raw("""<script src="https://www.google.com/recaptcha/api.js" async defer></script>""")
|
||||
|
||||
def apply(form: Form[_], recaptcha: lila.security.RecaptchaPublicConfig)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = trans.signUp.txt(),
|
||||
moreJs = frag(
|
||||
jsTag("signup.js"),
|
||||
recaptcha.enabled option recaptchaScript,
|
||||
fingerprintTag
|
||||
),
|
||||
moreCss = cssTag("auth"),
|
||||
csp = defaultCsp.withRecaptcha.some
|
||||
) {
|
||||
main(cls := "auth auth-signup box box-pad")(
|
||||
h1(trans.signUp()),
|
||||
st.form(
|
||||
id := "signup_form",
|
||||
cls := "form3",
|
||||
action := routes.Auth.signupPost,
|
||||
method := "post"
|
||||
)(
|
||||
auth.bits.formFields(form("username"), form("password"), form("email").some, register = true),
|
||||
input(id := "signup-fp-input", name := "fp", tpe := "hidden"),
|
||||
div(cls := "form-group text", dataIcon := "")(
|
||||
trans.computersAreNotAllowedToPlay(), br,
|
||||
small(trans.byRegisteringYouAgreeToBeBoundByOur(a(href := routes.Page.tos)(trans.termsOfService())))
|
||||
),
|
||||
if (recaptcha.enabled)
|
||||
button(
|
||||
cls := "g-recaptcha submit button text big",
|
||||
attr("data-sitekey") := recaptcha.key,
|
||||
attr("data-callback") := "signupSubmit"
|
||||
)(trans.signUp())
|
||||
else form3.submit(trans.signUp(), icon = none, klass = "big")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
@(form: Form[_], recaptcha: lila.security.RecaptchaPublicConfig)(implicit ctx: Context)
|
||||
|
||||
@moreJs = {
|
||||
@jsTag("signup.js")
|
||||
@if(recaptcha.enabled) {
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
}
|
||||
@fingerprintTag
|
||||
}
|
||||
|
||||
@tosLink = {
|
||||
<a href="@routes.Page.tos">@trans.termsOfService()</a>
|
||||
}
|
||||
|
||||
@auth.layout(
|
||||
title = trans.signUp.txt(),
|
||||
moreJs = moreJs,
|
||||
formCss = true,
|
||||
csp = defaultCsp.withRecaptcha.some) {
|
||||
<div class="content_box small_box signup">
|
||||
<div class="alternative">
|
||||
@trans.haveAnAccount()
|
||||
<a href="@routes.Auth.login()" class="text button" data-icon="F">@trans.signIn()</a>
|
||||
</div>
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">@trans.signUp()</h1>
|
||||
<form id="signup_form" class="form3" action="@routes.Auth.signupPost" method="POST">
|
||||
@auth.formFields(form("username"), form("password"), form("email").some, register = true)
|
||||
<input id="signup-fp-input" name="fp" type="hidden" />
|
||||
<div class="form-group text" data-icon="">
|
||||
@trans.computersAreNotAllowedToPlay()<br />
|
||||
<small>@trans.byRegisteringYouAgreeToBeBoundByOur(tosLink)</small>
|
||||
</div>
|
||||
@form3.actionHtml {
|
||||
@if(recaptcha.enabled) {
|
||||
<button class="g-recaptcha submit button text big" data-icon="F" data-sitekey="@recaptcha.key" data-callback='signupSubmit'>@trans.signUp()</button>
|
||||
} else {
|
||||
@form3.submit(trans.signUp.frag(), icon = "F".some, klass = "big")
|
||||
}
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
@auth.layout(
|
||||
title = "Tor exit node") {
|
||||
<div class="content_box small_box signup">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title text" data-icon="2">Ooops</h1>
|
||||
<p>
|
||||
Sorry, you can't signup to lichess through TOR!
|
||||
<br />
|
||||
<br />
|
||||
As an Anonymous user, you can play, train, and use all lichess features.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
@base.layout(
|
||||
title = "Access denied",
|
||||
moreCss = cssTag("authFailed.css")) {
|
||||
|
||||
<div class="content_box small_box">
|
||||
<header>
|
||||
<h1>403</h1>
|
||||
<strong>Access denied!</strong>
|
||||
<p>You tried to visit a page you're not authorized to access. Return to <a class="underline" href="@routes.Lobby.home">the homepage</a>.<p>
|
||||
</header>
|
||||
</div>
|
||||
}.toHtml
|
|
@ -0,0 +1,31 @@
|
|||
package views.html.base
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
object bits {
|
||||
|
||||
def mselect(id: String, current: Frag, items: List[Frag]) = div(cls := "mselect")(
|
||||
input(tpe := "checkbox", cls := "mselect__toggle fullscreen-toggle", st.id := s"mselect-$id", aria.label := "Other variants"),
|
||||
label(`for` := s"mselect-$id", cls := "mselect__label")(current),
|
||||
label(`for` := s"mselect-$id", cls := "fullscreen-mask"),
|
||||
st.nav(cls := "mselect__list")(items)
|
||||
)
|
||||
|
||||
lazy val stage = a(
|
||||
href := "https://lichess.org",
|
||||
style := """
|
||||
background: #7f1010;
|
||||
color: #fff;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: .5em 1em;
|
||||
border-top-right-radius: 3px;
|
||||
z-index: 99;
|
||||
"""
|
||||
)(
|
||||
"This is an empty lichess preview website, go to lichess.org instead"
|
||||
)
|
||||
}
|
|
@ -29,27 +29,29 @@ object captcha {
|
|||
),
|
||||
dataCheckUrl := routes.Main.captchaCheck(captcha.gameId)
|
||||
)(
|
||||
div(
|
||||
cls := "mini_board parse_fen is2d",
|
||||
dataPlayable := "1",
|
||||
dataX := encodeFen(safeJsonValue(Json.toJson(captcha.moves))),
|
||||
dataY := encodeFen(if (captcha.white) { "white" } else { "black" }),
|
||||
dataZ := encodeFen(captcha.fen)
|
||||
)(miniBoardContent),
|
||||
div(cls := "challenge")(
|
||||
div(
|
||||
cls := "mini-board cg-board-wrap parse-fen is2d",
|
||||
dataPlayable := "1",
|
||||
dataX := encodeFen(safeJsonValue(Json.toJson(captcha.moves))),
|
||||
dataY := encodeFen(if (captcha.white) { "white" } else { "black" }),
|
||||
dataZ := encodeFen(captcha.fen)
|
||||
)(div(cls := "cg-board"))
|
||||
),
|
||||
div(cls := "captcha-explanation")(
|
||||
label(cls := "form-label")(trans.colorPlaysCheckmateInOne.frag(
|
||||
(if (captcha.white) trans.white else trans.black).frag()
|
||||
label(cls := "form-label")(trans.colorPlaysCheckmateInOne(
|
||||
(if (captcha.white) trans.white else trans.black)()
|
||||
)),
|
||||
br, br,
|
||||
trans.thisIsAChessCaptcha.frag(),
|
||||
trans.thisIsAChessCaptcha(),
|
||||
br,
|
||||
trans.clickOnTheBoardToMakeYourMove.frag(),
|
||||
trans.clickOnTheBoardToMakeYourMove(),
|
||||
br, br,
|
||||
trans.help.frag(),
|
||||
trans.help(),
|
||||
" ",
|
||||
a(cls := "hint--bottom", dataHint := trans.viewTheSolution.txt(), target := "_blank", href := url)(url),
|
||||
div(cls := "result success text", dataIcon := "E")(trans.checkmate.frag()),
|
||||
div(cls := "result failure text", dataIcon := "k")(trans.notACheckmate.frag()),
|
||||
a(title := trans.viewTheSolution.txt(), target := "_blank", href := url)(url),
|
||||
div(cls := "result success text", dataIcon := "E")(trans.checkmate()),
|
||||
div(cls := "result failure text", dataIcon := "k")(trans.notACheckmate()),
|
||||
form3.hidden(form("move"))
|
||||
)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package views.html
|
||||
package base
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object errorPage {
|
||||
|
||||
def apply(ex: Throwable)(implicit ctx: Context) = layout(
|
||||
title = "Internal server error"
|
||||
) {
|
||||
main(cls := "page-small box box-pad")(
|
||||
h1("Something went wrong on this page"),
|
||||
p(
|
||||
"If the problem persists, please ",
|
||||
a(href := s"${routes.Main.contact}#help-error-page")("report the bug"),
|
||||
"."
|
||||
),
|
||||
code(ex.getMessage)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
@(ex: Throwable)(implicit ctx: Context)
|
||||
|
||||
@base.layout(title = "Internal server error") {
|
||||
|
||||
<div class="content_box small_box">
|
||||
<h1>Something went wrong on this page.</h1>
|
||||
<br />
|
||||
<br />
|
||||
<p>If the problem persists, please <a href="@routes.Main.contact#error-page">report the bug</a>.</p>
|
||||
<br />
|
||||
<br />
|
||||
<code>@ex.getMessage</code>
|
||||
</div>
|
||||
}.toHtml
|
|
@ -1,10 +0,0 @@
|
|||
@(field: play.api.data.Field, options: Iterable[(Any,String)], default: Option[String] = None)
|
||||
|
||||
<select name="@field.name">
|
||||
@default.map { d =>
|
||||
<option value="">@d</option>
|
||||
}
|
||||
@options.map { v =>
|
||||
<option value="@v._1" @(if(field.value == Some(v._1.toString)) "selected" else "")>@v._2</option>
|
||||
}
|
||||
</select>
|
|
@ -1,95 +0,0 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
<div class="inner">
|
||||
<div class="body">
|
||||
@ctx.me.map { me =>
|
||||
<div class="user">
|
||||
<section><h2>@me.username</h2></section>
|
||||
<div class="perfs">
|
||||
@topBarSortedPerfTypes.map { pt =>
|
||||
@me.perfs(pt.key).map { perf =>
|
||||
<a href="@routes.User.perfStat(me.username, pt.key)" class="perf@if(perf.nb == 0){ nope}" data-icon="@pt.iconChar">
|
||||
<h3>@pt.name</h3>
|
||||
@if(perf.nb > 0) {
|
||||
<div class="rating">
|
||||
<strong>@perf.glicko.intRating</strong>
|
||||
@showProgress(perf.progress, withTitle = false)
|
||||
</div>
|
||||
} else { N/A }
|
||||
}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}.getOrElse {
|
||||
<div class="anon">
|
||||
<section>
|
||||
<h2>@trans.signIn()</h2>
|
||||
<a class="login" href="@routes.Auth.login">@trans.signIn()</a>
|
||||
<div class="forgot">
|
||||
<a href="@routes.Auth.passwordReset">@trans.forgotPassword()</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="signup">
|
||||
<h2>@trans.newToLichess()</h2>
|
||||
<a class="signup" href="@routes.Auth.signup">@trans.signUp()</a>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
<div class="menu">
|
||||
<section>
|
||||
<h2>@trans.play()</h2>
|
||||
<a href="/?any#hook">@trans.createAGame()</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>
|
||||
<a href="@routes.Tv.games">@trans.currentGames()</a>
|
||||
</section>
|
||||
<section>
|
||||
<h2>@trans.learnMenu()</h2>
|
||||
<a href="@routes.Puzzle.home">@trans.training()</a>
|
||||
<a href="@routes.Practice.index">Practice</a>
|
||||
<a href="@routes.Coordinate.home">@trans.coordinates.coordinates()</a>
|
||||
<a href="@routes.Study.allDefault(1)">Study</a>
|
||||
<a href="@routes.Video.index">@trans.videoLibrary()</a>
|
||||
</section>
|
||||
<section>
|
||||
<h2>@trans.community()</h2>
|
||||
<a href="@routes.User.list">@trans.players()</a>
|
||||
@NotForKids {
|
||||
<a href="@routes.Team.home()">@trans.teams()</a>
|
||||
<a href="@routes.ForumCateg.index">@trans.forum()</a>
|
||||
}
|
||||
</section>
|
||||
<section>
|
||||
<h2>@trans.tools()</h2>
|
||||
<a href="@routes.Editor.index">@trans.boardEditor()</a>
|
||||
<a href="@routes.UserAnalysis.index">@trans.analysis()</a>
|
||||
<a href="@routes.Importer.importGame">@trans.importGame()</a>
|
||||
<a href="@routes.Search.index()">@trans.advancedSearch()</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
@NotForKids {
|
||||
<a href="/mobile">@trans.mobileApp()</a> ı
|
||||
}
|
||||
<a href="/blog">@trans.blog()</a> ı
|
||||
@NotForKids {
|
||||
<a href="/developers">@trans.webmasters()</a> ı
|
||||
<a href="/about">@trans.about()</a> ı
|
||||
<a href="/help/contribute">@trans.contribute()</a> ı
|
||||
}
|
||||
<a href="/thanks">@trans.thankYou()</a><br />
|
||||
@NotForKids {
|
||||
<a href="/patron">@trans.donate()</a> ı
|
||||
}
|
||||
<a href="/contact">@trans.contact()</a> ı
|
||||
<a href="@routes.Page.tos">@trans.termsOfService()</a> ı
|
||||
<a href="@routes.Page.privacy">@trans.privacy()</a>
|
||||
@NotForKids {
|
||||
ı <a href="https://database.lichess.org/" target="_blank">@trans.database()</a>
|
||||
ı <a href="https://github.com/ornicar/lila" target="_blank">@trans.sourceCode()</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue