Allow optional title, description, link
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
f823369f30
commit
03d31b9365
13
src/lib.rs
13
src/lib.rs
|
@ -28,11 +28,12 @@ const DAYS: u32 = 24 * HOURS;
|
|||
|
||||
mod connection;
|
||||
mod middleware;
|
||||
mod optional;
|
||||
mod pict;
|
||||
mod store;
|
||||
mod ui;
|
||||
|
||||
use self::{connection::Connection, middleware::ValidToken, store::Store};
|
||||
use self::{connection::Connection, middleware::ValidToken, optional::Optional, store::Store};
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct Config {
|
||||
|
@ -483,8 +484,9 @@ pub enum EntryKind {
|
|||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Entry {
|
||||
title: String,
|
||||
description: String,
|
||||
title: Optional<String>,
|
||||
description: Optional<String>,
|
||||
link: Optional<Url>,
|
||||
|
||||
#[serde(flatten)]
|
||||
file_info: EntryKind,
|
||||
|
@ -634,8 +636,9 @@ async fn upload(
|
|||
.stateful(&state)?;
|
||||
|
||||
let entry = Entry {
|
||||
title: String::new(),
|
||||
description: String::new(),
|
||||
title: None.into(),
|
||||
description: None.into(),
|
||||
link: None.into(),
|
||||
file_info: EntryKind::Pending {
|
||||
upload_id: upload.id().to_owned(),
|
||||
},
|
||||
|
|
72
src/optional.rs
Normal file
72
src/optional.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use serde::{de::Error, Deserialize};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub(crate) struct Optional<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
inner: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Deref for Optional<T> {
|
||||
type Target = Option<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Optional<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for Optional<T> {
|
||||
fn from(inner: Option<T>) -> Self {
|
||||
Optional { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Optional<T>> for Option<T> {
|
||||
fn from(e: Optional<T>) -> Self {
|
||||
e.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Optional<T> {
|
||||
pub fn as_ref(&self) -> Option<&T> {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
|
||||
pub fn as_deref(&self) -> Option<&T::Target>
|
||||
where
|
||||
T: Deref,
|
||||
{
|
||||
self.inner.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for Optional<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: Display,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s: Option<String> = Deserialize::<'de>::deserialize(deserializer)?;
|
||||
match s.as_deref() {
|
||||
None | Some("") => Ok(Optional { inner: None }),
|
||||
Some(s) => T::from_str(&s)
|
||||
.map_err(D::Error::custom)
|
||||
.map(Some)
|
||||
.map(|inner| Optional { inner }),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,15 +52,15 @@ statics::file_upload_js};
|
|||
</div>
|
||||
<div class="edit-item">
|
||||
<form method="POST" action="@state.update_entry_path(collection_id, *id, token)">
|
||||
@:text_input("title", Some("Image Title"), Some(&entry.title))
|
||||
@:text_area("description", Some("Image Description"), Some(&entry.description))
|
||||
@if let Some(upload_id) = entry.upload_id() {
|
||||
<input type="hidden" name="upload_id" value="@upload_id" />
|
||||
}
|
||||
@if let Some((filename, delete_token)) = entry.file_parts() {
|
||||
@:text_input("title", Some("Image Title"), entry.title.as_deref())
|
||||
@:text_area("description", Some("Image Description"), entry.description.as_deref())
|
||||
@:text_input("link", Some("https://..."), entry.link.as_ref().map(|l| l.as_str()))
|
||||
<input type="hidden" name="filename" value="@filename" />
|
||||
<input type="hidden" name="delete_token" value="@delete_token" />
|
||||
}
|
||||
<div class="button-group button-space">
|
||||
@:button("Update Image", ButtonKind::Submit)
|
||||
@:button_link("Delete Image", &state.delete_entry_path(collection_id, *id, token, false),
|
||||
|
@ -78,6 +78,7 @@ statics::file_upload_js};
|
|||
ButtonKind::Outline)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,15 @@
|
|||
|
||||
@:image_preview(entry, state)
|
||||
<div class="image-meta">
|
||||
<div class="image-title">@entry.title</div>
|
||||
<div class="image-description">@entry.description</div>
|
||||
@if let Some(title) = entry.title.as_ref() {
|
||||
<div class="image-title">@title</div>
|
||||
}
|
||||
@if let Some(description) = entry.description.as_ref() {
|
||||
<div class="image-description">@description</div>
|
||||
}
|
||||
@if let Some(link) = entry.link.as_ref() {
|
||||
<div class="image-href">
|
||||
<a href="@link" target="_blank" rel="noopener noreferer">@link</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
<picture>
|
||||
<source type="image/webp" srcset="@state.srcset(filename, Extension::Webp)" />
|
||||
<source type="image/jpeg" srcset="@state.srcset(filename, Extension::Jpg)" />
|
||||
<img src="@state.image_path(filename)" title="@entry.title" alt="@entry.description" />
|
||||
<img src="@state.image_path(filename)" @if let Some(title)=entry.title.as_ref() { title="@title" } @if let
|
||||
Some(description)=entry.description.as_ref() { alt="@description" } />
|
||||
</picture>
|
||||
</div>
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue