lila/app/views/base/layout.scala

257 lines
11 KiB
Scala
Raw Normal View History

2018-12-03 01:42:53 -07:00
package views.html.base
import lila.api.Context
import lila.app.templating.Environment._
2018-12-04 06:16:52 -07:00
import lila.app.ui.ScalatagsTemplate._
import lila.common.{ Lang, ContentSecurityPolicy }
2019-04-16 00:40:06 -06:00
import lila.pref.Pref
2018-12-03 01:42:53 -07:00
import controllers.routes
object layout {
2019-01-16 19:19:41 -07:00
object bits {
val doctype = raw("<!doctype html>")
def htmlTag(implicit lang: Lang) = html(st.lang := lang.language)
2019-01-16 19:19:41 -07:00
val topComment = raw("""<!-- Lichess is open source! See https://github.com/ornicar/lila -->""")
val charset = raw("""<meta charset="utf-8">""")
2019-02-08 04:13:50 -07:00
val viewport = raw("""<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"/>""")
2019-09-02 04:31:50 -06:00
def metaCsp(csp: ContentSecurityPolicy): Frag = raw {
s"""<meta http-equiv="Content-Security-Policy" content="$csp">"""
}
def metaCsp(csp: Option[ContentSecurityPolicy])(implicit ctx: Context): Frag = metaCsp(csp getOrElse defaultCsp)
2019-04-08 03:54:45 -06:00
def pieceSprite(implicit ctx: Context): Frag = pieceSprite(ctx.currentPieceSet)
def pieceSprite(ps: lila.pref.PieceSet): Frag =
2019-04-21 08:32:11 -06:00
link(id := "piece-sprite", href := assetUrl(s"piece-css/$ps.css"), tpe := "text/css", rel := "stylesheet")
2019-01-16 03:33:31 -07:00
}
2019-01-16 19:19:41 -07:00
import bits._
2019-01-16 03:33:31 -07:00
2018-12-03 01:42:53 -07:00
private val noTranslate = raw("""<meta name="google" content="notranslate" />""")
private def fontPreload(implicit ctx: Context) = raw {
s"""<link rel="preload" href="${assetUrl(s"font/lichess.woff2")}" as="font" type="font/woff2" crossorigin/>""" +
!ctx.pref.pieceNotationIsLetter ??
s"""<link rel="preload" href="${assetUrl(s"font/lichess.chess.woff2")}" as="font" type="font/woff2" crossorigin/>"""
}
2019-04-24 23:44:53 -06:00
private val manifests = raw("""<link rel="manifest" href="/manifest.json" /><meta name="twitter:site" content="@lichess" />""")
2018-12-03 01:42:53 -07:00
2019-04-26 07:35:32 -06:00
private val jsLicense = raw("""<link rel="jslicense" href="/source"/>""")
2018-12-03 01:42:53 -07:00
private val favicons = raw {
2018-12-04 06:16:52 -07:00
List(256, 128, 64) map { px =>
2018-12-03 01:42:53 -07:00
s"""<link rel="icon" type="image/png" href="${staticUrl(s"favicon.$px.png")}" sizes="${px}x${px}"/>"""
} mkString
}
2019-04-23 03:02:45 -06:00
private def blindModeForm(implicit ctx: Context) = raw(s"""<form id="blind-mode" action="${routes.Main.toggleBlindMode}" method="POST"><input type="hidden" name="enable" value="${if (ctx.blind) 0 else 1}" /><input type="hidden" name="redirect" value="${ctx.req.path}" /><button type="submit">Accessibility: ${if (ctx.blind) "Disable" else "Enable"} blind mode</button></form>""")
2018-12-03 01:42:53 -07:00
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 allNotifications(implicit ctx: Context) = spaceless(s"""<div>
<a id="challenge-toggle" class="toggle link">
<span title="${trans.challenges().render}" class="data-count" data-count="${ctx.nbChallenges}" data-icon="U"></span>
2018-12-03 01:42:53 -07:00
</a>
<div id="challenge-app" class="dropdown"></div>
2019-01-19 17:43:18 -07:00
</div>
<div>
<a id="notify-toggle" class="toggle link">
2019-07-09 06:17:09 -06:00
<span title="${trans.notifications().render}" class="data-count" data-count="${ctx.nbNotifications}" data-icon=""></span>
2018-12-03 01:42:53 -07:00
</a>
<div id="notify-app" class="dropdown"></div>
2018-12-03 01:42:53 -07:00
</div>""")
private def anonDasher(playing: Boolean)(implicit ctx: Context) = spaceless(s"""<div class="dasher">
2019-02-11 17:53:15 -07:00
<a class="toggle link anon">
2019-08-05 14:34:02 -06:00
<span title="${trans.preferences().render}" data-icon="%"></span>
2018-12-03 01:42:53 -07:00
</a>
<div id="dasher_app" class="dropdown" data-playing="$playing"></div>
</div>
2019-04-27 21:34:25 -06:00
<a href="${routes.Auth.login}?referrer=${ctx.req.path}" class="signin button button-empty">${trans.signIn().render}</a>""")
2018-12-03 01:42:53 -07:00
2018-12-10 21:14:13 -07:00
private val clinputLink = a(cls := "link")(span(dataIcon := "y"))
2018-12-03 01:42:53 -07:00
private def clinput(implicit ctx: Context) =
2018-12-10 07:18:55 -07:00
div(id := "clinput")(
2018-12-10 21:14:13 -07:00
clinputLink,
2018-12-10 07:18:55 -07:00
input(spellcheck := "false", placeholder := trans.search.txt())
)
2018-12-03 01:42:53 -07:00
2019-05-03 02:43:39 -06:00
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;vertical-align:text-top")
2018-12-03 01:42:53 -07:00
private val spaceRegex = """\s{2,}+""".r
private def spaceless(html: String) = raw(spaceRegex.replaceAllIn(html.replace("\\n", ""), ""))
private val dataVapid = attr("data-vapid")
2018-12-03 01:42:53 -07:00
private val dataUser = attr("data-user")
private val dataSoundSet = attr("data-sound-set")
private val dataSocketDomain = attr("data-socket-domain")
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")
private val dataNonce = attr("data-nonce")
2018-12-03 01:42:53 -07:00
def apply(
title: String,
fullTitle: Option[String] = None,
robots: Boolean = isGloballyCrawlable,
2019-04-08 03:54:45 -06:00
moreCss: Frag = emptyFrag,
moreJs: Frag = emptyFrag,
2018-12-03 01:42:53 -07:00
playing: Boolean = false,
openGraph: Option[lila.app.ui.OpenGraph] = None,
chessground: Boolean = true,
zoomable: Boolean = false,
2019-04-21 00:46:17 -06:00
deferJs: Boolean = false,
2019-02-08 04:13:50 -07:00
csp: Option[ContentSecurityPolicy] = None,
2019-02-24 00:02:59 -07:00
wrapClass: String = ""
2019-04-08 06:03:47 -06:00
)(body: Frag)(implicit ctx: Context) = frag(
2018-12-03 02:10:20 -07:00
doctype,
htmlTag(ctx.lang)(
2018-12-03 01:42:53 -07:00
topComment,
head(
charset,
2019-04-11 04:05:52 -06:00
viewport,
2019-01-16 03:33:31 -07:00
metaCsp(csp),
2019-03-07 03:26:45 -07:00
if (isProd && !isStage) frag(
2019-04-11 04:05:52 -06:00
st.headTitle(fullTitle | s"$title • lichess.org")
2018-12-03 01:42:53 -07:00
)
2019-03-07 03:29:21 -07:00
else st.headTitle(s"[dev] ${fullTitle | s"$title • lichess.dev"}"),
2019-04-21 08:33:50 -06:00
cssTag("site"),
ctx.pref.is3d option cssTag("board-3d"),
ctx.pageData.inquiry.isDefined option cssTagNoTheme("mod.inquiry"),
ctx.userContext.impersonatedBy.isDefined option cssTagNoTheme("mod.impersonate"),
2019-05-02 04:19:53 -06:00
ctx.blind option cssTagNoTheme("blind"),
2018-12-03 01:42:53 -07:00
moreCss,
2019-01-16 19:19:41 -07:00
pieceSprite,
2018-12-03 01:42:53 -07:00
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,
2019-04-09 03:46:13 -06:00
openGraph.map(_.frags),
link(href := routes.Blog.atom, `type` := "application/atom+xml", rel := "alternate", st.title := trans.blog.txt()),
2018-12-03 01:42:53 -07:00
ctx.transpBgImg map { img =>
raw(s"""<style type="text/css" id="bg-data">body.transp::before{background-image:url('$img');}</style>""")
},
fontPreload,
2019-04-26 07:35:32 -06:00
manifests,
jsLicense
2018-12-03 01:42:53 -07:00
),
st.body(
cls := List(
s"${ctx.currentBg} ${ctx.currentTheme.cssClass} ${ctx.currentTheme3d.cssClass} ${ctx.currentPieceSet3d.toString} coords-${ctx.pref.coordsClass}" -> true,
2019-04-08 04:23:20 -06:00
"piece-letter" -> ctx.pref.pieceNotationIsLetter,
2018-12-03 01:42:53 -07:00
"zen" -> ctx.pref.isZen,
2019-04-23 03:02:45 -06:00
"blind-mode" -> ctx.blind,
2018-12-03 01:42:53 -07:00
"kid" -> ctx.kid,
"mobile" -> ctx.isMobileBrowser,
"playing fixed-scroll" -> playing,
"coords-out" -> (ctx.pref.coords == Pref.Coords.OUTSIDE)
2018-12-03 01:42:53 -07:00
),
dataDev := (!isProd).option("true"),
2019-09-02 04:29:03 -06:00
dataVapid := vapidPublicKey,
2018-12-03 01:42:53 -07:00
dataUser := ctx.userId,
dataSoundSet := ctx.currentSoundSet.toString,
dataSocketDomain := socketDomain,
dataAssetUrl := assetBaseUrl,
dataAssetVersion := assetVersion.value,
dataNonce := ctx.nonce.ifTrue(sameAssetDomain).map(_.value),
2019-04-11 04:05:52 -06:00
dataTheme := ctx.currentBg,
2019-05-04 18:56:12 -06:00
style := zoomable option s"--zoom:${ctx.zoom}"
2018-12-03 01:42:53 -07:00
)(
blindModeForm,
ctx.pageData.inquiry map { views.html.mod.inquiry(_) },
ctx.me ifTrue ctx.userContext.impersonatedBy.isDefined map { views.html.mod.impersonate(_) },
2019-05-05 07:09:03 -06:00
isStage option views.html.base.bits.stage,
2019-03-21 05:18:03 -06:00
lila.security.EmailConfirm.cookie.get(ctx.req).map(views.html.auth.bits.checkYourEmailBanner(_)),
2018-12-03 01:42:53 -07:00
playing option zenToggle,
2019-04-11 04:05:52 -06:00
siteHeader(playing),
2019-04-07 20:21:04 -06:00
div(id := "main-wrap", cls := List(
2019-02-24 00:02:59 -07:00
wrapClass -> wrapClass.nonEmpty,
2019-02-13 04:48:10 -07:00
"is2d" -> ctx.pref.is2d,
"is3d" -> ctx.pref.is3d
2019-04-07 20:21:04 -06:00
))(body),
2018-12-03 01:42:53 -07:00
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 := "friend_box_title")(
2019-02-14 05:24:58 -07:00
strong(cls := "online")("?"),
2018-12-03 01:42:53 -07:00
" ",
2019-04-22 03:42:25 -06:00
trans.onlineFriends()
2018-12-03 01:42:53 -07:00
),
div(cls := "content_wrap")(
2018-12-13 12:50:21 -07:00
div(cls := "content list"),
div(cls := List(
"nobody" -> true,
"none" -> ctx.onlineFriends.users.nonEmpty
))(
2019-04-22 03:42:25 -06:00
span(trans.noFriendsOnline()),
2018-12-13 12:50:21 -07:00
a(cls := "find button", href := routes.User.opponents)(
2019-04-22 03:42:25 -06:00
span(cls := "is3 text", dataIcon := "h")(trans.findFriends())
2018-12-03 01:42:53 -07:00
)
)
)
)
},
2019-04-22 03:42:25 -06:00
a(id := "reconnecting", cls := "link text", dataIcon := "B")(trans.reconnecting()),
2018-12-03 01:42:53 -07:00
chessground option jsTag("vendor/chessground.min.js"),
ctx.requiresFingerprint option fingerprintTag,
if (isProd)
2019-04-21 00:46:17 -06:00
jsAt(s"compiled/lichess.site.min.js", defer = deferJs)
else frag(
2019-04-21 00:46:17 -06:00
jsAt(s"compiled/lichess.deps.js", defer = deferJs),
jsAt(s"compiled/lichess.site.js", defer = deferJs)
),
2018-12-03 01:42:53 -07:00
moreJs,
embedJsUnsafe(s"""lichess.quantity=${lila.i18n.JsQuantity(ctx.lang)};$timeagoLocaleScript"""),
2019-04-21 00:46:17 -06:00
ctx.pageData.inquiry.isDefined option jsTag("inquiry.js", defer = deferJs)
2018-12-03 01:42:53 -07:00
)
)
2018-12-03 02:10:20 -07:00
)
2019-02-08 04:13:50 -07:00
object siteHeader {
private val topnavToggle = spaceless("""
2019-02-13 09:54:24 -07:00
<input type="checkbox" id="tn-tg" class="topnav-toggle fullscreen-toggle" aria-label="Navigation">
<label for="tn-tg" class="fullscreen-mask"></label>
<label for="tn-tg" class="hbg"><span class="hbg__in"></span></label>""")
2019-02-08 04:13:50 -07:00
private def reports(implicit ctx: Context) = isGranted(_.SeeReport) option
2019-04-22 17:33:17 -06:00
a(cls := "link data-count link-center", title := "Moderation", href := routes.Report.list, dataCount := reportNbOpen, dataIcon := "")
2019-02-08 04:13:50 -07:00
private def teamRequests(implicit ctx: Context) = ctx.teamNbRequests > 0 option
2019-04-22 17:33:17 -06:00
a(cls := "link data-count link-center", href := routes.Team.requests, dataCount := ctx.teamNbRequests, dataIcon := "f", title := trans.teams.txt())
2019-02-08 04:13:50 -07:00
2019-04-11 04:05:52 -06:00
def apply(playing: Boolean)(implicit ctx: Context) =
2019-02-09 02:07:12 -07:00
header(id := "top")(
2019-02-08 04:13:50 -07:00
div(cls := "site-title-nav")(
topnavToggle,
h1(cls := "site-title")(
2019-05-03 02:39:07 -06:00
if (ctx.kid) span(title := trans.kidMode.txt(), cls := "kiddo")(":)")
else ctx.isBot option botImage,
2019-02-08 04:13:50 -07:00
a(href := "/")(
"lichess",
2019-03-07 03:29:21 -07:00
span(if (isProd && !isStage) ".org" else ".dev")
2019-02-08 04:13:50 -07:00
)
),
2019-05-02 04:19:53 -06:00
ctx.blind option h2("Navigation"),
2019-04-07 20:21:04 -06:00
topnav()
2019-02-08 04:13:50 -07:00
),
div(cls := "site-buttons")(
clinput,
reports,
teamRequests,
ctx.me map { me =>
frag(allNotifications, dasher(me))
} getOrElse { !ctx.pageData.error option anonDasher(playing) }
)
)
}
2018-12-03 01:42:53 -07:00
}