safer mod notes

pull/6119/head
Thibault Duplessis 2020-03-03 15:22:48 -06:00
parent ee2d375985
commit 2563a13e98
14 changed files with 66 additions and 73 deletions

View File

@ -404,7 +404,10 @@ final class User(
_ ?? { user =>
env.user.forms.note.bindFromRequest.fold(
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
)
}
}

View File

@ -99,22 +99,26 @@ trait FormHelper { self: I18nHelper =>
)(
div(
span(cls := "form-check-input")(
st.input(
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))
cmnToggle(id(field), field.name, field.value.has("true"), disabled)
),
groupLabel(field)(labelContent)
),
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(
field: Field,
options: Iterable[(Any, String)],

View File

@ -206,8 +206,7 @@ object inquiry {
div(cls := "actions close")(
span(cls := "switcher", title := "Automatically open next report")(
span(cls := "switch")(
input(id := "auto-next", cls := "cmn-toggle", tpe := "checkbox", checked),
label(`for` := "auto-next")
form3.cmnToggle("auto-next", "auto-next", true)
)
),
postForm(

View File

@ -41,17 +41,7 @@ object permissions {
s"Granted by package: $p"
}
})(
span(
input(
st.id := id,
cls := "cmn-toggle",
tpe := "checkbox",
name := "permissions[]",
value := perm.dbKey,
u.roles.contains(perm.dbKey) option checked
),
label(`for` := id)
),
span(form3.cmnToggle(id, "permissions[]", checked = u.roles.contains(perm.dbKey), value = perm.dbKey)),
label(`for` := id)(perm.name)
)
}

View File

@ -39,17 +39,7 @@ object create {
}
val id = s"oauth-scope-${scope.key.replace(":", "_")}"
div(
span(
input(
st.id := id,
cls := "cmn-toggle",
tpe := "checkbox",
name := s"${form("scopes").name}[]",
value := scope.key,
disabled option st.disabled
),
label(`for` := id)
),
span(form3.cmnToggle(id, s"${form("scopes").name}[]", value = scope.key, checked = false, disabled = disabled)),
label(`for` := id, st.title := disabled.option("You already have played games!"))(scope.name)
)
}

View File

@ -92,10 +92,7 @@ object bits {
"round-toggle-autoswitch" |> { id =>
span(cls := "move-on switcher", st.title := trans.automaticallyProceedToNextGameAfterMoving.txt())(
label(`for` := id)(trans.autoSwitch()),
span(cls := "switch")(
input(st.id := id, cls := "cmn-toggle", tpe := "checkbox"),
label(`for` := id)
)
span(cls := "switch")(form3.cmnToggle(id, id, false))
)
}
),

View File

@ -175,15 +175,7 @@ private object bits {
)
),
td(cls := "single")(
st.input(
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))
form3.cmnToggle(form3.id(field), field.name, checked = field.value.has("1"), value = "1")
)
)
}

View File

@ -508,7 +508,7 @@ object mod {
othersWithEmail.others.map {
case lila.security.UserSpy.OtherUser(o, byIp, byFp) =>
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"))(
if (dox || o == u) td(dataSort := o.id)(userLink(o, withBestRating = true, params = "?mod"))
else td,
@ -527,14 +527,14 @@ object mod {
markTd(o.marks.ipban ?? 1, ipban(cls := "is-red")),
markTd(o.disabled ?? 1, closed),
markTd(o.marks.reportban ?? 1, reportban),
myNotes.nonEmpty option {
td(dataSort := myNotes.size)(
userNotes.nonEmpty option {
td(dataSort := userNotes.size)(
a(href := s"${routes.User.show(o.username)}?notes")(
notesText(
title := s"Notes from ${myNotes.map(_.from).map(usernameOrId).mkString(", ")}",
title := s"Notes from ${userNotes.map(_.from).map(usernameOrId).mkString(", ")}",
cls := "is-green"
),
myNotes.size
userNotes.size
)
)
} getOrElse td(dataSort := 0),

View File

@ -128,29 +128,32 @@ object header {
postForm(action := s"${routes.User.writeNote(u.username)}?note")(
textarea(
name := "text",
placeholder := "Write a note about this user only you and your friends can read"
placeholder := "Write a private note about this user"
),
submitButton(cls := "button")(trans.send()),
if (isGranted(_.ModNote))
label(style := "margin-left: 1em;")(
input(
tpe := "checkbox",
name := "mod",
checked,
value := "true",
style := "vertical-align: middle;"
),
"For moderators only"
if (isGranted(_.ModNote)) div(cls := "mod-note")(
submitButton(cls := "button")(trans.send()),
div(
div(form3.cmnToggle("note-mod", "mod", true)),
label(`for` := "note-mod")("For moderators only")
),
isGranted(_.Doxing) option div(
div(form3.cmnToggle("note-dox", "dox", false)),
label(`for` := "note-dox")("Doxing info")
)
else input(tpe := "hidden", name := "mod", value := "false")
)
else frag(
input(tpe := "hidden", name := "mod", value := "false"),
submitButton(cls := "button")(trans.send()),
)
),
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")(
p(cls := "note__text")(richText(note.text)),
p(cls := "note__meta")(
userIdLink(note.from.some),
br,
note.dox option "dox ",
momentFromNow(note.date),
(ctx.me.exists(note.isFrom) && !note.mod) option frag(
br,

View File

@ -83,7 +83,8 @@ object Permission {
SeeInsight,
UserSearch,
RemoveRanking,
ModMessage
ModMessage,
ModNote
),
"Hunter"
)
@ -92,7 +93,6 @@ object Permission {
extends Permission(
"DOXING",
List(
ModNote,
ViewIpPrint
),
"Doxing"

View File

@ -11,11 +11,12 @@ final class DataForm(authenticator: Authenticator) {
val note = Form(
mapping(
"text" -> text(minLength = 3, maxLength = 2000),
"mod" -> boolean
"mod" -> boolean,
"dox" -> optional(boolean)
)(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] =
Form(

View File

@ -9,6 +9,7 @@ case class Note(
to: User.ID,
text: String,
mod: Boolean,
dox: Boolean,
date: DateTime
) {
def userIds = List(from, to)
@ -50,7 +51,7 @@ final class NoteApi(
.sort($sort desc "date")
.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(
_id = ornicar.scalalib.Random nextString 8,
@ -58,6 +59,7 @@ final class NoteApi(
to = to.id,
text = text,
mod = modOnly,
dox = modOnly && dox,
date = DateTime.now
)
@ -85,7 +87,7 @@ final class NoteApi(
def lichessWrite(to: User, text: String) =
userRepo.lichess flatMap {
_ ?? {
write(to, text, _, true)
write(to, text, _, true, false)
}
}

View File

@ -4,6 +4,7 @@
@import '../../../common/css/component/hover-text';
@import '../../../common/css/component/crosstable';
@import '../../../common/css/component/flash';
@import '../../../common/css/form/cmn-toggle';
@import '../../../common/css/base/scrollbar';
@import '../../../game/css/row';
@import '../user/show';

View File

@ -21,4 +21,15 @@
min-height: 2.7em;
}
}
.mod-note {
@extend %flex-center;
> div {
@extend %flex-center;
margin-left: 1.5em;
> label {
margin-left: .5em;
cursor: pointer;
}
}
}
}