user blog WIP

ublog
Thibault Duplessis 2021-08-30 10:05:36 +02:00
parent af4dd08768
commit c1f94ff736
8 changed files with 117 additions and 31 deletions

View File

@ -8,7 +8,7 @@ import lila.ublog.UblogPost
final class Ublog(env: Env) extends LilaController(env) {
import views.html.ublog.bits.{ url => urlOf }
import views.html.ublog.bits.urlOf
def index(username: String, page: Int) = Open { implicit ctx =>
OptionFuOk(env.user.repo named username) { user =>
@ -18,11 +18,19 @@ final class Ublog(env: Env) extends LilaController(env) {
}
}
def drafts(username: String, page: Int) = Auth { implicit ctx => me =>
if (!me.is(username)) Redirect(routes.Ublog.drafts(me.username)).fuccess
else
env.ublog.api.draftByUser(me, page) map { posts =>
Ok(html.ublog.index.drafts(me, posts))
}
}
def post(username: String, slug: String, id: String) = Open { implicit ctx =>
OptionFuResult(env.user.repo named username) { user =>
env.ublog.api.find(UblogPost.Id(id)) map {
env.ublog.api.findByAuthor(UblogPost.Id(id), user) map {
_ ?? { post =>
if (!user.is(post.user) || slug != post.slug) Redirect(urlOf(post))
if (slug != post.slug) Redirect(urlOf(post))
else {
val markup = scalatags.Text.all.raw(env.ublog.markup(post.markdown))
Ok(html.ublog.post(user, post, markup))
@ -37,14 +45,14 @@ final class Ublog(env: Env) extends LilaController(env) {
else Ok(html.ublog.post.create(me, env.ublog.form.create)).fuccess
}
def create(username: String) = AuthBody { implicit ctx => me =>
def create(unusedUsername: String) = AuthBody { implicit ctx => me =>
env.ublog.form.create
.bindFromRequest()(ctx.body, formBinding)
.fold(
err => BadRequest(html.ublog.post.create(me, err)).fuccess,
data =>
env.ublog.api.create(data, me) map { post =>
Redirect(urlOf(post))
Redirect(urlOf(post)).flashSuccess
}
)
}
@ -55,7 +63,20 @@ final class Ublog(env: Env) extends LilaController(env) {
}
}
def update(username: String, id: String) = AuthBody { implicit ctx => me =>
???
def update(unusedUsername: String, id: String) = AuthBody { implicit ctx => me =>
env.ublog.api.findByAuthor(UblogPost.Id(id), me) flatMap {
_ ?? { prev =>
env.ublog.form
.edit(prev)
.bindFromRequest()(ctx.body, formBinding)
.fold(
err => BadRequest(html.ublog.post.edit(me, prev, err)).fuccess,
data =>
env.ublog.api.update(data, prev) map { post =>
Redirect(urlOf(post)).flashSuccess
}
)
}
}
}
}

View File

@ -1,6 +1,7 @@
package views.html.ublog
import controllers.routes
import play.api.mvc.Call
import lila.api.Context
import lila.app.templating.Environment._
@ -9,12 +10,13 @@ import lila.ublog.UblogPost
object bits {
def mini(post: UblogPost)(implicit ctx: Context) =
a(cls := "ublog-post-mini", href := url(post))(
def mini(post: UblogPost, makeUrl: UblogPost => Call = urlOf)(implicit ctx: Context) =
a(cls := "ublog-post-mini", href := makeUrl(post))(
h2(cls := "ublog-post-mini__title", post.title),
strong(cls := "ublog-post-mini__intro", post.intro)
)
def url(post: UblogPost) = routes.Ublog.post(usernameOrId(post.user), post.slug, post.id.value)
def urlOf(post: UblogPost) = routes.Ublog.post(usernameOrId(post.user), post.slug, post.id.value)
def editUrlOf(post: UblogPost) = routes.Ublog.edit(usernameOrId(post.user), post.id.value)
}

View File

@ -11,6 +11,8 @@ import lila.user.User
object index {
import views.html.ublog.bits
def apply(user: User, posts: Paginator[UblogPost])(implicit ctx: Context) =
views.html.base.layout(
moreCss = frag(cssTag("ublog")),
@ -18,13 +20,37 @@ object index {
) {
main(cls := "box box-pad page ublog-index")(
h1(s"${user.username} blog"),
ctx.is(user) option a(href := routes.Ublog.form(user.username))("Write a new blog post"),
ctx.is(user) option
div(newPostLink, a(href := routes.Ublog.drafts(user.username))("My drafts")),
div(cls := "ublog-index__posts")(
div(cls := "infinite-scroll")(
posts.currentPageResults map views.html.ublog.bits.mini,
posts.currentPageResults map { bits.mini(_) },
pagerNext(posts, np => s"${routes.Ublog.index(user.username, np).url}")
)
)
)
}
def drafts(user: User, posts: Paginator[UblogPost])(implicit ctx: Context) =
views.html.base.layout(
moreCss = frag(cssTag("ublog")),
title = s"${user.username} drafts"
) {
main(cls := "box box-pad page ublog-index")(
h1(s"${user.username} drafts"),
newPostLink,
div(cls := "ublog-index__posts ublog-index__posts--drafts")(
div(cls := "infinite-scroll")(
posts.currentPageResults map {
bits.mini(_, bits.editUrlOf)
},
pagerNext(posts, np => s"${routes.Ublog.drafts(user.username, np).url}")
)
)
)
}
private def newPostLink(implicit ctx: Context) = ctx.me map { u =>
a(href := routes.Ublog.form(u.username))("Write a new blog post")
}
}

View File

@ -12,6 +12,8 @@ import lila.user.User
object post {
import views.html.ublog.bits
def apply(user: User, post: UblogPost, markup: Frag)(implicit ctx: Context) =
views.html.base.layout(
moreCss = frag(cssTag("ublog")),
@ -20,7 +22,8 @@ object post {
main(cls := "box box-pad page ublog-post")(
h1(cls := "ublog-post__title")(post.title),
ctx.is(user) option div(
a(href := routes.Ublog.edit(user.username, post.id.value))("Edit your blog post")
standardFlash(),
a(href := bits.editUrlOf(post))("Edit your blog post")
),
strong(cls := "ublog-post__intro")(post.intro),
div(cls := "ublog-post__markup")(markup)
@ -45,6 +48,9 @@ object post {
) {
main(cls := "box box-pad page ublog-post-form")(
h1("Edit your blog post"),
div(cls := "ublog-post-form__publish")(
p(if (post.live) "This post is published" else "This is a draft")
),
innerForm(user, f, post.some)
)
}
@ -66,8 +72,12 @@ object post {
"Post body",
help = markdownAvailable.some
)(form3.textarea(_)(rows := 30)),
form3.checkbox(
form("live"),
raw("Publish this post on your blog")
),
form3.actions(
a(href := post.fold(routes.Ublog.index(user.username))(bits.url))(trans.cancel()),
a(href := post.fold(routes.Ublog.index(user.username))(bits.urlOf))(trans.cancel()),
form3.submit(trans.apply())
)
)

View File

@ -59,6 +59,7 @@ GET /@/:username/tournaments/:path controllers.UserTournament.path(username:
# User blog
GET /@/:username/blog controllers.Ublog.index(username: String, page: Int ?= 1)
GET /@/:username/blog/drafts controllers.Ublog.drafts(username: String, page: Int ?= 1)
GET /@/:username/blog/new controllers.Ublog.form(username: String)
POST /@/:username/blog/new controllers.Ublog.create(username: String)
GET /@/:username/blog/$id<\w{8}>/edit controllers.Ublog.edit(username: String, id: String)

View File

@ -14,12 +14,20 @@ final class UblogApi(coll: Coll)(implicit ec: ExecutionContext) {
import UblogBsonHandlers._
def create(data: UblogForm.UblogPostData, user: User): Fu[UblogPost] = {
val post = UblogPost.make(user, data.title, data.intro, data.markdown)
val post = data.create(user)
coll.insert.one(post) inject post
}
def update(data: UblogForm.UblogPostData, prev: UblogPost): Fu[UblogPost] = {
val post = data.update(prev)
coll.update.one($id(prev.id), post) inject post
}
def find(id: UblogPost.Id): Fu[Option[UblogPost]] = coll.byId[UblogPost](id.value)
def findByAuthor(id: UblogPost.Id, author: User): Fu[Option[UblogPost]] =
coll.one[UblogPost]($id(id) ++ $doc("user" -> author.id))
def liveByUser(user: User, page: Int): Fu[Paginator[UblogPost]] =
paginatorByUser(user, true, page)

View File

@ -1,8 +1,11 @@
package lila.ublog
import org.joda.time.DateTime
import play.api.data._
import play.api.data.Forms._
import lila.common.Form.{ cleanNonEmptyText, cleanText }
import lila.user.User
final class UblogForm {
@ -12,15 +15,43 @@ final class UblogForm {
mapping(
"title" -> cleanNonEmptyText(minLength = 3, maxLength = 100),
"intro" -> cleanNonEmptyText(minLength = 0, maxLength = 2_000),
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000)
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000),
"live" -> boolean
)(UblogPostData.apply)(UblogPostData.unapply)
)
def edit(post: UblogPost) =
create.fill(UblogPostData(title = post.title, intro = post.intro, markdown = post.markdown))
create.fill(
UblogPostData(title = post.title, intro = post.intro, markdown = post.markdown, live = post.live)
)
}
object UblogForm {
case class UblogPostData(title: String, intro: String, markdown: String)
case class UblogPostData(title: String, intro: String, markdown: String, live: Boolean) {
def create(user: User) = {
val now = DateTime.now
UblogPost(
_id = UblogPost.Id(lila.common.ThreadLocalRandom nextString 8),
user = user.id,
title = title,
intro = intro,
markdown = markdown,
live = false,
createdAt = now,
updatedAt = now,
liveAt = live option now
)
}
def update(prev: UblogPost) =
prev.copy(
title = title,
intro = intro,
markdown = markdown,
live = live,
liveAt = prev.liveAt orElse live.option(DateTime.now)
)
}
}

View File

@ -28,18 +28,5 @@ case class UblogPost(
object UblogPost {
def make(user: User, title: String, intro: String, markdown: String) =
UblogPost(
_id = Id(lila.common.ThreadLocalRandom nextString 8),
user = user.id,
title = title,
intro = intro,
markdown = markdown,
live = false,
createdAt = DateTime.now,
updatedAt = DateTime.now,
liveAt = none
)
case class Id(value: String) extends AnyVal with StringValue
}