ublog post image alt & credit

pull/9850/head
Thibault Duplessis 2021-09-19 17:20:21 +02:00
parent 154382ca19
commit 503beddb89
13 changed files with 112 additions and 40 deletions

View File

@ -26,7 +26,7 @@ object form {
) {
main(cls := "page-menu page-small")(
views.html.blog.bits.menu(none, "mine".some),
div(cls := "page-menu__content box box-pad ublog-post-form")(
div(cls := "page-menu__content box ublog-post-form")(
standardFlash(),
h1(trans.ublog.newPost()),
etiquette,
@ -43,7 +43,7 @@ object form {
) {
main(cls := "page-menu page-small")(
views.html.blog.bits.menu(none, "mine".some),
div(cls := "page-menu__content box box-pad ublog-post-form")(
div(cls := "page-menu__content box ublog-post-form")(
standardFlash(),
div(cls := "box__top")(
h1(
@ -108,32 +108,41 @@ object form {
)
)
def formImage(post: UblogPost) = postView.thumbnail(post, _.Small)
def formImage(post: UblogPost) =
postView.thumbnail(post, _.Small)(cls := post.image.isDefined.option("user-image"))
private def inner(form: Form[UblogPostData], post: Either[User, UblogPost], captcha: Option[Captcha])(
implicit ctx: Context
) =
postForm(
cls := "form3",
cls := "form3 ublog-post-form__main",
action := post.fold(_ => routes.Ublog.create, p => routes.Ublog.update(p.id.value))
)(
form3.globalError(form),
post.isRight option form3.split(
form3.checkbox(
form("live"),
trans.ublog.publishOnYourBlog(),
help = trans.ublog.publishHelp().some,
half = true
),
form3.group(form("language"), trans.language(), half = true) { field =>
form3.select(
field,
LangList.popularNoRegion.map { l =>
l.code -> l.toLocale.getDisplayLanguage
post.toOption.map { p =>
frag(
form3.split(
form3.group(form("imageAlt"), trans.ublog.imageAlt(), half = true)(form3.input(_)),
form3.group(form("imageCredit"), trans.ublog.imageCredit(), half = true)(form3.input(_))
)(cls := s"ublog-post-form__image-text ${p.image.isDefined ?? "visible"}"),
form3.split(
form3.checkbox(
form("live"),
trans.ublog.publishOnYourBlog(),
help = trans.ublog.publishHelp().some,
half = true
),
form3.group(form("language"), trans.language(), half = true) { field =>
form3.select(
field,
LangList.popularNoRegion.map { l =>
l.code -> l.toLocale.getDisplayLanguage
}
)
}
)
}
),
)
},
form3.group(form("title"), trans.ublog.postTitle())(form3.input(_)(autofocus)),
form3.group(form("intro"), trans.ublog.postIntro())(form3.input(_)(autofocus)),
form3.group(

View File

@ -44,7 +44,12 @@ object post {
main(cls := "page-menu page-small")(
views.html.blog.bits.menu(none, (if (ctx is user) "mine" else "community").some),
div(cls := "page-menu__content box box-pad ublog-post")(
post.image.isDefined option thumbnail(post, _.Large)(cls := "ublog-post__image"),
post.image.map { image =>
frag(
thumbnail(post, _.Large)(cls := "ublog-post__image"),
image.credit.map { p(cls := "ublog-post__image-credit")(_) }
)
},
ctx.is(user) || isGranted(_.ModerateBlog) option standardFlash(),
h1(cls := "ublog-post__title")(post.title),
div(cls := "ublog-post__meta")(
@ -191,12 +196,13 @@ object post {
img(
cls := "ublog-post-image",
widthA := size(UblogPost.thumbnail).width,
heightA := size(UblogPost.thumbnail).height
heightA := size(UblogPost.thumbnail).height,
alt := post.image.flatMap(_.alt)
)(src := url(post, size))
def url(post: UblogPost.BasePost, size: UblogPost.thumbnail.SizeSelector) =
post.image match {
case Some(image) => UblogPost.thumbnail(picfitUrl, image, size)
case Some(image) => UblogPost.thumbnail(picfitUrl, image.id, size)
case _ => assetUrl("images/user-blog-default.png")
}
}

View File

@ -0,0 +1,3 @@
db.ublog_post
.find({ image: { $exists: 1 } }, { image: 1 })
.forEach(p => db.ublog_post.update({ _id: p._id }, { $set: { image: { id: p.image } } }));

View File

@ -161,7 +161,7 @@ final class PersonalDataExport(
"title" -> post.title,
"intro" -> post.intro,
"body" -> post.markdown,
"image" -> post.image.??(lila.ublog.UblogPost.thumbnail(picfitUrl, _, _.Large)),
"image" -> post.image.??(i => lila.ublog.UblogPost.thumbnail(picfitUrl, i.id, _.Large)),
"topics" -> post.topics.map(_.value).mkString(", ")
).map { case (k, v) =>
s"$k: $v"

View File

@ -2283,6 +2283,8 @@ val `noPostsInThisBlogYet` = new I18nKey("ublog:noPostsInThisBlogYet")
val `noDrafts` = new I18nKey("ublog:noDrafts")
val `latestBlogPosts` = new I18nKey("ublog:latestBlogPosts")
val `uploadAnImageForYourPost` = new I18nKey("ublog:uploadAnImageForYourPost")
val `imageAlt` = new I18nKey("ublog:imageAlt")
val `imageCredit` = new I18nKey("ublog:imageCredit")
val `publishedNbBlogPosts` = new I18nKey("ublog:publishedNbBlogPosts")
val `nbViews` = new I18nKey("ublog:nbViews")
val `viewAllNbPosts` = new I18nKey("ublog:viewAllNbPosts")

View File

@ -110,10 +110,10 @@ final class UblogApi(
def uploadImage(user: User, post: UblogPost, picture: PicfitApi.FilePart): Fu[UblogPost] =
for {
image <- picfitApi
.uploadFile(imageRel(post), picture, userId = user.id)
_ <- colls.post.update.one($id(post.id), $set("image" -> image.id))
} yield post.copy(image = image.id.some)
pic <- picfitApi.uploadFile(imageRel(post), picture, userId = user.id)
image = post.image.fold(UblogImage(pic.id))(_.copy(id = pic.id))
_ <- colls.post.updateField($id(post.id), "image", image)
} yield post.copy(image = image.some)
def deleteImage(post: UblogPost): Fu[UblogPost] =
picfitApi.deleteByRel(imageRel(post)) >>

View File

@ -25,6 +25,7 @@ private object UblogBsonHandlers {
.afterRead(_.filter(t => UblogTopic.exists(t.value)))
implicit val langBsonHandler = stringAnyValHandler[Lang](_.code, Lang.apply)
implicit val recordedBSONHandler = Macros.handler[Recorded]
implicit val imageBSONHandler = Macros.handler[UblogImage]
implicit val likesBSONHandler = intAnyValHandler[Likes](_.value, Likes)
implicit val viewsBSONHandler = intAnyValHandler[Views](_.value, Views)
implicit val rankBSONHandler = dateIsoHandler[Rank](Iso[DateTime, Rank](Rank, _.value))

View File

@ -17,14 +17,16 @@ final class UblogForm(markup: UblogMarkup, val captcher: lila.hub.actors.Captche
private val base =
mapping(
"title" -> cleanNonEmptyText(minLength = 3, maxLength = 80),
"intro" -> cleanNonEmptyText(minLength = 0, maxLength = 1_000),
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000).verifying(markdownImage.constraint),
"language" -> optional(stringIn(LangList.popularNoRegion.map(_.code).toSet)),
"topics" -> optional(text),
"live" -> boolean,
"gameId" -> text,
"move" -> text
"title" -> cleanNonEmptyText(minLength = 3, maxLength = 80),
"intro" -> cleanNonEmptyText(minLength = 0, maxLength = 1_000),
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000).verifying(markdownImage.constraint),
"imageAlt" -> optional(cleanNonEmptyText(minLength = 3, maxLength = 200)),
"imageCredit" -> optional(cleanNonEmptyText(minLength = 3, maxLength = 200)),
"language" -> optional(stringIn(LangList.popularNoRegion.map(_.code).toSet)),
"topics" -> optional(text),
"live" -> boolean,
"gameId" -> text,
"move" -> text
)(UblogPostData.apply)(UblogPostData.unapply)
val create = Form(
@ -37,6 +39,8 @@ final class UblogForm(markup: UblogMarkup, val captcher: lila.hub.actors.Captche
title = post.title,
intro = post.intro,
markdown = post.markdown,
imageAlt = post.image.flatMap(_.alt),
imageCredit = post.image.flatMap(_.credit),
language = post.language.code.some,
topics = post.topics.map(_.value).mkString(", ").some,
live = post.live,
@ -52,6 +56,8 @@ object UblogForm {
title: String,
intro: String,
markdown: String,
imageAlt: Option[String],
imageCredit: Option[String],
language: Option[String],
topics: Option[String],
live: Boolean,
@ -84,6 +90,9 @@ object UblogForm {
title = title,
intro = intro,
markdown = markdown,
image = prev.image.map { i =>
i.copy(alt = imageAlt, credit = imageCredit)
},
language = LangList.removeRegion(realLanguage | prev.language),
topics = topics ?? UblogTopic.fromStrList,
live = live,

View File

@ -13,7 +13,7 @@ case class UblogPost(
intro: String,
markdown: String,
language: Lang,
image: Option[PicfitImage.Id],
image: Option[UblogImage],
topics: List[UblogTopic],
live: Boolean,
created: UblogPost.Recorded,
@ -28,6 +28,8 @@ case class UblogPost(
def indexable = live && topics.exists(t => UblogTopic.chessExists(t.value))
}
case class UblogImage(id: PicfitImage.Id, alt: Option[String] = None, credit: Option[String] = None)
object UblogPost {
case class Id(value: String) extends AnyVal with StringValue
@ -50,7 +52,7 @@ object UblogPost {
val blog: UblogBlog.Id
val title: String
val intro: String
val image: Option[PicfitImage.Id]
val image: Option[UblogImage]
val created: Recorded
val lived: Option[Recorded]
def id = _id
@ -62,7 +64,7 @@ object UblogPost {
blog: UblogBlog.Id,
title: String,
intro: String,
image: Option[PicfitImage.Id],
image: Option[UblogImage],
created: Recorded,
lived: Option[Recorded]
) extends BasePost

View File

@ -31,4 +31,6 @@
<item quantity="other">View all %s posts</item>
</plurals>
<string name="uploadAnImageForYourPost">Upload an image for your post</string>
<string name="imageAlt">Image alternative text</string>
<string name="imageCredit">Image credit</string>
</resources>

View File

@ -1,6 +1,35 @@
@import 'flash';
.ublog-post-form {
padding-bottom: 5vh;
&__main .form-split,
&__main > .form-group,
&__main .form-actions,
&__delete .form-actions,
&__image {
padding-left: var(--box-padding);
padding-right: var(--box-padding);
}
&__image {
background: $c-bg-zebra;
padding-top: 5vh;
}
&__image-text {
background: $c-bg-zebra;
margin-bottom: 5vh;
border-bottom: $border;
font-size: 0.9em;
.form-group {
display: none;
}
&.visible {
height: auto;
.form-group {
display: block;
}
}
}
.ublog-post-image {
max-width: 100%;
height: auto;
@ -13,10 +42,8 @@
}
&__image {
.form-group {
text-align: center;
@extend %flex-column;
justify-content: center;
align-items: center;
}
img {
@extend %box-neat;

View File

@ -84,6 +84,11 @@
@extend %box-neat-force;
width: 100%;
height: auto;
&-credit {
font-size: 0.9em;
color: $c-font-dim;
text-align: right;
}
}
&__intro {
@extend %break-word;

View File

@ -28,11 +28,16 @@ const setupTopics = (el: HTMLTextAreaElement) =>
});
const setupImage = (form: HTMLFormElement) => {
const showText = () =>
$('.ublog-post-form__image-text').toggleClass('visible', $('.ublog-post-image').hasClass('user-image'));
const submit = () => {
const replace = (html: string) => $(form).find('.ublog-post-image').replaceWith(html);
const wrap = (html: string) => '<div class="ublog-post-image">' + html + '</div>';
xhr.formToXhr(form).then(
html => replace(html),
html => {
replace(html);
showText();
},
err => replace(wrap(`<bad>${err}</bad>`))
);
replace(wrap(spinner));
@ -40,6 +45,7 @@ const setupImage = (form: HTMLFormElement) => {
};
$(form).on('submit', submit);
$(form).find('input[name="image"]').on('change', submit);
showText();
};
const setupMarkdownEditor = (el: HTMLTextAreaElement) => {