diff --git a/app/controllers/Relation.scala b/app/controllers/Relation.scala index 13f35e56b0..04f69ca2e7 100644 --- a/app/controllers/Relation.scala +++ b/app/controllers/Relation.scala @@ -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) diff --git a/app/controllers/User.scala b/app/controllers/User.scala index 8915fc8fb8..88268eb6c1 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -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) = diff --git a/app/views/relation/bits.scala b/app/views/relation/bits.scala index 5398a8a859..2d46d33c02 100644 --- a/app/views/relation/bits.scala +++ b/app/views/relation/bits.scala @@ -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)) ) }, diff --git a/app/views/ublog/form.scala b/app/views/ublog/form.scala index f1daf43efd..1be22dece1 100644 --- a/app/views/ublog/form.scala +++ b/app/views/ublog/form.scala @@ -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( diff --git a/app/views/ublog/post.scala b/app/views/ublog/post.scala index 7ef3e15c96..ec4ddf4e75 100644 --- a/app/views/ublog/post.scala +++ b/app/views/ublog/post.scala @@ -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") } } diff --git a/app/views/user/opponents.scala b/app/views/user/opponents.scala deleted file mode 100644 index 8b49eb6709..0000000000 --- a/app/views/user/opponents.scala +++ /dev/null @@ -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())) - ) - ) - ) -} diff --git a/app/views/user/show/header.scala b/app/views/user/show/header.scala index 1e1c5b5e29..e3d6d21c39 100644 --- a/app/views/user/show/header.scala +++ b/app/views/user/show/header.scala @@ -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 => diff --git a/bin/mongodb/ublog-image.js b/bin/mongodb/ublog-image.js new file mode 100644 index 0000000000..64426bc9b4 --- /dev/null +++ b/bin/mongodb/ublog-image.js @@ -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 } } })); diff --git a/modules/api/src/main/PersonalDataExport.scala b/modules/api/src/main/PersonalDataExport.scala index 1eff5cd6cc..c7c095aa8b 100644 --- a/modules/api/src/main/PersonalDataExport.scala +++ b/modules/api/src/main/PersonalDataExport.scala @@ -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" diff --git a/modules/i18n/src/main/I18nKeys.scala b/modules/i18n/src/main/I18nKeys.scala index 9062665357..69075a4dbd 100644 --- a/modules/i18n/src/main/I18nKeys.scala +++ b/modules/i18n/src/main/I18nKeys.scala @@ -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") diff --git a/modules/irc/src/main/IrcApi.scala b/modules/irc/src/main/IrcApi.scala index 73f1e4929e..b46c1b2fa3 100644 --- a/modules/irc/src/main/IrcApi.scala +++ b/modules/irc/src/main/IrcApi.scala @@ -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 = diff --git a/modules/relation/src/main/RelationApi.scala b/modules/relation/src/main/RelationApi.scala index a5a7d53fa8..ce2760194e 100644 --- a/modules/relation/src/main/RelationApi.scala +++ b/modules/relation/src/main/RelationApi.scala @@ -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 } diff --git a/modules/relay/src/main/JsonView.scala b/modules/relay/src/main/JsonView.scala index 05137760a0..1a653747e6 100644 --- a/modules/relay/src/main/JsonView.scala +++ b/modules/relay/src/main/JsonView.scala @@ -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) } } diff --git a/modules/ublog/src/main/UblogApi.scala b/modules/ublog/src/main/UblogApi.scala index e015d5c64e..99d6a72817 100644 --- a/modules/ublog/src/main/UblogApi.scala +++ b/modules/ublog/src/main/UblogApi.scala @@ -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)) >> diff --git a/modules/ublog/src/main/UblogBsonHandlers.scala b/modules/ublog/src/main/UblogBsonHandlers.scala index bbd565cb40..bdad0bc8f9 100644 --- a/modules/ublog/src/main/UblogBsonHandlers.scala +++ b/modules/ublog/src/main/UblogBsonHandlers.scala @@ -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)) diff --git a/modules/ublog/src/main/UblogForm.scala b/modules/ublog/src/main/UblogForm.scala index 816bffff58..07a220e79a 100644 --- a/modules/ublog/src/main/UblogForm.scala +++ b/modules/ublog/src/main/UblogForm.scala @@ -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, diff --git a/modules/ublog/src/main/UblogPost.scala b/modules/ublog/src/main/UblogPost.scala index 90a630e1ce..eb3e7097a9 100644 --- a/modules/ublog/src/main/UblogPost.scala +++ b/modules/ublog/src/main/UblogPost.scala @@ -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 diff --git a/public/javascripts/chart/ratingHistory.js b/public/javascripts/chart/ratingHistory.js index e6aa4a8c23..0b8a90d3e1 100644 --- a/public/javascripts/chart/ratingHistory.js +++ b/public/javascripts/chart/ratingHistory.js @@ -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]; diff --git a/public/javascripts/study/tour-chapter.js b/public/javascripts/study/tour-chapter.js index b29bb68a34..6bd15ad2ec 100644 --- a/public/javascripts/study/tour-chapter.js +++ b/public/javascripts/study/tour-chapter.js @@ -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); diff --git a/public/javascripts/study/tour.js b/public/javascripts/study/tour.js index 307a85a595..0395751c01 100644 --- a/public/javascripts/study/tour.js +++ b/public/javascripts/study/tour.js @@ -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); diff --git a/public/vendor/shepherd-theme-dark.css b/public/vendor/shepherd-theme-dark.css new file mode 100644 index 0000000000..c55ef91f6f --- /dev/null +++ b/public/vendor/shepherd-theme-dark.css @@ -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; } diff --git a/public/vendor/shepherd-theme-default.css b/public/vendor/shepherd-theme-default.css new file mode 100644 index 0000000000..c35cecd5bb --- /dev/null +++ b/public/vendor/shepherd-theme-default.css @@ -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; } diff --git a/translation/source/site.xml b/translation/source/site.xml index 00f3861f23..5f55f22900 100644 --- a/translation/source/site.xml +++ b/translation/source/site.xml @@ -382,6 +382,8 @@ computer analysis, game chat and shareable URL. Follow Following Unfollow + Follow %s + Unfollow %s Block Blocked Unblock diff --git a/translation/source/ublog.xml b/translation/source/ublog.xml index 283d486fa9..cab4afda9f 100644 --- a/translation/source/ublog.xml +++ b/translation/source/ublog.xml @@ -31,4 +31,6 @@ View all %s posts Upload an image for your post + Image alternative text + Image credit diff --git a/ui/analyse/css/embed/_embed.scss b/ui/analyse/css/embed/_embed.scss index f23fe4b088..354afb3470 100644 --- a/ui/analyse/css/embed/_embed.scss +++ b/ui/analyse/css/embed/_embed.scss @@ -42,3 +42,7 @@ cg-board { padding: 2em 4em; } } + +button { + cursor: pointer; +} diff --git a/ui/analyse/css/embed/_layout.scss b/ui/analyse/css/embed/_layout.scss index 685b9a0bf2..f72f19d0ec 100644 --- a/ui/analyse/css/embed/_layout.scss +++ b/ui/analyse/css/embed/_layout.scss @@ -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'; } diff --git a/ui/common/css/component/_button.scss b/ui/common/css/component/_button.scss index 1c8740e3c0..11e7c85333 100644 --- a/ui/common/css/component/_button.scss +++ b/ui/common/css/component/_button.scss @@ -9,6 +9,7 @@ text-align: center; user-select: none; white-space: nowrap; + display: inline-block; @include transition; diff --git a/ui/site/css/_page.scss b/ui/site/css/_page.scss index 888e83b992..4559cc69c6 100644 --- a/ui/site/css/_page.scss +++ b/ui/site/css/_page.scss @@ -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 { diff --git a/ui/site/css/ublog/_form.scss b/ui/site/css/ublog/_form.scss index 6518dbec5b..5056b48946 100644 --- a/ui/site/css/ublog/_form.scss +++ b/ui/site/css/ublog/_form.scss @@ -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; diff --git a/ui/site/css/ublog/_markup.scss b/ui/site/css/ublog/_markup.scss index 4fc12f2aad..017dbc9c64 100644 --- a/ui/site/css/ublog/_markup.scss +++ b/ui/site/css/ublog/_markup.scss @@ -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 { diff --git a/ui/site/css/ublog/_post.scss b/ui/site/css/ublog/_post.scss index b98ff56f91..195df52edd 100644 --- a/ui/site/css/ublog/_post.scss +++ b/ui/site/css/ublog/_post.scss @@ -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; diff --git a/ui/site/src/analyseEmbed.ts b/ui/site/src/analyseEmbed.ts index 2a8095c570..ecd80e436e 100644 --- a/ui/site/src/analyseEmbed.ts +++ b/ui/site/src/analyseEmbed.ts @@ -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: () => {}, diff --git a/ui/site/src/ublogForm.ts b/ui/site/src/ublogForm.ts index c8a93e0942..9bda7200dd 100644 --- a/ui/site/src/ublogForm.ts +++ b/ui/site/src/ublogForm.ts @@ -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) => '
' + html + '
'; xhr.formToXhr(form).then( - html => replace(html), + html => { + replace(html); + showText(); + }, err => replace(wrap(`${err}`)) ); 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) => {