mirror of
https://codeberg.org/simonrepp/hyper8.git
synced 2026-08-14 13:45:27 +02:00
Implement release date for videos
This commit is contained in:
Generated
+68
@@ -286,6 +286,21 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.11"
|
||||
@@ -457,6 +472,20 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.1"
|
||||
@@ -526,6 +555,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
@@ -947,6 +982,7 @@ dependencies = [
|
||||
"actix-web",
|
||||
"base64 0.22.0",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"clap",
|
||||
"enolib",
|
||||
"form_urlencoded",
|
||||
@@ -963,6 +999,29 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -2004,6 +2063,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
|
||||
@@ -15,6 +15,7 @@ actix-multipart = "0.6.1"
|
||||
actix-web = "4.5.1"
|
||||
base64 = "0.22.0"
|
||||
bincode = "1.3.3"
|
||||
chrono = "0.4.35"
|
||||
clap = { features = ["derive"], version = "4.5.1" }
|
||||
enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.4.2" }
|
||||
form_urlencoded = "1.2.1"
|
||||
|
||||
@@ -7,6 +7,7 @@ use actix_multipart::Multipart;
|
||||
use actix_web::{Either, Error, HttpResponse};
|
||||
use actix_web::http::header::ContentType;
|
||||
use actix_web::web::{self, Data, Form, Path as ActixPath, Redirect};
|
||||
use chrono::NaiveDate;
|
||||
use futures_util::TryStreamExt as _;
|
||||
use indoc::formatdoc;
|
||||
use serde_derive::Deserialize;
|
||||
@@ -31,6 +32,7 @@ use crate::{
|
||||
use crate::editor::endpoints;
|
||||
use crate::editor::widgets::{
|
||||
form_checkbox,
|
||||
form_field_date,
|
||||
form_field,
|
||||
form_field_pattern,
|
||||
form_textarea,
|
||||
@@ -52,6 +54,7 @@ pub enum VideoFormFeedback<'a> {
|
||||
Site(endpoints::site::SiteFormFeedback),
|
||||
Update {
|
||||
description: &'a str,
|
||||
release_date: Field,
|
||||
title: Field,
|
||||
unlisted: bool
|
||||
}
|
||||
@@ -65,6 +68,7 @@ pub struct VideoMoveForm {
|
||||
#[derive(Deserialize)]
|
||||
pub struct VideoUpdateForm {
|
||||
description: String,
|
||||
release_date: String,
|
||||
title: String,
|
||||
unlisted: Option<String>
|
||||
}
|
||||
@@ -217,32 +221,6 @@ pub fn edit_page(
|
||||
"#)
|
||||
};
|
||||
|
||||
// let in_playlists = {
|
||||
// let playlists = context.collection.get_playlists_for_video(&video);
|
||||
|
||||
// if playlists.is_empty() {
|
||||
// String::new()
|
||||
// } else {
|
||||
// let list = playlists
|
||||
// .into_iter()
|
||||
// .map(|playlist| {
|
||||
// let id = &playlist.id;
|
||||
// let title = &playlist.title;
|
||||
|
||||
// format!(r#"<a href="/playlist/{id}">{title}</a>"#)
|
||||
// })
|
||||
// .collect::<Vec<String>>()
|
||||
// .join(", ");
|
||||
|
||||
// formatdoc!("
|
||||
// <br><br>
|
||||
// <div>
|
||||
// In playlists: {list}
|
||||
// </div>"
|
||||
// )
|
||||
// }
|
||||
// };
|
||||
|
||||
let poster = video.poster
|
||||
.as_ref()
|
||||
.map(|poster| {
|
||||
@@ -261,19 +239,27 @@ pub fn edit_page(
|
||||
let (
|
||||
feedback_update,
|
||||
input_description,
|
||||
input_release_date,
|
||||
input_title,
|
||||
input_unlisted,
|
||||
) = if let Some(VideoFormFeedback::Update { description, title, unlisted }) = &form_feedback {
|
||||
) = if let Some(VideoFormFeedback::Update { description, title, release_date, unlisted }) = &form_feedback {
|
||||
(
|
||||
r#"<span class="feedback">Not Saved</span>"#,
|
||||
form_textarea("description", "Description", "Description", Some(description)),
|
||||
form_field_date(release_date, "release_date", "Release Date"),
|
||||
form_field(title, "title", "Title", "Title"),
|
||||
form_checkbox(*unlisted, "unlisted", "Unlisted")
|
||||
)
|
||||
} else {
|
||||
let release_date = video.release_date
|
||||
.as_ref()
|
||||
.map(|date| date.to_string())
|
||||
.unwrap_or(String::new());
|
||||
|
||||
(
|
||||
"",
|
||||
form_textarea("description", "Description", "Description", video.description.as_deref()),
|
||||
form_field_date(&Field::valid(release_date), "release_date", "Release Date"),
|
||||
form_field(&Field::valid(video.title.as_deref().unwrap_or("")), "title", "Title", "Title"),
|
||||
form_checkbox(video.unlisted, "unlisted", "Unlisted")
|
||||
)
|
||||
@@ -330,6 +316,7 @@ pub fn edit_page(
|
||||
<form action="/video/{path}" method="post">
|
||||
{input_title}
|
||||
{input_description}
|
||||
{input_release_date}
|
||||
{input_unlisted}
|
||||
|
||||
<div style="align-items: center; column-gap: .5rem; display: flex;">
|
||||
@@ -459,13 +446,15 @@ pub async fn update(
|
||||
path: ActixPath<ContextPath>
|
||||
) -> Either<Redirect, HttpResponse> {
|
||||
let description_trimmed = form.description.trim();
|
||||
let release_date = NaiveDate::parse_from_str(&form.release_date, "%Y-%m-%d").map_err(|err| err.to_string());
|
||||
let title_trimmed = form.title.trim();
|
||||
let unlisted = form.unlisted.is_some();
|
||||
|
||||
// TODO: We're keeping this around for now because we might need to re-introduce some actual validation? (description/title length e.g.?)
|
||||
if true {
|
||||
|
||||
if release_date.is_ok() {
|
||||
let mutation = |video: &mut Video| {
|
||||
video.description = if description_trimmed.is_empty() { None } else { Some(description_trimmed.to_owned()) };
|
||||
video.release_date = release_date.ok();
|
||||
video.title = if title_trimmed.is_empty() { None } else { Some(title_trimmed.to_owned()) };
|
||||
video.unlisted = unlisted;
|
||||
video.write_manifest(&context);
|
||||
@@ -490,6 +479,7 @@ pub async fn update(
|
||||
|
||||
let form_feedback = VideoFormFeedback::Update {
|
||||
description: description_trimmed,
|
||||
release_date: Field::validated(&form.release_date, release_date.err()),
|
||||
title: Field::valid(title_trimmed),
|
||||
unlisted
|
||||
};
|
||||
|
||||
@@ -222,6 +222,27 @@ pub fn form_field(
|
||||
"#)
|
||||
}
|
||||
|
||||
|
||||
pub fn form_field_date(
|
||||
field: &Field,
|
||||
key: &str,
|
||||
label: &str
|
||||
) -> String {
|
||||
let error = field.error
|
||||
.as_deref()
|
||||
.map(|message| format!(r#"<span class="error">{message}</span>"#))
|
||||
.unwrap_or(String::new());
|
||||
let value = &field.value;
|
||||
|
||||
formatdoc!(r#"
|
||||
<div class="form_field">
|
||||
<label for="{key}">{label}</label>
|
||||
<input id="{key}" name="{key}" type="date" value="{value}">
|
||||
{error}
|
||||
</div>
|
||||
"#)
|
||||
}
|
||||
|
||||
pub fn form_field_pattern(
|
||||
field: &Field,
|
||||
key: &str,
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use enolib::SectionElement;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
@@ -24,6 +25,7 @@ pub struct Video {
|
||||
/// Relative to site dir, always without leading/trailing slashes
|
||||
pub path: String,
|
||||
pub poster: Option<PosterFile>,
|
||||
pub release_date: Option<NaiveDate>,
|
||||
pub title: Option<String>,
|
||||
/// Unlisted videos are not linked from the site anywhere,
|
||||
/// one needs to know the link to access them.
|
||||
@@ -119,6 +121,22 @@ impl Video {
|
||||
_ => ()
|
||||
}
|
||||
|
||||
match document.optional_field("release_date") {
|
||||
Ok(Some(field)) => {
|
||||
match field.required_value::<String>() {
|
||||
Ok(value) => match NaiveDate::parse_from_str(&value, "%Y-%m-%d") {
|
||||
Ok(date) => {
|
||||
self.release_date = Some(date);
|
||||
}
|
||||
Err(err) => self.errors.push(format!("Error in {} ({})", manifest_path.display(), err))
|
||||
}
|
||||
Err(err) => self.errors.push(format!("Error in {}:{} ({})", manifest_path.display(), err.line, err))
|
||||
}
|
||||
}
|
||||
Err(err) => self.errors.push(format!("Error in {}:{} ({})", manifest_path.display(), err.line, err)),
|
||||
_ => ()
|
||||
}
|
||||
|
||||
match document.optional_field("title") {
|
||||
Ok(Some(field)) => {
|
||||
match field.required_value() {
|
||||
@@ -182,6 +200,7 @@ impl Video {
|
||||
errors: Vec::new(),
|
||||
path,
|
||||
poster: None,
|
||||
release_date: None,
|
||||
title: None,
|
||||
unlisted: false,
|
||||
versions: Vec::new()
|
||||
@@ -311,6 +330,10 @@ impl Video {
|
||||
eno.push_str(&format!("poster: {}\n", poster.file_name));
|
||||
}
|
||||
|
||||
if let Some(release_date) = &self.release_date {
|
||||
eno.push_str(&format!("release_date: {}\n", release_date));
|
||||
}
|
||||
|
||||
if let Some(title) = &self.title {
|
||||
eno.push_str(&format!("title: {}\n", title));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user