Merge branch 'master' of github.com:ornicar/lila

* 'master' of github.com:ornicar/lila:
  tweak relation caches
  show full list of friends
  remove timeline entries about people following me
  Revert "no longer send follow to friends timelines"
  no longer send follow to friends timelines
  ublog post image alt & credit
  analyse/embed/css: button cursor: pointer
  analyse/embed/css: Fix zh layout on mobile
  Prettier
  broadcast: Fix error URL
  ublog: Translate follow button properly
  css: Reduce page h2 line-height
  css: Make .button display: inline-block
  ublog/css: Prevent linebreak inside view count
  Fix zulip chat panic link
  Make shepherd close button visible
  Fix shepherd themes being swapped
pull/9850/head
Thibault Duplessis 2021-09-20 07:39:19 +02:00
commit 2fe175294e
33 changed files with 646 additions and 159 deletions

View File

@ -117,8 +117,9 @@ final class Relation(
OptionFuResult(env.user.repo named username) { user =>
RelatedPager(api.followingPaginatorAdapter(user.id), page) flatMap { pag =>
negotiate(
html = api countFollowers user.id map { nbFollowers =>
Ok(html.relation.bits.following(user, pag, nbFollowers))
html = {
if (ctx is user) Ok(html.relation.bits.friends(user, pag)).fuccess
else ctx.me.fold(notFound)(me => Redirect(routes.Relation.following(me.username)).fuccess)
},
api = _ => Ok(jsonRelatedPaginator(pag)).fuccess
)
@ -129,18 +130,15 @@ final class Relation(
def followers(username: String, page: Int) =
Open { implicit ctx =>
Reasonable(page, 20) {
OptionFuResult(env.user.repo named username) { user =>
RelatedPager(api.followersPaginatorAdapter(user.id), page) flatMap { pag =>
negotiate(
html = api countFollowing user.id map { nbFollowing =>
Ok(html.relation.bits.followers(user, pag, nbFollowing))
},
api = _ => Ok(jsonRelatedPaginator(pag)).fuccess
)
negotiate(
html = notFound,
api = _ =>
Reasonable(page, 20) {
RelatedPager(api.followersPaginatorAdapter(UserModel normalize username), page) flatMap { pag =>
Ok(jsonRelatedPaginator(pag)).fuccess
}
}
}
}
)
}
def apiFollowing(name: String) = apiRelation(name, Direction.Following)

View File

@ -512,7 +512,7 @@ final class User(
}
}
.sequenceFu
} yield html.user.opponents(me, relateds)
} yield html.relation.bits.opponents(me, relateds)
}
def perfStat(username: String, perfKey: String) =

View File

@ -1,44 +1,23 @@
package views.html.relation
import controllers.routes
import play.api.mvc.Call
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.common.paginator.Paginator
import lila.game.FavoriteOpponents
import lila.relation.Related
import lila.user.User
import controllers.routes
object bits {
def followers(u: User, pag: Paginator[Related], nbFollowing: Int)(implicit ctx: Context) =
layout(s"${u.username}${trans.nbFollowers.pluralSameTxt(pag.nbResults)}")(
div(cls := "box__top")(
h1(userLink(u, withOnline = false)),
div(cls := "actions")(
trans.nbFollowers.pluralSame(pag.nbResults),
" ",
amp,
" ",
a(href := routes.Relation.following(u.username))(trans.nbFollowing.pluralSame(nbFollowing))
)
),
pagTable(pag, routes.Relation.followers(u.username))
)
def following(u: User, pag: Paginator[Related], nbFollowers: Int)(implicit ctx: Context) =
layout(s"${u.username}${trans.nbFollowing.pluralSameTxt(pag.nbResults)}")(
div(cls := "box__top")(
h1(userLink(u, withOnline = false)),
div(cls := "actions")(
trans.nbFollowing.pluralSame(pag.nbResults),
" ",
amp,
" ",
a(href := routes.Relation.followers(u.username))(trans.nbFollowers.pluralSame(nbFollowers))
)
def friends(u: User, pag: Paginator[Related])(implicit ctx: Context) =
layout(s"${u.username}${trans.friends.txt()}")(
h1(
a(href := routes.User.show(u.username), dataIcon := "", cls := "text"),
trans.friends()
),
pagTable(pag, routes.Relation.following(u.username))
)
@ -54,6 +33,38 @@ object bits {
pagTable(pag, routes.Relation.blocks())
)
def opponents(u: User, sugs: List[lila.relation.Related])(implicit ctx: Context) =
layout(s"${u.username}${trans.favoriteOpponents.txt()}")(
h1(
a(href := routes.User.show(u.username), dataIcon := "", cls := "text"),
trans.favoriteOpponents(),
" (",
trans.nbGames.pluralSame(FavoriteOpponents.gameLimit),
")"
),
table(cls := "slist")(
tbody(
if (sugs.nonEmpty) sugs.map { r =>
tr(
td(userLink(r.user)),
td(showBestPerf(r.user)),
td(
r.nbGames.filter(_ > 0).map { nbGames =>
a(href := s"${routes.User.games(u.username, "search")}?players.b=${r.user.username}")(
trans.nbGames.plural(nbGames, nbGames.localize)
)
}
),
td(
views.html.relation.actions(r.user.id, r.relation, followable = r.followable, blocked = false)
)
)
}
else tr(td(trans.none()))
)
)
)
def layout(title: String)(content: Modifier*)(implicit ctx: Context) =
views.html.base.layout(
title = title,
@ -71,7 +82,7 @@ object bits {
tr(cls := "paginated")(
td(userLink(r.user)),
td(showBestPerf(r.user)),
td(trans.nbGames.pluralSame(r.user.count.game)),
td(trans.nbGames.plural(r.user.count.game, r.user.count.game.localize)),
td(actions(r.user.id, relation = r.relation, followable = r.followable, blocked = false))
)
},

View File

@ -26,7 +26,7 @@ object form {
) {
main(cls := "page-menu page-small")(
views.html.blog.bits.menu(none, "mine".some),
div(cls := "page-menu__content box box-pad ublog-post-form")(
div(cls := "page-menu__content box ublog-post-form")(
standardFlash(),
h1(trans.ublog.newPost()),
etiquette,
@ -43,7 +43,7 @@ object form {
) {
main(cls := "page-menu page-small")(
views.html.blog.bits.menu(none, "mine".some),
div(cls := "page-menu__content box box-pad ublog-post-form")(
div(cls := "page-menu__content box ublog-post-form")(
standardFlash(),
div(cls := "box__top")(
h1(
@ -108,32 +108,41 @@ object form {
)
)
def formImage(post: UblogPost) = postView.thumbnail(post, _.Small)
def formImage(post: UblogPost) =
postView.thumbnail(post, _.Small)(cls := post.image.isDefined.option("user-image"))
private def inner(form: Form[UblogPostData], post: Either[User, UblogPost], captcha: Option[Captcha])(
implicit ctx: Context
) =
postForm(
cls := "form3",
cls := "form3 ublog-post-form__main",
action := post.fold(_ => routes.Ublog.create, p => routes.Ublog.update(p.id.value))
)(
form3.globalError(form),
post.isRight option form3.split(
form3.checkbox(
form("live"),
trans.ublog.publishOnYourBlog(),
help = trans.ublog.publishHelp().some,
half = true
),
form3.group(form("language"), trans.language(), half = true) { field =>
form3.select(
field,
LangList.popularNoRegion.map { l =>
l.code -> l.toLocale.getDisplayLanguage
post.toOption.map { p =>
frag(
form3.split(
form3.group(form("imageAlt"), trans.ublog.imageAlt(), half = true)(form3.input(_)),
form3.group(form("imageCredit"), trans.ublog.imageCredit(), half = true)(form3.input(_))
)(cls := s"ublog-post-form__image-text ${p.image.isDefined ?? "visible"}"),
form3.split(
form3.checkbox(
form("live"),
trans.ublog.publishOnYourBlog(),
help = trans.ublog.publishHelp().some,
half = true
),
form3.group(form("language"), trans.language(), half = true) { field =>
form3.select(
field,
LangList.popularNoRegion.map { l =>
l.code -> l.toLocale.getDisplayLanguage
}
)
}
)
}
),
)
},
form3.group(form("title"), trans.ublog.postTitle())(form3.input(_)(autofocus)),
form3.group(form("intro"), trans.ublog.postIntro())(form3.input(_)(autofocus)),
form3.group(

View File

@ -44,7 +44,12 @@ object post {
main(cls := "page-menu page-small")(
views.html.blog.bits.menu(none, (if (ctx is user) "mine" else "community").some),
div(cls := "page-menu__content box box-pad ublog-post")(
post.image.isDefined option thumbnail(post, _.Large)(cls := "ublog-post__image"),
post.image.map { image =>
frag(
thumbnail(post, _.Large)(cls := "ublog-post__image"),
image.credit.map { p(cls := "ublog-post__image-credit")(_) }
)
},
ctx.is(user) || isGranted(_.ModerateBlog) option standardFlash(),
h1(cls := "ublog-post__title")(post.title),
div(cls := "ublog-post__meta")(
@ -135,15 +140,15 @@ object post {
)
)(
List(
("yes", trans.following, routes.Relation.unfollow _, ""),
("no", trans.follow, routes.Relation.follow _, "")
("yes", trans.unfollowX, routes.Relation.unfollow _, ""),
("no", trans.followX, routes.Relation.follow _, "")
).map { case (role, text, route, icon) =>
button(
cls := s"ublog-post__follow__$role button button-big",
dataIcon := icon,
dataRel := route(user.id)
)(
span(cls := "button-label")(text.txt(), " ", user.titleUsername)
span(cls := "button-label")(text(user.titleUsername))
)
}
)
@ -191,12 +196,13 @@ object post {
img(
cls := "ublog-post-image",
widthA := size(UblogPost.thumbnail).width,
heightA := size(UblogPost.thumbnail).height
heightA := size(UblogPost.thumbnail).height,
alt := post.image.flatMap(_.alt)
)(src := url(post, size))
def url(post: UblogPost.BasePost, size: UblogPost.thumbnail.SizeSelector) =
post.image match {
case Some(image) => UblogPost.thumbnail(picfitUrl, image, size)
case Some(image) => UblogPost.thumbnail(picfitUrl, image.id, size)
case _ => assetUrl("images/user-blog-default.png")
}
}

View File

@ -1,42 +0,0 @@
package views.html
package user
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.user.User
import lila.game.FavoriteOpponents
import controllers.routes
object opponents {
def apply(u: User, sugs: List[lila.relation.Related])(implicit ctx: Context) =
relation.bits.layout(s"${u.username}${trans.favoriteOpponents.txt()}")(
h1(
a(href := routes.User.show(u.username), dataIcon := "", cls := "text"),
trans.favoriteOpponents(),
" (",
trans.nbGames.pluralSame(FavoriteOpponents.gameLimit),
")"
),
table(cls := "slist")(
tbody(
if (sugs.nonEmpty) sugs.map { r =>
tr(
td(userLink(r.user)),
td(showBestPerf(r.user)),
td(
r.nbGames.filter(_ > 0).map { nbGames =>
a(href := s"${routes.User.games(u.username, "search")}?players.b=${r.user.username}")(
trans.nbGames.pluralSame(nbGames)
)
}
),
td(relation.actions(r.user.id, r.relation, followable = r.followable, blocked = false))
)
}
else tr(td(trans.none()))
)
)
)
}

View File

@ -48,9 +48,6 @@ object header {
),
div(cls := "user-show__social")(
div(cls := "number-menu")(
a(cls := "nm-item", href := routes.Relation.followers(u.username))(
splitNumber(trans.nbFollowers.pluralSame(info.nbFollowers))
),
u.noBot option a(
href := routes.UserTournament.path(u.username, "recent"),
cls := "nm-item tournament_stats",
@ -232,6 +229,8 @@ object header {
trans.profileCompletion(s"${profile.completionPercent}%")
),
br,
a(href := routes.Relation.following(u.username))(trans.friends()),
br,
a(href := routes.User.opponents)(trans.favoriteOpponents())
),
u.playTime.map { playTime =>

View File

@ -0,0 +1,3 @@
db.ublog_post
.find({ image: { $exists: 1 } }, { image: 1 })
.forEach(p => db.ublog_post.update({ _id: p._id }, { $set: { image: { id: p.image } } }));

View File

@ -161,7 +161,7 @@ final class PersonalDataExport(
"title" -> post.title,
"intro" -> post.intro,
"body" -> post.markdown,
"image" -> post.image.??(lila.ublog.UblogPost.thumbnail(picfitUrl, _, _.Large)),
"image" -> post.image.??(i => lila.ublog.UblogPost.thumbnail(picfitUrl, i.id, _.Large)),
"topics" -> post.topics.map(_.value).mkString(", ")
).map { case (k, v) =>
s"$k: $v"

View File

@ -277,6 +277,8 @@ val `favoriteOpponents` = new I18nKey("favoriteOpponents")
val `follow` = new I18nKey("follow")
val `following` = new I18nKey("following")
val `unfollow` = new I18nKey("unfollow")
val `followX` = new I18nKey("followX")
val `unfollowX` = new I18nKey("unfollowX")
val `block` = new I18nKey("block")
val `blocked` = new I18nKey("blocked")
val `unblock` = new I18nKey("unblock")
@ -2281,6 +2283,8 @@ val `noPostsInThisBlogYet` = new I18nKey("ublog:noPostsInThisBlogYet")
val `noDrafts` = new I18nKey("ublog:noDrafts")
val `latestBlogPosts` = new I18nKey("ublog:latestBlogPosts")
val `uploadAnImageForYourPost` = new I18nKey("ublog:uploadAnImageForYourPost")
val `imageAlt` = new I18nKey("ublog:imageAlt")
val `imageCredit` = new I18nKey("ublog:imageCredit")
val `publishedNbBlogPosts` = new I18nKey("ublog:publishedNbBlogPosts")
val `nbViews` = new I18nKey("ublog:nbViews")
val `viewAllNbPosts` = new I18nKey("ublog:viewAllNbPosts")

View File

@ -87,7 +87,7 @@ final class IrcApi(
def chatPanic(mod: Holder, v: Boolean): Funit =
zulip(_.mod.log, "chat panic")(
s":stop: ${markdown.modLink(mod.user)} ${if (v) "enabled" else "disabled"} ${markdown.lichessLink("mod/chat-panic", " Chat Panic")}"
s":stop: ${markdown.modLink(mod.user)} ${if (v) "enabled" else "disabled"} ${markdown.lichessLink("/mod/chat-panic", " Chat Panic")}"
)
def garbageCollector(msg: String): Funit =

View File

@ -80,9 +80,8 @@ final class RelationApi(
def fetchAreFriends(u1: ID, u2: ID): Fu[Boolean] =
fetchFollows(u1, u2) >>& fetchFollows(u2, u1)
private val countFollowingCache = cacheApi[ID, Int](8192, "relation.count.following") {
_.expireAfterAccess(10 minutes)
.maximumSize(32768)
private val countFollowingCache = cacheApi[ID, Int](8_192, "relation.count.following") {
_.maximumSize(8_192)
.buildAsyncFuture { userId =>
coll.countSel($doc("u1" -> userId, "r" -> Follow))
}
@ -92,9 +91,8 @@ final class RelationApi(
def reachedMaxFollowing(userId: ID): Fu[Boolean] = countFollowingCache get userId map (config.maxFollow <=)
private val countFollowersCache = cacheApi[ID, Int](131_072, "relation.count.followers") {
_.expireAfterAccess(10 minutes)
.maximumSize(131_072)
private val countFollowersCache = cacheApi[ID, Int](32_768, "relation.count.followers") {
_.maximumSize(32_768)
.buildAsyncFuture { userId =>
coll.secondaryPreferred.countSel($doc("u2" -> userId, "r" -> Follow))
}
@ -147,7 +145,7 @@ final class RelationApi(
repo.follow(u1, u2) >> limitFollow(u1) >>- {
countFollowersCache.update(u2, 1 +)
countFollowingCache.update(u1, prev => (prev + 1) atMost config.maxFollow.value)
timeline ! Propagate(FollowUser(u1, u2)).toFriendsOf(u1).toUsers(List(u2))
timeline ! Propagate(FollowUser(u1, u2)).toFriendsOf(u1)
Bus.publish(lila.hub.actorApi.relation.Follow(u1, u2), "relation")
lila.mon.relation.follow.increment().unit
}

View File

@ -84,7 +84,7 @@ object JsonView {
"log" -> s.log.events
) ++
s.upstream.?? {
case RelayRound.Sync.UpstreamUrl(url) => Json.obj("url" -> url)
case url: RelayRound.Sync.UpstreamUrl => Json.obj("url" -> url.withRound.url)
case RelayRound.Sync.UpstreamIds(ids) => Json.obj("ids" -> ids)
}
}

View File

@ -110,10 +110,10 @@ final class UblogApi(
def uploadImage(user: User, post: UblogPost, picture: PicfitApi.FilePart): Fu[UblogPost] =
for {
image <- picfitApi
.uploadFile(imageRel(post), picture, userId = user.id)
_ <- colls.post.update.one($id(post.id), $set("image" -> image.id))
} yield post.copy(image = image.id.some)
pic <- picfitApi.uploadFile(imageRel(post), picture, userId = user.id)
image = post.image.fold(UblogImage(pic.id))(_.copy(id = pic.id))
_ <- colls.post.updateField($id(post.id), "image", image)
} yield post.copy(image = image.some)
def deleteImage(post: UblogPost): Fu[UblogPost] =
picfitApi.deleteByRel(imageRel(post)) >>

View File

@ -25,6 +25,7 @@ private object UblogBsonHandlers {
.afterRead(_.filter(t => UblogTopic.exists(t.value)))
implicit val langBsonHandler = stringAnyValHandler[Lang](_.code, Lang.apply)
implicit val recordedBSONHandler = Macros.handler[Recorded]
implicit val imageBSONHandler = Macros.handler[UblogImage]
implicit val likesBSONHandler = intAnyValHandler[Likes](_.value, Likes)
implicit val viewsBSONHandler = intAnyValHandler[Views](_.value, Views)
implicit val rankBSONHandler = dateIsoHandler[Rank](Iso[DateTime, Rank](Rank, _.value))

View File

@ -17,14 +17,16 @@ final class UblogForm(markup: UblogMarkup, val captcher: lila.hub.actors.Captche
private val base =
mapping(
"title" -> cleanNonEmptyText(minLength = 3, maxLength = 80),
"intro" -> cleanNonEmptyText(minLength = 0, maxLength = 1_000),
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000).verifying(markdownImage.constraint),
"language" -> optional(stringIn(LangList.popularNoRegion.map(_.code).toSet)),
"topics" -> optional(text),
"live" -> boolean,
"gameId" -> text,
"move" -> text
"title" -> cleanNonEmptyText(minLength = 3, maxLength = 80),
"intro" -> cleanNonEmptyText(minLength = 0, maxLength = 1_000),
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000).verifying(markdownImage.constraint),
"imageAlt" -> optional(cleanNonEmptyText(minLength = 3, maxLength = 200)),
"imageCredit" -> optional(cleanNonEmptyText(minLength = 3, maxLength = 200)),
"language" -> optional(stringIn(LangList.popularNoRegion.map(_.code).toSet)),
"topics" -> optional(text),
"live" -> boolean,
"gameId" -> text,
"move" -> text
)(UblogPostData.apply)(UblogPostData.unapply)
val create = Form(
@ -37,6 +39,8 @@ final class UblogForm(markup: UblogMarkup, val captcher: lila.hub.actors.Captche
title = post.title,
intro = post.intro,
markdown = post.markdown,
imageAlt = post.image.flatMap(_.alt),
imageCredit = post.image.flatMap(_.credit),
language = post.language.code.some,
topics = post.topics.map(_.value).mkString(", ").some,
live = post.live,
@ -52,6 +56,8 @@ object UblogForm {
title: String,
intro: String,
markdown: String,
imageAlt: Option[String],
imageCredit: Option[String],
language: Option[String],
topics: Option[String],
live: Boolean,
@ -84,6 +90,9 @@ object UblogForm {
title = title,
intro = intro,
markdown = markdown,
image = prev.image.map { i =>
i.copy(alt = imageAlt, credit = imageCredit)
},
language = LangList.removeRegion(realLanguage | prev.language),
topics = topics ?? UblogTopic.fromStrList,
live = live,

View File

@ -13,7 +13,7 @@ case class UblogPost(
intro: String,
markdown: String,
language: Lang,
image: Option[PicfitImage.Id],
image: Option[UblogImage],
topics: List[UblogTopic],
live: Boolean,
created: UblogPost.Recorded,
@ -28,6 +28,8 @@ case class UblogPost(
def indexable = live && topics.exists(t => UblogTopic.chessExists(t.value))
}
case class UblogImage(id: PicfitImage.Id, alt: Option[String] = None, credit: Option[String] = None)
object UblogPost {
case class Id(value: String) extends AnyVal with StringValue
@ -50,7 +52,7 @@ object UblogPost {
val blog: UblogBlog.Id
val title: String
val intro: String
val image: Option[PicfitImage.Id]
val image: Option[UblogImage]
val created: Recorded
val lived: Option[Recorded]
def id = _id
@ -62,7 +64,7 @@ object UblogPost {
blog: UblogBlog.Id,
title: String,
intro: String,
image: Option[PicfitImage.Id],
image: Option[UblogImage],
created: Recorded,
lived: Option[Recorded]
) extends BasePost

View File

@ -2,7 +2,7 @@ lichess.ratingHistoryChart = function (data, { singlePerfName, perfIndex }) {
var oneDay = 86400000;
function smoothDates(data) {
if (!data.length) return [];
// If last rating wasn't today, add to the array
var today = new Date().setUTCHours(0, 0, 0, 0);
var lastRating = data[data.length - 1];

View File

@ -1,6 +1,6 @@
function loadShepherd(f) {
var theme = 'shepherd-theme-' + ($('body').hasClass('dark') ? 'default' : 'dark');
lichess.loadCss('vendor/shepherd/dist/css/' + theme + '.css');
var theme = 'shepherd-theme-' + ($('body').hasClass('dark') ? 'dark' : 'default');
lichess.loadCss('vendor/' + theme + '.css');
lichess.loadScript('vendor/shepherd/dist/js/tether.js', { noVersion: true }).then(function () {
lichess.loadScript('vendor/shepherd/dist/js/shepherd.min.js', { noVersion: true }).then(function () {
f(theme);

View File

@ -1,7 +1,7 @@
function loadShepherd(f) {
if (typeof Shepherd === 'undefined' || Shepherd.activeTour === null) {
var theme = 'shepherd-theme-' + ($('body').hasClass('dark') ? 'default' : 'dark');
lichess.loadCss('vendor/shepherd/dist/css/' + theme + '.css');
var theme = 'shepherd-theme-' + ($('body').hasClass('dark') ? 'dark' : 'default');
lichess.loadCss('vendor/' + theme + '.css');
lichess.loadScript('vendor/shepherd/dist/js/tether.js', { noVersion: true }).then(function () {
lichess.loadScript('vendor/shepherd/dist/js/shepherd.min.js', { noVersion: true }).then(function () {
f(theme);

View File

@ -0,0 +1,213 @@
/* https://raw.githubusercontent.com/shipshapecode/shepherd/e3ed0fcbf5c31137ece409d3ad6fea4e264ca1cc/dist/css/shepherd-theme-dark.css */
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-dark {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-dark .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #232323;
color: #eee;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.shepherd-element.shepherd-theme-dark .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-dark {
z-index: 9999;
max-width: 24em;
font-size: 1em; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #303030; }
.shepherd-element.shepherd-theme-dark.shepherd-has-title .shepherd-content header {
background: #303030;
padding: 1em; }
.shepherd-element.shepherd-theme-dark.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-dark.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-dark .shepherd-content {
box-shadow: 0 0 1em rgba(0, 0, 0, 0.2);
padding: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-dark .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-dark .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-dark .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: white;
opacity: 0.6;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-dark .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-dark .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-dark .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-dark .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }
.shepherd-start-tour-button.shepherd-theme-dark {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }

View File

@ -0,0 +1,213 @@
/* https://raw.githubusercontent.com/shipshapecode/shepherd/e3ed0fcbf5c31137ece409d3ad6fea4e264ca1cc/dist/css/shepherd-theme-default.css */
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-default {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-default .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #f6f6f6;
color: #444;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.shepherd-element.shepherd-theme-default .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default {
z-index: 9999;
max-width: 24em;
font-size: 1em; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #e6e6e6; }
.shepherd-element.shepherd-theme-default.shepherd-has-title .shepherd-content header {
background: #e6e6e6;
padding: 1em; }
.shepherd-element.shepherd-theme-default.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-default.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-default .shepherd-content {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.17);
padding: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-default .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-default .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-default .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-default .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: black;
opacity: 0.6;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-default .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-default .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-default .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-default .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }
.shepherd-start-tour-button.shepherd-theme-default {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }

View File

@ -382,6 +382,8 @@ computer analysis, game chat and shareable URL.</string>
<string name="follow">Follow</string>
<string name="following">Following</string>
<string name="unfollow">Unfollow</string>
<string name="followX">Follow %s</string>
<string name="unfollowX">Unfollow %s</string>
<string name="block">Block</string>
<string name="blocked">Blocked</string>
<string name="unblock">Unblock</string>

View File

@ -31,4 +31,6 @@
<item quantity="other">View all %s posts</item>
</plurals>
<string name="uploadAnImageForYourPost">Upload an image for your post</string>
<string name="imageAlt">Image alternative text</string>
<string name="imageCredit">Image credit</string>
</resources>

View File

@ -42,3 +42,7 @@ cg-board {
padding: 2em 4em;
}
}
button {
cursor: pointer;
}

View File

@ -33,8 +33,10 @@
&.gamebook-play {
@media (min-width: 400px) and (min-aspect-ratio: 1/1) {
grid-template-columns: minmax(200px, calc(100vh - 2.5rem)) minmax(200px, 1fr);
grid-template-rows: auto 2.5rem;
grid-template-areas: 'board tools' 'board controls';
grid-template-rows: auto 3rem;
grid-template-areas:
'board tools'
'board controls';
}
}
}
@ -55,11 +57,21 @@
}
.analyse.variant-crazyhouse {
grid-template-rows: 60px auto 2.5rem 60px;
grid-template-areas:
'pocket-top'
'board'
'pocket-bot'
'tools'
'controls';
grid-template-rows: auto 100vw auto 1fr 3rem;
height: calc(100vh - 2.5rem);
body.supports-max-content & {
grid-template-rows: max-content auto 2.5rem max-content;
@media (min-width: 400px) and (min-aspect-ratio: 1/1) {
grid-template-rows: auto 1fr 3rem auto;
grid-template-areas:
'board pocket-top'
'board tools'
'board controls'
'board pocket-bot';
}
grid-template-areas: 'board pocket-top' 'board tools' 'board controls' 'board pocket-bot';
}

View File

@ -9,6 +9,7 @@
text-align: center;
user-select: none;
white-space: nowrap;
display: inline-block;
@include transition;

View File

@ -3,9 +3,10 @@
font-size: 1.1em;
h2 {
margin: 1.3em 0 0.5em 0;
margin: 1.8em 0 0.5em 0;
border-bottom: 1px solid $c-brag;
line-height: 2.5em;
line-height: 1.5em;
padding-bottom: 0.5em;
}
p,
@ -32,7 +33,8 @@
@extend %roboto;
font-size: 1.5em;
line-height: 2.5em;
line-height: 1.5em;
margin-top: 1.3em;
}
h4 {

View File

@ -1,6 +1,35 @@
@import 'flash';
.ublog-post-form {
padding-bottom: 5vh;
&__main .form-split,
&__main > .form-group,
&__main .form-actions,
&__delete .form-actions,
&__image {
padding-left: var(--box-padding);
padding-right: var(--box-padding);
}
&__image {
background: $c-bg-zebra;
padding-top: 5vh;
}
&__image-text {
background: $c-bg-zebra;
margin-bottom: 5vh;
border-bottom: $border;
font-size: 0.9em;
.form-group {
display: none;
}
&.visible {
height: auto;
.form-group {
display: block;
}
}
}
.ublog-post-image {
max-width: 100%;
height: auto;
@ -13,10 +42,8 @@
}
&__image {
.form-group {
text-align: center;
@extend %flex-column;
justify-content: center;
align-items: center;
}
img {
@extend %box-neat;

View File

@ -52,9 +52,10 @@
}
h2 {
margin-top: 1.3em;
margin-top: 1.8em;
border-bottom: 1px solid $c-brag;
line-height: 2.5em;
line-height: 1.5em;
padding-bottom: 0.5em;
}
h3 {

View File

@ -77,10 +77,18 @@
}
}
}
&__views {
white-space: nowrap;
}
&__image {
@extend %box-neat-force;
width: 100%;
height: auto;
&-credit {
font-size: 0.9em;
color: $c-font-dim;
text-align: right;
}
}
&__intro {
@extend %break-word;

View File

@ -3,8 +3,6 @@ import exportLichessGlobals from './site.lichess.globals';
exportLichessGlobals();
export default function (opts: any) {
document.body.classList.toggle('supports-max-content', !!window.chrome);
window.LichessAnalyse.start({
...opts,
socketSend: () => {},

View File

@ -28,11 +28,16 @@ const setupTopics = (el: HTMLTextAreaElement) =>
});
const setupImage = (form: HTMLFormElement) => {
const showText = () =>
$('.ublog-post-form__image-text').toggleClass('visible', $('.ublog-post-image').hasClass('user-image'));
const submit = () => {
const replace = (html: string) => $(form).find('.ublog-post-image').replaceWith(html);
const wrap = (html: string) => '<div class="ublog-post-image">' + html + '</div>';
xhr.formToXhr(form).then(
html => replace(html),
html => {
replace(html);
showText();
},
err => replace(wrap(`<bad>${err}</bad>`))
);
replace(wrap(spinner));
@ -40,6 +45,7 @@ const setupImage = (form: HTMLFormElement) => {
};
$(form).on('submit', submit);
$(form).find('input[name="image"]').on('change', submit);
showText();
};
const setupMarkdownEditor = (el: HTMLTextAreaElement) => {