more study wip
parent
051ac122ed
commit
a80fe9f04b
|
@ -42,7 +42,7 @@ object Study extends LilaController {
|
|||
studyId = id,
|
||||
uid = uid,
|
||||
userId = ctx.userId,
|
||||
owner = ctx.userId.contains(study.ownerId))
|
||||
owner = ctx.userId.exists(study.isOwner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,8 +125,12 @@ private object BSONHandlers {
|
|||
def write(x: StudyMember.Role) = BSONString(x.id)
|
||||
}
|
||||
private implicit val LightUserBSONHandler = Macros.handler[LightUser]
|
||||
private implicit val MemberBSONHandler = Macros.handler[StudyMember]
|
||||
private[study] implicit val MemberBSONHandler = Macros.handler[StudyMember]
|
||||
private[study] implicit val MemberMapBSONHandler = MapDocument.MapHandler[StudyMember]
|
||||
private[study] implicit val MembersBSONHandler = new BSONHandler[BSONDocument, StudyMembers] {
|
||||
def read(b: BSONDocument) = StudyMembers(MemberMapBSONHandler read b)
|
||||
def write(x: StudyMembers) = MemberMapBSONHandler write x.members
|
||||
}
|
||||
|
||||
implicit val StudyBSONHandler = Macros.handler[Study]
|
||||
}
|
||||
|
|
|
@ -94,5 +94,9 @@ object JsonView {
|
|||
}
|
||||
private[study] implicit val memberWrites: Writes[StudyMember] = Json.writes[StudyMember]
|
||||
|
||||
private[study] implicit val membersWrites: Writes[StudyMembers] = Writes[StudyMembers] { m =>
|
||||
Json toJson m.members
|
||||
}
|
||||
|
||||
case class BiData(study: JsObject, analysis: JsObject)
|
||||
}
|
||||
|
|
|
@ -92,5 +92,5 @@ private object Socket {
|
|||
case class MemberPosition(userId: User.ID, position: Position.Ref)
|
||||
case class AddNode(position: Position.Ref, node: Node)
|
||||
case class DelNode(position: Position.Ref)
|
||||
case class ReloadMembers(members: MemberMap)
|
||||
case class ReloadMembers(members: StudyMembers)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ private[study] final class SocketHandler(
|
|||
join = Socket.Join(uid = uid, userId = userId, owner = owner)
|
||||
handler ← Handler(hub, socket, uid, join, userId) {
|
||||
case Socket.Connected(enum, member) =>
|
||||
(controller(socket, studyId, uid, member), enum, member)
|
||||
(controller(socket, studyId, uid, member, owner = owner), enum, member)
|
||||
}
|
||||
} yield handler.some
|
||||
|
||||
|
@ -45,7 +45,8 @@ private[study] final class SocketHandler(
|
|||
socket: ActorRef,
|
||||
studyId: Study.ID,
|
||||
uid: String,
|
||||
member: Socket.Member): Handler.Controller = {
|
||||
member: Socket.Member,
|
||||
owner: Boolean): Handler.Controller = {
|
||||
case ("p", o) => o int "v" foreach { v =>
|
||||
socket ! PingVersion(uid, v)
|
||||
}
|
||||
|
@ -91,12 +92,21 @@ private[study] final class SocketHandler(
|
|||
}
|
||||
}
|
||||
}
|
||||
case ("setRole", o) => AnaRateLimit(uid) {
|
||||
case ("setRole", o) if owner => AnaRateLimit(uid) {
|
||||
reading[SetRole](o) { d =>
|
||||
member.userId foreach { userId =>
|
||||
api.setRole(studyId, userId, d.userId, d.role)
|
||||
}
|
||||
}
|
||||
}
|
||||
case ("invite", o) if owner => for {
|
||||
byUserId <- member.userId
|
||||
username <- o str "d"
|
||||
} api.invite(studyId, byUserId, username)
|
||||
|
||||
case ("kick", o) if owner => for {
|
||||
byUserId <- member.userId
|
||||
userId <- o str "d"
|
||||
} api.kick(studyId, byUserId, userId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import lila.user.User
|
|||
case class Study(
|
||||
_id: Study.ID,
|
||||
chapters: ChapterMap,
|
||||
members: MemberMap,
|
||||
members: StudyMembers,
|
||||
ownerId: User.ID,
|
||||
createdAt: DateTime) {
|
||||
|
||||
|
@ -22,18 +22,14 @@ case class Study(
|
|||
def orderedChapters: List[(Chapter.ID, Chapter)] =
|
||||
chapters.toList.sortBy(_._2.order)
|
||||
|
||||
def firstChapter = orderedChapters.headOption.map(_._2)
|
||||
def firstChapterId = orderedChapters.headOption.map(_._1)
|
||||
def firstChapter = firstChapterId flatMap chapters.get
|
||||
|
||||
def location(chapterId: Chapter.ID): Option[Location] =
|
||||
chapters get chapterId map { Location(this, chapterId, _) }
|
||||
|
||||
def nextChapterOrder: Int = orderedChapters.lastOption.fold(0)(_._2.order) + 1
|
||||
|
||||
def addMember(id: User.ID, member: StudyMember) =
|
||||
copy(members = members + (id -> member))
|
||||
|
||||
def hasMember(id: User.ID) = members contains id
|
||||
|
||||
def owner = members get ownerId
|
||||
|
||||
def isOwner(id: User.ID) = ownerId == id
|
||||
|
@ -52,11 +48,15 @@ object Study {
|
|||
setup: Chapter.Setup) = {
|
||||
val chapterId = Chapter.makeId
|
||||
val chapter = Chapter.make(setup, Node.Root.default, 1)
|
||||
val owner = StudyMember(user, Position.Ref(chapterId, Path.root), StudyMember.Role.Write)
|
||||
val owner = StudyMember(
|
||||
user,
|
||||
Position.Ref(chapterId, Path.root),
|
||||
StudyMember.Role.Write,
|
||||
DateTime.now)
|
||||
Study(
|
||||
_id = scala.util.Random.alphanumeric take idSize mkString,
|
||||
chapters = Map(chapterId -> chapter),
|
||||
members = Map(user.id -> owner),
|
||||
members = StudyMembers(Map(user.id -> owner)),
|
||||
ownerId = user.id,
|
||||
createdAt = DateTime.now)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import akka.actor.ActorRef
|
|||
import chess.format.{ Forsyth, FEN }
|
||||
import lila.hub.actorApi.map.Tell
|
||||
import lila.hub.Sequencer
|
||||
import lila.user.User
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class StudyApi(
|
||||
repo: StudyRepo,
|
||||
|
@ -31,9 +31,12 @@ final class StudyApi(
|
|||
def locationById(id: Location.Ref.ID): Fu[Option[Location]] =
|
||||
(Location.Ref parseId id) ?? locationByRef
|
||||
|
||||
def setMemberPosition(userId: User.ID, ref: Location.Ref, path: Path) =
|
||||
repo.setMemberPosition(userId, ref, path) >>-
|
||||
sendTo(ref.studyId, Socket.MemberPosition(userId, Position.Ref(ref.chapterId, path)))
|
||||
def setMemberPosition(userId: User.ID, ref: Location.Ref, path: Path) = sequenceLocation(ref) { location =>
|
||||
(location.study canWrite userId) ?? {
|
||||
repo.setMemberPosition(userId, ref, path) >>-
|
||||
sendTo(ref.studyId, Socket.MemberPosition(userId, Position.Ref(ref.chapterId, path)))
|
||||
}
|
||||
}
|
||||
|
||||
def addNode(ref: Location.Ref, path: Path, node: Node) = sequenceLocation(ref) { location =>
|
||||
(location.study canWrite node.by) ?? {
|
||||
|
@ -66,17 +69,31 @@ final class StudyApi(
|
|||
}
|
||||
|
||||
def setRole(studyId: Study.ID, byUserId: User.ID, userId: User.ID, roleStr: String) = sequenceStudy(studyId) { study =>
|
||||
(study isOwner byUserId) ?? {
|
||||
val role = StudyMember.Role.byId.getOrElse(roleStr, StudyMember.Role.Read)
|
||||
repo.setRole(study, userId, role) >>-
|
||||
repo.membersById(study.id).foreach {
|
||||
_ foreach { members =>
|
||||
sendTo(study.id, Socket.ReloadMembers(members))
|
||||
}
|
||||
}
|
||||
}
|
||||
val role = StudyMember.Role.byId.getOrElse(roleStr, StudyMember.Role.Read)
|
||||
repo.setRole(study, userId, role) >>- reloadMembers(study)
|
||||
}
|
||||
|
||||
def invite(studyId: Study.ID, byUserId: User.ID, username: String) = sequenceStudy(studyId) { study =>
|
||||
UserRepo.named(username).flatMap {
|
||||
_.filterNot(study.members.contains) ?? { user =>
|
||||
repo.addMember(study, StudyMember.make(study, user))
|
||||
}
|
||||
} >>- reloadMembers(study)
|
||||
}
|
||||
|
||||
def kick(studyId: Study.ID, byUserId: User.ID, userId: User.ID) = sequenceStudy(studyId) { study =>
|
||||
study.members.contains(userId) ?? {
|
||||
repo.removeMember(study, userId)
|
||||
} >>- reloadMembers(study)
|
||||
}
|
||||
|
||||
private def reloadMembers(study: Study) =
|
||||
repo.membersById(study.id).foreach {
|
||||
_ foreach { members =>
|
||||
sendTo(study.id, Socket.ReloadMembers(members))
|
||||
}
|
||||
}
|
||||
|
||||
private def sequenceRef(refId: Location.Ref.ID)(f: Location.Ref => Funit): Funit =
|
||||
Location.Ref.parseId(refId) ?? { ref =>
|
||||
sequence(ref.studyId) {
|
||||
|
|
|
@ -1,17 +1,29 @@
|
|||
package lila.study
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.common.LightUser
|
||||
import lila.user.User
|
||||
|
||||
case class StudyMember(
|
||||
user: LightUser,
|
||||
position: Position.Ref,
|
||||
role: StudyMember.Role) {
|
||||
role: StudyMember.Role,
|
||||
addedAt: DateTime) {
|
||||
|
||||
def canWrite = role == StudyMember.Role.Write
|
||||
}
|
||||
|
||||
object StudyMember {
|
||||
|
||||
type MemberMap = Map[User.ID, StudyMember]
|
||||
|
||||
def make(study: Study, user: User) = StudyMember(
|
||||
user = user.light,
|
||||
position = study.owner.fold(Position.Ref(~study.firstChapterId, Path.root))(_.position),
|
||||
role = Role.Read,
|
||||
addedAt = DateTime.now)
|
||||
|
||||
sealed abstract class Role(val id: String)
|
||||
object Role {
|
||||
case object Read extends Role("r")
|
||||
|
@ -19,3 +31,13 @@ object StudyMember {
|
|||
val byId = List(Read, Write).map { x => x.id -> x }.toMap
|
||||
}
|
||||
}
|
||||
|
||||
case class StudyMembers(members: StudyMember.MemberMap) {
|
||||
|
||||
def +(member: StudyMember) = copy(members = members + (member.user.id -> member))
|
||||
|
||||
def contains(userId: User.ID): Boolean = members contains userId
|
||||
def contains(user: User): Boolean = contains(user.id)
|
||||
|
||||
def get = members.get _
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ private final class StudyRepo(coll: Coll) {
|
|||
|
||||
def insert(s: Study): Funit = coll.insert(s).void
|
||||
|
||||
def membersById(id: Study.ID): Fu[Option[MemberMap]] =
|
||||
coll.primitiveOne[MemberMap]($id(id), "members")
|
||||
def membersById(id: Study.ID): Fu[Option[StudyMembers]] =
|
||||
coll.primitiveOne[StudyMembers]($id(id), "members")
|
||||
|
||||
def setChapter(loc: Location) = coll.update(
|
||||
$id(loc.study.id),
|
||||
|
@ -31,6 +31,18 @@ private final class StudyRepo(coll: Coll) {
|
|||
$set(s"members.$id.position" -> Position.Ref(ref.chapterId, path))
|
||||
).void
|
||||
|
||||
def addMember(study: Study, member: StudyMember): Funit =
|
||||
coll.update(
|
||||
$id(study.id),
|
||||
$set(s"members.${member.user.id}" -> member)
|
||||
).void
|
||||
|
||||
def removeMember(study: Study, userId: User.ID): Funit =
|
||||
coll.update(
|
||||
$id(study.id),
|
||||
$unset(s"members.$userId")
|
||||
).void
|
||||
|
||||
def setRole(study: Study, id: User.ID, role: StudyMember.Role): Funit =
|
||||
coll.update(
|
||||
$id(study.id),
|
||||
|
|
|
@ -6,7 +6,5 @@ package object study extends PackageObject with WithPlay with WithSocket {
|
|||
|
||||
private[study] val logger = lila.log("study")
|
||||
|
||||
private[study]type MemberMap = Map[lila.user.User.ID, lila.study.StudyMember]
|
||||
|
||||
private[study]type ChapterMap = Map[lila.study.Chapter.ID, lila.study.Chapter]
|
||||
}
|
||||
|
|
|
@ -238,6 +238,44 @@ lichess.challengeApp = (function() {
|
|||
return open(confirm('Open in lichess mobile app?') ? 10 : -10);
|
||||
};
|
||||
|
||||
lichess.userAutocomplete = function($input, opts) {
|
||||
opts = opts || {};
|
||||
lichess.loadCss('/assets/stylesheets/autocomplete.css');
|
||||
lichess.loadScript('/assets/javascripts/vendor/typeahead.jquery.min.js').done(function() {
|
||||
$input.typeahead(null, {
|
||||
minLength: 2,
|
||||
hint: true,
|
||||
highlight: false,
|
||||
source: function(query, sync, async) {
|
||||
$.ajax({
|
||||
url: '/player/autocomplete?term=' + query,
|
||||
success: function(res) {
|
||||
// hack to fix typeahead limit bug
|
||||
if (res.length === 10) res.push(null);
|
||||
async(res);
|
||||
}
|
||||
});
|
||||
},
|
||||
limit: 10,
|
||||
templates: {
|
||||
empty: '<div class="empty">No player found</div>',
|
||||
pending: lichess.spinnerHtml,
|
||||
suggestion: function(a) {
|
||||
return '<span class="ulpt" data-href="/@/' + a + '">' + a + '</span>';
|
||||
}
|
||||
}
|
||||
}).bind('typeahead:render', function() {
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
});
|
||||
if (opts.focus) $input.focus();
|
||||
if (opts.onSelect) $input.bind('typeahead:select', function(ev, sel) {
|
||||
opts.onSelect(sel);
|
||||
}).keypress(function(e) {
|
||||
if (e.which == 10 || e.which == 13) opts.onSelect($(this).val());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
lichess.parseFen = function($elem) {
|
||||
if (!$elem || !$elem.jquery) {
|
||||
$elem = $('.parse_fen');
|
||||
|
@ -665,42 +703,14 @@ lichess.challengeApp = (function() {
|
|||
translateTexts();
|
||||
$('body').on('lichess.content_loaded', translateTexts);
|
||||
|
||||
|
||||
var userAutocomplete = function($input) {
|
||||
lichess.loadCss('/assets/stylesheets/autocomplete.css');
|
||||
lichess.loadScript('/assets/javascripts/vendor/typeahead.jquery.min.js').done(function() {
|
||||
$input.typeahead(null, {
|
||||
minLength: 2,
|
||||
hint: true,
|
||||
highlight: false,
|
||||
source: function(query, sync, async) {
|
||||
$.ajax({
|
||||
url: '/player/autocomplete?term=' + query,
|
||||
success: function(res) {
|
||||
// hack to fix typeahead limit bug
|
||||
if (res.length === 10) res.push(null);
|
||||
async(res);
|
||||
}
|
||||
});
|
||||
},
|
||||
limit: 10,
|
||||
templates: {
|
||||
empty: '<div class="empty">No player found</div>',
|
||||
pending: lichess.spinnerHtml,
|
||||
suggestion: function(a) {
|
||||
return '<span class="ulpt" data-href="/@/' + a + '">' + a + '</span>';
|
||||
}
|
||||
}
|
||||
}).bind('typeahead:render', function() {
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
}).focus();
|
||||
});
|
||||
};
|
||||
|
||||
$('input.user-autocomplete').each(function() {
|
||||
if ($(this).attr('autofocus')) userAutocomplete($(this));
|
||||
if ($(this).attr('autofocus')) lichess.userAutocomplete($(this), {
|
||||
focus: 1
|
||||
});
|
||||
else $(this).one('focus', function() {
|
||||
userAutocomplete($(this));
|
||||
lichess.userAutocomplete($(this), {
|
||||
focus: 1
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,27 +1,39 @@
|
|||
.members > div {
|
||||
.members .member {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.members > div > * {
|
||||
.members.ownage .member {
|
||||
cursor: pointer;
|
||||
}
|
||||
.members .member > * {
|
||||
padding: 10px;
|
||||
}
|
||||
.members .follow {
|
||||
.members .action {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
opacity: 0.3;
|
||||
opacity: 0.4;
|
||||
transition: opacity 0.13s;
|
||||
background: #fff;
|
||||
}
|
||||
.members .follow i::before {
|
||||
.members .action.empty {
|
||||
background: #ddd;
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
}
|
||||
body.dark .members .action.empty {
|
||||
background: #333;
|
||||
}
|
||||
.members .action i::before {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.members > div:hover .follow {
|
||||
.members > div:hover .action {
|
||||
opacity: 1;
|
||||
}
|
||||
.members .following .follow {
|
||||
.members .following .action {
|
||||
background: #3893E8;
|
||||
color: #fff;
|
||||
opacity: 1;
|
||||
|
@ -29,3 +41,10 @@
|
|||
.members.ownage .role {
|
||||
cursor: pointer;
|
||||
}
|
||||
.members .invite input {
|
||||
width: 213px;
|
||||
border: none;
|
||||
border-top: 1px solid #ccc;
|
||||
background: white;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
|
|
@ -21,15 +21,16 @@ module.exports = {
|
|||
|
||||
var vm = {
|
||||
position: meOrOwner().position,
|
||||
follow: null
|
||||
follow: null, // which user is being followed by us
|
||||
memberConfig: null // which user is being configured by us
|
||||
};
|
||||
|
||||
function joiners() {
|
||||
var members = [];
|
||||
for (var id in data.members)
|
||||
if (id !== data.ownerId)
|
||||
members.push(data.members[id]);
|
||||
return members;
|
||||
function orderedMembers() {
|
||||
return Object.keys(data.members).map(function(id) {
|
||||
return data.members[id];
|
||||
}).sort(function(a, b) {
|
||||
return a.addedAt > b.addedAt;
|
||||
});
|
||||
}
|
||||
|
||||
function addChapterId(data) {
|
||||
|
@ -54,6 +55,10 @@ module.exports = {
|
|||
}
|
||||
follow(data.ownerId);
|
||||
|
||||
function invite(username) {
|
||||
if (ownage) send("invite", username);
|
||||
}
|
||||
|
||||
return {
|
||||
data: data,
|
||||
vm: vm,
|
||||
|
@ -77,9 +82,7 @@ module.exports = {
|
|||
path: path
|
||||
}));
|
||||
},
|
||||
orderedMembers: function() {
|
||||
return [owner()].concat(joiners());
|
||||
},
|
||||
orderedMembers: orderedMembers,
|
||||
follow: follow,
|
||||
toggleRole: function(userId) {
|
||||
if (!ownage) return;
|
||||
|
@ -90,6 +93,12 @@ module.exports = {
|
|||
role: next
|
||||
});
|
||||
},
|
||||
invite: function(username) {
|
||||
send("invite", username);
|
||||
},
|
||||
kick: function(userId) {
|
||||
send("kick", userId);
|
||||
},
|
||||
socketHandlers: {
|
||||
mpos: function(d) {
|
||||
updateMember(d.u, function(m) {
|
||||
|
|
|
@ -1,33 +1,90 @@
|
|||
var m = require('mithril');
|
||||
var partial = require('chessground').util.partial;
|
||||
var classSet = require('chessground').util.classSet;
|
||||
|
||||
module.exports = function(ctrl) {
|
||||
|
||||
var ownage = ctrl.userId === ctrl.data.ownerId;
|
||||
|
||||
var username = function(member) {
|
||||
return m('span.user_link.ulpt', {
|
||||
'data-href': '/@/' + member.user.name
|
||||
}, member.user.name);
|
||||
};
|
||||
|
||||
var roleToggle = function(member) {
|
||||
return m('span.role.hint--top', {
|
||||
'data-hint': member.role === 'r' ? 'Can read' : 'Can write',
|
||||
onclick: ownage ? function() {
|
||||
ctrl.toggleRole(member.user.id);
|
||||
} : $.noop
|
||||
}, member.role.toUpperCase());
|
||||
};
|
||||
|
||||
var rightButton = function(member) {
|
||||
if (member.user.id === ctrl.userId) return m('span.action.empty');
|
||||
if (member.role !== 'w') return m('span.action.empty');
|
||||
return m('span.action.follow.hint--top', {
|
||||
'data-hint': 'Follow/Unfollow',
|
||||
onclick: function(e) {
|
||||
e.stopPropagation();
|
||||
ctrl.follow(member.user.id);
|
||||
}
|
||||
}, m('i', {
|
||||
'data-icon': 'v'
|
||||
}));
|
||||
};
|
||||
|
||||
var invite = function() {
|
||||
return m('div.invite', [
|
||||
m('input', {
|
||||
config: function(el, isUpdate) {
|
||||
if (isUpdate) return;
|
||||
lichess.userAutocomplete($(el), {
|
||||
onSelect: function(v) {
|
||||
ctrl.invite(v);
|
||||
}
|
||||
});
|
||||
},
|
||||
class: 'add_member',
|
||||
placeholder: 'Invite someone'
|
||||
})
|
||||
]);
|
||||
};
|
||||
|
||||
var memberConfig = function(member) {
|
||||
return m('div.config', [
|
||||
m('div', 'Contributor'),
|
||||
m('div', m('a.button[data-icon=L]', {
|
||||
onclick: function() {
|
||||
ctrl.kick(member.user.id);
|
||||
}
|
||||
}, 'Kick from this study'))
|
||||
]);
|
||||
};
|
||||
|
||||
return m('div', {
|
||||
class: 'members' + (ownage ? ' ownage' : '')
|
||||
},
|
||||
ctrl.orderedMembers().map(function(member) {
|
||||
return m('div', {
|
||||
class: (ctrl.vm.follow === member.user.id ? 'following' : '')
|
||||
}, [
|
||||
m('span.ulpt', {
|
||||
'data-href': '/@/' + member.user.name
|
||||
}, member.user.name),
|
||||
m('span.role.hint--top', {
|
||||
'data-hint': member.role === 'r' ? 'Can read' : 'Can write',
|
||||
onclick: ownage ? function() {
|
||||
ctrl.toggleRole(member.user.id);
|
||||
} : $.noop
|
||||
}, member.role.toUpperCase()), (member.role !== 'w' || member.user.id === ctrl.userId) ? m('span.follow.empty') : m('span.follow.hint--top', {
|
||||
'data-hint': 'Follow/Unfollow',
|
||||
onclick: function() {
|
||||
ctrl.follow(member.user.id);
|
||||
}
|
||||
}, m('i', {
|
||||
'data-icon': 'v'
|
||||
}))
|
||||
])
|
||||
}));
|
||||
var confing = ctrl.vm.memberConfig === member.user.id;
|
||||
var attrs = {
|
||||
class: classSet({
|
||||
member: true,
|
||||
following: ctrl.vm.follow === member.user.id,
|
||||
confing: confing
|
||||
})
|
||||
};
|
||||
if (ownage) attrs.onclick = function() {
|
||||
ctrl.vm.memberConfig = confing ? null : member.user.id;
|
||||
}
|
||||
return [
|
||||
m('div', attrs, [
|
||||
username(member),
|
||||
rightButton(member),
|
||||
]),
|
||||
confing ? memberConfig(member) : null
|
||||
];
|
||||
}),
|
||||
ownage ? invite() : null);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue