rewrite layout
This commit is contained in:
parent
e1bf05c682
commit
ecbab40496
|
@ -27,10 +27,8 @@ trait I18nHelper {
|
|||
def i18nFullDbJsObject(db: I18nDb.Ref)(implicit lang: Lang): JsObject =
|
||||
JsDump.dbToObject(db, lang)
|
||||
|
||||
def i18nJsQuantityFunction(implicit lang: Lang): Html = Html(JsQuantity(lang))
|
||||
|
||||
private val defaultTimeagoLocale = TimeagoLocales.js.get("en") err "Missing en TimeagoLocales"
|
||||
def timeagoLocaleScript(implicit ctx: lila.api.Context) = Html {
|
||||
def timeagoLocaleScript(implicit ctx: lila.api.Context): String = {
|
||||
TimeagoLocales.js.get(ctx.lang.code) orElse
|
||||
TimeagoLocales.js.get(ctx.lang.language) getOrElse
|
||||
defaultTimeagoLocale
|
||||
|
|
|
@ -2,7 +2,6 @@ package lila.app
|
|||
package ui
|
||||
|
||||
import lila.common.String.html.escapeHtml
|
||||
import play.twirl.api.Html
|
||||
|
||||
case class OpenGraph(
|
||||
title: String,
|
||||
|
@ -14,7 +13,7 @@ case class OpenGraph(
|
|||
more: List[(String, String)] = Nil
|
||||
) {
|
||||
|
||||
def html = Html(og.str + twitter.str)
|
||||
def frag = scalatags.Text.RawFrag(s"${og.str}${twitter.str}")
|
||||
|
||||
object og {
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ trait Scalatags {
|
|||
|
||||
lazy val dataIcon = attr("data-icon")
|
||||
lazy val dataHint = attr("data-hint")
|
||||
lazy val dataHref = attr("data-href")
|
||||
lazy val dataCount = attr("data-count")
|
||||
|
||||
implicit val charAttr = genericAttr[Char]
|
||||
|
||||
|
|
265
app/views/base/layout.scala
Normal file
265
app/views/base/layout.scala
Normal file
|
@ -0,0 +1,265 @@
|
|||
package views.html.base
|
||||
|
||||
import play.twirl.api.Html
|
||||
import scalatags.Text.all._
|
||||
import scalatags.Text.{ all => st }
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.i18n.{ I18nKeys => trans }
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object layout {
|
||||
|
||||
private val fontVersion = 82
|
||||
|
||||
private val topComment = raw("""<!-- Lichess is open source! See https://github.com/ornicar/lila -->""")
|
||||
private val charset = raw("""<meta charset="utf-8">""")
|
||||
private val fontStylesheets = raw(List(
|
||||
"""<link href="https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Roboto:300" rel="stylesheet">""",
|
||||
"""<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:500&text=0123456789:." rel="stylesheet">"""
|
||||
).mkString)
|
||||
private val noTranslate = raw("""<meta name="google" content="notranslate" />""")
|
||||
private val fontPreload = raw(s"""<link rel="preload" href="${staticUrl("font$fontVersion/fonts/lichess.woff")}" as="font" type="font/woff" crossorigin/>""")
|
||||
private val wasmx = raw("""<meta http-equiv="origin-trial" data-feature="WebAssembly Threads" data-expires="2018-12-12" content="AvQS5g8cLXUfw7Vu3lmQ6B55HURS0KMuY6blwOUmAPYX3Jph8GXMSO4/jTx3el3BxG8SyJRlQTbsRRsjOHwiCAMAAABXeyJvcmlnaW4iOiJodHRwczovL2xpY2hlc3Mub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseVRocmVhZHMiLCJleHBpcnkiOjE1NDQ2MzY2MzN9">""")
|
||||
private val manifests = raw(List(
|
||||
"""<link rel="manifest" href="/manifest.json" />""",
|
||||
"""<meta name="twitter:site" content="@@lichess" />"""
|
||||
).mkString)
|
||||
|
||||
private val faviconSizes = List(256, 128, 64)
|
||||
private val favicons = raw {
|
||||
faviconSizes map { px =>
|
||||
s"""<link rel="icon" type="image/png" href="${staticUrl(s"favicon.$px.png")}" sizes="${px}x${px}"/>"""
|
||||
} mkString
|
||||
}
|
||||
private def titleTag(content: String) = raw(s"""<title>$content</title>""")
|
||||
private def blindModeForm(implicit ctx: Context) = raw("""<form id="blind_mode" action="${routes.Main.toggleBlindMode}" method="POST"><input type="hidden" name="enable" value="${if(ctx.blindMode) 0 else 1}" /><input type="hidden" name="redirect" value="${ctx.req.path}" /><button type="submit">Accessibility: ${if(ctx.blindMode) "Disable" else "Enable"} blind mode</button></form>""")
|
||||
private val zenToggle = raw("""<a data-icon="E" id="zentog" class="text fbt active">ZEN MODE</a>""")
|
||||
private def dasher(me: lila.user.User) = raw(s"""<div class="dasher"><a id="user_tag" class="toggle link">${me.username}</a><div id="dasher_app" class="dropdown"></div></div>""")
|
||||
|
||||
private def challenges(implicit ctx: Context) = spaceless(s"""<div class="challenge_notifications">
|
||||
<a id="challenge_notifications_tag" class="toggle link data-count" data-count="${ctx.nbChallenges}">
|
||||
<span class="hint--bottom-left" data-hint="${trans.challenges()}"><span data-icon="U"></span></span>
|
||||
</a>
|
||||
<div id="challenge_app" class="dropdown"></div>
|
||||
</div>""")
|
||||
|
||||
private def notifications(implicit ctx: Context) = spaceless(s"""<div class="site_notifications">
|
||||
<a id="site_notifications_tag" class="toggle link data-count" data-count="${ctx.nbNotifications}">
|
||||
<span class="hint--bottom-left" data-hint="${trans.notifications()}"><span data-icon=""></span></span>
|
||||
</a>
|
||||
<div id="notify_app" class="dropdown"></div>
|
||||
</div>""")
|
||||
|
||||
private def anonDasher(playing: Boolean)(implicit ctx: Context) = spaceless(s"""<div class="dasher">
|
||||
<a class="toggle anon">
|
||||
<span class="hint--bottom-left" data-hint="${trans.preferences()}"><span data-icon="%"></span></span>
|
||||
</a>
|
||||
<div id="dasher_app" class="dropdown" data-playing="$playing"></div>
|
||||
</div>
|
||||
<a href="${routes.Auth.login}?referrer=${currentPath}" class="signin button text">${trans.signIn()}</a>""")
|
||||
|
||||
private def clinput(implicit ctx: Context) =
|
||||
raw(s"""<div id="clinput"><a class="link"><span data-icon="y"></span></a><input spellcheck="false" placeholder="${trans.search.txt()}"/></div>""")
|
||||
|
||||
private lazy val botImage = img(src := staticUrl("images/icons/bot.png"), title := "Robot chess", style := "display:inline;width:34px;height:34px;vertical-align:top;margin-right:5px;")
|
||||
|
||||
private val spaceRegex = """\s{2,}+""".r
|
||||
private def spaceless(html: String) = raw(spaceRegex.replaceAllIn(html.replace("\\n", ""), ""))
|
||||
|
||||
private val dataDev = attr("data-dev")
|
||||
private val dataUser = attr("data-user")
|
||||
private val dataSoundSet = attr("data-sound-set")
|
||||
private val dataSocketDomain = attr("data-socket-domain")
|
||||
private val dataAssetUrl = attr("data-asset-url")
|
||||
private val dataAssetVersion = attr("data-asset-version")
|
||||
private val dataNonce = attr("data-nonce")
|
||||
private val dataZoom = attr("data-zoom")
|
||||
private val dataPreload = attr("data-preload")
|
||||
private val dataPlaying = attr("data-playing")
|
||||
private val dataPatrons = attr("data-patrons")
|
||||
private val dataStudying = attr("data-studying")
|
||||
|
||||
def apply(
|
||||
title: String,
|
||||
fullTitle: Option[String] = None,
|
||||
baseline: Option[Html] = None,
|
||||
side: Option[Html] = None,
|
||||
menu: Option[Html] = None,
|
||||
chat: Option[Html] = None,
|
||||
underchat: Option[Html] = None,
|
||||
robots: Boolean = isGloballyCrawlable,
|
||||
moreCss: Html = emptyHtml,
|
||||
moreJs: Html = emptyHtml,
|
||||
playing: Boolean = false,
|
||||
openGraph: Option[lila.app.ui.OpenGraph] = None,
|
||||
atom: Option[Html] = None,
|
||||
chessground: Boolean = true,
|
||||
zoomable: Boolean = false,
|
||||
asyncJs: Boolean = false,
|
||||
csp: Option[lila.common.ContentSecurityPolicy] = None
|
||||
)(body: Html)(implicit ctx: Context) =
|
||||
html(st.lang := ctx.lang.language)(
|
||||
topComment,
|
||||
head(
|
||||
charset,
|
||||
cspEnabled() option raw(
|
||||
s"""<meta http-equiv="Content-Security-Policy" content="${csp.getOrElse(defaultCsp)}">"""
|
||||
),
|
||||
if (isProd) frag(
|
||||
wasmxEnabled() option wasmx,
|
||||
titleTag(fullTitle | s"$title • lichess.org"),
|
||||
fontStylesheets
|
||||
)
|
||||
else frag(
|
||||
titleTag(s"[dev] ${fullTitle | s"$title • lichess.org"}"),
|
||||
cssAt("offline/font.noto.css"),
|
||||
cssAt("offline/font.roboto.mono.css")
|
||||
),
|
||||
ctx.currentBg match {
|
||||
case "dark" => cssTag("dark.css")
|
||||
case "transp" => cssTags("dark.css", "transp.css")
|
||||
case _ => emptyHtml
|
||||
},
|
||||
cssTag("common.css"),
|
||||
cssTag("board.css"),
|
||||
ctx.zoom ifTrue zoomable map { z =>
|
||||
zoomStyle(z / 100f, ctx.pref.is3d)
|
||||
},
|
||||
ctx.pref.is3d option cssTag("board-3d.css"),
|
||||
ctx.pref.coords == 1 option cssTag("board.coords.inner.css"),
|
||||
ctx.pageData.inquiry.isDefined option cssTag("inquiry.css"),
|
||||
ctx.userContext.impersonatedBy.isDefined option cssTag("impersonate.css"),
|
||||
isStage option cssTag("stage.css"),
|
||||
moreCss,
|
||||
link(id := "piece-sprite", href := assetUrl(s"stylesheets/piece/${ctx.currentPieceSet}.css"), `type` := "text/css", rel := "stylesheet"),
|
||||
meta(content := openGraph.fold(trans.siteDescription.txt())(o => o.description), name := "description"),
|
||||
link(id := "favicon", rel := "shortcut icon", href := staticUrl("images/favicon-32-white.png"), `type` := "image/x-icon"),
|
||||
link(rel := "mask-icon", href := staticUrl("favicon.svg"), color := "black"),
|
||||
favicons,
|
||||
!robots option raw("""<meta content="noindex, nofollow" name="robots">"""),
|
||||
noTranslate,
|
||||
openGraph.map(_.frag),
|
||||
atom | link(href := routes.Blog.atom, `type` := "application/atom+xml", rel := "alternate", st.title := trans.blog.txt()),
|
||||
ctx.transpBgImg map { img =>
|
||||
raw(s"""<style type="text/css" id="bg-data">body.transp::before{background-image:url('$img');}</style>""")
|
||||
},
|
||||
fontPreload,
|
||||
manifests
|
||||
),
|
||||
st.body(
|
||||
cls := List(
|
||||
"preload base" -> true,
|
||||
ctx.currentTheme.cssClass -> true,
|
||||
ctx.currentTheme3d.cssClass -> true,
|
||||
(if (ctx.currentBg == "transp") "dark transp" else ctx.currentBg) -> true,
|
||||
ctx.currentPieceSet3d.toString -> true,
|
||||
"piece_letter" -> ctx.pref.pieceNotationIsLetter,
|
||||
"zen" -> ctx.pref.isZen,
|
||||
"blind_mode" -> ctx.blindMode,
|
||||
"kid" -> ctx.kid,
|
||||
"mobile" -> ctx.isMobileBrowser,
|
||||
"playing fixed-scroll" -> playing
|
||||
),
|
||||
dataDev := (!isProd).option("true"),
|
||||
dataUser := ctx.userId,
|
||||
dataSoundSet := ctx.currentSoundSet.toString,
|
||||
dataSocketDomain := socketDomain,
|
||||
dataAssetUrl := assetBaseUrl,
|
||||
dataAssetVersion := assetVersion.value,
|
||||
dataNonce := ctx.nonce.map(_.value),
|
||||
dataZoom := ctx.zoom.map(_.toString)
|
||||
)(
|
||||
blindModeForm,
|
||||
div(id := "site_description")(trans.siteDescription()),
|
||||
ctx.pageData.inquiry map { views.html.mod.inquiry(_) },
|
||||
ctx.me ifTrue ctx.userContext.impersonatedBy.isDefined map { views.html.mod.impersonate(_) },
|
||||
isStage option div(id := "stage")(
|
||||
"This is an empty lichess preview website for developers. ",
|
||||
a(href := "https://lichess.org")("Go to lichess.org instead")
|
||||
),
|
||||
lila.security.EmailConfirm.cookie.get(ctx.req).map(views.html.auth.emailConfirmBanner(_)),
|
||||
playing option zenToggle,
|
||||
div(id := "top", cls := (if (ctx.pref.is3d) "is3d" else "is2d"))(
|
||||
topmenu(),
|
||||
div(id := "ham-plate", cls := "link hint--bottom", dataHint := trans.menu.txt())(
|
||||
div(id := "hamburger", dataIcon := "[")
|
||||
),
|
||||
ctx.me map { me =>
|
||||
frag(dasher(me), challenges, notifications)
|
||||
} getOrElse {
|
||||
!ctx.pageData.error option anonDasher(playing)
|
||||
},
|
||||
ctx.teamNbRequests > 0 option
|
||||
a(cls := "link data-count", href := routes.Team.requests, dataCount := ctx.teamNbRequests)(
|
||||
span(cls := "hint--bottom-left", dataHint := trans.teams.txt())(span(dataIcon := "f"))
|
||||
),
|
||||
isGranted(_.SeeReport) option
|
||||
a(cls := "link text data-count", href := routes.Report.list, dataCount := reportNbOpen, dataIcon := ""),
|
||||
clinput,
|
||||
a(id := "reconnecting", cls := "link text", dataIcon := "B")(trans.reconnecting())
|
||||
),
|
||||
div(cls := s"content ${if (ctx.pref.is3d) "is3d" else "is2d"}")(
|
||||
div(id := "site_header")(
|
||||
div(id := "notifications"),
|
||||
div(cls := "board_left")(
|
||||
h1(
|
||||
a(id := "site_title", href := routes.Lobby.home)(
|
||||
if (ctx.kid) span(st.title := trans.kidMode.txt(), cls := "kiddo")("😊")
|
||||
else ctx.isBot option botImage,
|
||||
"lichess",
|
||||
span(cls := "extension")(if (isProd) ".org" else " dev")
|
||||
)
|
||||
),
|
||||
baseline,
|
||||
menu map { sideMenu =>
|
||||
div(cls := "side_menu")(sideMenu)
|
||||
},
|
||||
side,
|
||||
chat
|
||||
),
|
||||
underchat map { g =>
|
||||
div(cls := "under_chat")(g)
|
||||
}
|
||||
),
|
||||
div(id := "lichess")(body)
|
||||
),
|
||||
ctx.me.map { me =>
|
||||
div(
|
||||
id := "friend_box",
|
||||
dataPreload := ctx.onlineFriends.users.map(_.titleName).mkString(","),
|
||||
dataPlaying := ctx.onlineFriends.playing.mkString(","),
|
||||
dataPatrons := ctx.onlineFriends.patrons.mkString(","),
|
||||
dataStudying := ctx.onlineFriends.studying.mkString(",")
|
||||
)(
|
||||
div(cls := "title")(
|
||||
strong(cls := "online")(" "),
|
||||
" ",
|
||||
trans.onlineFriends()
|
||||
),
|
||||
div(cls := "content_wrap")(
|
||||
div(cls := "content list")(
|
||||
div(cls := List(
|
||||
"nobody" -> true,
|
||||
"none" -> ctx.onlineFriends.users.nonEmpty
|
||||
))(
|
||||
span(trans.noFriendsOnline()),
|
||||
a(cls := "find button", href := routes.User.opponents)(
|
||||
span(cls := "is3 text", dataIcon := "h")(trans.findFriends())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
chessground option jsTag("vendor/chessground.min.js"),
|
||||
ctx.requiresFingerprint option fingerprintTag,
|
||||
jsAt(s"compiled/lichess.site${isProd ?? ".min"}.js", async = asyncJs),
|
||||
moreJs,
|
||||
embedJs(s"""lichess.quantity=${lila.i18n.JsQuantity(ctx.lang)};$timeagoLocaleScript;"""),
|
||||
ctx.pageData.inquiry.isDefined option jsTag("inquiry.js", async = asyncJs)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
@(
|
||||
title: String,
|
||||
fullTitle: Option[String] = None,
|
||||
baseline: Option[Html] = None,
|
||||
side: Option[Html] = None,
|
||||
menu: Option[Html] = None,
|
||||
chat: Option[Html] = None,
|
||||
underchat: Option[Html] = None,
|
||||
robots: Boolean = isGloballyCrawlable,
|
||||
moreCss: Html = emptyHtml,
|
||||
moreJs: Html = emptyHtml,
|
||||
playing: Boolean = false,
|
||||
openGraph: Option[lila.app.ui.OpenGraph] = None,
|
||||
atom: Option[Html] = None,
|
||||
chessground: Boolean = true,
|
||||
zoomable: Boolean = false,
|
||||
asyncJs: Boolean = false,
|
||||
csp: Option[lila.common.ContentSecurityPolicy] = None)(body: Html)(implicit ctx: Context)
|
||||
<!doctype html>
|
||||
<html lang="@lang.language">
|
||||
<!-- Lichess is open source! See https://github.com/ornicar/lila -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@if(cspEnabled()) {
|
||||
<meta http-equiv="Content-Security-Policy" content="@csp.getOrElse(defaultCsp)">
|
||||
}
|
||||
@if(isProd) {
|
||||
@if(wasmxEnabled()) {
|
||||
<meta http-equiv="origin-trial" data-feature="WebAssembly Threads" data-expires="2018-12-12" content="AvQS5g8cLXUfw7Vu3lmQ6B55HURS0KMuY6blwOUmAPYX3Jph8GXMSO4/jTx3el3BxG8SyJRlQTbsRRsjOHwiCAMAAABXeyJvcmlnaW4iOiJodHRwczovL2xpY2hlc3Mub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseVRocmVhZHMiLCJleHBpcnkiOjE1NDQ2MzY2MzN9">
|
||||
}
|
||||
<title>@fullTitle.getOrElse{@title • lichess.org}</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Roboto:300" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:500&text=0123456789:." rel="stylesheet">
|
||||
} else {
|
||||
<title>[dev] @fullTitle.getOrElse{@title • lichess.org}</title>
|
||||
@cssAt("offline/font.noto.css")
|
||||
@cssAt("offline/font.roboto.mono.css")
|
||||
}
|
||||
@ctx.currentBg match {
|
||||
case "dark" => { @cssTag("dark.css") }
|
||||
case "transp" => { @cssTag("dark.css")@cssTag("transp.css") }
|
||||
case _ => {}
|
||||
}
|
||||
@cssTag("common.css")
|
||||
@cssTag("board.css")
|
||||
@if(zoomable) {
|
||||
@ctx.zoom.map { z =>
|
||||
@zoomStyle(z / 100f, ctx.pref.is3d) }
|
||||
}
|
||||
@if(ctx.pref.is3d) { @cssTag("board-3d.css") }
|
||||
@if(ctx.pref.coords == 1) { @cssTag("board.coords.inner.css") }
|
||||
@if(ctx.pageData.inquiry.isDefined) { @cssTag("inquiry.css") }
|
||||
@if(ctx.userContext.impersonatedBy.isDefined) { @cssTag("impersonate.css") }
|
||||
@if(isStage) { @cssTag("stage.css") }
|
||||
@moreCss
|
||||
<link id="piece-sprite" href="@assetUrl(s"stylesheets/piece/${ctx.currentPieceSet}.css")" type="text/css" rel="stylesheet"/>
|
||||
<meta content="@openGraph.fold(trans.siteDescription.txt())(o => o.description)" name="description">
|
||||
<link id="favicon" rel="shortcut icon" href="@staticUrl("images/favicon-32-white.png")" type="image/x-icon" />
|
||||
<link rel="mask-icon" href="@staticUrl("favicon.svg")" color="black">
|
||||
<link rel="icon" type="image/png" href="@staticUrl("favicon.256.png")" sizes="256x256"/><link rel="icon" type="image/png" href="@staticUrl("favicon.128.png")" sizes="128x128"/><link rel="icon" type="image/png" href="@staticUrl("favicon.64.png")" sizes="64x64"/>
|
||||
@if(!robots) {<meta content="noindex, nofollow" name="robots">}
|
||||
<meta name="google" content="notranslate" />
|
||||
@openGraph.map(_.html)
|
||||
@atom.getOrElse {
|
||||
<link href="@routes.Blog.atom()" type="application/atom+xml" rel="alternate" title="@trans.blog()" />
|
||||
}
|
||||
@ctx.transpBgImg.map { img =>
|
||||
<style type="text/css" id="bg-data">body.transp::before{background-image:url('@img');}</style>
|
||||
}
|
||||
<link rel="preload" href="@staticUrl("font82/fonts/lichess.woff")" as="font" type="font/woff" crossorigin/>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="twitter:site" content="@@lichess" />
|
||||
</head>
|
||||
<body class="preload base @List(
|
||||
ctx.currentTheme.cssClass,
|
||||
ctx.currentTheme3d.cssClass,
|
||||
if (ctx.currentBg == "transp") "dark transp" else ctx.currentBg,
|
||||
ctx.currentPieceSet3d,
|
||||
ctx.pref.pieceNotationIsLetter ?? "piece_letter",
|
||||
ctx.pref.isZen ?? "zen",
|
||||
ctx.blindMode ?? "blind_mode",
|
||||
ctx.kid ?? "kid",
|
||||
ctx.isMobileBrowser ?? "mobile",
|
||||
playing ?? "playing fixed-scroll").mkString(" ")"
|
||||
@if(!isProd){data-dev="true"}
|
||||
@ctx.userId.map {id => data-user="@id"}
|
||||
data-sound-set="@ctx.currentSoundSet"
|
||||
data-socket-domain="@socketDomain"
|
||||
data-asset-url="@assetBaseUrl"
|
||||
data-asset-version="@assetVersion"
|
||||
@ctx.nonce.map { nonce => data-nonce="@nonce" }
|
||||
@ctx.zoom.map { zoom => data-zoom="@zoom" }>
|
||||
<form id="blind_mode" action="@routes.Main.toggleBlindMode" method="POST"><input type="hidden" name="enable" value="@if(ctx.blindMode){0}else{1}" /><input type="hidden" name="redirect" value="@ctx.req.path" /><button type="submit">Accessibility: @if(ctx.blindMode){Disable}else{Enable} blind mode</button></form>
|
||||
<div id="site_description">@trans.siteDescription()</div>
|
||||
@ctx.pageData.inquiry.map { inquiry =>
|
||||
@mod.inquiry(inquiry)
|
||||
}
|
||||
@ctx.me.map { me =>
|
||||
@if(ctx.userContext.impersonatedBy.isDefined) { @mod.impersonate(me) }
|
||||
}
|
||||
@if(isStage) {
|
||||
<div id="stage">This is an empty lichess preview website for developers. <a href="https://lichess.org">Go to lichess.org instead</a></div>
|
||||
}
|
||||
@lila.security.EmailConfirm.cookie.get(ctx.req).map(auth.emailConfirmBanner(_))
|
||||
@if(playing) { <a data-icon="E" id="zentog" class="text fbt active">ZEN MODE</a> }
|
||||
<div id="top" class="@if(ctx.pref.is3d){is3d}else{is2d}">
|
||||
@topmenu()
|
||||
<div id="ham-plate" class="link hint--bottom" data-hint="@trans.menu()">
|
||||
<div id="hamburger" data-icon="["></div>
|
||||
</div>
|
||||
@ctx.me.map { me =>
|
||||
<div class="dasher">
|
||||
<a id="user_tag" class="toggle link">@me.username</a>
|
||||
<div id="dasher_app" class="dropdown"></div>
|
||||
</div>
|
||||
<div class="challenge_notifications">
|
||||
<a id="challenge_notifications_tag" class="toggle link data-count" data-count="@ctx.nbChallenges">
|
||||
<span class="hint--bottom-left" data-hint="@trans.challenges()">
|
||||
<span data-icon="U"></span>
|
||||
</span>
|
||||
</a>
|
||||
<div id="challenge_app" class="dropdown"></div>
|
||||
</div>
|
||||
<div class="site_notifications">
|
||||
<a id="site_notifications_tag" class="toggle link data-count" data-count="@ctx.nbNotifications">
|
||||
<span class="hint--bottom-left" data-hint="@trans.notifications()">
|
||||
<span data-icon=""></span>
|
||||
</span>
|
||||
</a>
|
||||
<div id="notify_app" class="dropdown"></div>
|
||||
</div>
|
||||
}.getOrElse {
|
||||
@if(!ctx.pageData.error) {
|
||||
<div class="dasher">
|
||||
<a class="toggle anon">
|
||||
<span class="hint--bottom-left" data-hint="@trans.preferences()">
|
||||
<span data-icon="%"></span>
|
||||
</span>
|
||||
</a>
|
||||
<div id="dasher_app" class="dropdown"@if(playing){ data-playing="true"}></div>
|
||||
</div>
|
||||
<a href="@routes.Auth.login?referrer=@currentPath" class="signin button text">@trans.signIn()</a>
|
||||
}
|
||||
}
|
||||
@if(ctx.teamNbRequests > 0) {
|
||||
<a class="link data-count" href="@routes.Team.requests()" data-count="@ctx.teamNbRequests">
|
||||
<span class="hint--bottom-left" data-hint="@trans.teams()">
|
||||
<span data-icon="f"></span>
|
||||
</span>
|
||||
</a>
|
||||
}
|
||||
@if(isGranted(_.SeeReport)) {
|
||||
<a class="link text data-count" href="@routes.Report.list" data-count="@reportNbOpen" data-icon=""></a>
|
||||
}
|
||||
<div id="clinput"><a class="link"><span data-icon="y"></span></a><input spellcheck="false" placeholder="@trans.search()"/></div>
|
||||
<a id="reconnecting" class="link text" data-icon="B">@trans.reconnecting()</a>
|
||||
</div>
|
||||
<div class="content @if(ctx.pref.is3d){is3d}else{is2d}">
|
||||
<div id="site_header">
|
||||
<div id="notifications"></div>
|
||||
<div class="board_left">
|
||||
<h1>
|
||||
<a id="site_title" href="@routes.Lobby.home">@if(ctx.kid){<span title="@trans.kidMode()" class="kiddo">😊</span>}else{@if(ctx.isBot){<img src="@staticUrl("images/icons/bot.png")" title="Robot chess" style="display:inline;width:34px;height:34px;vertical-align:top;margin-right:5px;"/>}}lichess<span class="extension">@if(isProd){.org}else{ dev}</span></a>
|
||||
</h1>
|
||||
@baseline
|
||||
@menu.map { sideMenu =>
|
||||
<div class="side_menu">@sideMenu</div>
|
||||
}
|
||||
@side
|
||||
@chat
|
||||
</div>
|
||||
@underchat.map { g =>
|
||||
<div class="under_chat">@g</div>
|
||||
}
|
||||
</div>
|
||||
<div id="lichess">
|
||||
@body
|
||||
</div>
|
||||
</div>
|
||||
@ctx.me.map { me =>
|
||||
<div id="friend_box" data-preload="@ctx.onlineFriends.users.map(_.titleName).mkString(",")"
|
||||
data-playing="@ctx.onlineFriends.playing.mkString(",")"
|
||||
data-patrons="@ctx.onlineFriends.patrons.mkString(",")"
|
||||
data-studying="@ctx.onlineFriends.studying.mkString(",")">
|
||||
<div class="title"><strong class="online"> </strong> @trans.onlineFriends()</div>
|
||||
<div class="content_wrap">
|
||||
<div class="content list"></div>
|
||||
<div class="nobody@if(ctx.onlineFriends.users.nonEmpty){ none}">
|
||||
<span>@trans.noFriendsOnline()</span>
|
||||
<a class="find button" href="@routes.User.opponents">
|
||||
<span class="is3 text" data-icon="h">@trans.findFriends()</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if(chessground) {@jsTag("vendor/chessground.min.js")}
|
||||
@if(ctx.requiresFingerprint) { @fingerprintTag }
|
||||
@jsAt(s"compiled/lichess.site${isProd??".min"}.js", async = asyncJs)
|
||||
@moreJs
|
||||
@embedJs {lichess.quantity=@i18nJsQuantityFunction;@timeagoLocaleScript;}
|
||||
@if(ctx.pageData.inquiry.isDefined) { @jsTag("inquiry.js", async = asyncJs) }
|
||||
</body>
|
||||
</html>
|
71
app/views/base/topmenu.scala
Normal file
71
app/views/base/topmenu.scala
Normal file
|
@ -0,0 +1,71 @@
|
|||
package views.html.base
|
||||
|
||||
import scalatags.Text.all._
|
||||
import scalatags.Text.tags2.section
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.i18n.{ I18nKeys => trans }
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object topmenu {
|
||||
|
||||
def apply()(implicit ctx: Context) = div(id := "topmenu", cls := "hover")(
|
||||
section(
|
||||
a(href := "/")(trans.play()),
|
||||
div(
|
||||
if (ctx.noBot) a(href := "/?any#hook")(trans.createAGame())
|
||||
else a(href := "/?any#friend")(trans.playWithAFriend()),
|
||||
ctx.noBot option frag(
|
||||
a(href := routes.Tournament.home())(trans.tournament()),
|
||||
a(href := routes.Simul.home)(trans.simultaneousExhibitions())
|
||||
)
|
||||
)
|
||||
),
|
||||
section(
|
||||
a(href := "@routes.Puzzle.home")(trans.learnMenu()),
|
||||
div(
|
||||
ctx.noBot option frag(
|
||||
a(href := routes.Learn.index)(trans.chessBasics()),
|
||||
a(href := routes.Puzzle.home)(trans.training()),
|
||||
a(href := routes.Practice.index)("Practice"),
|
||||
a(href := routes.Coordinate.home)(trans.coordinates.coordinates())
|
||||
),
|
||||
a(href := routes.Study.allDefault(1))("Study"),
|
||||
a(href := routes.Coach.allDefault(1))(trans.coaches())
|
||||
)
|
||||
),
|
||||
section(
|
||||
a(href := "@routes.Tv.index")(trans.watch()),
|
||||
div(
|
||||
a(href := routes.Tv.index)("Lichess TV"),
|
||||
a(href := routes.Tv.games)(trans.currentGames()),
|
||||
a(href := routes.Streamer.index())("Streamers"),
|
||||
a(href := routes.Relay.index())("Broadcasts (beta)"),
|
||||
ctx.noBot option a(href := routes.Video.index)("@trans.videoLibrary()")
|
||||
)
|
||||
),
|
||||
section(
|
||||
a(href := "@routes.User.list")(trans.community()),
|
||||
div(
|
||||
a(href := routes.User.list)(trans.players()),
|
||||
NotForKids(frag(
|
||||
a(href := routes.Team.home())(trans.teams()),
|
||||
a(href := routes.ForumCateg.index)(trans.forum())
|
||||
)),
|
||||
a(href := routes.QaQuestion.index())(trans.questionsAndAnswers())
|
||||
)
|
||||
),
|
||||
section(
|
||||
a(href := "@routes.UserAnalysis.index")(trans.tools()),
|
||||
div(
|
||||
a(href := routes.UserAnalysis.index)(trans.analysis()),
|
||||
a(href := s"${routes.UserAnalysis.index}#explorer")(trans.openingExplorer()),
|
||||
a(href := routes.Editor.index)(trans.boardEditor()),
|
||||
a(href := routes.Importer.importGame)(trans.importGame()),
|
||||
a(href := routes.Search.index())(trans.advancedSearch())
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
<div id="topmenu" class="hover">
|
||||
<section>
|
||||
<a href="/">@trans.play()</a>
|
||||
<div>
|
||||
@if(ctx.noBot){<a href="/?any#hook">@trans.createAGame()</a>}else{<a href="/?any#friend">@trans.playWithAFriend()</a>}
|
||||
@if(ctx.noBot){<a href="@routes.Tournament.home()">@trans.tournament()</a>}
|
||||
@if(ctx.noBot){<a href="@routes.Simul.home">@trans.simultaneousExhibitions()</a>}
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<a href="@routes.Puzzle.home">@trans.learnMenu()</a>
|
||||
<div>
|
||||
@if(ctx.noBot){<a href="@routes.Learn.index">@trans.chessBasics()</a>}
|
||||
@if(ctx.noBot){<a href="@routes.Puzzle.home">@trans.training()</a>}
|
||||
@if(ctx.noBot){<a href="@routes.Practice.index">Practice</a>}
|
||||
@if(ctx.noBot){<a href="@routes.Coordinate.home">@trans.coordinates.coordinates()</a>}
|
||||
<a href="@routes.Study.allDefault(1)">Study</a>
|
||||
<a href="@routes.Coach.allDefault(1)">@trans.coaches()</a>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<a href="@routes.Tv.index">@trans.watch()</a>
|
||||
<div>
|
||||
<a href="@routes.Tv.index">Lichess TV</a>
|
||||
<a href="@routes.Tv.games">@trans.currentGames()</a>
|
||||
<a href="@routes.Streamer.index()">Streamers</a>
|
||||
<a href="@routes.Relay.index()">Broadcasts (beta)</a>
|
||||
@if(ctx.noBot){<a href="@routes.Video.index">@trans.videoLibrary()</a>}
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<a href="@routes.User.list">@trans.community()</a>
|
||||
<div>
|
||||
<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>
|
||||
}
|
||||
<a href="@routes.QaQuestion.index()">@trans.questionsAndAnswers()</a>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<a href="@routes.UserAnalysis.index">@trans.tools()</a>
|
||||
<div>
|
||||
<a href="@routes.UserAnalysis.index">@trans.analysis()</a>
|
||||
<a href="@routes.UserAnalysis.index#explorer">@trans.openingExplorer()</a>
|
||||
<a href="@routes.Editor.index">@trans.boardEditor()</a>
|
||||
<a href="@routes.Importer.importGame">@trans.importGame()</a>
|
||||
<a href="@routes.Search.index()">@trans.advancedSearch()</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
|
@ -16,7 +16,9 @@ object recent {
|
|||
a(dataIcon := p.isTeam.option("f"), cls := "post_link", href := routes.ForumPost.redirect(p.postId), title := p.topicName)(
|
||||
shorten(p.topicName, 30)
|
||||
),
|
||||
" ",
|
||||
userIdLink(p.userId, withOnline = false),
|
||||
" ",
|
||||
span(cls := "extract")(shorten(p.text, 70))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ import controllers.routes
|
|||
|
||||
object bits {
|
||||
|
||||
private val dataTitle = attr("data-title")
|
||||
|
||||
def featuredJs(pov: Pov) = Html {
|
||||
s"""${gameFenNoCtx(pov, tv = true)}${vstext(pov)(none)}"""
|
||||
}
|
||||
|
@ -75,7 +77,7 @@ object bits {
|
|||
|
||||
private def playerTitle(player: Player) =
|
||||
lightUser(player.userId).flatMap(_.title) map { t =>
|
||||
span(cls := "title", attr("data-title") := t, title := Title titleName Title(t))(t)
|
||||
span(cls := "title", dataTitle := t, title := Title titleName Title(t))(t)
|
||||
}
|
||||
|
||||
def vstext(pov: Pov)(ctxOption: Option[Context]) =
|
||||
|
|
|
@ -12,6 +12,8 @@ import controllers.routes
|
|||
object side {
|
||||
|
||||
private val separator = " • "
|
||||
private val dataUserTv = attr("data-user-tv")
|
||||
private val dataTime = attr("data-time")
|
||||
|
||||
def apply(
|
||||
pov: lila.game.Pov,
|
||||
|
@ -103,7 +105,7 @@ object side {
|
|||
|
||||
userTv.map { u =>
|
||||
div(cls := "side_box")(
|
||||
h2(cls := "top user_tv text", attr("data-user-tv") := u.id, dataIcon := "1")(u.titleUsername)
|
||||
h2(cls := "top user_tv text", dataUserTv := u.id, dataIcon := "1")(u.titleUsername)
|
||||
)
|
||||
} orElse {
|
||||
lila.common.HTTPRequest.isMobile(ctx.req) option
|
||||
|
@ -116,7 +118,7 @@ object side {
|
|||
tour.map { t =>
|
||||
div(cls := "game_tournament side_box no_padding scroll-shadow-soft")(
|
||||
p(cls := "top text", dataIcon := "g")(a(href := routes.Tournament.show(t.id))(t.fullName)),
|
||||
div(cls := "clock", attr("data-time") := t.secondsToFinish)(
|
||||
div(cls := "clock", dataTime := t.secondsToFinish)(
|
||||
div(cls := "time")(t.clockStatus)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -53,7 +53,7 @@ object home {
|
|||
simuls.find(_.spotlightable) take 2 map { views.html.simul.homepageSpotlight(_) } toList
|
||||
),
|
||||
ctx.me map { u =>
|
||||
div(id := "timeline", attr("data-href") := routes.Timeline.home)(
|
||||
div(id := "timeline", dataHref := routes.Timeline.home)(
|
||||
views.html.timeline entries userTimeline,
|
||||
div(cls := "links")(
|
||||
userTimeline.size >= 8 option
|
||||
|
|
|
@ -12,12 +12,16 @@ import controllers.routes
|
|||
|
||||
object bits {
|
||||
|
||||
private val dataColor = attr("data-color")
|
||||
private val dataFen = attr("data-fen")
|
||||
private val dataLastmove = attr("data-lastmove")
|
||||
|
||||
def daily(p: lila.puzzle.Puzzle, fen: String, lastMove: String) = a(
|
||||
href := routes.Puzzle.daily(),
|
||||
cls := "mini_board parse_fen is2d",
|
||||
attr("data-color") := p.color.name,
|
||||
attr("data-fen") := fen,
|
||||
attr("data-lastmove") := lastMove
|
||||
dataColor := p.color.name,
|
||||
dataFen := fen,
|
||||
dataLastmove := lastMove
|
||||
)(miniBoardContent)
|
||||
|
||||
def jsI18n(implicit context: Context) = toJson(i18nJsObject(translations))
|
||||
|
|
Loading…
Reference in a new issue