From 2563a13e98427a548393d02da1c4c49ecd2b13e9 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Tue, 3 Mar 2020 15:22:48 -0600 Subject: [PATCH] safer mod notes --- app/controllers/User.scala | 5 +++- app/templating/FormHelper.scala | 24 ++++++++++------- app/views/mod/inquiry.scala | 3 +-- app/views/mod/permissions.scala | 12 +-------- app/views/oAuth/token/create.scala | 12 +-------- app/views/round/bits.scala | 5 +--- app/views/search/bits.scala | 10 +------ app/views/user/mod.scala | 10 +++---- app/views/user/show/header.scala | 31 ++++++++++++---------- modules/security/src/main/Permission.scala | 4 +-- modules/user/src/main/DataForm.scala | 5 ++-- modules/user/src/main/NoteApi.scala | 6 +++-- ui/site/css/build/_user.show.scss | 1 + ui/site/css/user/_note-zone.scss | 11 ++++++++ 14 files changed, 66 insertions(+), 73 deletions(-) diff --git a/app/controllers/User.scala b/app/controllers/User.scala index ab23dab6c1..10efef8207 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -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 ) } } diff --git a/app/templating/FormHelper.scala b/app/templating/FormHelper.scala index 03693477ac..f9e35b9972 100644 --- a/app/templating/FormHelper.scala +++ b/app/templating/FormHelper.scala @@ -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)], diff --git a/app/views/mod/inquiry.scala b/app/views/mod/inquiry.scala index 38447f3105..6c19ff0aa9 100644 --- a/app/views/mod/inquiry.scala +++ b/app/views/mod/inquiry.scala @@ -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( diff --git a/app/views/mod/permissions.scala b/app/views/mod/permissions.scala index 60e656cd01..767bc2bd7d 100644 --- a/app/views/mod/permissions.scala +++ b/app/views/mod/permissions.scala @@ -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) ) } diff --git a/app/views/oAuth/token/create.scala b/app/views/oAuth/token/create.scala index 579179798d..78f0a49a9d 100644 --- a/app/views/oAuth/token/create.scala +++ b/app/views/oAuth/token/create.scala @@ -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) ) } diff --git a/app/views/round/bits.scala b/app/views/round/bits.scala index 72a47cbc91..1061d0d3bb 100644 --- a/app/views/round/bits.scala +++ b/app/views/round/bits.scala @@ -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)) ) } ), diff --git a/app/views/search/bits.scala b/app/views/search/bits.scala index a836db6b1c..aad591fc11 100644 --- a/app/views/search/bits.scala +++ b/app/views/search/bits.scala @@ -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") ) ) } diff --git a/app/views/user/mod.scala b/app/views/user/mod.scala index 15a2f51935..92001ad370 100644 --- a/app/views/user/mod.scala +++ b/app/views/user/mod.scala @@ -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), diff --git a/app/views/user/show/header.scala b/app/views/user/show/header.scala index 74a589019d..758539bfa8 100644 --- a/app/views/user/show/header.scala +++ b/app/views/user/show/header.scala @@ -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, diff --git a/modules/security/src/main/Permission.scala b/modules/security/src/main/Permission.scala index 18c4b4b705..ed774ed8dc 100644 --- a/modules/security/src/main/Permission.scala +++ b/modules/security/src/main/Permission.scala @@ -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" diff --git a/modules/user/src/main/DataForm.scala b/modules/user/src/main/DataForm.scala index 6b4ef17741..f131085786 100644 --- a/modules/user/src/main/DataForm.scala +++ b/modules/user/src/main/DataForm.scala @@ -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( diff --git a/modules/user/src/main/NoteApi.scala b/modules/user/src/main/NoteApi.scala index e928fcba84..0756c16dae 100644 --- a/modules/user/src/main/NoteApi.scala +++ b/modules/user/src/main/NoteApi.scala @@ -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) } } diff --git a/ui/site/css/build/_user.show.scss b/ui/site/css/build/_user.show.scss index 68797e1641..d0a7310bf7 100644 --- a/ui/site/css/build/_user.show.scss +++ b/ui/site/css/build/_user.show.scss @@ -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'; diff --git a/ui/site/css/user/_note-zone.scss b/ui/site/css/user/_note-zone.scss index 2da727da23..4992867ff0 100644 --- a/ui/site/css/user/_note-zone.scss +++ b/ui/site/css/user/_note-zone.scss @@ -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; + } + } + } }