more work on followers and user profile

pull/83/head
Thibault Duplessis 2013-05-23 18:59:46 +02:00
parent ed0e2174ec
commit b4d89f0929
17 changed files with 127 additions and 65 deletions

View File

@ -1,7 +1,7 @@
package controllers
import lila.app._
import lila.user.{ User UserModel, Context }
import lila.user.{ User UserModel, UserRepo, Context }
import views._
import play.api.mvc._
@ -32,4 +32,20 @@ object Relation extends LilaController {
me
env.api.unblock(me.id, userId).nevermind inject html.relation.actions(userId)
}
def following(userId: String) = Open { implicit ctx
OptionFuOk(UserRepo named userId) { user
env.api.following(user.id) map { userIds
html.relation.following(user, userIds.toList.sorted)
}
}
}
def followers(userId: String) = Open { implicit ctx
OptionFuOk(UserRepo named userId) { user
env.api.followers(user.id) map { userIds
html.relation.followers(user, userIds.toList.sorted)
}
}
}
}

View File

@ -41,7 +41,9 @@ object User extends LilaController {
pag (filters.query.fold(Env.bookmark.api.gamePaginatorByUser(u, page)) { query
gamePaginator.recentlyCreated(query, filters.cachedNb)(page)
})
} yield html.user.show(u, info, pag, filters)
nbFollowing Env.relation.api nbFollowing u.id
nbFollowers Env.relation.api nbFollowers u.id
} yield html.user.show(u, info, pag, nbFollowing, nbFollowers, filters)
}, fuccess(html.user.disabled(u)))
private def mini(username: String)(implicit ctx: Context) =

View File

@ -1,13 +1,14 @@
package lila.app
package templating
import lila.user.User
import lila.user.{ User, Context }
import mashup._
import controllers.routes
import play.api.templates.Html
trait UserHelper {
trait UserHelper { self: I18nHelper
def userIdToUsername(userId: String): String =
(Env.user usernameOrAnonymous userId).await
@ -113,4 +114,23 @@ trait UserHelper {
withOnline option isOnline(userId).fold("online", "offline")
).flatten
}.mkString("class=\"", " ", "\"")
private val NumberFirstRegex = """^(\d+)\s(.+)$""".r
private val NumberLastRegex = """^(.+)\s(\d+)$""".r
def userGameFilterTitle(info: UserInfo, filter: GameFilter)(implicit ctx: Context) = Html {
((filter match {
case GameFilter.All info.user.nbGames + " " + trans.gamesPlayed()
case GameFilter.Me ctx.me.fold("-")(me "%d vs me" format ~info.nbWithMe)
case GameFilter.Rated info.nbRated + " " + trans.rated()
case GameFilter.Win trans.nbWins(info.user.nbWins)
case GameFilter.Loss trans.nbLosses(info.user.nbLosses)
case GameFilter.Draw trans.nbDraws(info.user.nbDraws)
case GameFilter.Playing info.nbPlaying + " playing"
case GameFilter.Bookmark trans.nbBookmarks(info.nbBookmark)
}).toString match {
case NumberFirstRegex(number, text) "<strong>%s</strong><br />%s".format(number, text)
case NumberLastRegex(text, number) "%s<br /><strong>%s</strong>".format(text, number)
case h h
})
}
}

View File

@ -0,0 +1,8 @@
@(u: User, userIds: List[String])(implicit ctx: Context)
@user.layout(title = u.username + " - " + trans.nbFollowers(userIds.size)) {
<div class="content_box no_padding">
<h1>@userLink(u, withOnline = false) @trans.nbFollowers(userIds.size)</h1>
@relation.userTable(userIds)
</div>
}

View File

@ -0,0 +1,8 @@
@(u: User, userIds: List[String])(implicit ctx: Context)
@user.layout(title = u.username + " - " + trans.nbFollowing(userIds.size)) {
<div class="content_box no_padding">
<h1>@userLink(u, withOnline = false) @trans.nbFollowing(userIds.size)</h1>
@relation.userTable(userIds)
</div>
}

View File

@ -0,0 +1,18 @@
@(userIds: List[String])(implicit ctx: Context)
<table class="slist">
@if(userIds.size > 0) {
<tbody class="infinitescroll">
@userIds.map { userId =>
<tr>
<td>@userIdLink(userId.some)</td>
<td>@relation.actions(userId)</td>
</tr>
}
</tbody>
} else {
<tbody>
<tr><td colspan="2">None found.<br /></td></tr>
</tbody>
}
</table>

View File

@ -1,28 +0,0 @@
@(info: lila.app.mashup.UserInfo, filter: lila.app.mashup.GameFilter)(implicit ctx: Context)
@filter match {
case lila.app.mashup.GameFilter.All => {
@info.user.nbGames @trans.gamesPlayed()
}
case lila.app.mashup.GameFilter.Me => {
@ctx.me.fold("-")(me => "%d vs me".format(info.nbWithMe | 0))
}
case lila.app.mashup.GameFilter.Rated => {
@info.nbRated @trans.rated()
}
case lila.app.mashup.GameFilter.Win => {
@trans.nbWins(info.user.nbWins)
}
case lila.app.mashup.GameFilter.Loss => {
@trans.nbLosses(info.user.nbLosses)
}
case lila.app.mashup.GameFilter.Draw => {
@trans.nbDraws(info.user.nbDraws)
}
case lila.app.mashup.GameFilter.Playing => {
@info.nbPlaying playing
}
case lila.app.mashup.GameFilter.Bookmark => {
@trans.nbBookmarks(info.nbBookmark)
}
}

View File

@ -1,6 +1,6 @@
@(u: User, info: lila.app.mashup.UserInfo, games: Paginator[Game], filters: lila.app.mashup.GameFilterMenu)(implicit ctx: Context)
@(u: User, info: lila.app.mashup.UserInfo, games: Paginator[Game], nbFollowing: Int, nbFollowers: Int, filters: lila.app.mashup.GameFilterMenu)(implicit ctx: Context)
@title = @{ "%s : %s - page %d".format(u.username, filterTitle(info, filters.current), games.currentPage) }
@title = @{ "%s : %s - page %d".format(u.username, userGameFilterTitle(info, filters.current), games.currentPage) }
@evenMoreJs = {
@if(ctx is u) {
@ -34,7 +34,7 @@
@if(ctx is u) {
<a class="small action" href="@routes.User.passwd">Change password</a>
<a class="small action" href="@routes.User.close">Close account</a>
}
}
}
@user.layout(
@ -59,6 +59,13 @@ evenMoreCss = evenMoreCss) {
<span class="staff">CLOSED</span>
}
</div>
<div class="social">
<a href="@routes.Relation.following(u.username)">@trans.nbFollowing(nbFollowing)</a>
<a href="@routes.Relation.followers(u.username)">@trans.nbFollowers(nbFollowers)</a>
@if(ctx.isAuth && !ctx.is(u)) {
@relation.actions(u.id)
}
</div>
<div class="clearfix">
@info.eloChart.map { eloChart =>
<div class="elo_history" title="Elo history" data-columns="@eloChart.columns" data-rows="@eloChart.rows">
@ -77,9 +84,6 @@ evenMoreCss = evenMoreCss) {
}
</div>
}
@if(ctx.isAuth && !ctx.is(u)) {
@relation.actions(u.id)
}
<div class="stats">
<p class="numbers">
@trans.nbWins(strong(u.nbWinsH)),
@ -109,7 +113,7 @@ evenMoreCss = evenMoreCss) {
<div class="content_box_inter clearfix">
@filters.list.map { f =>
<a @{ (filters.current == f).??(Html("class=\"active\"")) } href="@routes.User.showFilter(u.username, f.name)">
@filterTitle(info, f)
@userGameFilterTitle(info, f)
</a>
}
</div>

View File

@ -201,3 +201,5 @@ block=Block
blocked=Blocked
unblock=Unblock
followsYou=Follows you
nbFollowers=%s followers
nbFollowing=%s following

View File

@ -2,6 +2,14 @@
GET / controllers.Lobby.home
GET /lobby/socket controllers.Lobby.socket
# Relation
POST /rel/follow/:userId controllers.Relation.follow(userId: String)
POST /rel/unfollow/:userId controllers.Relation.unfollow(userId: String)
POST /rel/block/:userId controllers.Relation.block(userId: String)
POST /rel/unblock/:userId controllers.Relation.unblock(userId: String)
GET /@/:username/following controllers.Relation.following(username: String)
GET /@/:username/followers controllers.Relation.followers(username: String)
# User
GET /@/:username/opponents controllers.User.opponents(username: String)
POST /@/:username/export controllers.User.export(username: String)
@ -18,12 +26,6 @@ POST /account/passwd controllers.User.passwdApply
GET /account/close controllers.User.close
POST /account/closeConfirm controllers.User.closeConfirm
# Relation
POST /rel/follow/:userId controllers.Relation.follow(userId: String)
POST /rel/unfollow/:userId controllers.Relation.unfollow(userId: String)
POST /rel/block/:userId controllers.Relation.block(userId: String)
POST /rel/unblock/:userId controllers.Relation.unblock(userId: String)
#Site
GET /socket controllers.Main.websocket

View File

@ -225,6 +225,8 @@ final class I18nKeys(translator: Translator) {
val blocked = new Key("blocked")
val unblock = new Key("unblock")
val followsYou = new Key("followsYou")
val nbFollowers = new Key("nbFollowers")
val nbFollowing = new Key("nbFollowing")
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, computerAnalysisInProgress, theComputerAnalysisYouRequestedIsNowAvailable, theComputerAnalysisHasFailed, viewTheComputerAnalysis, requestAComputerAnalysis, blunders, mistakes, inaccuracies, viewGameStats, flipBoard, threefoldRepetition, claimADraw, offerDraw, draw, nbConnectedPlayers, talkAboutChessAndDiscussLichessFeaturesInTheForum, seeTheGamesBeingPlayedInRealTime, gamesBeingPlayedRightNow, viewAllNbGames, viewNbCheckmates, nbBookmarks, nbPopularGames, nbAnalysedGames, bookmarkedByNbPlayers, viewInFullSize, logOut, signIn, newToLichess, youNeedAUsernameToDoThat, signUp, people, games, forum, searchInForum, 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, noMessageHereYet, subject, recipient, send, incrementInSeconds, freeOnlineChess, spectators, nbWins, nbLosses, nbDraws, exportGames, color, eloRange, giveNbSeconds, searchAPlayer, whoIsOnline, allPlayers, premoveEnabledClickAnywhereToCancel, thisPlayerUsesChessComputerAssistance, opening, takeback, proposeATakeback, takebackPropositionSent, takebackPropositionDeclined, takebackPropositionAccepted, takebackPropositionCanceled, yourOpponentProposesATakeback, bookmarkThisGame, toggleBackground, advancedSearch, tournament, tournamentPoints, viewTournament, freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents, teams, nbMembers, allTeams, newTeam, myTeams, noTeamFound, joinTeam, quitTeam, anyoneCanJoin, aConfirmationIsRequiredToJoin, joiningPolicy, teamLeader, teamBestPlayers, teamRecentMembers, searchATeam, averageElo, location, settings, filterGames, reset, apply, leaderboard, pasteTheFenStringHere, pasteThePgnStringHere, fromPosition, continueFromHere, importGame, nbImportedGames, thisIsAChessCaptcha, clickOnTheBoardToMakeYourMove, notACheckmate, colorPlaysCheckmateInOne, retry, reconnecting, friends, noFriendsOnline, findFriends, favoriteOpponents, follow, following, unfollow, block, blocked, unblock, followsYou)
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, computerAnalysisInProgress, theComputerAnalysisYouRequestedIsNowAvailable, theComputerAnalysisHasFailed, viewTheComputerAnalysis, requestAComputerAnalysis, blunders, mistakes, inaccuracies, viewGameStats, flipBoard, threefoldRepetition, claimADraw, offerDraw, draw, nbConnectedPlayers, talkAboutChessAndDiscussLichessFeaturesInTheForum, seeTheGamesBeingPlayedInRealTime, gamesBeingPlayedRightNow, viewAllNbGames, viewNbCheckmates, nbBookmarks, nbPopularGames, nbAnalysedGames, bookmarkedByNbPlayers, viewInFullSize, logOut, signIn, newToLichess, youNeedAUsernameToDoThat, signUp, people, games, forum, searchInForum, 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, noMessageHereYet, subject, recipient, send, incrementInSeconds, freeOnlineChess, spectators, nbWins, nbLosses, nbDraws, exportGames, color, eloRange, giveNbSeconds, searchAPlayer, whoIsOnline, allPlayers, premoveEnabledClickAnywhereToCancel, thisPlayerUsesChessComputerAssistance, opening, takeback, proposeATakeback, takebackPropositionSent, takebackPropositionDeclined, takebackPropositionAccepted, takebackPropositionCanceled, yourOpponentProposesATakeback, bookmarkThisGame, toggleBackground, advancedSearch, tournament, tournamentPoints, viewTournament, freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents, teams, nbMembers, allTeams, newTeam, myTeams, noTeamFound, joinTeam, quitTeam, anyoneCanJoin, aConfirmationIsRequiredToJoin, joiningPolicy, teamLeader, teamBestPlayers, teamRecentMembers, searchATeam, averageElo, location, settings, filterGames, reset, apply, leaderboard, pasteTheFenStringHere, pasteThePgnStringHere, fromPosition, continueFromHere, importGame, nbImportedGames, thisIsAChessCaptcha, clickOnTheBoardToMakeYourMove, notACheckmate, colorPlaysCheckmateInOne, retry, reconnecting, friends, noFriendsOnline, findFriends, favoriteOpponents, follow, following, unfollow, block, blocked, unblock, followsYou, nbFollowers, nbFollowing)
}

View File

@ -42,15 +42,15 @@ final class Env(
getFriendIds = cached.friends.apply
)), name = ActorName)
// {
// import scala.concurrent.duration._
// import makeTimeout.short
// import lila.hub.actorApi.WithUserIds
{
import scala.concurrent.duration._
import makeTimeout.short
import lila.hub.actorApi.WithUserIds
// scheduler.message(ActorNotifyFreq) {
// actor -> actorApi.NotifyMovement
// }
// }
scheduler.message(ActorNotifyFreq) {
actor -> actorApi.NotifyMovement
}
}
private[relation] lazy val relationColl = db(CollectionRelation)
}

View File

@ -14,6 +14,9 @@ final class RelationApi(cached: Cached) {
def blockers(userId: ID) = cached blockers userId
def blocking(userId: ID) = cached blocking userId
def nbFollowers(userId: ID) = followers(userId) map (_.size)
def nbFollowing(userId: ID) = following(userId) map (_.size)
def friends(userId: ID) = cached friends userId
def follows(u1: ID, u2: ID) = following(u1) map (_ contains u2)

View File

@ -322,12 +322,12 @@ var lichess_sri = Math.random().toString(36).substring(5); // 8 chars
});
function userPowertips() {
$('a.user_link:not(.powertiped)').addClass('.powertiped').powerTip({
$('a.user_link:not(.jsed)').addClass('.jsed').powerTip({
placement: 's',
smartPlacement: true,
mouseOnToPopup: true,
// closeDelay: 200
closeDelay: 999999
closeDelay: 200
// closeDelay: 999999
}).on({
powerTipPreRender: function() {
$.ajax({
@ -1263,11 +1263,11 @@ var lichess_sri = Math.random().toString(36).substring(5); // 8 chars
this.nobody.toggle(nb == 0);
},
set: function(users) {
this.list.html(_.map(users, $.userLink).join(""));
this.list.html(_.map(users, this._renderUser).join(""));
this.repaint();
},
enters: function(user) {
this.list.append($.userLink(user));
this.list.append(renderUser(user));
this.repaint();
},
leaves: function(user) {
@ -1275,6 +1275,9 @@ var lichess_sri = Math.random().toString(36).substring(5); // 8 chars
return $(this).text() == user;
}).remove();
this.repaint();
},
_renderUser: function(user) {
return '<a href="/@/' + user + '">' + user + '</a>';
}
});

View File

@ -226,9 +226,10 @@ div.content_box_inter {
}
div.content_box_inter a {
float: left;
line-height: 40px;
text-align: center;
line-height: 1.5em;
margin: 5px 0 -1px 0;
padding: 0 0.7em;
padding: 0.5em 0.7em;
font-size: 1em;
text-decoration: none;
text-transform: capitalize;
@ -598,14 +599,12 @@ a#sound_state.unavailable {
border-radius: 4px 4px 0 0;
}
#friend_box .content a {
background: #fff;
padding: 3px 5px;
text-decoration: none;
display: block;
}
#friend_box .content a:hover {
background: #F0F0F0;
color: #444;
}
#friend_box .nobody {
text-align: center;

View File

@ -160,7 +160,7 @@ body.dark div.undergame_box,
body.dark div.undertable td,
body.dark div.lichess_table,
body.dark div.lichess_table_wrap div.clock,
body.dark #friend_box,
body.dark #friend_box .content a:hover,
body.dark div.footer_wrap
{
background: #242424;
@ -245,7 +245,8 @@ body.dark table.translations tbody tr:nth-child(odd),
body.dark form.translation_form div.message:nth-child(even),
body.dark div.content_box table.datatable tr:nth-child(odd),
body.dark div.game_config .optional_config,
body.dark div.search_status
body.dark div.search_status,
body.dark #friend_box
{
background: #343434;
}

2
todo
View File

@ -69,6 +69,8 @@ send message to sockets when a friendship is created or revoked
convert background board css to single image (needs gimp)
correspondance chess http://en.lichess.org/forum/lichess-feedback/correspondence-chess#1
takeback/enpassant glitch http://en.lichess.org/forum/lichess-feedback/i-found-a-glitch#1
show teams in user mini
badges for top players in ELO and number of games
DEPLOY p21
----------