safer mod notes
This commit is contained in:
parent
ee2d375985
commit
2563a13e98
|
@ -404,7 +404,10 @@ final class User(
|
||||||
_ ?? { user =>
|
_ ?? { user =>
|
||||||
env.user.forms.note.bindFromRequest.fold(
|
env.user.forms.note.bindFromRequest.fold(
|
||||||
e => err(e)(user),
|
e => err(e)(user),
|
||||||
data => env.user.noteApi.write(user, data.text, me, data.mod && isGranted(_.ModNote, me)) inject suc
|
data => {
|
||||||
|
val isMod = data.mod && isGranted(_.ModNote, me)
|
||||||
|
env.user.noteApi.write(user, data.text, me, isMod, isMod && ~data.dox)
|
||||||
|
} inject suc
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,22 +99,26 @@ trait FormHelper { self: I18nHelper =>
|
||||||
)(
|
)(
|
||||||
div(
|
div(
|
||||||
span(cls := "form-check-input")(
|
span(cls := "form-check-input")(
|
||||||
st.input(
|
cmnToggle(id(field), field.name, field.value.has("true"), disabled)
|
||||||
st.id := id(field),
|
|
||||||
name := field.name,
|
|
||||||
value := "true",
|
|
||||||
tpe := "checkbox",
|
|
||||||
cls := "form-control cmn-toggle",
|
|
||||||
field.value.has("true") option checked,
|
|
||||||
disabled option st.disabled
|
|
||||||
),
|
|
||||||
label(`for` := id(field))
|
|
||||||
),
|
),
|
||||||
groupLabel(field)(labelContent)
|
groupLabel(field)(labelContent)
|
||||||
),
|
),
|
||||||
help map { helper(_) }
|
help map { helper(_) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def cmnToggle(fieldId: String, fieldName: String, checked: Boolean, disabled: Boolean = false, value: String = "true") = frag(
|
||||||
|
st.input(
|
||||||
|
st.id := fieldId,
|
||||||
|
name := fieldName,
|
||||||
|
st.value := value,
|
||||||
|
tpe := "checkbox",
|
||||||
|
cls := "form-control cmn-toggle",
|
||||||
|
checked option st.checked,
|
||||||
|
disabled option st.disabled
|
||||||
|
),
|
||||||
|
label(`for` := fieldId)
|
||||||
|
)
|
||||||
|
|
||||||
def select(
|
def select(
|
||||||
field: Field,
|
field: Field,
|
||||||
options: Iterable[(Any, String)],
|
options: Iterable[(Any, String)],
|
||||||
|
|
|
@ -206,8 +206,7 @@ object inquiry {
|
||||||
div(cls := "actions close")(
|
div(cls := "actions close")(
|
||||||
span(cls := "switcher", title := "Automatically open next report")(
|
span(cls := "switcher", title := "Automatically open next report")(
|
||||||
span(cls := "switch")(
|
span(cls := "switch")(
|
||||||
input(id := "auto-next", cls := "cmn-toggle", tpe := "checkbox", checked),
|
form3.cmnToggle("auto-next", "auto-next", true)
|
||||||
label(`for` := "auto-next")
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
postForm(
|
postForm(
|
||||||
|
|
|
@ -41,17 +41,7 @@ object permissions {
|
||||||
s"Granted by package: $p"
|
s"Granted by package: $p"
|
||||||
}
|
}
|
||||||
})(
|
})(
|
||||||
span(
|
span(form3.cmnToggle(id, "permissions[]", checked = u.roles.contains(perm.dbKey), value = perm.dbKey)),
|
||||||
input(
|
|
||||||
st.id := id,
|
|
||||||
cls := "cmn-toggle",
|
|
||||||
tpe := "checkbox",
|
|
||||||
name := "permissions[]",
|
|
||||||
value := perm.dbKey,
|
|
||||||
u.roles.contains(perm.dbKey) option checked
|
|
||||||
),
|
|
||||||
label(`for` := id)
|
|
||||||
),
|
|
||||||
label(`for` := id)(perm.name)
|
label(`for` := id)(perm.name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,17 +39,7 @@ object create {
|
||||||
}
|
}
|
||||||
val id = s"oauth-scope-${scope.key.replace(":", "_")}"
|
val id = s"oauth-scope-${scope.key.replace(":", "_")}"
|
||||||
div(
|
div(
|
||||||
span(
|
span(form3.cmnToggle(id, s"${form("scopes").name}[]", value = scope.key, checked = false, disabled = disabled)),
|
||||||
input(
|
|
||||||
st.id := id,
|
|
||||||
cls := "cmn-toggle",
|
|
||||||
tpe := "checkbox",
|
|
||||||
name := s"${form("scopes").name}[]",
|
|
||||||
value := scope.key,
|
|
||||||
disabled option st.disabled
|
|
||||||
),
|
|
||||||
label(`for` := id)
|
|
||||||
),
|
|
||||||
label(`for` := id, st.title := disabled.option("You already have played games!"))(scope.name)
|
label(`for` := id, st.title := disabled.option("You already have played games!"))(scope.name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,10 +92,7 @@ object bits {
|
||||||
"round-toggle-autoswitch" |> { id =>
|
"round-toggle-autoswitch" |> { id =>
|
||||||
span(cls := "move-on switcher", st.title := trans.automaticallyProceedToNextGameAfterMoving.txt())(
|
span(cls := "move-on switcher", st.title := trans.automaticallyProceedToNextGameAfterMoving.txt())(
|
||||||
label(`for` := id)(trans.autoSwitch()),
|
label(`for` := id)(trans.autoSwitch()),
|
||||||
span(cls := "switch")(
|
span(cls := "switch")(form3.cmnToggle(id, id, false))
|
||||||
input(st.id := id, cls := "cmn-toggle", tpe := "checkbox"),
|
|
||||||
label(`for` := id)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -175,15 +175,7 @@ private object bits {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
td(cls := "single")(
|
td(cls := "single")(
|
||||||
st.input(
|
form3.cmnToggle(form3.id(field), field.name, checked = field.value.has("1"), value = "1")
|
||||||
tpe := "checkbox",
|
|
||||||
cls := "cmn-toggle",
|
|
||||||
id := form3.id(field),
|
|
||||||
name := field.name,
|
|
||||||
value := "1",
|
|
||||||
field.value.has("1") option checked
|
|
||||||
),
|
|
||||||
label(`for` := form3.id(field))
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -508,7 +508,7 @@ object mod {
|
||||||
othersWithEmail.others.map {
|
othersWithEmail.others.map {
|
||||||
case lila.security.UserSpy.OtherUser(o, byIp, byFp) =>
|
case lila.security.UserSpy.OtherUser(o, byIp, byFp) =>
|
||||||
val dox = isGranted(_.Doxing) || (o.lameOrAlt && !o.hasTitle)
|
val dox = isGranted(_.Doxing) || (o.lameOrAlt && !o.hasTitle)
|
||||||
val myNotes = notes.filter(_.to == o.id)
|
val userNotes = notes.filter(n => n.to == o.id && (ctx.me.exists(n.isFrom) || isGranted(_.Doxing)))
|
||||||
tr(o == u option (cls := "same"))(
|
tr(o == u option (cls := "same"))(
|
||||||
if (dox || o == u) td(dataSort := o.id)(userLink(o, withBestRating = true, params = "?mod"))
|
if (dox || o == u) td(dataSort := o.id)(userLink(o, withBestRating = true, params = "?mod"))
|
||||||
else td,
|
else td,
|
||||||
|
@ -527,14 +527,14 @@ object mod {
|
||||||
markTd(o.marks.ipban ?? 1, ipban(cls := "is-red")),
|
markTd(o.marks.ipban ?? 1, ipban(cls := "is-red")),
|
||||||
markTd(o.disabled ?? 1, closed),
|
markTd(o.disabled ?? 1, closed),
|
||||||
markTd(o.marks.reportban ?? 1, reportban),
|
markTd(o.marks.reportban ?? 1, reportban),
|
||||||
myNotes.nonEmpty option {
|
userNotes.nonEmpty option {
|
||||||
td(dataSort := myNotes.size)(
|
td(dataSort := userNotes.size)(
|
||||||
a(href := s"${routes.User.show(o.username)}?notes")(
|
a(href := s"${routes.User.show(o.username)}?notes")(
|
||||||
notesText(
|
notesText(
|
||||||
title := s"Notes from ${myNotes.map(_.from).map(usernameOrId).mkString(", ")}",
|
title := s"Notes from ${userNotes.map(_.from).map(usernameOrId).mkString(", ")}",
|
||||||
cls := "is-green"
|
cls := "is-green"
|
||||||
),
|
),
|
||||||
myNotes.size
|
userNotes.size
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} getOrElse td(dataSort := 0),
|
} getOrElse td(dataSort := 0),
|
||||||
|
|
|
@ -128,29 +128,32 @@ object header {
|
||||||
postForm(action := s"${routes.User.writeNote(u.username)}?note")(
|
postForm(action := s"${routes.User.writeNote(u.username)}?note")(
|
||||||
textarea(
|
textarea(
|
||||||
name := "text",
|
name := "text",
|
||||||
placeholder := "Write a note about this user only you and your friends can read"
|
placeholder := "Write a private note about this user"
|
||||||
),
|
),
|
||||||
|
if (isGranted(_.ModNote)) div(cls := "mod-note")(
|
||||||
submitButton(cls := "button")(trans.send()),
|
submitButton(cls := "button")(trans.send()),
|
||||||
if (isGranted(_.ModNote))
|
div(
|
||||||
label(style := "margin-left: 1em;")(
|
div(form3.cmnToggle("note-mod", "mod", true)),
|
||||||
input(
|
label(`for` := "note-mod")("For moderators only")
|
||||||
tpe := "checkbox",
|
|
||||||
name := "mod",
|
|
||||||
checked,
|
|
||||||
value := "true",
|
|
||||||
style := "vertical-align: middle;"
|
|
||||||
),
|
),
|
||||||
"For moderators only"
|
isGranted(_.Doxing) option div(
|
||||||
|
div(form3.cmnToggle("note-dox", "dox", false)),
|
||||||
|
label(`for` := "note-dox")("Doxing info")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else frag(
|
||||||
|
input(tpe := "hidden", name := "mod", value := "false"),
|
||||||
|
submitButton(cls := "button")(trans.send()),
|
||||||
)
|
)
|
||||||
else input(tpe := "hidden", name := "mod", value := "false")
|
|
||||||
),
|
),
|
||||||
social.notes.isEmpty option div("No note yet"),
|
social.notes.isEmpty option div("No note yet"),
|
||||||
social.notes.map { note =>
|
social.notes.filter(n => ctx.me.exists(n.isFrom) || isGranted(_.Doxing)).map { note =>
|
||||||
div(cls := "note")(
|
div(cls := "note")(
|
||||||
p(cls := "note__text")(richText(note.text)),
|
p(cls := "note__text")(richText(note.text)),
|
||||||
p(cls := "note__meta")(
|
p(cls := "note__meta")(
|
||||||
userIdLink(note.from.some),
|
userIdLink(note.from.some),
|
||||||
br,
|
br,
|
||||||
|
note.dox option "dox ",
|
||||||
momentFromNow(note.date),
|
momentFromNow(note.date),
|
||||||
(ctx.me.exists(note.isFrom) && !note.mod) option frag(
|
(ctx.me.exists(note.isFrom) && !note.mod) option frag(
|
||||||
br,
|
br,
|
||||||
|
|
|
@ -83,7 +83,8 @@ object Permission {
|
||||||
SeeInsight,
|
SeeInsight,
|
||||||
UserSearch,
|
UserSearch,
|
||||||
RemoveRanking,
|
RemoveRanking,
|
||||||
ModMessage
|
ModMessage,
|
||||||
|
ModNote
|
||||||
),
|
),
|
||||||
"Hunter"
|
"Hunter"
|
||||||
)
|
)
|
||||||
|
@ -92,7 +93,6 @@ object Permission {
|
||||||
extends Permission(
|
extends Permission(
|
||||||
"DOXING",
|
"DOXING",
|
||||||
List(
|
List(
|
||||||
ModNote,
|
|
||||||
ViewIpPrint
|
ViewIpPrint
|
||||||
),
|
),
|
||||||
"Doxing"
|
"Doxing"
|
||||||
|
|
|
@ -11,11 +11,12 @@ final class DataForm(authenticator: Authenticator) {
|
||||||
val note = Form(
|
val note = Form(
|
||||||
mapping(
|
mapping(
|
||||||
"text" -> text(minLength = 3, maxLength = 2000),
|
"text" -> text(minLength = 3, maxLength = 2000),
|
||||||
"mod" -> boolean
|
"mod" -> boolean,
|
||||||
|
"dox" -> optional(boolean)
|
||||||
)(NoteData.apply)(NoteData.unapply)
|
)(NoteData.apply)(NoteData.unapply)
|
||||||
)
|
)
|
||||||
|
|
||||||
case class NoteData(text: String, mod: Boolean)
|
case class NoteData(text: String, mod: Boolean, dox: Option[Boolean])
|
||||||
|
|
||||||
def username(user: User): Form[String] =
|
def username(user: User): Form[String] =
|
||||||
Form(
|
Form(
|
||||||
|
|
|
@ -9,6 +9,7 @@ case class Note(
|
||||||
to: User.ID,
|
to: User.ID,
|
||||||
text: String,
|
text: String,
|
||||||
mod: Boolean,
|
mod: Boolean,
|
||||||
|
dox: Boolean,
|
||||||
date: DateTime
|
date: DateTime
|
||||||
) {
|
) {
|
||||||
def userIds = List(from, to)
|
def userIds = List(from, to)
|
||||||
|
@ -50,7 +51,7 @@ final class NoteApi(
|
||||||
.sort($sort desc "date")
|
.sort($sort desc "date")
|
||||||
.list[Note](50)
|
.list[Note](50)
|
||||||
|
|
||||||
def write(to: User, text: String, from: User, modOnly: Boolean) = {
|
def write(to: User, text: String, from: User, modOnly: Boolean, dox: Boolean) = {
|
||||||
|
|
||||||
val note = Note(
|
val note = Note(
|
||||||
_id = ornicar.scalalib.Random nextString 8,
|
_id = ornicar.scalalib.Random nextString 8,
|
||||||
|
@ -58,6 +59,7 @@ final class NoteApi(
|
||||||
to = to.id,
|
to = to.id,
|
||||||
text = text,
|
text = text,
|
||||||
mod = modOnly,
|
mod = modOnly,
|
||||||
|
dox = modOnly && dox,
|
||||||
date = DateTime.now
|
date = DateTime.now
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ final class NoteApi(
|
||||||
def lichessWrite(to: User, text: String) =
|
def lichessWrite(to: User, text: String) =
|
||||||
userRepo.lichess flatMap {
|
userRepo.lichess flatMap {
|
||||||
_ ?? {
|
_ ?? {
|
||||||
write(to, text, _, true)
|
write(to, text, _, true, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
@import '../../../common/css/component/hover-text';
|
@import '../../../common/css/component/hover-text';
|
||||||
@import '../../../common/css/component/crosstable';
|
@import '../../../common/css/component/crosstable';
|
||||||
@import '../../../common/css/component/flash';
|
@import '../../../common/css/component/flash';
|
||||||
|
@import '../../../common/css/form/cmn-toggle';
|
||||||
@import '../../../common/css/base/scrollbar';
|
@import '../../../common/css/base/scrollbar';
|
||||||
@import '../../../game/css/row';
|
@import '../../../game/css/row';
|
||||||
@import '../user/show';
|
@import '../user/show';
|
||||||
|
|
|
@ -21,4 +21,15 @@
|
||||||
min-height: 2.7em;
|
min-height: 2.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.mod-note {
|
||||||
|
@extend %flex-center;
|
||||||
|
> div {
|
||||||
|
@extend %flex-center;
|
||||||
|
margin-left: 1.5em;
|
||||||
|
> label {
|
||||||
|
margin-left: .5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue