show user timeline on homepage
parent
b6a09ab149
commit
f691230d2c
|
@ -1,3 +1,6 @@
|
||||||
[submodule "scalachess"]
|
[submodule "scalachess"]
|
||||||
path = modules/chess
|
path = modules/chess
|
||||||
url = git://github.com/ornicar/scalachess.git
|
url = git://github.com/ornicar/scalachess.git
|
||||||
|
[submodule "public/vendor/timeago"]
|
||||||
|
path = public/vendor/timeago
|
||||||
|
url = https://github.com/rmm5t/jquery-timeago
|
||||||
|
|
|
@ -3,17 +3,26 @@ package lila.app
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
|
||||||
final class Env(config: Config, system: ActorSystem) {
|
final class Env(
|
||||||
|
config: Config,
|
||||||
|
system: ActorSystem,
|
||||||
|
appPath: String) {
|
||||||
|
|
||||||
val CliUsername = config getString "cli.username"
|
val CliUsername = config getString "cli.username"
|
||||||
|
|
||||||
private val RendererName = config getString "app.renderer.name"
|
private val RendererName = config getString "app.renderer.name"
|
||||||
private val RouterName = config getString "app.router.name"
|
private val RouterName = config getString "app.router.name"
|
||||||
|
private val WebPath = config getString "app.web_path"
|
||||||
|
private val TimeagoLocalesPath = config getString "app.timeago_locales_path"
|
||||||
|
|
||||||
|
def timeagoLocalesPath = appPath + "/" + TimeagoLocalesPath
|
||||||
|
|
||||||
lazy val preloader = new mashup.Preload(
|
lazy val preloader = new mashup.Preload(
|
||||||
lobby = Env.lobby.lobby,
|
lobby = Env.lobby.lobby,
|
||||||
history = Env.lobby.history,
|
history = Env.lobby.history,
|
||||||
featured = Env.game.featured)
|
featured = Env.game.featured,
|
||||||
|
recentGames = () ⇒ Env.timeline.getter.recentGames,
|
||||||
|
timelineEntries = Env.timeline.getter.userEntries _)
|
||||||
|
|
||||||
lazy val userInfo = mashup.UserInfo(
|
lazy val userInfo = mashup.UserInfo(
|
||||||
countUsers = () ⇒ Env.user.countEnabled,
|
countUsers = () ⇒ Env.user.countEnabled,
|
||||||
|
@ -55,7 +64,8 @@ object Env {
|
||||||
|
|
||||||
lazy val current = "[boot] app" describes new Env(
|
lazy val current = "[boot] app" describes new Env(
|
||||||
config = lila.common.PlayApp.loadConfig,
|
config = lila.common.PlayApp.loadConfig,
|
||||||
system = lila.common.PlayApp.system)
|
system = lila.common.PlayApp.system,
|
||||||
|
appPath = lila.common.PlayApp withApp (_.path.getCanonicalPath))
|
||||||
|
|
||||||
def api = lila.api.Env.current
|
def api = lila.api.Env.current
|
||||||
def db = lila.db.Env.current
|
def db = lila.db.Env.current
|
||||||
|
|
|
@ -23,9 +23,6 @@ private[app] final class Renderer extends Actor {
|
||||||
sender ! V.tournament.createdTable(tours)
|
sender ! V.tournament.createdTable(tours)
|
||||||
|
|
||||||
case entry: lila.timeline.GameEntry =>
|
case entry: lila.timeline.GameEntry =>
|
||||||
sender ! V.lobby.gameTimelineEntry(entry)
|
sender ! V.timeline.gameEntry(entry)
|
||||||
|
|
||||||
case entry: lila.timeline.Entry =>
|
|
||||||
sender ! Html(entry.toString) // V.lobby.timelineEntry(entry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,12 @@ object Lobby extends LilaController with Results {
|
||||||
|
|
||||||
private def renderHome[A](status: Status)(implicit ctx: Context): Fu[Result] =
|
private def renderHome[A](status: Status)(implicit ctx: Context): Fu[Result] =
|
||||||
Env.current.preloader(
|
Env.current.preloader(
|
||||||
timeline = Env.timeline.recentGames,
|
|
||||||
posts = Env.forum.recent(ctx.me, Env.team.cached.teamIds.apply),
|
posts = Env.forum.recent(ctx.me, Env.team.cached.teamIds.apply),
|
||||||
tours = TournamentRepo.created,
|
tours = TournamentRepo.created,
|
||||||
filter = Env.setup.filter
|
filter = Env.setup.filter
|
||||||
).map(_.fold(Redirect(_), {
|
).map(_.fold(Redirect(_), {
|
||||||
case (preload, entries, posts, tours, featured) ⇒ status(html.lobby.home(
|
case (preload, entries, gameEntries, posts, tours, featured) ⇒ status(html.lobby.home(
|
||||||
Json stringify preload, entries, posts, tours, featured)) |> { response ⇒
|
Json stringify preload, entries, gameEntries, posts, tours, featured)) |> { response ⇒
|
||||||
ctx.req.session.data.contains(LilaCookie.sessionId).fold(
|
ctx.req.session.data.contains(LilaCookie.sessionId).fold(
|
||||||
response,
|
response,
|
||||||
response withCookies LilaCookie.makeSessionId(ctx.req)
|
response withCookies LilaCookie.makeSessionId(ctx.req)
|
||||||
|
|
|
@ -1,47 +1,49 @@
|
||||||
package lila.app
|
package lila.app
|
||||||
package mashup
|
package mashup
|
||||||
|
|
||||||
import lila.lobby.{ Hook, HookRepo }
|
|
||||||
import lila.lobby.actorApi.lobby._
|
|
||||||
import lila.timeline.GameEntry
|
|
||||||
import lila.game.{ Game, GameRepo, Featured }
|
|
||||||
import lila.forum.PostLiteView
|
|
||||||
import lila.socket.History
|
|
||||||
import lila.tournament.Created
|
|
||||||
import lila.setup.FilterConfig
|
|
||||||
import lila.user.{ User, Context }
|
|
||||||
import controllers.routes
|
|
||||||
import makeTimeout.large
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import play.api.mvc.Call
|
|
||||||
import play.api.libs.json.{ Json, JsObject, JsArray }
|
import play.api.libs.json.{ Json, JsObject, JsArray }
|
||||||
|
import play.api.mvc.Call
|
||||||
|
|
||||||
|
import controllers.routes
|
||||||
|
import lila.forum.PostLiteView
|
||||||
|
import lila.game.{ Game, GameRepo, Featured }
|
||||||
|
import lila.lobby.actorApi.lobby._
|
||||||
|
import lila.lobby.{ Hook, HookRepo }
|
||||||
|
import lila.setup.FilterConfig
|
||||||
|
import lila.socket.History
|
||||||
|
import lila.timeline.{ Entry, GameEntry }
|
||||||
|
import lila.tournament.Created
|
||||||
|
import lila.user.{ User, Context }
|
||||||
|
import makeTimeout.large
|
||||||
|
|
||||||
final class Preload(
|
final class Preload(
|
||||||
lobby: ActorRef,
|
lobby: ActorRef,
|
||||||
history: History,
|
history: History,
|
||||||
featured: Featured) {
|
featured: Featured,
|
||||||
|
recentGames: () ⇒ Fu[List[GameEntry]],
|
||||||
|
timelineEntries: String ⇒ Fu[List[Entry]]) {
|
||||||
|
|
||||||
private type RightResponse = (JsObject, List[GameEntry], List[PostLiteView], List[Created], Option[Game])
|
private type RightResponse = (JsObject, List[Entry], List[GameEntry], List[PostLiteView], List[Created], Option[Game])
|
||||||
private type Response = Either[Call, RightResponse]
|
private type Response = Either[Call, RightResponse]
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
timeline: Fu[List[GameEntry]],
|
|
||||||
posts: Fu[List[PostLiteView]],
|
posts: Fu[List[PostLiteView]],
|
||||||
tours: Fu[List[Created]],
|
tours: Fu[List[Created]],
|
||||||
filter: Fu[FilterConfig])(implicit ctx: Context): Fu[Response] =
|
filter: Fu[FilterConfig])(implicit ctx: Context): Fu[Response] =
|
||||||
ctx.isAuth.fold(lobby ? GetOpen, lobby ? GetOpenCasual).mapTo[List[Hook]] zip
|
ctx.isAuth.fold(lobby ? GetOpen, lobby ? GetOpenCasual).mapTo[List[Hook]] zip
|
||||||
timeline zip
|
recentGames() zip
|
||||||
posts zip
|
posts zip
|
||||||
tours zip
|
tours zip
|
||||||
featured.one zip
|
featured.one zip
|
||||||
filter map {
|
(ctx.userId ?? timelineEntries) zip
|
||||||
case (((((hooks, entries), posts), tours), feat), filter) ⇒
|
filter map {
|
||||||
|
case ((((((hooks, gameEntries), posts), tours), feat), entries), filter) ⇒
|
||||||
(Right((Json.obj(
|
(Right((Json.obj(
|
||||||
"version" -> history.version,
|
"version" -> history.version,
|
||||||
"pool" -> JsArray(hooks map (_.render)),
|
"pool" -> JsArray(hooks map (_.render)),
|
||||||
"filter" -> filter.render
|
"filter" -> filter.render
|
||||||
), entries, posts, tours, feat)))
|
), entries, gameEntries, posts, tours, feat)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
package lila.app
|
package lila.app
|
||||||
package templating
|
package templating
|
||||||
|
|
||||||
import lila.user.Context
|
|
||||||
|
|
||||||
import org.joda.time.DateTime
|
|
||||||
import org.joda.time.format.{ DateTimeFormat, DateTimeFormatter }
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import org.joda.time.format._
|
||||||
|
import play.api.templates.Html
|
||||||
|
|
||||||
|
import lila.user.Context
|
||||||
|
|
||||||
trait DateHelper { self: I18nHelper ⇒
|
trait DateHelper { self: I18nHelper ⇒
|
||||||
|
|
||||||
private val style = "MS"
|
private val style = "MS"
|
||||||
|
|
||||||
private val formatters = mutable.Map[String, DateTimeFormatter]()
|
private val formatters = mutable.Map[String, DateTimeFormatter]()
|
||||||
|
|
||||||
|
private val isoFormatter = ISODateTimeFormat.dateTime
|
||||||
|
|
||||||
private def formatter(ctx: Context): DateTimeFormatter =
|
private def formatter(ctx: Context): DateTimeFormatter =
|
||||||
formatters.getOrElseUpdate(
|
formatters.getOrElseUpdate(
|
||||||
lang(ctx).language,
|
lang(ctx).language,
|
||||||
|
@ -21,4 +25,25 @@ trait DateHelper { self: I18nHelper ⇒
|
||||||
|
|
||||||
def showDate(date: DateTime)(implicit ctx: Context): String =
|
def showDate(date: DateTime)(implicit ctx: Context): String =
|
||||||
formatter(ctx) print date
|
formatter(ctx) print date
|
||||||
|
|
||||||
|
def timeago(date: DateTime)(implicit ctx: Context): Html = Html(
|
||||||
|
"""<time class="timeago" datetime="%s">%s</time>"""
|
||||||
|
.format(isoFormatter print date, showDate(date))
|
||||||
|
)
|
||||||
|
|
||||||
|
def timeagoLocale(implicit ctx: Context): Option[String] =
|
||||||
|
lang(ctx).language match {
|
||||||
|
case "en" ⇒ none
|
||||||
|
case "pt" ⇒ "pt-br".some
|
||||||
|
case "zh" ⇒ "zh-CN".some
|
||||||
|
case l ⇒ timeagoLocales(l) option l
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy val timeagoLocales: Set[String] = {
|
||||||
|
import java.io.File
|
||||||
|
val Regex = """^jquery\.timeago\.(\w{2})\.js$""".r
|
||||||
|
(new File(Env.current.timeagoLocalesPath).listFiles map (_.getName) collect {
|
||||||
|
case Regex(l) ⇒ l
|
||||||
|
}).toSet: Set[String]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ package templating
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import java.util.regex.Matcher.quoteReplacement
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
||||||
import play.api.templates.Html
|
import play.api.templates.Html
|
||||||
import java.util.regex.Matcher.quoteReplacement
|
|
||||||
|
|
||||||
trait StringHelper {
|
trait StringHelper {
|
||||||
|
|
||||||
|
|
|
@ -141,5 +141,8 @@ moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||||
@if(lang.language != "en") {
|
@if(lang.language != "en") {
|
||||||
<script src="@routes.Assets.at("trans/" + lang.language + ".js")?v=@assetVersion"></script>
|
<script src="@routes.Assets.at("trans/" + lang.language + ".js")?v=@assetVersion"></script>
|
||||||
}
|
}
|
||||||
|
@timeagoLocale.map { l =>
|
||||||
|
<script src="@routes.Assets.at("vendor/timeago/locales/jquery.timeago." + l + ".js")"></script>
|
||||||
|
}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@(preload: String, gameTimeline: List[lila.timeline.GameEntry], forumRecent: List[lila.forum.PostLiteView], tours: List[lila.tournament.Created], featured: Option[Game])(implicit ctx: Context)
|
@(preload: String, userTimeline: List[lila.timeline.Entry], gameTimeline: List[lila.timeline.GameEntry], forumRecent: List[lila.forum.PostLiteView], tours: List[lila.tournament.Created], featured: Option[Game])(implicit ctx: Context)
|
||||||
|
|
||||||
@underchat = {
|
@underchat = {
|
||||||
<div id="featured_game">
|
<div id="featured_game">
|
||||||
|
@ -13,6 +13,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@goodies = {
|
@goodies = {
|
||||||
|
<div id="timeline">
|
||||||
|
@userTimeline.map { entry =>
|
||||||
|
@timeline.entry(entry)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@base.layout(
|
@base.layout(
|
||||||
|
@ -44,39 +49,7 @@ underchat = underchat.some) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@lobby.buttons()
|
@lobby.buttons()
|
||||||
<div class="open_tournaments undertable">
|
@lobby.undertable(gameTimeline, forumRecent, tours)
|
||||||
<div class="undertable_top">
|
|
||||||
<a class="more" title="See all tournaments" href="@routes.Tournament.home()">More »</a>
|
|
||||||
<span class="title">Open tournaments</span>
|
|
||||||
</div>
|
|
||||||
<div class="undertable_inner">
|
|
||||||
<table class="tournaments">
|
|
||||||
@tournament.createdTable(tours)
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="lichess_bot undertable">
|
|
||||||
<div class="undertable_top">
|
|
||||||
<a class="more" title="@trans.seeTheGamesBeingPlayedInRealTime()" href="@routes.Game.realtime()">@trans.games() »</a>
|
|
||||||
<span class="title">@trans.gamesBeingPlayedRightNow()</span>
|
|
||||||
</div>
|
|
||||||
<div class="undertable_inner">
|
|
||||||
<div class="content">
|
|
||||||
<table class="lichess_messages">@gameTimelineEntries(gameTimeline)</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="new_posts undertable" data-url="@routes.ForumPost.recent">
|
|
||||||
<div class="undertable_top">
|
|
||||||
<a class="more" title="@trans.talkAboutChessAndDiscussLichessFeaturesInTheForum()" href="@routes.ForumCateg.index">@trans.forum() »</a>
|
|
||||||
<span class="title">@trans.forum()</span>
|
|
||||||
</div>
|
|
||||||
<div class="undertable_inner">
|
|
||||||
<div class="content">
|
|
||||||
<ol>@forum.post.recent(forumRecent)</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@embedJs("var lichess_preload = " + preload)
|
@embedJs("var lichess_preload = " + preload)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
@(gameTimeline: List[lila.timeline.GameEntry], forumRecent: List[lila.forum.PostLiteView], tours: List[lila.tournament.Created])(implicit ctx: Context)
|
||||||
|
|
||||||
|
<div class="open_tournaments undertable">
|
||||||
|
<div class="undertable_top">
|
||||||
|
<a class="more" title="See all tournaments" href="@routes.Tournament.home()">More »</a>
|
||||||
|
<span class="title">Open tournaments</span>
|
||||||
|
</div>
|
||||||
|
<div class="undertable_inner">
|
||||||
|
<table class="tournaments">
|
||||||
|
@tournament.createdTable(tours)
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lichess_bot undertable">
|
||||||
|
<div class="undertable_top">
|
||||||
|
<a class="more" title="@trans.seeTheGamesBeingPlayedInRealTime()" href="@routes.Game.realtime()">@trans.games() »</a>
|
||||||
|
<span class="title">@trans.gamesBeingPlayedRightNow()</span>
|
||||||
|
</div>
|
||||||
|
<div class="undertable_inner">
|
||||||
|
<div class="content">
|
||||||
|
<table>@timeline.gameEntries(gameTimeline)</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="new_posts undertable" data-url="@routes.ForumPost.recent">
|
||||||
|
<div class="undertable_top">
|
||||||
|
<a class="more" title="@trans.talkAboutChessAndDiscussLichessFeaturesInTheForum()" href="@routes.ForumCateg.index">@trans.forum() »</a>
|
||||||
|
<span class="title">@trans.forum()</span>
|
||||||
|
</div>
|
||||||
|
<div class="undertable_inner">
|
||||||
|
<div class="content">
|
||||||
|
<ol>@forum.post.recent(forumRecent)</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,12 @@
|
||||||
|
@(e: lila.timeline.Entry)(implicit ctx: Context)
|
||||||
|
|
||||||
|
@import lila.timeline.Entry._
|
||||||
|
@e.decode.map { decoded =>
|
||||||
|
<div class="entry">
|
||||||
|
@decoded match {
|
||||||
|
case Follow(userId) => {
|
||||||
|
@timeline.follow(userId) @timeago(e.date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
@(userId: String)(implicit ctx: Context)
|
||||||
|
|
||||||
|
@userIdLink(userId.some) @trans.followsYou()
|
|
@ -1,5 +1,5 @@
|
||||||
@(entries: List[lila.timeline.GameEntry])
|
@(entries: List[lila.timeline.GameEntry])
|
||||||
|
|
||||||
@entries.map { e =>
|
@entries.map { e =>
|
||||||
<tr>@gameTimelineEntry(e)</tr>
|
<tr>@timeline.gameEntry(e)</tr>
|
||||||
}
|
}
|
|
@ -17,12 +17,12 @@ object PlayApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private def enableScheduler = loadConfig getBoolean "app.scheduler.enabled"
|
private def enableScheduler = loadConfig getBoolean "app.scheduler.enabled"
|
||||||
|
|
||||||
def scheduler = new Scheduler(system, enabled = enableScheduler && isServer)
|
def scheduler = new Scheduler(system, enabled = enableScheduler && isServer)
|
||||||
|
|
||||||
def isDev = isMode(_.Dev)
|
def isDev = isMode(_.Dev)
|
||||||
def isTest = isMode(_.Test)
|
def isTest = isMode(_.Test)
|
||||||
def isProd = isMode(_.Prod)
|
def isProd = isMode(_.Prod)
|
||||||
// def isServer = !(isDev || isTest)
|
|
||||||
def isServer = !isTest
|
def isServer = !isTest
|
||||||
def isMode(f: Mode.type ⇒ Mode.Mode) = withApp { _.mode == f(Mode) }
|
def isMode(f: Mode.type ⇒ Mode.Mode) = withApp { _.mode == f(Mode) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ package timeline {
|
||||||
def apply[A: Writes](user: String, typ: MakeEntry.type ⇒ String, data: A): MakeEntry =
|
def apply[A: Writes](user: String, typ: MakeEntry.type ⇒ String, data: A): MakeEntry =
|
||||||
MakeEntry(user, typ(MakeEntry), Json toJson data)
|
MakeEntry(user, typ(MakeEntry), Json toJson data)
|
||||||
}
|
}
|
||||||
case class EntryView(user: String, rendered: String)
|
case class ReloadTimeline(user: String)
|
||||||
case class GameEntryView(rendered: String)
|
case class GameEntryView(rendered: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
package lila.lobby
|
package lila.lobby
|
||||||
|
|
||||||
import actorApi._
|
import scala.concurrent.duration._
|
||||||
import lila.socket.{ SocketActor, History, Historical }
|
|
||||||
import lila.socket.actorApi.{ Connected ⇒ _, _ }
|
|
||||||
import lila.game.actorApi._
|
|
||||||
import lila.hub.actorApi.lobby._
|
|
||||||
import lila.hub.actorApi.timeline._
|
|
||||||
import lila.hub.actorApi.router.{ Homepage, Player }
|
|
||||||
import makeTimeout.short
|
|
||||||
|
|
||||||
|
import actorApi._
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import play.api.libs.json._
|
|
||||||
import play.api.libs.iteratee._
|
import play.api.libs.iteratee._
|
||||||
|
import play.api.libs.json._
|
||||||
import play.api.templates.Html
|
import play.api.templates.Html
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
import lila.game.actorApi._
|
||||||
|
import lila.hub.actorApi.lobby._
|
||||||
|
import lila.hub.actorApi.router.{ Homepage, Player }
|
||||||
|
import lila.hub.actorApi.timeline._
|
||||||
|
import lila.socket.actorApi.{ Connected ⇒ _, _ }
|
||||||
|
import lila.socket.{ SocketActor, History, Historical }
|
||||||
|
import makeTimeout.short
|
||||||
|
|
||||||
private[lobby] final class Socket(
|
private[lobby] final class Socket(
|
||||||
val history: History,
|
val history: History,
|
||||||
|
@ -37,15 +38,15 @@ private[lobby] final class Socket(
|
||||||
sender ! Connected(enumerator, member)
|
sender ! Connected(enumerator, member)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ReloadTournaments(html) ⇒ notifyTournaments(html)
|
case ReloadTournaments(html) ⇒ notifyTournaments(html)
|
||||||
|
|
||||||
case GameEntryView(rendered) ⇒ notifyVersion("game_entry", rendered)
|
case GameEntryView(rendered) ⇒ notifyVersion("game_entry", rendered)
|
||||||
|
|
||||||
case EntryView(user, rendered) ⇒ sendTo(user, makeMessage("entry", rendered))
|
case ReloadTimeline(user) ⇒ sendTo(user, makeMessage("reload_timeline", JsNull))
|
||||||
|
|
||||||
case AddHook(hook) ⇒ notifyVersion("hook_add", hook.render)
|
case AddHook(hook) ⇒ notifyVersion("hook_add", hook.render)
|
||||||
|
|
||||||
case RemoveHook(hookId) ⇒ notifyVersion("hook_remove", hookId)
|
case RemoveHook(hookId) ⇒ notifyVersion("hook_remove", hookId)
|
||||||
|
|
||||||
case JoinHook(uid, hook, game) ⇒
|
case JoinHook(uid, hook, game) ⇒
|
||||||
playerUrl(game fullIdOf game.creatorColor) zip
|
playerUrl(game fullIdOf game.creatorColor) zip
|
||||||
|
|
|
@ -3,6 +3,8 @@ package lila.timeline
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import play.api.libs.json._
|
import play.api.libs.json._
|
||||||
|
|
||||||
|
import lila.common.PimpedJson._
|
||||||
|
|
||||||
case class Entry(
|
case class Entry(
|
||||||
user: String,
|
user: String,
|
||||||
typ: String,
|
typ: String,
|
||||||
|
@ -13,11 +15,20 @@ case class Entry(
|
||||||
(user == other.user) &&
|
(user == other.user) &&
|
||||||
(typ == other.typ) &&
|
(typ == other.typ) &&
|
||||||
(data == other.data)
|
(data == other.data)
|
||||||
|
|
||||||
|
def decode: Option[Entry.Decoded] = typ match {
|
||||||
|
case "follow" ⇒ data str "user" map { Entry.Follow(_) }
|
||||||
|
case _ ⇒ none
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Entry {
|
object Entry {
|
||||||
|
|
||||||
def make(user: String, typ: String, data: JsValue): Option[Entry] =
|
sealed trait Decoded
|
||||||
|
|
||||||
|
case class Follow(userId: String) extends Decoded
|
||||||
|
|
||||||
|
private[timeline] def make(user: String, typ: String, data: JsValue): Option[Entry] =
|
||||||
data.asOpt[JsObject] map { Entry(user, typ, _, DateTime.now) }
|
data.asOpt[JsObject] map { Entry(user, typ, _, DateTime.now) }
|
||||||
|
|
||||||
import lila.db.Tube
|
import lila.db.Tube
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package lila.timeline
|
package lila.timeline
|
||||||
|
|
||||||
import tube.gameEntryTube
|
|
||||||
import lila.db.api._
|
|
||||||
import lila.db.Implicits._
|
|
||||||
|
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
|
||||||
|
@ -22,8 +18,9 @@ final class Env(
|
||||||
private val UserDisplayMax = config getInt "user.display_max"
|
private val UserDisplayMax = config getInt "user.display_max"
|
||||||
private val UserActorName = config getString "user.actor.name"
|
private val UserActorName = config getString "user.actor.name"
|
||||||
|
|
||||||
def recentGames: Fu[List[GameEntry]] =
|
lazy val getter = new Getter(
|
||||||
$query[GameEntry]($select.all) sort $sort.naturalOrder toListFlatten GameDisplayMax.some
|
gameMax = GameDisplayMax,
|
||||||
|
userMax = UserDisplayMax)
|
||||||
|
|
||||||
system.actorOf(Props(new GamePush(
|
system.actorOf(Props(new GamePush(
|
||||||
lobbySocket = lobbySocket,
|
lobbySocket = lobbySocket,
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package lila.timeline
|
||||||
|
|
||||||
|
import play.api.libs.json.Json
|
||||||
|
|
||||||
|
import lila.db.api._
|
||||||
|
import lila.db.Implicits._
|
||||||
|
import tube.{ entryTube, gameEntryTube }
|
||||||
|
|
||||||
|
private[timeline] final class Getter(
|
||||||
|
gameMax: Int,
|
||||||
|
userMax: Int) {
|
||||||
|
|
||||||
|
def recentGames: Fu[List[GameEntry]] =
|
||||||
|
$find[GameEntry](
|
||||||
|
$query[GameEntry]($select.all) sort $sort.naturalOrder,
|
||||||
|
gameMax)
|
||||||
|
|
||||||
|
def userEntries(userId: String): Fu[List[Entry]] =
|
||||||
|
$find[Entry](
|
||||||
|
$query[Entry](Json.obj("user" -> userId)) sort $sort.desc("date"),
|
||||||
|
userMax)
|
||||||
|
}
|
|
@ -18,9 +18,7 @@ private[timeline] final class Push(
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case maker @ MakeEntry(user, typ, data) ⇒ makeEntry(user, typ, data) foreach { entry ⇒
|
case maker @ MakeEntry(user, typ, data) ⇒ makeEntry(user, typ, data) foreach { entry ⇒
|
||||||
renderer ? entry map {
|
lobbySocket.ref ! ReloadTimeline(user)
|
||||||
case view: Html ⇒ EntryView(user, view.body)
|
|
||||||
} pipeTo lobbySocket.ref
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -341,6 +341,12 @@ var lichess_sri = Math.random().toString(36).substring(5); // 8 chars
|
||||||
setTimeout(userPowertips, 600);
|
setTimeout(userPowertips, 600);
|
||||||
$('body').on('lichess.content_loaded', userPowertips);
|
$('body').on('lichess.content_loaded', userPowertips);
|
||||||
|
|
||||||
|
function setTimeAgo() {
|
||||||
|
$("time:not(.jsed)").addClass('.jsed').timeago();
|
||||||
|
}
|
||||||
|
setTimeAgo();
|
||||||
|
$('body').on('lichess.content_loaded', setTimeAgo);
|
||||||
|
|
||||||
// Start game
|
// Start game
|
||||||
var $game = $('div.lichess_game').orNot();
|
var $game = $('div.lichess_game').orNot();
|
||||||
if ($game) $game.game(_ld_);
|
if ($game) $game.game(_ld_);
|
||||||
|
@ -1836,8 +1842,9 @@ var lichess_sri = Math.random().toString(36).substring(5); // 8 chars
|
||||||
game_entry: function(e) {
|
game_entry: function(e) {
|
||||||
renderTimeline([e]);
|
renderTimeline([e]);
|
||||||
},
|
},
|
||||||
entry: function(e) {
|
reload_timeline: function() {
|
||||||
console.debug(e);
|
// TODO
|
||||||
|
console.debug("reload timeline");
|
||||||
},
|
},
|
||||||
hook_add: addHook,
|
hook_add: addHook,
|
||||||
hook_remove: removeHook,
|
hook_remove: removeHook,
|
||||||
|
|
|
@ -19,3 +19,6 @@ if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).
|
||||||
|
|
||||||
// jQuery.cookie
|
// jQuery.cookie
|
||||||
(function(e,h,j){function k(b){return b}function l(b){return decodeURIComponent(b.replace(m," "))}var m=/\+/g,d=e.cookie=function(b,c,a){if(c!==j){a=e.extend({},d.defaults,a);null===c&&(a.expires=-1);if("number"===typeof a.expires){var f=a.expires,g=a.expires=new Date;g.setDate(g.getDate()+f)}c=d.json?JSON.stringify(c):String(c);return h.cookie=[encodeURIComponent(b),"=",d.raw?c:encodeURIComponent(c),a.expires?"; expires="+a.expires.toUTCString():"",a.path?"; path="+a.path:"",a.domain?"; domain="+ a.domain:"",a.secure?"; secure":""].join("")}c=d.raw?k:l;a=h.cookie.split("; ");for(f=0;g=a[f]&&a[f].split("=");f++)if(c(g.shift())===b)return b=c(g.join("=")),d.json?JSON.parse(b):b;return null};d.defaults={};e.removeCookie=function(b,c){return null!==e.cookie(b)?(e.cookie(b,null,c),!0):!1}})(jQuery,document);
|
(function(e,h,j){function k(b){return b}function l(b){return decodeURIComponent(b.replace(m," "))}var m=/\+/g,d=e.cookie=function(b,c,a){if(c!==j){a=e.extend({},d.defaults,a);null===c&&(a.expires=-1);if("number"===typeof a.expires){var f=a.expires,g=a.expires=new Date;g.setDate(g.getDate()+f)}c=d.json?JSON.stringify(c):String(c);return h.cookie=[encodeURIComponent(b),"=",d.raw?c:encodeURIComponent(c),a.expires?"; expires="+a.expires.toUTCString():"",a.path?"; path="+a.path:"",a.domain?"; domain="+ a.domain:"",a.secure?"; secure":""].join("")}c=d.raw?k:l;a=h.cookie.split("; ");for(f=0;g=a[f]&&a[f].split("=");f++)if(c(g.shift())===b)return b=c(g.join("=")),d.json?JSON.parse(b):b;return null};d.defaults={};e.removeCookie=function(b,c){return null!==e.cookie(b)?(e.cookie(b,null,c),!0):!1}})(jQuery,document);
|
||||||
|
|
||||||
|
// jquery.timeago 1.2.0 https://github.com/rmm5t/jquery-timeago
|
||||||
|
(function(d){"function"===typeof define&&define.amd?define(["jquery"],d):d(jQuery)})(function(d){function l(){var a;a=d(this);if(!a.data("timeago")){a.data("timeago",{datetime:e.datetime(a)});var b=d.trim(a.text());e.settings.localeTitle?a.attr("title",a.data("timeago").datetime.toLocaleString()):0<b.length&&(!e.isTime(a)||!a.attr("title"))&&a.attr("title",b)}a=a.data("timeago");b=e.settings;isNaN(a.datetime)||(0==b.cutoff||(new Date).getTime()-a.datetime.getTime()<b.cutoff)&&d(this).text(f(a.datetime));return this}function f(a){return e.inWords((new Date).getTime()-a.getTime())}d.timeago=function(a){return a instanceof Date?f(a):"string"===typeof a?f(d.timeago.parse(a)):"number"===typeof a?f(new Date(a)):f(d.timeago.datetime(a))};var e=d.timeago;d.extend(d.timeago,{settings:{refreshMillis:6E4,allowFuture:!1,localeTitle:!1,cutoff:0,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"about a minute",minutes:"%d minutes",hour:"about an hour",hours:"about %d hours",day:"a day",days:"%d days",month:"about a month",months:"%d months",year:"about a year",years:"%d years",wordSeparator:" ",numbers:[]}},inWords:function(a){function b(b,e){return(d.isFunction(b)?b(e,a):b).replace(/%d/i,c.numbers&&c.numbers[e]||e)}var c=this.settings.strings,e=c.prefixAgo,f=c.suffixAgo;this.settings.allowFuture&&0>a&&(e=c.prefixFromNow,f=c.suffixFromNow);var h=Math.abs(a)/1E3,g=h/60,m=g/60,k=m/24,l=k/365,h=45>h&&b(c.seconds,Math.round(h))||90>h&&b(c.minute,1)||45>g&&b(c.minutes,Math.round(g))||90>g&&b(c.hour,1)||24>m&&b(c.hours,Math.round(m))||42>m&&b(c.day,1)||30>k&&b(c.days,Math.round(k))||45>k&&b(c.month,1)||365>k&&b(c.months,Math.round(k/30))||1.5>l&&b(c.year,1)||b(c.years,Math.round(l)),g=c.wordSeparator||"";void 0===c.wordSeparator&&(g=" ");return d.trim([e,h,f].join(g))},parse:function(a){a=d.trim(a);a=a.replace(/\.\d+/,"");a=a.replace(/-/,"/").replace(/-/,"/");a=a.replace(/T/," ").replace(/Z/," UTC");a=a.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");return new Date(a)},datetime:function(a){a=e.isTime(a)?d(a).attr("datetime"):d(a).attr("title");return e.parse(a)},isTime:function(a){return"time"===d(a).get(0).tagName.toLowerCase()}});var n={init:function(){var a=d.proxy(l,this);a();var b=e.settings;0<b.refreshMillis&&setInterval(a,b.refreshMillis)},update:function(a){d(this).data("timeago",{datetime:e.parse(a)});l.apply(this)}};d.fn.timeago=function(a,b){var c=a?n[a]:n.init;if(!c)throw Error("Unknown function name '"+a+"' for timeago");this.each(function(){c.call(this,b)});return this};document.createElement("abbr");document.createElement("time")});
|
||||||
|
|
|
@ -515,9 +515,6 @@ div.lichess_chat {
|
||||||
div.lichess_chat.small_chat {
|
div.lichess_chat.small_chat {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
div.lichess_chat.lobby_chat {
|
|
||||||
top: 126px;
|
|
||||||
}
|
|
||||||
div.lichess_chat_top {
|
div.lichess_chat_top {
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
@ -552,9 +549,6 @@ div.lichess_chat:hover .lichess_messages {
|
||||||
div.lichess_chat.small_chat .lichess_messages {
|
div.lichess_chat.small_chat .lichess_messages {
|
||||||
width: 186px;
|
width: 186px;
|
||||||
}
|
}
|
||||||
div.lichess_chat.lobby_chat .lichess_messages {
|
|
||||||
height: 341px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.lichess_chat form {
|
div.lichess_chat form {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -825,6 +825,18 @@ div.game_extra div.bookmarkers {
|
||||||
float: right;
|
float: right;
|
||||||
max-width: 48%;
|
max-width: 48%;
|
||||||
}
|
}
|
||||||
|
#timeline {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
#timeline > .entry {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
border-bottom: 1px solid #c0c0c0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
#timeline time {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #afafaf;
|
||||||
|
}
|
||||||
span.bookmark {
|
span.bookmark {
|
||||||
position: relative;
|
position: relative;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
|
@ -134,7 +134,8 @@ body.dark div.lichess_chat a.user_link,
|
||||||
body.dark div.lichess_chat a.user_link,
|
body.dark div.lichess_chat a.user_link,
|
||||||
body.dark div.new_posts li span,
|
body.dark div.new_posts li span,
|
||||||
body.dark #team .forum a.user_link,
|
body.dark #team .forum a.user_link,
|
||||||
body.dark span.board_mark
|
body.dark span.board_mark,
|
||||||
|
body.dark #timeline time
|
||||||
{
|
{
|
||||||
color: #808080;
|
color: #808080;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d52af890182a0779598d7001834a3708a0ea5746
|
Loading…
Reference in New Issue