teams wip
This commit is contained in:
parent
38d3325a5f
commit
d663aa8a83
|
@ -1,8 +1,10 @@
|
|||
package controllers
|
||||
|
||||
import lila._
|
||||
import user.{ User ⇒ UserModel }
|
||||
import views._
|
||||
import http.Context
|
||||
import security.Granter
|
||||
|
||||
import scalaz.effects._
|
||||
import play.api.mvc._
|
||||
|
@ -25,22 +27,31 @@ object Team extends LilaController {
|
|||
}
|
||||
|
||||
def form = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.team.form(forms.create, forms.captchaCreate))
|
||||
me ⇒ OnePerWeek(me) {
|
||||
Ok(html.team.form(forms.create, forms.captchaCreate))
|
||||
}
|
||||
}
|
||||
|
||||
def create = TODO
|
||||
// AuthBody { implicit ctx ⇒
|
||||
// implicit me ⇒
|
||||
// NoEngine {
|
||||
// IOResult {
|
||||
// implicit val req = ctx.body
|
||||
// forms.create.bindFromRequest.fold(
|
||||
// err ⇒ io(BadRequest(html.tournament.form(err, forms))),
|
||||
// setup ⇒ api.createTournament(setup, me) map { tour ⇒
|
||||
// Redirect(routes.Tournament.show(tour.id))
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
def create = AuthBody { implicit ctx ⇒
|
||||
implicit me ⇒ OnePerWeek(me) {
|
||||
IOResult {
|
||||
implicit val req = ctx.body
|
||||
forms.create.bindFromRequest.fold(
|
||||
err ⇒ io(BadRequest(html.team.form(err, forms.captchaCreate))),
|
||||
setup ⇒ api.create(setup, me) map { team ⇒
|
||||
Redirect(routes.Team.show(team.id))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def mine = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
IOk(api mine me map { html.team.mine(_) })
|
||||
}
|
||||
|
||||
private def OnePerWeek[A <: Result](me: UserModel)(a: ⇒ A)(implicit ctx: Context): Result = {
|
||||
!Granter.superAdmin(me) &&
|
||||
api.hasCreatedRecently(me).unsafePerformIO
|
||||
} fold (Forbidden(views.html.team.createLimit()), a)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import lila._
|
|||
import views._
|
||||
import http.Context
|
||||
import game.{ DbGame, Pov }
|
||||
import security.{ Permission, Granter }
|
||||
import security.Granter
|
||||
|
||||
import play.api.mvc._
|
||||
import play.api.mvc.Results.Redirect
|
||||
|
@ -15,5 +15,5 @@ trait TheftPrevention {
|
|||
isTheft(pov).fold(Redirect(routes.Round.watcher(pov.gameId, pov.color.name)), ok)
|
||||
|
||||
protected def isTheft(pov: Pov)(implicit ctx: Context) =
|
||||
pov.player.userId != ctx.userId && !Granter.option(Permission.SuperAdmin)(ctx.me)
|
||||
pov.player.userId != ctx.userId && !Granter.superAdmin(ctx.me)
|
||||
}
|
||||
|
|
|
@ -168,6 +168,7 @@ final class I18nKeys(translator: Translator) {
|
|||
val nbMembers = new Key("nbMembers")
|
||||
val allTeams = new Key("allTeams")
|
||||
val newTeam = new Key("newTeam")
|
||||
val myTeams = new Key("myTeams")
|
||||
|
||||
def keys = List(playWithAFriend, inviteAFriendToPlayWithYou, playWithTheMachine, challengeTheArtificialIntelligence, toInviteSomeoneToPlayGiveThisUrl, gameOver, waitingForOpponent, waiting, yourTurn, aiNameLevelAiLevel, level, toggleTheChat, toggleSound, chat, resign, checkmate, stalemate, white, black, createAGame, noGameAvailableRightNowCreateOne, whiteIsVictorious, blackIsVictorious, playWithTheSameOpponentAgain, newOpponent, playWithAnotherOpponent, yourOpponentWantsToPlayANewGameWithYou, joinTheGame, whitePlays, blackPlays, theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim, makeYourOpponentResign, forceResignation, talkInChat, theFirstPersonToComeOnThisUrlWillPlayWithYou, whiteCreatesTheGame, blackCreatesTheGame, whiteJoinsTheGame, blackJoinsTheGame, whiteResigned, blackResigned, whiteLeftTheGame, blackLeftTheGame, shareThisUrlToLetSpectatorsSeeTheGame, youAreViewingThisGameAsASpectator, replayAndAnalyse, viewGameStats, flipBoard, threefoldRepetition, claimADraw, offerDraw, draw, nbConnectedPlayers, talkAboutChessAndDiscussLichessFeaturesInTheForum, seeTheGamesBeingPlayedInRealTime, gamesBeingPlayedRightNow, viewAllNbGames, viewNbCheckmates, nbBookmarks, nbPopularGames, nbAnalysedGames, bookmarkedByNbPlayers, viewInFullSize, logOut, signIn, signUp, people, games, forum, chessPlayers, minutesPerSide, variant, timeControl, start, username, password, haveAnAccount, allYouNeedIsAUsernameAndAPassword, learnMoreAboutLichess, rank, gamesPlayed, declineInvitation, cancel, timeOut, drawOfferSent, drawOfferDeclined, drawOfferAccepted, drawOfferCanceled, yourOpponentOffersADraw, accept, decline, playingRightNow, abortGame, gameAborted, standard, unlimited, mode, casual, rated, thisGameIsRated, rematch, rematchOfferSent, rematchOfferAccepted, rematchOfferCanceled, rematchOfferDeclined, cancelRematchOffer, viewRematch, play, inbox, chatRoom, spectatorRoom, composeMessage, sentMessages, incrementInSeconds, freeOnlineChess, spectators, nbWins, nbLosses, nbDraws, exportGames, color, eloRange, giveNbSeconds, searchAPlayer, whoIsOnline, allPlayers, namedPlayers, premoveEnabledClickAnywhereToCancel, thisPlayerUsesChessComputerAssistance, opening, takeback, proposeATakeback, takebackPropositionSent, takebackPropositionDeclined, takebackPropositionAccepted, takebackPropositionCanceled, yourOpponentProposesATakeback, bookmarkThisGame, toggleBackground, advancedSearch, tournament, freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents, teams, nbMembers, allTeams, newTeam)
|
||||
def keys = List(playWithAFriend, inviteAFriendToPlayWithYou, playWithTheMachine, challengeTheArtificialIntelligence, toInviteSomeoneToPlayGiveThisUrl, gameOver, waitingForOpponent, waiting, yourTurn, aiNameLevelAiLevel, level, toggleTheChat, toggleSound, chat, resign, checkmate, stalemate, white, black, createAGame, noGameAvailableRightNowCreateOne, whiteIsVictorious, blackIsVictorious, playWithTheSameOpponentAgain, newOpponent, playWithAnotherOpponent, yourOpponentWantsToPlayANewGameWithYou, joinTheGame, whitePlays, blackPlays, theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim, makeYourOpponentResign, forceResignation, talkInChat, theFirstPersonToComeOnThisUrlWillPlayWithYou, whiteCreatesTheGame, blackCreatesTheGame, whiteJoinsTheGame, blackJoinsTheGame, whiteResigned, blackResigned, whiteLeftTheGame, blackLeftTheGame, shareThisUrlToLetSpectatorsSeeTheGame, youAreViewingThisGameAsASpectator, replayAndAnalyse, viewGameStats, flipBoard, threefoldRepetition, claimADraw, offerDraw, draw, nbConnectedPlayers, talkAboutChessAndDiscussLichessFeaturesInTheForum, seeTheGamesBeingPlayedInRealTime, gamesBeingPlayedRightNow, viewAllNbGames, viewNbCheckmates, nbBookmarks, nbPopularGames, nbAnalysedGames, bookmarkedByNbPlayers, viewInFullSize, logOut, signIn, signUp, people, games, forum, chessPlayers, minutesPerSide, variant, timeControl, start, username, password, haveAnAccount, allYouNeedIsAUsernameAndAPassword, learnMoreAboutLichess, rank, gamesPlayed, declineInvitation, cancel, timeOut, drawOfferSent, drawOfferDeclined, drawOfferAccepted, drawOfferCanceled, yourOpponentOffersADraw, accept, decline, playingRightNow, abortGame, gameAborted, standard, unlimited, mode, casual, rated, thisGameIsRated, rematch, rematchOfferSent, rematchOfferAccepted, rematchOfferCanceled, rematchOfferDeclined, cancelRematchOffer, viewRematch, play, inbox, chatRoom, spectatorRoom, composeMessage, sentMessages, incrementInSeconds, freeOnlineChess, spectators, nbWins, nbLosses, nbDraws, exportGames, color, eloRange, giveNbSeconds, searchAPlayer, whoIsOnline, allPlayers, namedPlayers, premoveEnabledClickAnywhereToCancel, thisPlayerUsesChessComputerAssistance, opening, takeback, proposeATakeback, takebackPropositionSent, takebackPropositionDeclined, takebackPropositionAccepted, takebackPropositionCanceled, yourOpponentProposesATakeback, bookmarkThisGame, toggleBackground, advancedSearch, tournament, freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents, teams, nbMembers, allTeams, newTeam, myTeams)
|
||||
}
|
||||
|
|
|
@ -10,4 +10,8 @@ object Granter {
|
|||
|
||||
def option(permission: Permission)(user: Option[User]): Boolean =
|
||||
user.fold(apply(permission), false)
|
||||
|
||||
def superAdmin(user: User): Boolean = apply(Permission.SuperAdmin)(user)
|
||||
|
||||
def superAdmin(user: Option[User]): Boolean = ~(user map superAdmin)
|
||||
}
|
||||
|
|
|
@ -7,22 +7,32 @@ import play.api.data._
|
|||
import play.api.data.Forms._
|
||||
import play.api.data.validation.Constraints._
|
||||
|
||||
final class DataForm(captcher: Captcha) {
|
||||
final class DataForm(
|
||||
repo: TeamRepo,
|
||||
captcher: Captcha) {
|
||||
|
||||
import lila.core.Form._
|
||||
|
||||
val create = Form(mapping(
|
||||
"name" -> text(minLength = 3, maxLength = 60),
|
||||
"location" -> optional(text(minLength = 3, maxLength = 80)),
|
||||
"description" -> text(minLength = 60, maxLength = 1000),
|
||||
"gameId" -> nonEmptyText,
|
||||
"move" -> nonEmptyText
|
||||
"description" -> text(minLength = 30, maxLength = 2000),
|
||||
"gameId" -> text,
|
||||
"move" -> text
|
||||
)(TeamSetup.apply)(TeamSetup.unapply)
|
||||
.verifying("This team already exists", d ⇒ !teamExists(d))
|
||||
.verifying(
|
||||
"Not a checkmate",
|
||||
data ⇒ captcher get data.gameId valid data.move.trim.toLowerCase
|
||||
)
|
||||
)
|
||||
|
||||
def createWithCaptcha = create -> captchaCreate
|
||||
|
||||
def captchaCreate: Captcha.Challenge = captcher.create
|
||||
|
||||
private def teamExists(setup: TeamSetup) =
|
||||
repo.exists(Team nameToId setup.name).unsafePerformIO
|
||||
}
|
||||
|
||||
private[team] case class TeamSetup(
|
||||
|
|
|
@ -27,7 +27,7 @@ object Team {
|
|||
location: Option[String],
|
||||
description: String,
|
||||
createdBy: User): Team = new Team(
|
||||
id = templating.StringHelper.slugify(name),
|
||||
id = nameToId(name),
|
||||
name = name,
|
||||
location = location,
|
||||
description = description,
|
||||
|
@ -35,4 +35,6 @@ object Team {
|
|||
nbMembers = 1,
|
||||
createdAt = DateTime.now,
|
||||
createdBy = createdBy.id)
|
||||
|
||||
def nameToId(name: String) = templating.StringHelper slugify name
|
||||
}
|
||||
|
|
|
@ -3,11 +3,16 @@ package team
|
|||
|
||||
import scalaz.effects._
|
||||
import com.github.ornicar.paginator._
|
||||
import org.scala_tools.time.Imports._
|
||||
|
||||
import user.User
|
||||
|
||||
final class TeamApi(
|
||||
repo: TeamRepo,
|
||||
maxPerPage: Int) {
|
||||
|
||||
val creationPeriod = 1 week
|
||||
|
||||
def popular(page: Int): Paginator[Team] = Paginator(
|
||||
SalatAdapter(
|
||||
dao = repo,
|
||||
|
@ -16,4 +21,17 @@ final class TeamApi(
|
|||
currentPage = page,
|
||||
maxPerPage = maxPerPage
|
||||
) | popular(1)
|
||||
|
||||
def mine(me: User): List[Team] = repo byUser user.id
|
||||
|
||||
def create(setup: TeamSetup, me: User): IO[Team] = Team(
|
||||
name = setup.name,
|
||||
location = setup.location,
|
||||
description = setup.description,
|
||||
createdBy = me) |> { team ⇒
|
||||
repo saveIO team inject team
|
||||
}
|
||||
|
||||
def hasCreatedRecently(me: User): IO[Boolean] =
|
||||
repo.userHasCreatedSince(me.id, creationPeriod)
|
||||
}
|
||||
|
|
|
@ -19,5 +19,5 @@ final class TeamEnv(
|
|||
repo = repo,
|
||||
maxPerPage = TeamPaginatorMaxPerPage)
|
||||
|
||||
lazy val forms = new DataForm(captcha)
|
||||
lazy val forms = new DataForm(repo, captcha)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.novus.salat.dao._
|
|||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.query.Imports._
|
||||
import scalaz.effects._
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.{ DateTime, Period }
|
||||
import org.scala_tools.time.Imports._
|
||||
|
||||
import user.User
|
||||
|
@ -33,6 +33,15 @@ final class TeamRepo(collection: MongoCollection)
|
|||
remove(selectId(team.id))
|
||||
}
|
||||
|
||||
def exists(id: String): IO[Boolean] = byId(id) map (_.nonEmpty)
|
||||
|
||||
def userHasCreatedSince(userId: String, duration: Period): IO[Boolean] = io {
|
||||
collection.find(
|
||||
("createdAt" $gt (DateTime.now - duration)) +
|
||||
("createdBy" -> userId)
|
||||
).limit(1).size > 0
|
||||
}
|
||||
|
||||
def userQuery(user: User) = DBObject("members.id" -> user.id)
|
||||
|
||||
def selectId(id: String) = DBObject("_id" -> id)
|
||||
|
|
|
@ -18,8 +18,11 @@ trait StringHelper {
|
|||
slug.toLowerCase
|
||||
}
|
||||
|
||||
def shorten(text: String, length: Int): String =
|
||||
text.replace("\n", " ") take length
|
||||
def shorten(text: String, length: Int, sep: String = " [...]"): String = {
|
||||
val t = text.replace("\n", " ")
|
||||
if (t.size > (length + sep.size)) (t take length) ++ sep
|
||||
else t
|
||||
}
|
||||
|
||||
def shortenWithBr(text: String, length: Int) = Html {
|
||||
nl2br(escape(text).take(length))
|
||||
|
|
|
@ -36,6 +36,11 @@ case class User(
|
|||
|
||||
def canMessage = !muted
|
||||
|
||||
def canCreateTeam =
|
||||
!isChatBan &&
|
||||
nbGames >= 3 &&
|
||||
createdAt < (DateTime.now - 3.hours)
|
||||
|
||||
def disabled = !enabled
|
||||
|
||||
def usernameWithElo = "%s (%d)".format(username, elo)
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
</style>
|
||||
<div class="content_box">
|
||||
<h1>@title</h1>
|
||||
<br /><br />
|
||||
<table class="datatable"><tbody>
|
||||
@logs.map { log =>
|
||||
<tr>
|
||||
|
|
19
app/views/site/message.scala.html
Normal file
19
app/views/site/message.scala.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
@(title: String)(message: Html)(implicit ctx: Context)
|
||||
|
||||
@site.layout(title = title) {
|
||||
|
||||
<div class="content_box small_box">
|
||||
<div class="head">
|
||||
<h1>@title</h1>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<p>
|
||||
@message
|
||||
</p>
|
||||
<br />
|
||||
<script>
|
||||
document.write('<a href="' + document.referrer + '">Go Back</a>');
|
||||
</script>
|
||||
</div>
|
||||
}
|
|
@ -1,9 +1,5 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
@site.layout(
|
||||
title = "No engine area") {
|
||||
|
||||
<div class="content_box small_box">
|
||||
Sorry, engines are not allowed in this area.
|
||||
</div>
|
||||
@site.message("No engine area") {
|
||||
Sorry, engines are not allowed in this area.
|
||||
}
|
||||
|
|
5
app/views/team/createLimit.scala.html
Normal file
5
app/views/team/createLimit.scala.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
@site.message("Cannot create a team") {
|
||||
You have already created a team this week.
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
@(form: Form[_], captcha: lila.site.Captcha.Challenge)(implicit ctx: Context)
|
||||
|
||||
@team.layout(
|
||||
title = "New Team") {
|
||||
title = "New Team",
|
||||
currentTab = "form".some) {
|
||||
<div id="team">
|
||||
<div class="content_box team_box">
|
||||
<h1>@trans.newTeam()</h1>
|
||||
|
@ -25,8 +26,8 @@ title = "New Team") {
|
|||
}
|
||||
</label>
|
||||
</section>
|
||||
@base.captcha(form("team")("move"), form("team")("gameId"), captcha)
|
||||
@errMsg(form("team"))
|
||||
@base.captcha(form("move"), form("gameId"), captcha)
|
||||
@errMsg(form)
|
||||
<button class="submit button" type="submit">@trans.newTeam()</button>
|
||||
<a href="@routes.Team.home(1)" style="margin-left:20px">Cancel</a>
|
||||
</form>
|
||||
|
|
|
@ -1,30 +1,7 @@
|
|||
@(teams: Paginator[lila.team.Team])(implicit ctx: Context)
|
||||
|
||||
@team.layout(
|
||||
title = trans.teams.str()) {
|
||||
<div class="head">
|
||||
<h1>@trans.teams()</h1>
|
||||
</div>
|
||||
<table>
|
||||
<tbody class="infinitescroll">
|
||||
@if(teams.hasToPaginate) {
|
||||
<tr><th class="pager none">
|
||||
<a href="@routes.Team.home(teams.nextPage | 1)">Next</a>
|
||||
</th></tr>
|
||||
}
|
||||
@teams.currentPageResults.map { team =>
|
||||
<tr class="paginated_element">
|
||||
<td class="subject">
|
||||
<a href="@routes.Team.show(team.id)">@team.name</a>
|
||||
@autoLink(team.description)
|
||||
</td>
|
||||
<td class="info">
|
||||
<p>@trans.nbMembers(team.nbMembers)</p>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@team.list(
|
||||
name = trans.teams.str(),
|
||||
teams = teams,
|
||||
next = teams.nextPage map { n => routes.Team.home(n) },
|
||||
tab = "all")
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
@(title: String)(body: Html)(implicit ctx: Context)
|
||||
@(title: String, currentTab: Option[String] = None, moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("team.css")
|
||||
}
|
||||
|
||||
@goodies = {
|
||||
<div class="sidebar">
|
||||
<a href="@routes.Team.home(1)">@trans.allTeams()</a>
|
||||
@if(ctx.me.fold(_.canMessage, false)) {
|
||||
<a href="@routes.Team.form()">@trans.newTeam()</a>
|
||||
<div class="side_menu">
|
||||
@defining(~currentTab) { tab =>
|
||||
<a class="@tab.active("all")" href="@routes.Team.home()">
|
||||
@trans.allTeams()
|
||||
</a>
|
||||
@if(ctx.me.fold(_.canCreateTeam, false)) {
|
||||
<a class="@tab.active("mine")" href="@routes.Team.mine()">
|
||||
@trans.myTeams()
|
||||
</a>
|
||||
<a class="@tab.active("form")" href="@routes.Team.form()">
|
||||
@trans.newTeam()
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -16,6 +25,7 @@
|
|||
@base.layout(
|
||||
title = title,
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
goodies = goodies.some,
|
||||
active = siteMenu.team.some) {
|
||||
@body
|
||||
|
|
47
app/views/team/list.scala.html
Normal file
47
app/views/team/list.scala.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
@(name: String, teams: Paginator[lila.team.Team], next: Option[Call], tab: String)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s - page %d".format(name, teams.currentPage) }
|
||||
|
||||
@moreJs = {
|
||||
@jsTag("vendor/jquery.infinitescroll.min.js")
|
||||
}
|
||||
|
||||
@team.layout(
|
||||
title = title,
|
||||
currentTab = tab.some,
|
||||
moreJs = moreJs) {
|
||||
<div id="team" class="content_box team_box no_padding">
|
||||
<h1>@name</h1>
|
||||
<table class="slist">
|
||||
@if(teams.nbResults > 0) {
|
||||
<tbody class="infinitescroll">
|
||||
@next.map { n =>
|
||||
<div class="pager none"><a href="@n">Next</a></div>
|
||||
}.getOrElse {
|
||||
<div class="none"></div>
|
||||
}
|
||||
@teams.currentPageResults.map { team =>
|
||||
<tr class="paginated_element">
|
||||
<td class="subject">
|
||||
<a class="team-name" href="@routes.Team.show(team.id)">@team.name</a>
|
||||
@shorten(team.description, 200)
|
||||
</td>
|
||||
<td class="info">
|
||||
<p>@trans.nbMembers(team.nbMembers)</p>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
} else {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<br />
|
||||
No team found
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
}
|
7
app/views/team/mine.scala.html
Normal file
7
app/views/team/mine.scala.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
@(teams: Paginator[lila.team.Team])(implicit ctx: Context)
|
||||
|
||||
@team.list(
|
||||
name = trans.teams.str(),
|
||||
teams = teams,
|
||||
next = teams.nextPage map { n => routes.Team.home(n) },
|
||||
tab = "all")
|
11
app/views/team/sideMenu.scala.html
Normal file
11
app/views/team/sideMenu.scala.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
@(active: String)(implicit ctx: Context)
|
||||
|
||||
<a class="@active.active("all")" href="@routes.Team.home()">
|
||||
@trans.allTeams()
|
||||
</a>
|
||||
<a class="@active.active("form")" href="@routes.Team.form()">
|
||||
@trans.newTeam()
|
||||
</a>
|
||||
<a class="@active.active("mine")" href="@routes.Team.mine()">
|
||||
@trans.myTeams
|
||||
</a>
|
|
@ -1,8 +1,8 @@
|
|||
@(createds: List[lila.tournament.Created], starteds: List[lila.tournament.Started], finisheds: List[lila.tournament.Finished])(implicit ctx: Context)
|
||||
|
||||
<div class="content_box tournament_box">
|
||||
<div class="content_box tournament_box no_padding">
|
||||
<h1>Tournaments</h1>
|
||||
<table class="data all_tournaments">
|
||||
<table class="slist all_tournaments">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="large">Open tournaments</th>
|
||||
|
|
|
@ -144,3 +144,4 @@ teams=Teams
|
|||
nbMembers=%s members
|
||||
allTeams=All teams
|
||||
newTeam=New team
|
||||
myTeams=My teams
|
||||
|
|
|
@ -54,6 +54,7 @@ GET /tournament/faq controllers.Tournament.faq
|
|||
GET /team controllers.Team.home(page: Int ?= 1)
|
||||
GET /team/new controllers.Team.form
|
||||
POST /team/new controllers.Team.create
|
||||
POST /team/me controllers.Team.mine
|
||||
GET /team/:id controllers.Team.show(id: String)
|
||||
|
||||
# Analyse
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 10 KiB |
|
@ -166,6 +166,11 @@ div.content_box.small_box {
|
|||
div.content_box.no_padding {
|
||||
padding: 0;
|
||||
}
|
||||
div.content_box.no_padding h1 {
|
||||
display: block;
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.content_box_title {
|
||||
padding: 20px 25px 0 25px;
|
||||
}
|
||||
|
@ -220,6 +225,9 @@ div.content_box .lichess_title {
|
|||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
div.content_box table.datatable {
|
||||
margin-top: 20px;
|
||||
}
|
||||
div.content_box table.datatable td {
|
||||
padding: 5px 10px 5px 10px;
|
||||
}
|
||||
|
@ -227,6 +235,40 @@ div.content_box table.datatable tr:nth-child(odd) {
|
|||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
table.slist {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
table.slist thead th {
|
||||
border-top: 1px solid #d4d4d4;
|
||||
padding: 0.4em 0.6em;
|
||||
}
|
||||
table.slist thead th.large {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
table.slist td {
|
||||
padding: 0.6em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
table.slist td .label {
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
}
|
||||
table.slist .s16 {
|
||||
display: block;
|
||||
height: 16px;
|
||||
padding: 0 0 0 20px;
|
||||
}
|
||||
table.slist tbody tr:nth-child(even) {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
table.slist td:first-child,
|
||||
table.slist th:first-child {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
div.lichess_goodies div.box {
|
||||
margin-top: 1em;
|
||||
margin-left: -30px;
|
||||
|
@ -586,7 +628,7 @@ div.content_box_top,
|
|||
div.hooks tr,
|
||||
a.translation_call,
|
||||
div.locale_menu a,
|
||||
#tournament table.data thead {
|
||||
table.data thead {
|
||||
color: #666;
|
||||
background: #fafafa;
|
||||
background: -moz-linear-gradient(center top , #fafafa, #e0e0e0) repeat scroll 0 0 #fafafa;
|
||||
|
|
|
@ -87,8 +87,8 @@ body.dark div.adv_chart,
|
|||
body.dark #top .dropdown,
|
||||
body.dark div.lichess_overboard p.explanations,
|
||||
body.dark div.search_status,
|
||||
body.dark #tournament table.data,
|
||||
body.dark #tournament table.data thead th,
|
||||
body.dark table.slist,
|
||||
body.dark table.slist thead th,
|
||||
body.dark div.notifications > div,
|
||||
body.dark form.wide input[type="text"],
|
||||
body.dark form.wide textarea
|
||||
|
@ -211,7 +211,7 @@ body.dark #lichess_message tr:nth-child(even),
|
|||
body.dark div.user_show .elo_with_me,
|
||||
body.dark div.content_box_inter,
|
||||
body.dark #GameText tr:nth-child(even),
|
||||
body.dark #tournament table.data tbody tr:nth-child(even),
|
||||
body.dark table.slist tbody tr:nth-child(even),
|
||||
body.dark table.translations tbody tr:nth-child(even),
|
||||
body.dark form.translation_form div.message:nth-child(even),
|
||||
body.dark div.content_box table.datatable tr:nth-child(odd),
|
||||
|
@ -286,7 +286,7 @@ body.dark div.hooks tr,
|
|||
body.dark a.translation_call,
|
||||
body.dark div.notification,
|
||||
body.dark div.locale_menu a,
|
||||
body.dark #tournament table.data thead
|
||||
body.dark table.slist thead
|
||||
{
|
||||
background: #3a3a3a;
|
||||
color: #b0b0b0;
|
||||
|
|
|
@ -36,3 +36,13 @@ form.new-team p.error {
|
|||
margin-bottom: 10px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
#team table a.team-name {
|
||||
font-size: 1.3em;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
#team table .info {
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
#tournament div.tournament_box {
|
||||
padding: 0;
|
||||
}
|
||||
#tournament div.tournament_box h1 {
|
||||
display: block;
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
#tournament .title_tag {
|
||||
float: right;
|
||||
font-size: 20px;
|
||||
|
@ -33,51 +25,18 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tournament table.data {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
#tournament table.data thead th {
|
||||
border-top: 1px solid #d4d4d4;
|
||||
padding: 0.4em 0.6em;
|
||||
}
|
||||
#tournament table.data thead th.large {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
#tournament table.data td {
|
||||
padding: 0.6em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#tournament table.data .create td {
|
||||
#tournament table.slist .create td {
|
||||
padding: 0.6em 1.2em 0.6em 0.6em;
|
||||
text-align: center;
|
||||
}
|
||||
#tournament table.data td .label {
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
}
|
||||
#tournament table.data .s16 {
|
||||
display: block;
|
||||
height: 16px;
|
||||
padding: 0 0 0 20px;
|
||||
}
|
||||
#tournament table.data tbody tr:nth-child(even) {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
#tournament table.data td:first-child,
|
||||
#tournament table.data th:first-child {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
#tournament table.data span.rank {
|
||||
#tournament table.slist span.rank {
|
||||
font-weight: bold;
|
||||
padding-right: 20px;
|
||||
background: transparent url(../images/s16.png) top right no-repeat;
|
||||
background-position: right 900px;
|
||||
}
|
||||
#tournament table.data tbody span.withdraw {
|
||||
#tournament table.slist tbody span.withdraw {
|
||||
background-position: right -320px;
|
||||
}
|
||||
#tournament table.data tbody span.winner {
|
||||
|
|
Loading…
Reference in a new issue