team battle WIP
parent
f0e9a09422
commit
44695e7e6a
|
@ -1,5 +1,8 @@
|
|||
package controllers
|
||||
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.{ HTTPRequest, MaxPerSecond }
|
||||
|
@ -9,8 +12,6 @@ import lila.team.{ Joined, Motivate, Team => TeamModel, TeamRepo, MemberRepo }
|
|||
import lila.user.{ User => UserModel }
|
||||
import views._
|
||||
|
||||
import play.api.mvc._
|
||||
|
||||
object Team extends LilaController {
|
||||
|
||||
private def forms = Env.team.forms
|
||||
|
@ -221,6 +222,25 @@ object Team extends LilaController {
|
|||
}
|
||||
)
|
||||
|
||||
def autocomplete = Action.async { req =>
|
||||
get("term", req).filter(_.nonEmpty) match {
|
||||
case None => BadRequest("No search term provided").fuccess
|
||||
case Some(term) => for {
|
||||
teams <- api.autocomplete(term, 10)
|
||||
_ <- Env.user.lightUserApi preloadMany teams.map(_.createdBy)
|
||||
} yield Ok {
|
||||
JsArray(teams map { team =>
|
||||
Json.obj(
|
||||
"id" -> team.id,
|
||||
"name" -> team.name,
|
||||
"owner" -> Env.user.lightUserApi.sync(team.createdBy).fold(team.createdBy)(_.name),
|
||||
"members" -> team.nbMembers
|
||||
)
|
||||
})
|
||||
} as JSON
|
||||
}
|
||||
}
|
||||
|
||||
private def OnePerWeek[A <: Result](me: UserModel)(a: => Fu[A])(implicit ctx: Context): Fu[Result] =
|
||||
api.hasCreatedRecently(me) flatMap { did =>
|
||||
if (did && !Granter(_.ManageTeam)(me)) Forbidden(views.html.site.message.teamCreateLimit).fuccess
|
||||
|
|
|
@ -180,7 +180,7 @@ object Tournament extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def formTeamBattle(teamId: String) = Auth { implicit ctx => me =>
|
||||
def teamBattleForm(teamId: String) = Auth { implicit ctx => me =>
|
||||
NoLameOrBot {
|
||||
Env.team.api.owns(teamId, me.id) map {
|
||||
_ ?? {
|
||||
|
@ -222,15 +222,10 @@ object Tournament extends LilaController {
|
|||
setup.password.isDefined) 1 else 4
|
||||
CreateLimitPerUser(me.id, cost = cost) {
|
||||
CreateLimitPerIP(HTTPRequest lastRemoteAddress ctx.req, cost = cost) {
|
||||
env.api.createTournament(
|
||||
setup,
|
||||
me,
|
||||
teams,
|
||||
getUserTeamIds,
|
||||
Env.team.api.filterExistingIds
|
||||
) flatMap { tour =>
|
||||
fuccess(Redirect(routes.Tournament.show(tour.id)))
|
||||
}
|
||||
env.api.createTournament(setup, me, teams, getUserTeamIds) map { tour =>
|
||||
if (tour.teamBattle.isDefined) Redirect(routes.Tournament.teamBattleEdit(tour.id))
|
||||
else Redirect(routes.Tournament.show(tour.id))
|
||||
}
|
||||
}(rateLimited)
|
||||
}(rateLimited)
|
||||
}
|
||||
|
@ -250,12 +245,46 @@ object Tournament extends LilaController {
|
|||
env.forms(me).bindFromRequest.fold(
|
||||
jsonFormErrorDefaultLang,
|
||||
setup => teamsIBelongTo(me) flatMap { teams =>
|
||||
env.api.createTournament(setup, me, teams, getUserTeamIds, Env.team.api.filterExistingIds) flatMap { tour =>
|
||||
env.api.createTournament(setup, me, teams, getUserTeamIds) flatMap { tour =>
|
||||
Env.tournament.jsonView(tour, none, none, getUserTeamIds, none, none, partial = false, lila.i18n.defaultLang)
|
||||
}
|
||||
} map { Ok(_) }
|
||||
)
|
||||
|
||||
def teamBattleEdit(id: String) = Auth { implicit ctx => me =>
|
||||
repo byId id flatMap {
|
||||
_ ?? {
|
||||
case tour if tour.createdBy == me.id =>
|
||||
tour.teamBattle ?? { battle =>
|
||||
lila.team.TeamRepo.byOrderedIds(battle.sortedTeamIds) flatMap { teams =>
|
||||
Env.user.lightUserApi.preloadMany(teams.map(_.createdBy)) >> {
|
||||
val form = lila.tournament.TeamBattle.DataForm.edit(teams.map { t =>
|
||||
s"""${t.id} "${t.name}" by ${Env.user.lightUserApi.sync(t.createdBy).fold(t.createdBy)(_.name)}"""
|
||||
})
|
||||
Ok(html.tournament.teamBattle.edit(tour, form)).fuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
case tour => Redirect(routes.Tournament.show(tour.id)).fuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def teamBattleUpdate(id: String) = AuthBody { implicit ctx => me =>
|
||||
repo byId id flatMap {
|
||||
_ ?? {
|
||||
case tour if tour.createdBy == me.id && !tour.isFinished =>
|
||||
implicit val req = ctx.body
|
||||
lila.tournament.TeamBattle.DataForm.edit(Nil).bindFromRequest.fold(
|
||||
err => BadRequest(html.tournament.teamBattle.edit(tour, err)).fuccess,
|
||||
res => env.api.teamBattleUpdate(tour, res, Env.team.api.filterExistingIds) inject
|
||||
Redirect(routes.Tournament.show(tour.id))
|
||||
)
|
||||
case tour => Redirect(routes.Tournament.show(tour.id)).fuccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def limitedInvitation = Auth { implicit ctx => me =>
|
||||
for {
|
||||
(tours, _) <- upcomingCache.get
|
||||
|
|
|
@ -146,7 +146,7 @@ trait FormHelper { self: I18nHelper =>
|
|||
nameValue: Option[(String, String)] = None,
|
||||
klass: String = "",
|
||||
confirm: Option[String] = None
|
||||
): Frag = submitButton(
|
||||
): Tag = submitButton(
|
||||
dataIcon := icon,
|
||||
name := nameValue.map(_._1),
|
||||
value := nameValue.map(_._2),
|
||||
|
|
|
@ -67,7 +67,7 @@ object show {
|
|||
),
|
||||
(info.createdByMe || isGranted(_.Admin)) option frag(
|
||||
a(href := routes.Team.edit(t.id), cls := "button button-empty text", dataIcon := "%")(trans.settings()),
|
||||
a(href := routes.Tournament.formTeamBattle(t.id), cls := "button button-empty text", dataIcon := "g")("Team Battle")
|
||||
a(href := routes.Tournament.teamBattleForm(t.id), cls := "button button-empty text", dataIcon := "g")("Team Battle")
|
||||
)
|
||||
),
|
||||
NotForKids {
|
||||
|
|
|
@ -30,7 +30,7 @@ object bits {
|
|||
td(cls := "name")(
|
||||
a(cls := "text", dataIcon := tournamentIconChar(tour), href := routes.Tournament.show(tour.id))(tour.name)
|
||||
),
|
||||
tour.schedule.fold(td()) { s => td(momentFromNow(s.at)) },
|
||||
tour.schedule.fold(td) { s => td(momentFromNow(s.at)) },
|
||||
td(tour.durationString),
|
||||
td(dataIcon := "r", cls := "text")(tour.nbPlayers)
|
||||
)
|
||||
|
|
|
@ -30,7 +30,7 @@ object side {
|
|||
if (tour.variant == chess.variant.KingOfTheHill) tour.variant.shortName else tour.variant.name
|
||||
)
|
||||
} else tour.perfType.map(_.name),
|
||||
(!tour.position.initial) ?? s"$separator ${trans.thematic.txt()}",
|
||||
(!tour.position.initial) ?? s"$separator${trans.thematic.txt()}",
|
||||
separator,
|
||||
tour.durationString
|
||||
),
|
||||
|
@ -40,7 +40,9 @@ object side {
|
|||
isGranted(_.TerminateTournament) option
|
||||
postForm(cls := "terminate", action := routes.Tournament.terminate(tour.id))(
|
||||
submitButton(dataIcon := "j", cls := "fbt fbt-red confirm", title := "Terminates the tournament immediately")
|
||||
)
|
||||
),
|
||||
ctx.userId.has(tour.createdBy) && tour.teamBattle.isDefined option
|
||||
a(href := routes.Tournament.teamBattleEdit(tour.id))("Update team list")
|
||||
)
|
||||
),
|
||||
tour.spotlight map { s =>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package views.html
|
||||
package tournament
|
||||
|
||||
import play.api.data.{ Field, Form }
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.tournament.Tournament
|
||||
import lila.user.User
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object teamBattle {
|
||||
|
||||
def edit(tour: Tournament, form: Form[_])(implicit ctx: Context) = views.html.base.layout(
|
||||
title = tour.fullName,
|
||||
moreCss = cssTag("tournament.form"),
|
||||
moreJs = jsTag("tournamentTeamBattleForm.js")
|
||||
)(main(cls := "page-small")(
|
||||
div(cls := "tour__form box box-pad")(
|
||||
h1(tour.fullName),
|
||||
if (tour.isFinished) p("This tournament is over, and the teams can no longer be updated.")
|
||||
else p("List the teams that will compete in this battle."),
|
||||
postForm(cls := "form3", action := routes.Tournament.teamBattleUpdate(tour.id))(
|
||||
form3.group(form("teams"), raw("Team IDs or names, one per line. Use the auto-completion."),
|
||||
help = frag("You can copy-paste this list from a tournament to another!").some)(
|
||||
form3.textarea(_)(rows := 25, tour.isFinished.option(disabled))
|
||||
),
|
||||
form3.submit("Update teams")(tour.isFinished.option(disabled))
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
|
@ -213,8 +213,10 @@ GET /whats-next/$fullId<\w{12}> controllers.Round.whatsNext(ful
|
|||
GET /tournament controllers.Tournament.home(page: Int ?= 1)
|
||||
GET /tournament/featured controllers.Tournament.featured
|
||||
GET /tournament/new controllers.Tournament.form
|
||||
GET /tournament/new-team-battle controllers.Tournament.formTeamBattle(teamId: String)
|
||||
POST /tournament/new controllers.Tournament.create
|
||||
GET /tournament/team-battle/new/:teamId controllers.Tournament.teamBattleForm(teamId: String)
|
||||
GET /tournament/team-battle/edit controllers.Tournament.teamBattleEdit(id: String)
|
||||
POST /tournament/team-battle/edit controllers.Tournament.teamBattleUpdate(id: String)
|
||||
GET /tournament/calendar controllers.Tournament.calendar
|
||||
GET /tournament/$id<\w{8}> controllers.Tournament.show(id: String)
|
||||
GET /tournament/$id<\w{8}>/standing/:page controllers.Tournament.standing(id: String, page: Int)
|
||||
|
@ -261,6 +263,7 @@ GET /team/me controllers.Team.mine
|
|||
GET /team/all controllers.Team.all(page: Int ?= 1)
|
||||
GET /team/requests controllers.Team.requests
|
||||
GET /team/search controllers.Team.search(text: String ?= "", page: Int ?= 1)
|
||||
GET /team/autocomplete controllers.Team.autocomplete
|
||||
GET /team/:id controllers.Team.show(id: String, page: Int ?= 1)
|
||||
POST /team/:id/join controllers.Team.join(id: String)
|
||||
POST /team/:id/quit controllers.Team.quit(id: String)
|
||||
|
|
|
@ -337,8 +337,8 @@ trait dsl extends LowPriorityDsl {
|
|||
def $regex(value: String, options: String = ""): SimpleExpression[BSONRegex] =
|
||||
SimpleExpression(field, BSONRegex(value, options))
|
||||
|
||||
def $startsWith(value: String): SimpleExpression[BSONRegex] =
|
||||
$regex(s"^$value", "")
|
||||
def $startsWith(value: String, options: String = ""): SimpleExpression[BSONRegex] =
|
||||
$regex(s"^$value", options)
|
||||
}
|
||||
|
||||
trait ArrayOperators { self: ElementBuilder =>
|
||||
|
|
|
@ -204,6 +204,12 @@ final class TeamApi(
|
|||
def filterExistingIds(ids: Set[String]): Fu[Set[Team.ID]] =
|
||||
coll.team.distinct[Team.ID, Set]("_id", Some("_id" $in ids))
|
||||
|
||||
def autocomplete(term: String, max: Int): Fu[List[Team]] =
|
||||
coll.team.find($doc(
|
||||
"name".$startsWith(java.util.regex.Pattern.quote(term), "i"),
|
||||
"enabled" -> true
|
||||
)).sort($sort desc "nbMembers").list[Team](max, ReadPreference.secondaryPreferred)
|
||||
|
||||
def nbRequests(teamId: Team.ID) = cached.nbRequests get teamId
|
||||
|
||||
def recomputeNbMembers =
|
||||
|
|
|
@ -19,10 +19,7 @@ object TeamRepo {
|
|||
def cursor(
|
||||
selector: Bdoc,
|
||||
readPreference: ReadPreference = ReadPreference.secondaryPreferred
|
||||
)(
|
||||
implicit
|
||||
cp: CursorProducer[Team]
|
||||
) =
|
||||
)(implicit cp: CursorProducer[Team]) =
|
||||
coll.find(selector).cursor[Team](readPreference)
|
||||
|
||||
def owned(id: Team.ID, createdBy: User.ID): Fu[Option[Team]] =
|
||||
|
|
|
@ -39,6 +39,8 @@ object BSONHandlers {
|
|||
|
||||
private implicit val spotlightBSONHandler = Macros.handler[Spotlight]
|
||||
|
||||
implicit val battleBSONHandler = Macros.handler[TeamBattle]
|
||||
|
||||
private implicit val leaderboardRatio = new BSONHandler[BSONInteger, LeaderboardApi.Ratio] {
|
||||
def read(b: BSONInteger) = LeaderboardApi.Ratio(b.value.toDouble / 100000)
|
||||
def write(x: LeaderboardApi.Ratio) = BSONInteger((x.value * 100000).toInt)
|
||||
|
@ -66,6 +68,7 @@ object BSONHandlers {
|
|||
mode = r.intO("mode") flatMap Mode.apply getOrElse Mode.Rated,
|
||||
password = r.strO("password"),
|
||||
conditions = conditions,
|
||||
teamBattle = r.getO[TeamBattle]("teamBattle"),
|
||||
noBerserk = r boolD "noBerserk",
|
||||
schedule = for {
|
||||
doc <- r.getO[Bdoc]("schedule")
|
||||
|
@ -93,6 +96,7 @@ object BSONHandlers {
|
|||
"mode" -> o.mode.some.filterNot(_.rated).map(_.id),
|
||||
"password" -> o.password,
|
||||
"conditions" -> o.conditions.ifNonEmpty,
|
||||
"teamBattle" -> o.teamBattle,
|
||||
"noBerserk" -> w.boolO(o.noBerserk),
|
||||
"schedule" -> o.schedule.map { s =>
|
||||
$doc(
|
||||
|
|
|
@ -55,7 +55,7 @@ final class DataForm {
|
|||
"rated" -> optional(boolean),
|
||||
"password" -> optional(nonEmptyText),
|
||||
"conditions" -> Condition.DataForm.all,
|
||||
"teamBattle" -> optional(TeamBattle.DataForm.form),
|
||||
"teamBattle" -> optional(TeamBattle.DataForm.fields),
|
||||
"berserkable" -> optional(boolean)
|
||||
)(TournamentSetup.apply)(TournamentSetup.unapply)
|
||||
.verifying("Invalid clock", _.validClock)
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package lila.tournament
|
||||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
import lila.hub.lightTeam._
|
||||
|
||||
case class TeamBattle(
|
||||
teams: Set[TeamId]
|
||||
)
|
||||
) {
|
||||
def sortedTeamIds = teams.toList.sorted
|
||||
}
|
||||
|
||||
object TeamBattle {
|
||||
|
||||
|
@ -12,7 +17,9 @@ object TeamBattle {
|
|||
import play.api.data.Forms._
|
||||
import lila.common.Form._
|
||||
|
||||
val form = mapping(
|
||||
def edit(teams: List[String]) = Form(fields) fill Setup(s"${teams mkString "\n"}\n")
|
||||
|
||||
val fields = mapping(
|
||||
"teams" -> nonEmptyText
|
||||
)(Setup.apply)(Setup.unapply)
|
||||
|
||||
|
@ -20,7 +27,7 @@ object TeamBattle {
|
|||
teams: String
|
||||
) {
|
||||
def potentialTeamIds: Set[String] =
|
||||
teams.lines.mkString(" ").split(" ").filter(_.nonEmpty).toSet
|
||||
teams.lines.map(_.takeWhile(' ' !=)).filter(_.nonEmpty).toSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,12 +39,14 @@ case class Tournament(
|
|||
|
||||
def isPrivate = password.isDefined
|
||||
|
||||
def fullName = schedule.map(_.freq).fold(s"$name $system") {
|
||||
case Schedule.Freq.ExperimentalMarathon | Schedule.Freq.Marathon | Schedule.Freq.Unique => name
|
||||
case Schedule.Freq.Shield => s"$name $system"
|
||||
case _ if clock.hasIncrement => s"$name Inc $system"
|
||||
case _ => s"$name $system"
|
||||
}
|
||||
def fullName =
|
||||
if (teamBattle.isDefined) s"$name Team Battle"
|
||||
else schedule.map(_.freq).fold(s"$name $system") {
|
||||
case Schedule.Freq.ExperimentalMarathon | Schedule.Freq.Marathon | Schedule.Freq.Unique => name
|
||||
case Schedule.Freq.Shield => s"$name $system"
|
||||
case _ if clock.hasIncrement => s"$name Inc $system"
|
||||
case _ => s"$name $system"
|
||||
}
|
||||
|
||||
def isMarathon = schedule.map(_.freq) exists {
|
||||
case Schedule.Freq.ExperimentalMarathon | Schedule.Freq.Marathon => true
|
||||
|
@ -143,7 +145,8 @@ object Tournament {
|
|||
password: Option[String],
|
||||
waitMinutes: Int,
|
||||
startDate: Option[DateTime],
|
||||
berserkable: Boolean
|
||||
berserkable: Boolean,
|
||||
teamBattle: Option[TeamBattle]
|
||||
) = Tournament(
|
||||
id = makeId,
|
||||
name = name | {
|
||||
|
@ -162,6 +165,7 @@ object Tournament {
|
|||
mode = mode,
|
||||
password = password,
|
||||
conditions = Condition.All.empty,
|
||||
teamBattle = teamBattle,
|
||||
noBerserk = !berserkable,
|
||||
schedule = None,
|
||||
startsAt = startDate | {
|
||||
|
|
|
@ -50,10 +50,9 @@ final class TournamentApi(
|
|||
setup: TournamentSetup,
|
||||
me: User,
|
||||
myTeams: List[LightTeam],
|
||||
getUserTeamIds: User => Fu[List[TeamId]],
|
||||
filterExistingTeamIds: Set[TeamId] => Fu[Set[TeamId]]
|
||||
getUserTeamIds: User => Fu[List[TeamId]]
|
||||
): Fu[Tournament] = {
|
||||
Tournament.make(
|
||||
val tour = Tournament.make(
|
||||
by = Right(me),
|
||||
name = DataForm.canPickName(me) ?? setup.name,
|
||||
clock = setup.clockConfig,
|
||||
|
@ -65,19 +64,15 @@ final class TournamentApi(
|
|||
system = System.Arena,
|
||||
variant = setup.realVariant,
|
||||
position = DataForm.startingPosition(setup.position | chess.StartingPosition.initial.fen, setup.realVariant),
|
||||
berserkable = setup.berserkable | true
|
||||
berserkable = setup.berserkable | true,
|
||||
teamBattle = setup.teamBattle.map { tb =>
|
||||
TeamBattle(tb.potentialTeamIds)
|
||||
}
|
||||
) |> { tour =>
|
||||
tour.perfType.fold(tour) { perfType =>
|
||||
tour.copy(conditions = setup.conditions.convert(perfType, myTeams.map(_.pair)(collection.breakOut)))
|
||||
}
|
||||
} |> { tour =>
|
||||
setup.teamBattle.fold(fuccess(tour)) { battle =>
|
||||
filterExistingTeamIds(battle.potentialTeamIds) map { teamIds =>
|
||||
tour.copy(teamBattle = teamIds.nonEmpty option TeamBattle(teamIds))
|
||||
}
|
||||
}
|
||||
}
|
||||
} flatMap { tour =>
|
||||
if (tour.name != me.titleUsername && lila.common.LameName.anyNameButLichessIsOk(tour.name))
|
||||
bus.publish(lila.hub.actorApi.slack.TournamentName(me.username, tour.id, tour.name), 'slack)
|
||||
logger.info(s"Create $tour")
|
||||
|
@ -89,6 +84,15 @@ final class TournamentApi(
|
|||
TournamentRepo.insert(tournament).void
|
||||
}
|
||||
|
||||
def teamBattleUpdate(
|
||||
tour: Tournament,
|
||||
data: TeamBattle.DataForm.Setup,
|
||||
filterExistingTeamIds: Set[TeamId] => Fu[Set[TeamId]]
|
||||
): Funit =
|
||||
filterExistingTeamIds(data.potentialTeamIds) flatMap { teamIds =>
|
||||
TournamentRepo.setTeamBattle(tour.id, TeamBattle(teamIds))
|
||||
}
|
||||
|
||||
private[tournament] def makePairings(oldTour: Tournament, users: WaitingUsers, startAt: Long): Unit = {
|
||||
Sequencing(oldTour.id)(TournamentRepo.startedById) { tour =>
|
||||
cached ranking tour flatMap { ranking =>
|
||||
|
|
|
@ -9,6 +9,8 @@ import lila.common.paginator.Paginator
|
|||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
import lila.db.paginator.{ Adapter, CachedAdapter }
|
||||
import lila.game.Game
|
||||
import lila.user.User
|
||||
|
||||
object TournamentRepo {
|
||||
|
||||
|
@ -112,27 +114,22 @@ object TournamentRepo {
|
|||
def clockById(id: Tournament.ID): Fu[Option[chess.Clock.Config]] =
|
||||
coll.primitiveOne[chess.Clock.Config]($id(id), "clock")
|
||||
|
||||
def setStatus(tourId: String, status: Status) = coll.update(
|
||||
$id(tourId),
|
||||
$set("status" -> status.id)
|
||||
).void
|
||||
def setStatus(tourId: Tournament.ID, status: Status) =
|
||||
coll.update($id(tourId), $set("status" -> status.id)).void
|
||||
|
||||
def setNbPlayers(tourId: String, nb: Int) = coll.update(
|
||||
$id(tourId),
|
||||
$set("nbPlayers" -> nb)
|
||||
).void
|
||||
def setNbPlayers(tourId: Tournament.ID, nb: Int) =
|
||||
coll.update($id(tourId), $set("nbPlayers" -> nb)).void
|
||||
|
||||
def setWinnerId(tourId: String, userId: String) = coll.update(
|
||||
$id(tourId),
|
||||
$set("winner" -> userId)
|
||||
).void
|
||||
def setWinnerId(tourId: Tournament.ID, userId: User.ID) =
|
||||
coll.update($id(tourId), $set("winner" -> userId)).void
|
||||
|
||||
def setFeaturedGameId(tourId: String, gameId: String) = coll.update(
|
||||
$id(tourId),
|
||||
$set("featured" -> gameId)
|
||||
).void
|
||||
def setFeaturedGameId(tourId: Tournament.ID, gameId: Game.ID) =
|
||||
coll.update($id(tourId), $set("featured" -> gameId)).void
|
||||
|
||||
def featuredGameId(tourId: String) = coll.primitiveOne[String]($id(tourId), "featured")
|
||||
def setTeamBattle(tourId: Tournament.ID, battle: TeamBattle) =
|
||||
coll.update($id(tourId), $set("teamBattle" -> battle)).void
|
||||
|
||||
def featuredGameId(tourId: Tournament.ID) = coll.primitiveOne[Game.ID]($id(tourId), "featured")
|
||||
|
||||
private def allCreatedSelect(aheadMinutes: Int) = createdSelect ++
|
||||
$doc("startsAt" $lt (DateTime.now plusMinutes aheadMinutes))
|
||||
|
|
|
@ -65,7 +65,8 @@ final class CrudApi {
|
|||
password = None,
|
||||
waitMinutes = 0,
|
||||
startDate = none,
|
||||
berserkable = true
|
||||
berserkable = true,
|
||||
teamBattle = none
|
||||
)
|
||||
|
||||
private def updateTour(tour: Tournament, data: CrudForm.Data) = {
|
||||
|
|
|
@ -20,62 +20,4 @@ $(function() {
|
|||
altInput: true,
|
||||
altFormat: 'Y-m-d h:i K'
|
||||
});
|
||||
|
||||
// if (topicId) lichess.loadScript('vendor/textcomplete.min.js').then(function() {
|
||||
|
||||
// var searchCandidates = function(term, candidateUsers) {
|
||||
// return candidateUsers.filter(function(user) {
|
||||
// return user.toLowerCase().startsWith(term.toLowerCase());
|
||||
// });
|
||||
// };
|
||||
|
||||
// // We only ask the server for the thread participants once the user has clicked the text box as most hits to the
|
||||
// // forums will be only to read the thread. So the 'thread participants' starts out empty until the post text area
|
||||
// // is focused.
|
||||
// var threadParticipants = $.ajax({
|
||||
// url: "/forum/participants/" + topicId
|
||||
// });
|
||||
|
||||
// var textcomplete = new Textcomplete(new Textcomplete.editors.Textarea(textarea));
|
||||
|
||||
// textcomplete.register([{
|
||||
// match: /(^|\s)@(|[a-zA-Z_-][\w-]{0,19})$/,
|
||||
// search: function(term, callback) {
|
||||
|
||||
// // Initially we only autocomplete by participants in the thread. As the user types more,
|
||||
// // we can autocomplete against all users on the site.
|
||||
// threadParticipants.then(function(participants) {
|
||||
// var forumParticipantCandidates = searchCandidates(term, participants);
|
||||
|
||||
// if (forumParticipantCandidates.length != 0) {
|
||||
// // We always prefer a match on the forum thread partcipants' usernames
|
||||
// callback(forumParticipantCandidates);
|
||||
// }
|
||||
// else if (term.length >= 3) {
|
||||
// // We fall back to every site user after 3 letters of the username have been entered
|
||||
// // and there are no matches in the forum thread participants
|
||||
// $.ajax({
|
||||
// url: "/player/autocomplete",
|
||||
// data: {
|
||||
// term: term
|
||||
// },
|
||||
// success: function(candidateUsers) {
|
||||
// callback(searchCandidates(term, candidateUsers));
|
||||
// },
|
||||
// cache: true
|
||||
// });
|
||||
// } else {
|
||||
// callback([]);
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
// replace: function(mention) {
|
||||
// return '$1@' + mention + ' ';
|
||||
// }
|
||||
// }], {
|
||||
// placement: 'top',
|
||||
// appendTo: '#lichess_forum'
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
$(function() {
|
||||
|
||||
$('#form3-teams').each(function() {
|
||||
|
||||
const textarea = this;
|
||||
|
||||
lichess.loadScript('vendor/textcomplete.js').then(function() {
|
||||
|
||||
const textcomplete = new Textcomplete(new Textcomplete.editors.Textarea(textarea), {
|
||||
dropdown: {
|
||||
maxCount: 10,
|
||||
placement: 'bottom'
|
||||
}
|
||||
});
|
||||
|
||||
textcomplete.register([{
|
||||
id: 'team',
|
||||
match: /(^|\s)(.+)$/,
|
||||
index: 2,
|
||||
search: function(term, callback) {
|
||||
$.ajax({
|
||||
url: "/team/autocomplete",
|
||||
data: {
|
||||
term: term
|
||||
},
|
||||
success: function(teams) {
|
||||
callback(teams);
|
||||
},
|
||||
error: function() {
|
||||
callback([]);
|
||||
},
|
||||
cache: true
|
||||
})
|
||||
},
|
||||
template: function(team, i) {
|
||||
return team.name + ', by ' + team.owner + ', with ' + team.members + ' members';
|
||||
},
|
||||
replace: function(team) {
|
||||
return '$1' + team.id + ' "' + team.name + '" by ' + team.owner + '\n'
|
||||
}
|
||||
}]);
|
||||
|
||||
textcomplete.on('rendered', function() {
|
||||
if (textcomplete.dropdown.items.length) {
|
||||
// Activate the first item by default.
|
||||
textcomplete.dropdown.items[0].activate();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -33,3 +33,26 @@
|
|||
margin: 1.2em 0 .5em 0;
|
||||
}
|
||||
}
|
||||
.textcomplete-dropdown {
|
||||
@extend %popup-shadow;
|
||||
background: $c-bg-popup;
|
||||
li {
|
||||
list-style: none;
|
||||
border-top: $border;
|
||||
padding: .5em;
|
||||
min-width: 100px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
li.textcomplete-header,
|
||||
li.textcomplete-footer {
|
||||
display: none;
|
||||
}
|
||||
li:hover,
|
||||
.active {
|
||||
background-color: mix($c-accent, $c-bg-popup, 10%);
|
||||
}
|
||||
a {
|
||||
color: $c-font;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue