msg wip
parent
a0f9e187b2
commit
4a7498e60d
|
@ -93,6 +93,7 @@ final class LilaComponents(ctx: ApplicationLoader.Context)
|
||||||
lazy val lobby: Lobby = wire[Lobby]
|
lazy val lobby: Lobby = wire[Lobby]
|
||||||
lazy val main: Main = wire[Main]
|
lazy val main: Main = wire[Main]
|
||||||
lazy val message: Message = wire[Message]
|
lazy val message: Message = wire[Message]
|
||||||
|
lazy val msg: Msg = wire[Msg]
|
||||||
lazy val mod: Mod = wire[Mod]
|
lazy val mod: Mod = wire[Mod]
|
||||||
lazy val notifyC: Notify = wire[Notify]
|
lazy val notifyC: Notify = wire[Notify]
|
||||||
lazy val oAuthApp: OAuthApp = wire[OAuthApp]
|
lazy val oAuthApp: OAuthApp = wire[OAuthApp]
|
||||||
|
|
|
@ -22,6 +22,7 @@ final class Env(
|
||||||
val socket: lila.socket.Env,
|
val socket: lila.socket.Env,
|
||||||
val memo: lila.memo.Env,
|
val memo: lila.memo.Env,
|
||||||
val message: lila.message.Env,
|
val message: lila.message.Env,
|
||||||
|
val msg: lila.msg.Env,
|
||||||
val i18n: lila.i18n.Env,
|
val i18n: lila.i18n.Env,
|
||||||
val game: lila.game.Env,
|
val game: lila.game.Env,
|
||||||
val bookmark: lila.bookmark.Env,
|
val bookmark: lila.bookmark.Env,
|
||||||
|
@ -179,6 +180,7 @@ final class EnvBoot(
|
||||||
lazy val hub: lila.hub.Env = wire[lila.hub.Env]
|
lazy val hub: lila.hub.Env = wire[lila.hub.Env]
|
||||||
lazy val socket: lila.socket.Env = wire[lila.socket.Env]
|
lazy val socket: lila.socket.Env = wire[lila.socket.Env]
|
||||||
lazy val message: lila.message.Env = wire[lila.message.Env]
|
lazy val message: lila.message.Env = wire[lila.message.Env]
|
||||||
|
lazy val msg: lila.msg.Env = wire[lila.msg.Env]
|
||||||
lazy val i18n: lila.i18n.Env = wire[lila.i18n.Env]
|
lazy val i18n: lila.i18n.Env = wire[lila.i18n.Env]
|
||||||
lazy val game: lila.game.Env = wire[lila.game.Env]
|
lazy val game: lila.game.Env = wire[lila.game.Env]
|
||||||
lazy val bookmark: lila.bookmark.Env = wire[lila.bookmark.Env]
|
lazy val bookmark: lila.bookmark.Env = wire[lila.bookmark.Env]
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import play.api.libs.json._
|
||||||
|
|
||||||
|
import lila.app._
|
||||||
|
import lila.common.LightUser.lightUserWrites
|
||||||
|
|
||||||
|
final class Msg(
|
||||||
|
env: Env
|
||||||
|
) extends LilaController(env) {
|
||||||
|
|
||||||
|
def home = Auth { implicit ctx => me =>
|
||||||
|
env.msg.api.threads(me) flatMap env.msg.json.threads(me) map { threads =>
|
||||||
|
Ok(
|
||||||
|
views.html.msg.home(
|
||||||
|
Json.obj(
|
||||||
|
"me" -> me.light,
|
||||||
|
"threads" -> threads
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import play.api.mvc._
|
||||||
import lila.api.Context
|
import lila.api.Context
|
||||||
import lila.app._
|
import lila.app._
|
||||||
import lila.common.config.MaxPerSecond
|
import lila.common.config.MaxPerSecond
|
||||||
import lila.common.HTTPRequest
|
|
||||||
import lila.hub.LightTeam
|
import lila.hub.LightTeam
|
||||||
import lila.security.Granter
|
import lila.security.Granter
|
||||||
import lila.team.{ Joined, Motivate, Team => TeamModel }
|
import lila.team.{ Joined, Motivate, Team => TeamModel }
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package views.html
|
||||||
|
|
||||||
|
import play.api.libs.json._
|
||||||
|
|
||||||
|
import lila.api.Context
|
||||||
|
import lila.app.templating.Environment._
|
||||||
|
import lila.app.ui.ScalatagsTemplate._
|
||||||
|
import lila.common.String.html.safeJsonValue
|
||||||
|
|
||||||
|
object msg {
|
||||||
|
|
||||||
|
def home(json: JsObject)(implicit ctx: Context) =
|
||||||
|
views.html.base.layout(
|
||||||
|
moreCss = frag(cssTag("msg")),
|
||||||
|
moreJs = frag(
|
||||||
|
jsAt(s"compiled/lichess.msg${isProd ?? (".min")}.js"),
|
||||||
|
embedJsUnsafe(
|
||||||
|
s"""LichessMsg.app(document.querySelector('.msg-app'), ${safeJsonValue(
|
||||||
|
Json.obj(
|
||||||
|
"data" -> json,
|
||||||
|
"i18n" -> jsI18n
|
||||||
|
)
|
||||||
|
)})"""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
title = "Lichess Inbox"
|
||||||
|
) {
|
||||||
|
main(cls := "page box msg-app")(
|
||||||
|
"loading"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def jsI18n(implicit ctx: Context) = i18nJsObject(translations)
|
||||||
|
|
||||||
|
private val translations = List(
|
||||||
|
trans.inbox
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
db.msg_msg.remove({});
|
db.msg_msg.remove({});
|
||||||
db.msg_thread.remove({});
|
db.msg_thread.remove({});
|
||||||
|
|
||||||
print("Create db.m_thread_sorted");
|
if (false) {
|
||||||
if (true) {
|
print("Create db.m_thread_sorted");
|
||||||
db.m_thread_sorted.drop();
|
db.m_thread_sorted.drop();
|
||||||
db.m_thread.find({visibleByUserIds:{$size:2}}).forEach(t => {
|
db.m_thread.find({visibleByUserIds:{$size:2}}).forEach(t => {
|
||||||
t.visibleByUserIds.sort();
|
t.visibleByUserIds.sort();
|
||||||
|
@ -17,25 +17,35 @@ db.m_thread_sorted.aggregate([
|
||||||
|
|
||||||
let first = o.threads[0];
|
let first = o.threads[0];
|
||||||
|
|
||||||
let posts = [];
|
let msgs = [];
|
||||||
|
|
||||||
o.posts.forEach(ps => {
|
o.threads.forEach(t => {
|
||||||
ps.forEach(p => posts.push);
|
t.posts.forEach(p => {
|
||||||
|
msgs.push({
|
||||||
|
_id: p.id,
|
||||||
|
thread: first._id,
|
||||||
|
text: p.text,
|
||||||
|
user: p.isByCreator ? t.creatorId : t.invitedId,
|
||||||
|
date: p.createdAt
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
posts.sort((a,b) => new Date(b.createdAt) - new Date(a.createdAt));
|
msgs.sort((a,b) => new Date(b.date) - new Date(a.date));
|
||||||
|
|
||||||
let msgs = posts.map(p => ({
|
let last = msgs[msgs.length - 1];
|
||||||
_id: p.id,
|
|
||||||
text: p.text,
|
|
||||||
date: p.createdAt,
|
|
||||||
read: p.isRead
|
|
||||||
}));
|
|
||||||
|
|
||||||
let thread = {
|
let thread = {
|
||||||
_id: first._id,
|
_id: first._id,
|
||||||
users: o._id.sort(),
|
users: o._id.sort(),
|
||||||
lastMsg: lastMsg
|
lastMsg: {
|
||||||
|
text: last.text.slice(0, 50),
|
||||||
|
user: last.user,
|
||||||
|
date: last.date,
|
||||||
|
read: !o.threads.find(t => t.posts.find(p => p.isRead))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.msg_thread.insert(thread);
|
||||||
|
db.msg_msg.insertMany(msgs, {ordered: false});
|
||||||
});
|
});
|
||||||
|
|
19
conf/routes
19
conf/routes
|
@ -415,14 +415,17 @@ POST /forum/post/:id controllers.ForumPost.edit(id: String)
|
||||||
GET /forum/redirect/post/:id controllers.ForumPost.redirect(id: String)
|
GET /forum/redirect/post/:id controllers.ForumPost.redirect(id: String)
|
||||||
|
|
||||||
# Message
|
# Message
|
||||||
GET /inbox controllers.Message.inbox(page: Int ?= 1)
|
GET /old/inbox controllers.Message.inbox(page: Int ?= 1)
|
||||||
GET /inbox/new controllers.Message.form
|
GET /old/inbox/new controllers.Message.form
|
||||||
GET /inbox/unread-count controllers.Message.unreadCount
|
GET /old/inbox/unread-count controllers.Message.unreadCount
|
||||||
POST /inbox/new controllers.Message.create
|
POST /old/inbox/new controllers.Message.create
|
||||||
POST /inbox/batch controllers.Message.batch
|
POST /old/inbox/batch controllers.Message.batch
|
||||||
GET /inbox/$id<\w{8}> controllers.Message.thread(id: String)
|
GET /old/inbox/$id<\w{8}> controllers.Message.thread(id: String)
|
||||||
POST /inbox/$id<\w{8}> controllers.Message.answer(id: String)
|
POST /old/inbox/$id<\w{8}> controllers.Message.answer(id: String)
|
||||||
POST /inbox/$id<\w{8}>/delete controllers.Message.delete(id: String)
|
POST /old/inbox/$id<\w{8}>/delete controllers.Message.delete(id: String)
|
||||||
|
|
||||||
|
# Message
|
||||||
|
GET /inbox controllers.Msg.home
|
||||||
|
|
||||||
# Coach
|
# Coach
|
||||||
GET /coach controllers.Coach.allDefault(page: Int ?= 1)
|
GET /coach controllers.Coach.allDefault(page: Int ?= 1)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package lila.msg
|
package lila.msg
|
||||||
|
|
||||||
import lila.db.dsl._
|
import lila.db.dsl._
|
||||||
|
import lila.db.BSON
|
||||||
import reactivemongo.api.bson._
|
import reactivemongo.api.bson._
|
||||||
|
|
||||||
private[msg] object BsonHandlers {
|
private[msg] object BsonHandlers {
|
||||||
|
@ -9,7 +10,24 @@ private[msg] object BsonHandlers {
|
||||||
implicit val msgContentBSONHandler = Macros.handler[Last]
|
implicit val msgContentBSONHandler = Macros.handler[Last]
|
||||||
|
|
||||||
implicit val threadIdBSONHandler = stringAnyValHandler[MsgThread.Id](_.value, MsgThread.Id.apply)
|
implicit val threadIdBSONHandler = stringAnyValHandler[MsgThread.Id](_.value, MsgThread.Id.apply)
|
||||||
implicit val threadBSONHandler = Macros.handler[MsgThread]
|
|
||||||
|
implicit val threadBSONHandler = new BSON[MsgThread] {
|
||||||
|
def reads(r: BSON.Reader) = r.strsD("users") match {
|
||||||
|
case List(u1, u2) =>
|
||||||
|
MsgThread(
|
||||||
|
id = r.get[MsgThread.Id]("_id"),
|
||||||
|
user1 = u1,
|
||||||
|
user2 = u2,
|
||||||
|
lastMsg = r.get[Last]("lastMsg")
|
||||||
|
)
|
||||||
|
case x => sys error s"Invalid MsgThread users: $x"
|
||||||
|
}
|
||||||
|
def writes(w: BSON.Writer, t: MsgThread) = $doc(
|
||||||
|
"_id" -> t.id,
|
||||||
|
"users" -> t.users.sorted,
|
||||||
|
"lastMsg" -> t.lastMsg
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
implicit val msgIdBSONHandler = stringAnyValHandler[Msg.Id](_.value, Msg.Id.apply)
|
implicit val msgIdBSONHandler = stringAnyValHandler[Msg.Id](_.value, Msg.Id.apply)
|
||||||
implicit val msgBSONHandler = Macros.handler[Msg]
|
implicit val msgBSONHandler = Macros.handler[Msg]
|
||||||
|
|
|
@ -7,12 +7,16 @@ import lila.common.config._
|
||||||
@Module
|
@Module
|
||||||
final class Env(
|
final class Env(
|
||||||
db: lila.db.Db,
|
db: lila.db.Db,
|
||||||
userRepo: lila.user.UserRepo
|
userRepo: lila.user.UserRepo,
|
||||||
|
lightUserApi: lila.user.LightUserApi,
|
||||||
|
isOnline: lila.socket.IsOnline
|
||||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||||
|
|
||||||
private val colls = wire[MsgColls]
|
private val colls = wire[MsgColls]
|
||||||
|
|
||||||
lazy val api: MsgApi = wire[MsgApi]
|
lazy val api: MsgApi = wire[MsgApi]
|
||||||
|
|
||||||
|
lazy val json = wire[MsgJson]
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MsgColls(db: lila.db.Db) {
|
private class MsgColls(db: lila.db.Db) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ final class MsgApi(
|
||||||
|
|
||||||
import BsonHandlers._
|
import BsonHandlers._
|
||||||
|
|
||||||
def inbox(me: User): Fu[List[MsgThread]] =
|
def threads(me: User): Fu[List[MsgThread]] =
|
||||||
colls.thread.ext
|
colls.thread.ext
|
||||||
.find(
|
.find(
|
||||||
$doc(
|
$doc(
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package lila.msg
|
||||||
|
|
||||||
|
import play.api.libs.json._
|
||||||
|
|
||||||
|
import lila.user.User
|
||||||
|
import lila.common.Json._
|
||||||
|
import lila.common.LightUser
|
||||||
|
|
||||||
|
final class MsgJson(
|
||||||
|
lightUserApi: lila.user.LightUserApi,
|
||||||
|
isOnline: lila.socket.IsOnline
|
||||||
|
) {
|
||||||
|
|
||||||
|
implicit val threadIdWrites: Writes[MsgThread.Id] = Writes.of[String].contramap[MsgThread.Id](_.value)
|
||||||
|
implicit val lastMsgWrites: OWrites[Msg.Last] = Json.writes[Msg.Last]
|
||||||
|
|
||||||
|
def threads(me: User)(threads: List[MsgThread]): Fu[JsArray] =
|
||||||
|
lightUserApi.preloadMany(threads.map(_ other me)) inject Json.arr(
|
||||||
|
threads.map { t =>
|
||||||
|
Json.obj(
|
||||||
|
"id" -> t.id,
|
||||||
|
"contact" -> contact(t other me),
|
||||||
|
"lastMsg" -> t.lastMsg
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private def contact(userId: User.ID): JsObject =
|
||||||
|
LightUser.lightUserWrites
|
||||||
|
.writes(lightUserApi.sync(userId) | LightUser.fallback(userId))
|
||||||
|
.add("online" -> isOnline(userId))
|
||||||
|
}
|
|
@ -5,10 +5,16 @@ import org.joda.time.DateTime
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
case class MsgThread(
|
case class MsgThread(
|
||||||
_id: MsgThread.Id, // random
|
id: MsgThread.Id, // random
|
||||||
users: List[User.ID], // unique
|
user1: User.ID,
|
||||||
|
user2: User.ID,
|
||||||
lastMsg: Msg.Last
|
lastMsg: Msg.Last
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
def users = List(user1, user2)
|
||||||
|
|
||||||
|
def other(user: User) = if (user1 == user.id) user2 else user1
|
||||||
|
}
|
||||||
|
|
||||||
object MsgThread {
|
object MsgThread {
|
||||||
|
|
||||||
|
@ -16,11 +22,12 @@ object MsgThread {
|
||||||
|
|
||||||
def make(
|
def make(
|
||||||
msg: Msg.Last,
|
msg: Msg.Last,
|
||||||
orig: User.ID,
|
user1: User.ID,
|
||||||
dest: User.ID
|
user2: User.ID
|
||||||
): MsgThread = MsgThread(
|
): MsgThread = MsgThread(
|
||||||
_id = Id(ornicar.scalalib.Random nextString 8),
|
id = Id(ornicar.scalalib.Random nextString 8),
|
||||||
users = List(orig, dest).sorted,
|
user1 = user1,
|
||||||
|
user2 = user2,
|
||||||
lastMsg = msg
|
lastMsg = msg
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
"ui/tournamentSchedule",
|
"ui/tournamentSchedule",
|
||||||
"ui/tournamentCalendar",
|
"ui/tournamentCalendar",
|
||||||
"ui/tree",
|
"ui/tree",
|
||||||
|
"ui/msg",
|
||||||
"ui/@build/cssProject",
|
"ui/@build/cssProject",
|
||||||
"ui/@build/jsProject",
|
"ui/@build/jsProject",
|
||||||
"ui/@build/tsProject",
|
"ui/@build/tsProject",
|
||||||
|
|
2
ui/build
2
ui/build
|
@ -15,7 +15,7 @@ mkdir -p public/compiled
|
||||||
|
|
||||||
ts_apps1="common chess"
|
ts_apps1="common chess"
|
||||||
ts_apps2="ceval game tree chat nvui"
|
ts_apps2="ceval game tree chat nvui"
|
||||||
apps="site chat cli challenge notify learn insight editor puzzle round analyse lobby tournament tournamentSchedule tournamentCalendar simul dasher speech palantir serviceWorker"
|
apps="site msg chat cli challenge notify learn insight editor puzzle round analyse lobby tournament tournamentSchedule tournamentCalendar simul dasher speech palantir serviceWorker"
|
||||||
|
|
||||||
if [ $mode == "upgrade" ]; then
|
if [ $mode == "upgrade" ]; then
|
||||||
yarn upgrade --non-interactive
|
yarn upgrade --non-interactive
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
@import '../../../common/css/plugin';
|
||||||
|
@import '../msg';
|
|
@ -0,0 +1,2 @@
|
||||||
|
@import '../../../common/css/theme/dark';
|
||||||
|
@import 'msg';
|
|
@ -0,0 +1,2 @@
|
||||||
|
@import '../../../common/css/theme/light';
|
||||||
|
@import 'msg';
|
|
@ -0,0 +1,2 @@
|
||||||
|
@import '../../../common/css/theme/transp';
|
||||||
|
@import 'msg';
|
|
@ -0,0 +1,2 @@
|
||||||
|
require('@build/tsProject')('LichessMsg', 'lichess.msg', __dirname);
|
||||||
|
require('@build/cssProject')(__dirname);
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "msg",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "lichess.org msg",
|
||||||
|
"author": "Thibault Duplessis",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@build/tsProject": "2.0.0",
|
||||||
|
"@types/jquery": "^2.0",
|
||||||
|
"@types/lichess": "2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"snabbdom": "ornicar/snabbdom#0.7.1-lichess"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { MsgData, MsgOpts } from './interfaces';
|
||||||
|
|
||||||
|
export default class MsgCtrl {
|
||||||
|
|
||||||
|
data: MsgData;
|
||||||
|
trans: Trans;
|
||||||
|
|
||||||
|
constructor(opts: MsgOpts, readonly redraw: () => void) {
|
||||||
|
this.data = opts.data;
|
||||||
|
this.trans = window.lichess.trans(opts.i18n);
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
export interface MsgOpts {
|
||||||
|
data: MsgData;
|
||||||
|
i18n: any;
|
||||||
|
}
|
||||||
|
export interface MsgData {
|
||||||
|
me: User;
|
||||||
|
threads: Thread[];
|
||||||
|
thread?: Thread;
|
||||||
|
}
|
||||||
|
export interface Thread {
|
||||||
|
id: string;
|
||||||
|
contact: User;
|
||||||
|
lastMsg: LastMsg;
|
||||||
|
}
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
title?: string;
|
||||||
|
patron: boolean;
|
||||||
|
online: boolean;
|
||||||
|
}
|
||||||
|
export interface LastMsg extends BaseMsg {
|
||||||
|
read: boolean;
|
||||||
|
}
|
||||||
|
export interface BaseMsg {
|
||||||
|
user: string;
|
||||||
|
text: string;
|
||||||
|
date: number;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import view from './view';
|
||||||
|
|
||||||
|
import { init } from 'snabbdom';
|
||||||
|
import { VNode } from 'snabbdom/vnode'
|
||||||
|
import klass from 'snabbdom/modules/class';
|
||||||
|
import attributes from 'snabbdom/modules/attributes';
|
||||||
|
|
||||||
|
import { MsgOpts } from './interfaces'
|
||||||
|
import MsgCtrl from './ctrl';
|
||||||
|
|
||||||
|
const patch = init([klass, attributes]);
|
||||||
|
|
||||||
|
export function app(element: HTMLElement, opts: MsgOpts) {
|
||||||
|
|
||||||
|
let vnode: VNode, ctrl: MsgCtrl;
|
||||||
|
|
||||||
|
function redraw() {
|
||||||
|
vnode = patch(vnode, view(ctrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl = new MsgCtrl(opts, redraw);
|
||||||
|
|
||||||
|
const blueprint = view(ctrl);
|
||||||
|
element.innerHTML = '';
|
||||||
|
vnode = patch(element, blueprint);
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
};
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { h } from 'snabbdom'
|
||||||
|
import { VNode } from 'snabbdom/vnode'
|
||||||
|
import { Thread, BaseMsg, User } from './interfaces'
|
||||||
|
import MsgCtrl from './ctrl';
|
||||||
|
|
||||||
|
function userIcon(user: User): VNode {
|
||||||
|
return h('i.line' + (user.patron ? '.patron' : ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function userName(user: User): Array<string | VNode> {
|
||||||
|
return user.title ? [
|
||||||
|
h(
|
||||||
|
'span.title',
|
||||||
|
user.title == 'BOT' ? { attrs: {'data-bot': true } } : {},
|
||||||
|
user.title
|
||||||
|
), ' ', user.name
|
||||||
|
] : [user.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDate(msg: BaseMsg) {
|
||||||
|
var date = new Date(msg.date);
|
||||||
|
return h('time.timeago', {
|
||||||
|
attrs: {
|
||||||
|
title: date.toLocaleString(),
|
||||||
|
datetime: msg.date
|
||||||
|
}
|
||||||
|
}, window.lichess.timeago.format(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sideThread(thread: Thread) {
|
||||||
|
return h('div.msg-app__threads__thread', [
|
||||||
|
h('a.msg-app__threads__thread__icon.user-link.ulpt', {
|
||||||
|
class: {
|
||||||
|
online: thread.contact.online,
|
||||||
|
offline: !thread.contact.online
|
||||||
|
},
|
||||||
|
attrs: {
|
||||||
|
href: '/@/' + thread.contact.name
|
||||||
|
}
|
||||||
|
}, [userIcon(thread.contact)]),
|
||||||
|
h('div.msg-app__threads__thread__contact', [
|
||||||
|
h('div.msg-app__threads__thread__head', [
|
||||||
|
h('a.msg-app__threads__thread__name', userName(thread.contact)),
|
||||||
|
h('a.msg-app__threads__thread__date', renderDate(thread.lastMsg))
|
||||||
|
]),
|
||||||
|
h('a.msg-app__threads__thread__msg', thread.lastMsg.text)
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(ctrl: MsgCtrl) {
|
||||||
|
return h('div.msg-app', [
|
||||||
|
h('div.msg-app__side', [
|
||||||
|
h('div.msg-app__side__search', [
|
||||||
|
h('input')
|
||||||
|
]),
|
||||||
|
h('div.msg-app__threads', [
|
||||||
|
ctrl.data.threads.map(sideThread)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
h('div.msg-app__main', [
|
||||||
|
'main'
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue