mirror of
https://codeberg.org/simonrepp/hyper8.git
synced 2026-08-14 13:45:27 +02:00
Split main crate into workspace subcrates (cli/core/icons/translations/web)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
/docs/target/
|
||||
/target/
|
||||
|
||||
@@ -36,7 +36,7 @@ cd hyper8
|
||||
Now run this command:
|
||||
|
||||
```bash
|
||||
cargo install --locked --path .
|
||||
cargo install --locked --path cli
|
||||
```
|
||||
|
||||
## Uninstalling
|
||||
|
||||
Generated
+799
-629
File diff suppressed because it is too large
Load Diff
+22
-12
@@ -1,19 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "hyper8"
|
||||
rust-version = "1.82"
|
||||
version = "0.23.0"
|
||||
[workspace]
|
||||
members = [
|
||||
"cli",
|
||||
"core",
|
||||
"docs",
|
||||
"icons",
|
||||
"translations",
|
||||
"version",
|
||||
"web"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[build-dependencies]
|
||||
base64 = "0.22.1"
|
||||
|
||||
[dependencies]
|
||||
[workspace.dependencies]
|
||||
actix-files = "0.6.6"
|
||||
actix-multipart = "0.7.2"
|
||||
actix-web = "4.11.0"
|
||||
actix-session = { features = ["cookie-session"], version = "0.11.0" }
|
||||
actix-web = { version = "4.11.0" }
|
||||
base64 = "0.22.1"
|
||||
# bincode 2.x is available but it includes breaking changes (on the API level,
|
||||
# encoded data is "completely compatible if the same configuration is used")
|
||||
@@ -21,7 +25,7 @@ base64 = "0.22.1"
|
||||
# update.
|
||||
bincode = "1.3.3"
|
||||
chrono = { features = ["unstable-locales"], version = "0.4.41" }
|
||||
clap = { features = ["derive"], version = "4.5.40" }
|
||||
clap = "4.5.40"
|
||||
dirs = "6.0.0"
|
||||
dunce = "1.0.5"
|
||||
enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.5.0" }
|
||||
@@ -37,6 +41,7 @@ log = "0.4.27"
|
||||
minreq = { features = ["https-rustls"], version = "2.13.4" }
|
||||
nanoid = "0.4.0"
|
||||
notify = "8.0.0"
|
||||
pulldown-cmark = { default-features = false, features = ["html", "simd"], version = "0.13.0" }
|
||||
rand = "0.9.1"
|
||||
rsubs-lib = "0.3.3"
|
||||
serde = "1.0.219"
|
||||
@@ -46,7 +51,12 @@ slug = "0.1.6"
|
||||
suppaftp = { features = ["native-tls"], version = "6.3.0" }
|
||||
sys-locale = "0.3.2"
|
||||
tokio = { features = ["macros", "rt-multi-thread"], version = "1.45.1" }
|
||||
uuid = { features = ["v5"], version = "1.17.0" }
|
||||
url = "2.5.4"
|
||||
urlencoding = "2.1.3"
|
||||
uuid = { features = ["v5"], version = "1.17.0" }
|
||||
webbrowser = "1.0.5"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
version = "0.23.0"
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
//! Build hyper8 with (e.g.) HYPER8_VERSION=2.0.0~pre1 to override
|
||||
//! the version that is displayed and reported in resulting builds.
|
||||
|
||||
use std::env;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::process::Command;
|
||||
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
|
||||
/// Provides four variables to the consecutive build step:
|
||||
/// - HYPER8_REVISION (If git is available the short commit hash, otherwise "unknown revision")
|
||||
/// - HYPER8_VERSION_ADAPTIVE (The version down to patch level only if it is a patch release, e.g. "1.2.1")
|
||||
/// - HYPER8_VERSION_DETAILED (The version down to patch level, e.g. "1.2.0")
|
||||
/// - HYPER8_VERSION_DISPLAY (The version for pretty display, without patch level, e.g. "1.2")
|
||||
///
|
||||
/// If HYPER8_VERSION is used to override the version, both HYPER8_VERSION_DETAILED and
|
||||
/// HYPER8_VERSION_DISPLAY will contain its value, e.g. "2.0.0~pre1".
|
||||
fn main() {
|
||||
let version_adaptive;
|
||||
let version_detailed;
|
||||
let version_display;
|
||||
if let Ok(override_version) = env::var("HYPER8_VERSION") {
|
||||
version_adaptive = override_version.clone();
|
||||
version_detailed = override_version.clone();
|
||||
version_display = override_version;
|
||||
} else {
|
||||
version_adaptive = match env!("CARGO_PKG_VERSION_PATCH") {
|
||||
"0" => concat!(env!("CARGO_PKG_VERSION_MAJOR"), '.', env!("CARGO_PKG_VERSION_MINOR")).to_string(),
|
||||
_ => env!("CARGO_PKG_VERSION").to_string()
|
||||
};
|
||||
version_detailed = env!("CARGO_PKG_VERSION").to_string();
|
||||
version_display = concat!(env!("CARGO_PKG_VERSION_MAJOR"), '.', env!("CARGO_PKG_VERSION_MINOR")).to_string();
|
||||
}
|
||||
|
||||
let mut git = Command::new("git");
|
||||
git.args(["rev-parse", "--short", "HEAD"]);
|
||||
let revision = match git.output() {
|
||||
Ok(output) => String::from_utf8(output.stdout).unwrap(),
|
||||
Err(_) => String::from("unknown revision")
|
||||
};
|
||||
|
||||
println!("cargo:rerun-if-env-changed=HYPER8_VERSION");
|
||||
println!("cargo:rustc-env=HYPER8_REVISION={revision}");
|
||||
println!("cargo:rustc-env=HYPER8_VERSION_ADAPTIVE={version_adaptive}");
|
||||
println!("cargo:rustc-env=HYPER8_VERSION_DETAILED={version_detailed}");
|
||||
println!("cargo:rustc-env=HYPER8_VERSION_DISPLAY={version_display}");
|
||||
|
||||
let static_asset_hash_adaptive_theme_js = url_safe_hash_base64(include_bytes!("src/build/assets/adaptive_theme.js"));
|
||||
let static_asset_hash_clipboard_js = url_safe_hash_base64(include_bytes!("src/build/assets/clipboard.js"));
|
||||
let static_asset_hash_embed_js = url_safe_hash_base64(include_bytes!("src/build/assets/embed.js"));
|
||||
let static_asset_hash_favicon_png = url_safe_hash_base64(include_bytes!("src/build/assets/favicon.png"));
|
||||
let static_asset_hash_video_js = url_safe_hash_base64(include_bytes!("src/build/assets/video.js"));
|
||||
|
||||
println!("cargo:rustc-env=HYPER8_STATIC_ASSET_HASH_ADAPTIVE_THEME_JS={static_asset_hash_adaptive_theme_js}");
|
||||
println!("cargo:rustc-env=HYPER8_STATIC_ASSET_HASH_CLIPBOARD_JS={static_asset_hash_clipboard_js}");
|
||||
println!("cargo:rustc-env=HYPER8_STATIC_ASSET_HASH_EMBED_JS={static_asset_hash_embed_js}");
|
||||
println!("cargo:rustc-env=HYPER8_STATIC_ASSET_HASH_FAVICON_PNG={static_asset_hash_favicon_png}");
|
||||
println!("cargo:rustc-env=HYPER8_STATIC_ASSET_HASH_VIDEO_JS={static_asset_hash_video_js}");
|
||||
}
|
||||
|
||||
pub fn url_safe_hash_base64(hashable: &impl Hash) -> String {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hashable.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
URL_SAFE_NO_PAD.encode(hash.to_le_bytes())
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
[package]
|
||||
edition.workspace = true
|
||||
name = "hyper8-cli"
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64.workspace = true
|
||||
clap.workspace = true
|
||||
dunce.workspace = true
|
||||
enolib.workspace = true
|
||||
env_logger.workspace = true
|
||||
hyper8-core.path = "../core"
|
||||
hyper8-translations.path = "../translations"
|
||||
hyper8-version.path = "../version"
|
||||
hyper8-web.path = "../web"
|
||||
indoc.workspace = true
|
||||
slug.workspace = true
|
||||
sys-locale.workspace = true
|
||||
webbrowser.workspace = true
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap::value_parser;
|
||||
|
||||
use hyper8_version::VERSION_WITH_PATCH_AND_REVISION;
|
||||
|
||||
pub fn parse() -> ArgMatches {
|
||||
let build = Arg::new("build")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Directly builds the site without starting the editor")
|
||||
.long("build")
|
||||
.short('b');
|
||||
|
||||
let build_dir = Arg::new("build-dir")
|
||||
.help("The path to which the deployable, built site is written")
|
||||
.long("build-dir")
|
||||
.value_parser(value_parser!(PathBuf));
|
||||
|
||||
let cache_dir = Arg::new("cache-dir")
|
||||
.help("The path at which all computational results are stored (computed metadata, images and videos)")
|
||||
.long("cache-dir")
|
||||
.value_parser(value_parser!(PathBuf));
|
||||
|
||||
let deploy = Arg::new("deploy")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Directly deploys the site - only applicable in combination with --build")
|
||||
.long("deploy")
|
||||
.short('d');
|
||||
|
||||
let ip = Arg::new("ip")
|
||||
.help("Manually sets the ip address used by the web editor (otherwise defaults to localhost)")
|
||||
.long("ip")
|
||||
.value_parser(value_parser!(IpAddr));
|
||||
|
||||
let port = Arg::new("port")
|
||||
.help("Manually sets the port used by the web editor (otherwise hyper8 chooses an available port on its own)")
|
||||
.long("port")
|
||||
.value_parser(value_parser!(u16));
|
||||
|
||||
let preview = Arg::new("preview")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use in conjunction with --build to immediately open the site in the browser after the build is complete")
|
||||
.long("preview")
|
||||
.short('p');
|
||||
|
||||
let site_dir = Arg::new("site-dir")
|
||||
.help("The path at which the site resides (user videos and metadata)")
|
||||
.required(true)
|
||||
.value_parser(value_parser!(PathBuf));
|
||||
|
||||
let workers = Arg::new("workers")
|
||||
.default_value("2")
|
||||
.help("The number of threads to run for background processing (metadata computation, image processing, video/audio transcoding, etc.)")
|
||||
.long("workers")
|
||||
.value_parser(worker_count);
|
||||
|
||||
let command = Command::new("Hyper 8 Video System")
|
||||
.version(VERSION_WITH_PATCH_AND_REVISION)
|
||||
.about("A static site generator for video publishing")
|
||||
.arg(build)
|
||||
.arg(build_dir)
|
||||
.arg(cache_dir)
|
||||
.arg(deploy)
|
||||
.arg(ip)
|
||||
.arg(port)
|
||||
.arg(preview)
|
||||
.arg(site_dir)
|
||||
.arg(workers);
|
||||
|
||||
command.get_matches()
|
||||
}
|
||||
|
||||
fn worker_count(string: &str) -> Result<usize, String> {
|
||||
match string.parse::<usize>() {
|
||||
Ok(number) => match number >= 2 {
|
||||
true => Ok(number),
|
||||
false => Err(String::from("At least 2 workers required"))
|
||||
}
|
||||
Err(err) => Err(err.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper8_core::{Context, Site};
|
||||
use hyper8_core::{config_probe, start_deploy};
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
/// Performs deployment in a synchronous fashion, reporting back all messages
|
||||
/// and errors to the terminal. Also asks for explicit ("opt-in") confirmation
|
||||
/// before performing the actual deployment.
|
||||
pub fn perform_deploy_blocking_cli(
|
||||
context_ref: &Arc<Context>,
|
||||
site: &Site,
|
||||
translations: &Translations
|
||||
) -> Result<(), String> {
|
||||
if site.base_url.is_none() {
|
||||
let site_manifest_path = context_ref.site_dir.join("site.eno");
|
||||
let message = translations.public_address_must_be_configured_for_deployment_text_cli(&site_manifest_path);
|
||||
return Err(message);
|
||||
}
|
||||
|
||||
if !site.complete_deployment_config() {
|
||||
let message = translations.deployment_configuration_is_incomplete_can_not_deploy.to_string();
|
||||
return Err(message);
|
||||
}
|
||||
|
||||
let t_really_deploy = translations.really_deploy;
|
||||
let base_url = &site.base_url.as_ref().unwrap().representation;
|
||||
println!("{t_really_deploy} ({base_url})");
|
||||
println!("{}", translations.type_y_and_press_enter_to_confirm_anything_else_will_abort);
|
||||
|
||||
let mut input = String::new();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
if input.trim() != "y" {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.to_string())
|
||||
}
|
||||
|
||||
if let Err(err) = config_probe(context_ref, site, translations) {
|
||||
let t_deployment_configuration_could_not_be_verified = translations.deployment_configuration_could_not_be_verified;
|
||||
let message = format!("{t_deployment_configuration_could_not_be_verified} ({err})");
|
||||
return Err(message);
|
||||
}
|
||||
|
||||
start_deploy(context_ref, site, translations)?;
|
||||
|
||||
let mut seen_feedback = 0;
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
if let Ok(deployment) = context_ref.deploying.lock() {
|
||||
let feedback = &deployment.feedback;
|
||||
|
||||
if feedback.len() > seen_feedback {
|
||||
eprintln!("{}", &feedback[seen_feedback..]);
|
||||
seen_feedback = feedback.len();
|
||||
}
|
||||
|
||||
if !deployment.active() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sys_locale::get_locale;
|
||||
|
||||
use hyper8_core::{
|
||||
Cache,
|
||||
Context,
|
||||
Language,
|
||||
Site
|
||||
};
|
||||
use hyper8_core::{perform_build, start_preview_server, start_workers};
|
||||
use hyper8_core::watcher::watch_site_dir;
|
||||
|
||||
mod args;
|
||||
mod deploy;
|
||||
|
||||
use deploy::perform_deploy_blocking_cli;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
env_logger::init();
|
||||
|
||||
let language = get_locale()
|
||||
.map(|locale| Language::from_code(&locale))
|
||||
.unwrap_or_else(|| Language::default());
|
||||
|
||||
let translations = &language.translations;
|
||||
|
||||
let args = args::parse();
|
||||
|
||||
let site_dir = args.get_one::<PathBuf>("site-dir").unwrap();
|
||||
|
||||
if !site_dir.exists() {
|
||||
eprintln!(
|
||||
"Site directory {site_dir:?} does not exist, please create it manually (just an empty folder).",
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
// This compiles to fs::canonicalize(...) on all platforms but windows,
|
||||
// on windows it instead tries to supply a canonicalized path that uses
|
||||
// the regular form (e.g. "C:\\Foo") instead of the UNC form ("\\?C:\\Foo")
|
||||
// when possible (for background see https://crates.io/crates/dunce).
|
||||
let site_dir = dunce::canonicalize(site_dir).unwrap();
|
||||
|
||||
let build_dir = args.get_one::<PathBuf>("build-dir")
|
||||
.as_ref()
|
||||
.map(|path| path.to_path_buf())
|
||||
.unwrap_or_else(|| site_dir.join(".hyper8_build"));
|
||||
|
||||
let cache_dir = args.get_one::<PathBuf>("cache-dir")
|
||||
.as_ref()
|
||||
.map(|path| path.to_path_buf())
|
||||
.unwrap_or_else(|| site_dir.join(".hyper8_cache"));
|
||||
|
||||
let cache = Mutex::new(Cache::retrieve(&cache_dir));
|
||||
|
||||
let context = Context::new(
|
||||
build_dir,
|
||||
cache,
|
||||
cache_dir,
|
||||
Arc::new(Site::new()),
|
||||
site_dir.clone()
|
||||
);
|
||||
|
||||
// TODO: Would be great if we could somehow resolve the conundrum that
|
||||
// context depends on site and site depends on context, which
|
||||
// forces us to initialize context with a dummy site and replace it here.
|
||||
context.replace_site(Site::read_dir(&context, &site_dir));
|
||||
|
||||
let context = Arc::new(context);
|
||||
|
||||
let workers = args.get_one::<usize>("workers").unwrap();
|
||||
|
||||
context.compute_job_queue();
|
||||
start_workers(&context, *workers, &translations);
|
||||
|
||||
let ip = args.get_one::<IpAddr>("ip");
|
||||
let port = args.get_one::<u16>("port");
|
||||
|
||||
if args.get_flag("build") {
|
||||
if let Err(err) = context.await_job_queue() {
|
||||
eprintln!("{err}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
perform_build(&context);
|
||||
|
||||
if args.get_flag("preview") {
|
||||
let site = context.get_site();
|
||||
|
||||
if site.clean_urls {
|
||||
// Here we serve the preview through an actual http server so
|
||||
// that /foo/ gets resolved to /foo/index.html.
|
||||
let result = start_preview_server(&context.build_dir, ip, port);
|
||||
|
||||
if result.is_ok() { ExitCode::SUCCESS } else { ExitCode::FAILURE }
|
||||
} else {
|
||||
// We don't need an actively running server to preview a build
|
||||
// without clean urls, we can just open everything directly in
|
||||
// a browser.
|
||||
let local_file_url = context.build_dir.join("index.html");
|
||||
if webbrowser::open(&local_file_url.to_string_lossy()).is_err() {
|
||||
eprintln!("Could not open browser for previewing the site");
|
||||
ExitCode::FAILURE
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
}
|
||||
} else if args.get_flag("deploy") {
|
||||
let site = context.get_site();
|
||||
|
||||
match perform_deploy_blocking_cli(&context, &site, &translations) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
} else {
|
||||
println!("Using site directory: {}", context.site_dir.display());
|
||||
|
||||
watch_site_dir(Arc::clone(&context));
|
||||
|
||||
let result = hyper8_web::start_server(
|
||||
context,
|
||||
ip,
|
||||
port
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
[package]
|
||||
edition.workspace = true
|
||||
name = "hyper8-core"
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
base64.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-files.workspace = true
|
||||
actix-web.workspace = true
|
||||
base64.workspace = true
|
||||
bincode.workspace = true
|
||||
chrono.workspace = true
|
||||
enolib.workspace = true
|
||||
form_urlencoded.workspace = true
|
||||
hyper8-icons.path = "../icons"
|
||||
hyper8-translations.path = "../translations"
|
||||
hyper8-version.path = "../version"
|
||||
icu_collator.workspace = true
|
||||
icu_locale.workspace = true
|
||||
image.workspace = true
|
||||
indoc.workspace = true
|
||||
language-tags.workspace = true
|
||||
log.workspace = true
|
||||
minreq.workspace = true
|
||||
nanoid.workspace = true
|
||||
notify.workspace = true
|
||||
rand.workspace = true
|
||||
rsubs-lib.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
slug.workspace = true
|
||||
suppaftp.workspace = true
|
||||
tokio.workspace = true
|
||||
uuid.workspace = true
|
||||
url.workspace = true
|
||||
urlencoding.workspace = true
|
||||
webbrowser.workspace = true
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
|
||||
fn main() {
|
||||
provide_asset_hash(
|
||||
include_bytes!("src/build/assets/adaptive_theme.js"),
|
||||
"ADAPTIVE_THEME_JS"
|
||||
);
|
||||
|
||||
provide_asset_hash(
|
||||
include_bytes!("src/build/assets/clipboard.js"),
|
||||
"CLIPBOARD_JS"
|
||||
);
|
||||
|
||||
provide_asset_hash(
|
||||
include_bytes!("src/build/assets/embed.js"),
|
||||
"EMBED_JS"
|
||||
);
|
||||
|
||||
provide_asset_hash(
|
||||
include_bytes!("src/build/assets/favicon.png"),
|
||||
"FAVICON_PNG"
|
||||
);
|
||||
|
||||
provide_asset_hash(
|
||||
include_bytes!("src/build/assets/video.js"),
|
||||
"VIDEO_JS"
|
||||
);
|
||||
}
|
||||
|
||||
fn provide_asset_hash(bytes: &[u8], key: &str) {
|
||||
let hash = url_safe_hash_base64(bytes);
|
||||
println!("cargo:rustc-env=HYPER8_STATIC_ASSET_HASH_{key}={hash}");
|
||||
}
|
||||
|
||||
pub fn url_safe_hash_base64(content: &[u8]) -> String {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
content.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
URL_SAFE_NO_PAD.encode(hash.to_le_bytes())
|
||||
}
|
||||
@@ -95,4 +95,4 @@ pub fn video_analysis(
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use std::path::Path;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper8_version::{VERSION_WITH_PATCH_AND_REVISION};
|
||||
use rsubs_lib::SRT;
|
||||
|
||||
use crate::{
|
||||
@@ -20,6 +21,7 @@ use crate::{
|
||||
use crate::build::widgets::thumbnail;
|
||||
use crate::util::{ensure_empty_dir, hard_link_or_copy};
|
||||
|
||||
mod asset_hashes;
|
||||
mod collection;
|
||||
mod download;
|
||||
mod embed;
|
||||
@@ -45,38 +47,12 @@ use playlist::playlist_html;
|
||||
use subscribe::subscribe_html;
|
||||
use video::video_html;
|
||||
|
||||
pub const GENERATOR_INFO: &str = concat!("Hyper 8 Video System ", env!("HYPER8_VERSION_DETAILED"), " (", env!("HYPER8_REVISION"), ")");
|
||||
|
||||
/// Placed in the head of each page to identify the Hyper 8 build that generated it.
|
||||
const META_GENERATOR: &str = concat!(r#"<meta name="generator" content="Hyper 8 Video System "#, env!("HYPER8_VERSION_DETAILED"), " (", env!("HYPER8_REVISION"), r#")">"#);
|
||||
use asset_hashes::AssetHashes;
|
||||
|
||||
const META_ROBOTS_NOINDEX_NOFOLLOW: &str = r#"<meta name="robots" content="noindex, nofollow">"#;
|
||||
|
||||
/// When we link to assets on the rendered pages, we append a unique asset
|
||||
/// hash to each path (e.g. "site.js?g1VVfPoEjUw"), which is derived from
|
||||
/// the file content of the asset. We do this in order to prompt browsers to
|
||||
/// fetch new, uncached assets when their content has changed. This struct
|
||||
/// groups together those hashes for all assets we use.
|
||||
pub struct AssetHashes {
|
||||
pub embed_css: Option<String>,
|
||||
pub navigation_js: Option<String>,
|
||||
pub site_css: Option<String>
|
||||
}
|
||||
|
||||
impl AssetHashes {
|
||||
pub const ADAPTIVE_THEME_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_ADAPTIVE_THEME_JS");
|
||||
pub const CLIPBOARD_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_CLIPBOARD_JS");
|
||||
pub const EMBED_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_EMBED_JS");
|
||||
pub const FAVICON_PNG: &str = env!("HYPER8_STATIC_ASSET_HASH_FAVICON_PNG");
|
||||
pub const VIDEO_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_VIDEO_JS");
|
||||
|
||||
pub fn new() -> AssetHashes {
|
||||
AssetHashes {
|
||||
embed_css: None,
|
||||
navigation_js: None,
|
||||
site_css: None
|
||||
}
|
||||
}
|
||||
pub fn meta_generator() -> String {
|
||||
format!(r#"<meta name="generator" content="Hyper 8 Video System {VERSION_WITH_PATCH_AND_REVISION}">"#)
|
||||
}
|
||||
|
||||
pub fn perform_build(context: &Context) {
|
||||
@@ -87,7 +63,7 @@ pub fn perform_build(context: &Context) {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
site = context.get_site();
|
||||
}
|
||||
|
||||
|
||||
ensure_empty_dir(&context.build_dir);
|
||||
|
||||
let mut asset_hashes = AssetHashes::new();
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
/// When we link to assets on the rendered pages, we append a unique asset
|
||||
/// hash to each path (e.g. "site.js?g1VVfPoEjUw"), which is derived from
|
||||
/// the file content of the asset. We do this in order to prompt browsers to
|
||||
/// fetch new, uncached assets when their content has changed. This struct
|
||||
/// groups together those hashes for all assets we use.
|
||||
pub struct AssetHashes {
|
||||
pub embed_css: Option<String>,
|
||||
pub navigation_js: Option<String>,
|
||||
pub site_css: Option<String>
|
||||
}
|
||||
|
||||
impl AssetHashes {
|
||||
pub const ADAPTIVE_THEME_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_ADAPTIVE_THEME_JS");
|
||||
pub const CLIPBOARD_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_CLIPBOARD_JS");
|
||||
pub const EMBED_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_EMBED_JS");
|
||||
pub const FAVICON_PNG: &str = env!("HYPER8_STATIC_ASSET_HASH_FAVICON_PNG");
|
||||
pub const VIDEO_JS: &str = env!("HYPER8_STATIC_ASSET_HASH_VIDEO_JS");
|
||||
|
||||
pub fn new() -> AssetHashes {
|
||||
AssetHashes {
|
||||
embed_css: None,
|
||||
navigation_js: None,
|
||||
site_css: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -6,11 +6,13 @@ use std::fs;
|
||||
use indoc::formatdoc;
|
||||
|
||||
use crate::{Context, Playlist, Site, Video};
|
||||
use crate::build::{META_GENERATOR, META_ROBOTS_NOINDEX_NOFOLLOW};
|
||||
use crate::build::{META_ROBOTS_NOINDEX_NOFOLLOW};
|
||||
use crate::build::AssetHashes;
|
||||
use crate::build::outline::Traversal;
|
||||
use crate::build::player::{player, player_icon_templates};
|
||||
|
||||
use super::meta_generator;
|
||||
|
||||
/// Writes html for the embed itself, i.e. what is pulled in from external
|
||||
/// site B through an iframe when they embed a video from site A.
|
||||
pub fn embed_html(
|
||||
@@ -51,7 +53,7 @@ pub fn embed_layout(
|
||||
site: &Site,
|
||||
traversal: &Traversal
|
||||
) -> String {
|
||||
let meta_generator = META_GENERATOR;
|
||||
let meta_generator = meta_generator();
|
||||
let meta_robots_noindex_nofollow = META_ROBOTS_NOINDEX_NOFOLLOW;
|
||||
|
||||
let embed_css_asset_hash = asset_hashes.embed_css.as_ref().unwrap();
|
||||
@@ -4,13 +4,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use hyper8_translations::Translations;
|
||||
use indoc::formatdoc;
|
||||
|
||||
use crate::{
|
||||
Context,
|
||||
Site,
|
||||
SitePath,
|
||||
Translations,
|
||||
Video
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ use std::sync::Arc;
|
||||
use chrono::{DateTime, Utc};
|
||||
use indoc::formatdoc;
|
||||
|
||||
use hyper8_version::{VERSION_WITH_PATCH, VERSION_WITH_PATCH_AND_REVISION};
|
||||
|
||||
use crate::{
|
||||
Context,
|
||||
MarkdownSubset,
|
||||
@@ -18,7 +20,6 @@ use crate::{
|
||||
SitePath,
|
||||
Video
|
||||
};
|
||||
use crate::build::GENERATOR_INFO;
|
||||
use crate::util::html_escape_outside_attribute;
|
||||
|
||||
use super::ATOM_FILENAME;
|
||||
@@ -82,15 +83,14 @@ pub fn atom(
|
||||
|
||||
let title_escaped = html_escape_outside_attribute(title);
|
||||
|
||||
let version_detailed = env!("HYPER8_VERSION_DETAILED");
|
||||
let xml = formatdoc!(r#"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<author>
|
||||
<name>{author_name}</name>
|
||||
</author>
|
||||
<generator uri="https://simonrepp.com/hyper8" version="{version_detailed}">
|
||||
{GENERATOR_INFO}
|
||||
<generator uri="https://simonrepp.com/hyper8" version="{VERSION_WITH_PATCH}">
|
||||
Hyper 8 Video System {VERSION_WITH_PATCH_AND_REVISION}
|
||||
</generator>
|
||||
<id>{site_url}</id>
|
||||
<link href="{atom_feed_url}" rel="self"/>
|
||||
@@ -15,8 +15,9 @@ use std::sync::Arc;
|
||||
use chrono::{DateTime, Utc};
|
||||
use indoc::formatdoc;
|
||||
|
||||
use hyper8_version::{VERSION_WITH_PATCH_AND_REVISION, VERSION_WITHOUT_PATCH};
|
||||
|
||||
use crate::{MarkdownSubset, Site, Video};
|
||||
use crate::build::GENERATOR_INFO;
|
||||
use crate::util::html_escape_outside_attribute;
|
||||
|
||||
pub fn rss(
|
||||
@@ -54,7 +55,7 @@ pub fn rss(
|
||||
} else {
|
||||
// TODO: Eventually find something better to fallback to.
|
||||
// Note that this is a mandatory field in RSS (https://www.rssboard.org/rss-specification#requiredChannelElements)
|
||||
format!("Hyper 8 Video System {}", env!("HYPER8_VERSION_DISPLAY"))
|
||||
format!("Hyper 8 Video System {VERSION_WITHOUT_PATCH}")
|
||||
};
|
||||
|
||||
let title_escaped = html_escape_outside_attribute(title);
|
||||
@@ -99,7 +100,7 @@ pub fn rss(
|
||||
<channel>
|
||||
<atom:link href="{feed_url}" rel="self" type="application/rss+xml"/>
|
||||
<description>{description}</description>
|
||||
<generator>{GENERATOR_INFO}</generator>
|
||||
<generator>Hyper 8 Video System {VERSION_WITH_PATCH_AND_REVISION}</generator>
|
||||
<language>{language}</language>
|
||||
<lastBuildDate>{build_time_rfc2822}</lastBuildDate>
|
||||
<link>{link}</link>
|
||||
@@ -3,15 +3,17 @@
|
||||
|
||||
use indoc::formatdoc;
|
||||
|
||||
use hyper8_version::VERSION_WITHOUT_PATCH;
|
||||
|
||||
use crate::{PlatformIntegrationMeta, Site, ThemeKind};
|
||||
use crate::build::{AssetHashes, Traversal};
|
||||
use crate::build::feeds::meta_link_tags;
|
||||
use crate::build::player::player_icon_templates;
|
||||
use crate::build::widgets::copy_button_icon_templates;
|
||||
use crate::icons;
|
||||
use crate::util::html_escape_outside_attribute;
|
||||
|
||||
use super::{META_GENERATOR, META_ROBOTS_NOINDEX_NOFOLLOW};
|
||||
use super::{META_ROBOTS_NOINDEX_NOFOLLOW};
|
||||
use super::meta_generator;
|
||||
|
||||
pub struct Layout {
|
||||
clipboard_script: bool,
|
||||
@@ -106,9 +108,9 @@ impl Layout {
|
||||
let navigation;
|
||||
let navigation_script;
|
||||
if let Some(navigation_js_asset_hash) = &asset_hashes.navigation_js {
|
||||
let browse_icon = icons::BROWSE;
|
||||
let close_icon = icons::failure(translations.close);
|
||||
let search_icon = icons::search(translations.search);
|
||||
let browse_icon = hyper8_icons::BROWSE;
|
||||
let close_icon = hyper8_icons::failure(translations.close);
|
||||
let search_icon = hyper8_icons::search(translations.search);
|
||||
let t_browse = translations.browse;
|
||||
let t_search = translations.search;
|
||||
|
||||
@@ -135,7 +137,7 @@ impl Layout {
|
||||
|
||||
navigation_script = format!(r#"<script defer src="{root_prefix}navigation.js?{navigation_js_asset_hash}"></script>"#);
|
||||
|
||||
let chevron_right_icon = icons::CHEVRON_RIGHT;
|
||||
let chevron_right_icon = hyper8_icons::CHEVRON_RIGHT;
|
||||
let navigation_templates = formatdoc!(r#"
|
||||
<template id="chevron_right_icon">
|
||||
{chevron_right_icon}
|
||||
@@ -151,12 +153,14 @@ impl Layout {
|
||||
let favicon_png_asset_hash = AssetHashes::FAVICON_PNG;
|
||||
let site_css_asset_hash = asset_hashes.site_css.as_ref().unwrap();
|
||||
|
||||
let meta_generator = meta_generator();
|
||||
|
||||
let head = formatdoc!(r#"
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{META_GENERATOR}
|
||||
{meta_generator}
|
||||
{extra_meta}
|
||||
<link href="{root_prefix}favicon.png?{favicon_png_asset_hash}" rel="icon" type="image/png">
|
||||
<link rel="stylesheet" href="{root_prefix}site.css?{site_css_asset_hash}">
|
||||
@@ -167,8 +171,8 @@ impl Layout {
|
||||
</head>
|
||||
"#);
|
||||
|
||||
let dark_icon = icons::dark(translations.dark_color_scheme);
|
||||
let light_icon = icons::light(translations.light_color_scheme);
|
||||
let dark_icon = hyper8_icons::dark(translations.dark_color_scheme);
|
||||
let light_icon = hyper8_icons::light(translations.light_color_scheme);
|
||||
let theme_toggle = match &site.theme.kind {
|
||||
ThemeKind::Adaptive { .. } => formatdoc!(r#"
|
||||
<div id="theme-toggles">
|
||||
@@ -179,8 +183,6 @@ impl Layout {
|
||||
ThemeKind::Static(_) => String::new()
|
||||
};
|
||||
|
||||
let version_display = env!("HYPER8_VERSION_DISPLAY");
|
||||
|
||||
if self.clipboard_script {
|
||||
templates.push_str(©_button_icon_templates(translations));
|
||||
}
|
||||
@@ -192,9 +194,9 @@ impl Layout {
|
||||
let root_url = traversal.index_asc(site);
|
||||
let site_path = traversal.site_path().normalized();
|
||||
|
||||
let key_icon = icons::KEY;
|
||||
let logo_monochrome_icon = icons::logo_monochrome(None);
|
||||
let logo_polychrome_icon = icons::logo_polychrome(Some("Hyper 8"));
|
||||
let key_icon = hyper8_icons::KEY;
|
||||
let logo_monochrome_icon = hyper8_icons::logo_monochrome(None);
|
||||
let logo_polychrome_icon = hyper8_icons::logo_polychrome(Some("Hyper 8"));
|
||||
let t_adjust_playback_speed_cursor_above_playback_speed_indicator = translations.adjust_playback_speed_cursor_above_playback_speed_indicator;
|
||||
let t_arrow_left_right = translations.arrow_left_right;
|
||||
let t_clear_search_or_close_browse_panel = translations.clear_search_or_close_browse_panel;
|
||||
@@ -234,7 +236,7 @@ impl Layout {
|
||||
{e_site_title}
|
||||
</a>
|
||||
<a href="https://simonrepp.com/hyper8" target="_blank">
|
||||
{logo_monochrome_icon} Hyper 8 Video System {version_display}
|
||||
{logo_monochrome_icon} Hyper 8 Video System {VERSION_WITHOUT_PATCH}
|
||||
</a>
|
||||
<button class="shortcuts">
|
||||
{key_icon} {t_shortcuts}
|
||||
@@ -6,18 +6,18 @@ use std::path::Path;
|
||||
|
||||
use indoc::formatdoc;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
Asc,
|
||||
AudioFile,
|
||||
Desc,
|
||||
Playlist,
|
||||
Site,
|
||||
Translations,
|
||||
Video,
|
||||
VideoFile
|
||||
};
|
||||
use crate::build::widgets::thumbnail;
|
||||
use crate::icons;
|
||||
use crate::util::{
|
||||
casual_duration,
|
||||
html_escape_inside_attribute,
|
||||
@@ -83,7 +83,7 @@ pub fn player(
|
||||
None => String::new()
|
||||
};
|
||||
|
||||
let active_marker_icon = icons::active_marker(translations.active);
|
||||
let active_marker_icon = hyper8_icons::active_marker(translations.active);
|
||||
|
||||
let subtitle_controls;
|
||||
let subtitle_tracks;
|
||||
@@ -126,7 +126,7 @@ pub fn player(
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let subtitles_icon = icons::subtitles(Some(translations.subtitles));
|
||||
let subtitles_icon = hyper8_icons::subtitles(Some(translations.subtitles));
|
||||
let t_no_subtitles = translations.no_subtitles;
|
||||
subtitle_controls = formatdoc!(r#"
|
||||
<div class="selector_control subtitles">
|
||||
@@ -242,7 +242,7 @@ pub fn player(
|
||||
);
|
||||
|
||||
number_context = format!(r#"<span class="number_context">{t_current_video_xxx_of_xxx_in_xxx}</span>"#);
|
||||
replay_indicator = icons::replay(translations.replay);
|
||||
replay_indicator = hyper8_icons::replay(translations.replay);
|
||||
video_iterated_prefix = video_prefix.to_string();
|
||||
} else {
|
||||
current = "";
|
||||
@@ -297,7 +297,7 @@ pub fn player(
|
||||
</div>
|
||||
"#)
|
||||
} else {
|
||||
let replay_icon = icons::replay(translations.replay);
|
||||
let replay_icon = hyper8_icons::replay(translations.replay);
|
||||
let t_replay = translations.replay;
|
||||
formatdoc!(r#"
|
||||
<div class="outro_video_context">
|
||||
@@ -308,14 +308,14 @@ pub fn player(
|
||||
"#)
|
||||
};
|
||||
|
||||
let maximize_icon = icons::maximize(translations.maximize);
|
||||
let volume_loud_icon = icons::volume_loud(translations.loud_volume);
|
||||
let maximize_icon = hyper8_icons::maximize(translations.maximize);
|
||||
let volume_loud_icon = hyper8_icons::volume_loud(translations.loud_volume);
|
||||
|
||||
let video_title = video.title_or_slug_or_generic_label(translations);
|
||||
let video_title_escaped = html_escape_outside_attribute(video_title);
|
||||
|
||||
let embed_intro_extensions = if embed {
|
||||
let link_icon = icons::link(Some(translations.external_link));
|
||||
let link_icon = hyper8_icons::link(Some(translations.external_link));
|
||||
let site_url = site.absolute_url_unchecked();
|
||||
let video_url = match site.clean_urls {
|
||||
true => video_prefix.to_string(),
|
||||
@@ -339,7 +339,7 @@ pub fn player(
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let subtitles_icon = icons::subtitles(Some(translations.subtitles));
|
||||
let subtitles_icon = hyper8_icons::subtitles(Some(translations.subtitles));
|
||||
let t_no_subtitles = translations.no_subtitles;
|
||||
|
||||
formatdoc!(r#"
|
||||
@@ -391,7 +391,7 @@ pub fn player(
|
||||
|
||||
let duration_formatted = precise_duration(duration);
|
||||
|
||||
let play_icon = icons::play(translations.play);
|
||||
let play_icon = hyper8_icons::play(translations.play);
|
||||
formatdoc!(r#"
|
||||
<style>
|
||||
:root {{ --video-aspect: {aspect_ratio}; }}
|
||||
@@ -438,15 +438,15 @@ pub fn player(
|
||||
/// these are injected to the end of the body in the regular page and embed
|
||||
/// layouts.
|
||||
pub fn player_icon_templates(translations: &Translations) -> String {
|
||||
let maximize_icon = icons::maximize(translations.maximize);
|
||||
let minimize_icon = icons::minimize(translations.minimize);
|
||||
let pause_icon = icons::pause(translations.pause);
|
||||
let play_icon = icons::play(translations.play);
|
||||
let replay_icon = icons::replay(translations.replay);
|
||||
let subtitles_icon = icons::subtitles(Some(translations.subtitles));
|
||||
let volume_loud_icon = icons::volume_loud(translations.loud_volume);
|
||||
let volume_medium_icon = icons::volume_medium(translations.medium_volume);
|
||||
let volume_muted_icon = icons::volume_muted(translations.sound_muted);
|
||||
let maximize_icon = hyper8_icons::maximize(translations.maximize);
|
||||
let minimize_icon = hyper8_icons::minimize(translations.minimize);
|
||||
let pause_icon = hyper8_icons::pause(translations.pause);
|
||||
let play_icon = hyper8_icons::play(translations.play);
|
||||
let replay_icon = hyper8_icons::replay(translations.replay);
|
||||
let subtitles_icon = hyper8_icons::subtitles(Some(translations.subtitles));
|
||||
let volume_loud_icon = hyper8_icons::volume_loud(translations.loud_volume);
|
||||
let volume_medium_icon = hyper8_icons::volume_medium(translations.medium_volume);
|
||||
let volume_muted_icon = hyper8_icons::volume_muted(translations.sound_muted);
|
||||
|
||||
formatdoc!(r#"
|
||||
<template id="maximize_icon">
|
||||
@@ -5,16 +5,16 @@ use std::fs;
|
||||
|
||||
use indoc::formatdoc;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
Context,
|
||||
Site,
|
||||
SitePath,
|
||||
Translations
|
||||
SitePath
|
||||
};
|
||||
use crate::build::{AssetHashes, Layout};
|
||||
use crate::build::outline::Traversal;
|
||||
use crate::build::widgets::{copy_button, unlisted_badge};
|
||||
use crate::icons;
|
||||
use crate::util::html_escape_outside_attribute;
|
||||
|
||||
use super::feeds::{
|
||||
@@ -84,7 +84,7 @@ pub fn subscribe_html(
|
||||
let atom_feed_choice = feed_choice(
|
||||
translations.atom_description,
|
||||
ATOM_FILENAME,
|
||||
icons::ATOM,
|
||||
hyper8_icons::ATOM,
|
||||
"Atom",
|
||||
site,
|
||||
site_path,
|
||||
@@ -94,7 +94,7 @@ pub fn subscribe_html(
|
||||
let media_rss_choice = feed_choice(
|
||||
translations.media_rss_description,
|
||||
MEDIA_RSS_FILENAME,
|
||||
&icons::rss(None),
|
||||
&hyper8_icons::rss(None),
|
||||
"Media RSS",
|
||||
site,
|
||||
site_path,
|
||||
@@ -104,7 +104,7 @@ pub fn subscribe_html(
|
||||
let plain_rss_feed_choice = feed_choice(
|
||||
translations.plain_rss_description,
|
||||
PLAIN_RSS_FILENAME,
|
||||
&icons::rss(None),
|
||||
&hyper8_icons::rss(None),
|
||||
translations.plain_rss,
|
||||
site,
|
||||
site_path,
|
||||
@@ -114,7 +114,7 @@ pub fn subscribe_html(
|
||||
let podcast_rss_choice = feed_choice(
|
||||
translations.podcast_rss_description,
|
||||
PODCAST_RSS_FILENAME,
|
||||
&icons::rss(None),
|
||||
&hyper8_icons::rss(None),
|
||||
"Podcast RSS",
|
||||
site,
|
||||
site_path,
|
||||
@@ -17,7 +17,6 @@ use crate::build::{AssetHashes, Layout};
|
||||
use crate::build::outline::{Traversal, TraversalStep};
|
||||
use crate::build::player::player;
|
||||
use crate::build::widgets::{copy_button, links, unlisted_badge};
|
||||
use crate::icons;
|
||||
use crate::util::{
|
||||
html_escape_outside_attribute,
|
||||
localized_date_d_mmmm_yyyy
|
||||
@@ -83,7 +82,7 @@ pub fn video_html(
|
||||
}
|
||||
|
||||
if video.embedding() && site.base_url.is_some() && !video.video_files.is_empty() {
|
||||
let embed_icon = icons::EMBED;
|
||||
let embed_icon = hyper8_icons::EMBED;
|
||||
|
||||
let t_embed = &translations.embed;
|
||||
let t_embed_codes_permalink = &translations.embed_codes_permalink;
|
||||
@@ -100,7 +99,7 @@ pub fn video_html(
|
||||
if video.download() && !video.video_files.is_empty() {
|
||||
let t_download = translations.download;
|
||||
let t_download_permalink = translations.download_permalink;
|
||||
let download_icon = icons::DOWNLOAD;
|
||||
let download_icon = hyper8_icons::DOWNLOAD;
|
||||
|
||||
let download = formatdoc!(r#"
|
||||
<a href="{t_download_permalink}{index_suffix}">
|
||||
@@ -113,7 +112,7 @@ pub fn video_html(
|
||||
|
||||
if let Some(playlist) = playlist {
|
||||
let t_playlist = translations.playlist;
|
||||
let playlist_icon = icons::playlist(None);
|
||||
let playlist_icon = hyper8_icons::playlist(None);
|
||||
let playlist_site_path = playlist.site_path.normalized();
|
||||
let toggle_playlist = formatdoc!(r#"
|
||||
<button data-site-path="{playlist_site_path}" id="toggle_playlist">
|
||||
@@ -126,7 +125,7 @@ pub fn video_html(
|
||||
|
||||
if !video.subtitles.is_empty() {
|
||||
let t_transcript = translations.transcript;
|
||||
let subtitles_icon = icons::subtitles(None);
|
||||
let subtitles_icon = hyper8_icons::subtitles(None);
|
||||
let toggle_transcript = formatdoc!(r#"
|
||||
<button id="toggle_transcript">
|
||||
{subtitles_icon} {t_transcript}
|
||||
@@ -6,15 +6,15 @@ use std::sync::Arc;
|
||||
|
||||
use indoc::formatdoc;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
Banner,
|
||||
Language,
|
||||
Link,
|
||||
Site,
|
||||
Translations,
|
||||
Video
|
||||
};
|
||||
use crate::icons;
|
||||
use crate::util::{
|
||||
casual_duration,
|
||||
html_escape_outside_attribute
|
||||
@@ -67,7 +67,7 @@ pub fn copy_button(
|
||||
content_value: &str,
|
||||
label: &str
|
||||
) -> String {
|
||||
let copy_icon = icons::COPY;
|
||||
let copy_icon = hyper8_icons::COPY;
|
||||
format!(r#"
|
||||
<button data-{content_key}="{content_value}" data-copy>
|
||||
<span class="icon">{copy_icon}</span>
|
||||
@@ -80,9 +80,9 @@ pub fn copy_button(
|
||||
/// these are injected to the end of the body in the regular page and embed
|
||||
/// layouts.
|
||||
pub fn copy_button_icon_templates(translations: &Translations) -> String {
|
||||
let copy_icon = icons::COPY;
|
||||
let failure_icon = icons::failure(translations.failed);
|
||||
let success_icon = icons::success(translations.copied);
|
||||
let copy_icon = hyper8_icons::COPY;
|
||||
let failure_icon = hyper8_icons::failure(translations.failed);
|
||||
let success_icon = hyper8_icons::success(translations.copied);
|
||||
|
||||
formatdoc!(r#"
|
||||
<template id="copy_icon">
|
||||
@@ -102,7 +102,7 @@ pub fn links(language: &Language, links: &[Link]) -> String {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let link_icon = icons::link(Some(language.translations.external_link));
|
||||
let link_icon = hyper8_icons::link(Some(language.translations.external_link));
|
||||
|
||||
let items = links
|
||||
.iter()
|
||||
@@ -134,7 +134,7 @@ pub fn subscribe_button(
|
||||
site: &Site
|
||||
) -> String {
|
||||
let index_suffix = site.index_suffix();
|
||||
let feed_icon = icons::rss(Some(site.language.translations.feed));
|
||||
let feed_icon = hyper8_icons::rss(Some(site.language.translations.feed));
|
||||
let t_subscribe = site.language.translations.subscribe;
|
||||
|
||||
formatdoc!(r#"
|
||||
@@ -9,15 +9,14 @@ use std::sync::Arc;
|
||||
use chrono::NaiveDate;
|
||||
use url::Url;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
Asc,
|
||||
Banner,
|
||||
CollectionUpdate,
|
||||
Comparison,
|
||||
Container,
|
||||
ContainerOrder,
|
||||
Context,
|
||||
Desc,
|
||||
FileMeta,
|
||||
GetAnyResult,
|
||||
HyperDir,
|
||||
@@ -29,8 +28,6 @@ use crate::{
|
||||
PlaylistUpdate,
|
||||
Site,
|
||||
SitePath,
|
||||
Translations,
|
||||
TreeViewOrder,
|
||||
Video,
|
||||
VideoOrder,
|
||||
VideoUpdate
|
||||
@@ -515,36 +512,6 @@ impl Collection {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn containers_in_tree_view_order(
|
||||
&self,
|
||||
language: &Language,
|
||||
tree_view_order: &TreeViewOrder
|
||||
) -> Vec<Container> {
|
||||
let mut containers = Vec::new();
|
||||
|
||||
for playlist in &self.playlists {
|
||||
containers.push(Container::Playlist(playlist.clone()));
|
||||
}
|
||||
|
||||
for subcollection in &self.subcollections {
|
||||
containers.push(Container::Collection(subcollection.clone()));
|
||||
}
|
||||
|
||||
let (comparison, direction) = match tree_view_order {
|
||||
TreeViewOrder::Default |
|
||||
TreeViewOrder::SiteOrder =>
|
||||
Container::container_order_comparison_and_direction(&self.container_order),
|
||||
TreeViewOrder::TitleAsc =>
|
||||
(Container::cmp_slug_then_title as Comparison<Container>, Asc),
|
||||
TreeViewOrder::TitleDesc =>
|
||||
(Container::cmp_slug_then_title as Comparison<Container>, Desc)
|
||||
};
|
||||
|
||||
containers.sort_unstable_by(|a, b| comparison(a, b, &language.icu_collator, direction));
|
||||
|
||||
containers
|
||||
}
|
||||
|
||||
/// When we convert between types (collection, playlist, video) we pass
|
||||
/// over existing inherited options using this method.
|
||||
pub fn copy_inherited_options(&self) -> InheritedOptions {
|
||||
@@ -1571,28 +1538,6 @@ impl Collection {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn videos_in_tree_view_order(
|
||||
&self,
|
||||
language: &Language,
|
||||
tree_view_order: &TreeViewOrder
|
||||
) -> Vec<Arc<Video>> {
|
||||
let mut videos: Vec<Arc<Video>> = self.videos.clone();
|
||||
|
||||
let (comparison, direction) = match tree_view_order {
|
||||
TreeViewOrder::Default |
|
||||
TreeViewOrder::SiteOrder =>
|
||||
Video::video_order_comparison_and_direction(&self.video_order),
|
||||
TreeViewOrder::TitleAsc =>
|
||||
(Video::cmp_slug_then_title as Comparison<Video>, Asc),
|
||||
TreeViewOrder::TitleDesc =>
|
||||
(Video::cmp_slug_then_title as Comparison<Video>, Desc)
|
||||
};
|
||||
|
||||
videos.sort_unstable_by(|a, b| comparison(a, b, &language.icu_collator, direction));
|
||||
|
||||
videos
|
||||
}
|
||||
|
||||
pub fn write_manifest(&self, context: &Context) {
|
||||
let mut eno = String::new();
|
||||
|
||||
@@ -26,8 +26,6 @@ use crate::{
|
||||
Site,
|
||||
SiteContent,
|
||||
SitePath,
|
||||
Theme,
|
||||
TreeViewOrder,
|
||||
Video,
|
||||
Worker
|
||||
};
|
||||
@@ -43,9 +41,6 @@ pub struct Context {
|
||||
pub cache_dir: PathBuf,
|
||||
pub completed_jobs: Mutex<Vec<CompletedJob>>,
|
||||
pub deploying: Mutex<DeploymentState>,
|
||||
pub editor_language: Mutex<Language>,
|
||||
pub editor_theme: Mutex<Theme>,
|
||||
pub editor_tree_view_order: Mutex<TreeViewOrder>,
|
||||
pub job_queue: Mutex<Vec<QueuedJob>>,
|
||||
pub site: Mutex<Arc<Site>>,
|
||||
pub site_dir: PathBuf,
|
||||
@@ -253,7 +248,11 @@ impl Context {
|
||||
/// empty, the empty site itself) and convert it to a collection. This
|
||||
/// may fail if we're converting from a video that is inside a playlist,
|
||||
/// as a playlist can not contain a collection.
|
||||
pub fn convert_to_collection(&self, site_path: &SitePath) -> Result<Arc<Collection>, String> {
|
||||
pub fn convert_to_collection(
|
||||
&self,
|
||||
language: &Language,
|
||||
site_path: &SitePath
|
||||
) -> Result<Arc<Collection>, String> {
|
||||
let mut site_mut = self.get_site_mut();
|
||||
|
||||
if site_path.is_root_path() {
|
||||
@@ -369,7 +368,6 @@ impl Context {
|
||||
match self.get_any(site_path) {
|
||||
GetAnyResult::Collection(collection) => Ok(collection),
|
||||
GetAnyResult::None => {
|
||||
let language = self.get_editor_language();
|
||||
Err(language.translations.the_resource_xxx_was_not_found_message(site_path.normalized()))
|
||||
}
|
||||
GetAnyResult::Playlist(playlist) => {
|
||||
@@ -469,7 +467,11 @@ impl Context {
|
||||
/// playlist can not contain), or if we're converting from a video that
|
||||
/// is contained inside a playlist (as a playlist can not contain another
|
||||
/// playlist).
|
||||
pub fn convert_to_playlist(&self, site_path: &SitePath) -> Result<Arc<Playlist>, String> {
|
||||
pub fn convert_to_playlist(
|
||||
&self,
|
||||
language: &Language,
|
||||
site_path: &SitePath
|
||||
) -> Result<Arc<Playlist>, String> {
|
||||
let mut site_mut = self.get_site_mut();
|
||||
|
||||
if site_path.is_root_path() {
|
||||
@@ -631,7 +633,6 @@ impl Context {
|
||||
Ok(playlist)
|
||||
}
|
||||
GetAnyResult::None => {
|
||||
let language = self.get_editor_language();
|
||||
Err(language.translations.the_resource_xxx_was_not_found_message(site_path.normalized()))
|
||||
}
|
||||
GetAnyResult::Playlist(playlist) => Ok(playlist),
|
||||
@@ -693,7 +694,11 @@ impl Context {
|
||||
/// Take the collection or playlist at the given path (or if the site is still empty,
|
||||
/// the empty site itself) and convert it to a video. This only succeeds if we're
|
||||
/// converting from a collection or playlist that contains exactly one video (or none).
|
||||
pub fn convert_to_video(&self, site_path: &SitePath) -> Result<Arc<Video>, String> {
|
||||
pub fn convert_to_video(
|
||||
&self,
|
||||
language: &Language,
|
||||
site_path: &SitePath
|
||||
) -> Result<Arc<Video>, String> {
|
||||
let mut site_mut = self.get_site_mut();
|
||||
|
||||
if site_path.is_root_path() {
|
||||
@@ -990,7 +995,6 @@ impl Context {
|
||||
Ok(video)
|
||||
}
|
||||
GetAnyResult::None => {
|
||||
let language = self.get_editor_language();
|
||||
Err(language.translations.the_resource_xxx_was_not_found_message(site_path.normalized()))
|
||||
}
|
||||
GetAnyResult::Playlist(playlist) => {
|
||||
@@ -1212,24 +1216,6 @@ impl Context {
|
||||
self.site.lock().unwrap().get_collection(site_path)
|
||||
}
|
||||
|
||||
/// Returns cloned `Language`, allowing subsequent read-only usage
|
||||
/// of the language without keeping a lock on it.
|
||||
pub fn get_editor_language(&self) -> Language {
|
||||
self.editor_language.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Returns cloned `TreeViewOrder`, allowing subsequent read-only usage
|
||||
/// of the tree view order without keeping a lock on it.
|
||||
pub fn get_editor_tree_view_order(&self) -> TreeViewOrder {
|
||||
self.editor_tree_view_order.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Returns cloned `Theme`, allowing subsequent read-only usage
|
||||
/// of the theme without keeping a lock on it.
|
||||
pub fn get_editor_theme(&self) -> Theme {
|
||||
self.editor_theme.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Returns the playlist at the given path.
|
||||
pub fn get_playlist(&self, site_path: &SitePath) -> Option<Arc<Playlist>> {
|
||||
self.site.lock().unwrap().get_playlist(site_path)
|
||||
@@ -1258,9 +1244,6 @@ impl Context {
|
||||
build_dir: PathBuf,
|
||||
cache: Mutex<Cache>,
|
||||
cache_dir: PathBuf,
|
||||
editor_language: &str,
|
||||
editor_theme: &str,
|
||||
editor_tree_view_order: TreeViewOrder,
|
||||
site: Arc<Site>,
|
||||
site_dir: PathBuf
|
||||
) -> Context {
|
||||
@@ -1270,9 +1253,6 @@ impl Context {
|
||||
cache_dir,
|
||||
completed_jobs: Mutex::new(Vec::new()),
|
||||
deploying: Mutex::new(DeploymentState::new()),
|
||||
editor_language: Mutex::new(Language::from_code(editor_language)),
|
||||
editor_theme: Mutex::new(Theme::from_key(editor_theme).unwrap_or_else(|_| Theme::default())),
|
||||
editor_tree_view_order: Mutex::new(editor_tree_view_order),
|
||||
job_queue: Mutex::new(Vec::new()),
|
||||
site: Mutex::new(site),
|
||||
site_dir
|
||||
@@ -1,12 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::{Context, Site, Translations};
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{Context, Site};
|
||||
|
||||
mod ftp;
|
||||
mod ftp_config;
|
||||
@@ -86,70 +86,6 @@ pub fn config_probe(
|
||||
panic!("Caller must ensure that a complete FTP or rsync config is available")
|
||||
}
|
||||
|
||||
/// Performs deployment in a synchronous fashion, reporting back all messages
|
||||
/// and errors to the terminal. Also asks for explicit ("opt-in") confirmation
|
||||
/// before performing the actual deployment.
|
||||
pub fn perform_deploy_blocking_cli(
|
||||
context_ref: &Arc<Context>,
|
||||
site: &Site,
|
||||
translations: &Translations
|
||||
) -> Result<(), String> {
|
||||
if site.base_url.is_none() {
|
||||
let site_manifest_path = context_ref.site_dir.join("site.eno");
|
||||
let message = translations.public_address_must_be_configured_for_deployment_text_cli(&site_manifest_path);
|
||||
return Err(message);
|
||||
}
|
||||
|
||||
if !site.complete_deployment_config() {
|
||||
let message = translations.deployment_configuration_is_incomplete_can_not_deploy.to_string();
|
||||
return Err(message);
|
||||
}
|
||||
|
||||
let t_really_deploy = translations.really_deploy;
|
||||
let base_url = &site.base_url.as_ref().unwrap().representation;
|
||||
println!("{t_really_deploy} ({base_url})");
|
||||
println!("{}", translations.type_y_and_press_enter_to_confirm_anything_else_will_abort);
|
||||
|
||||
let mut input = String::new();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
if input.trim() != "y" {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.to_string())
|
||||
}
|
||||
|
||||
if let Err(err) = config_probe(context_ref, site, translations) {
|
||||
let t_deployment_configuration_could_not_be_verified = translations.deployment_configuration_could_not_be_verified;
|
||||
let message = format!("{t_deployment_configuration_could_not_be_verified} ({err})");
|
||||
return Err(message);
|
||||
}
|
||||
|
||||
start_deploy(context_ref, site, translations)?;
|
||||
|
||||
let mut seen_feedback = 0;
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
if let Ok(deployment) = context_ref.deploying.lock() {
|
||||
let feedback = &deployment.feedback;
|
||||
|
||||
if feedback.len() > seen_feedback {
|
||||
eprintln!("{}", &feedback[seen_feedback..]);
|
||||
seen_feedback = feedback.len();
|
||||
}
|
||||
|
||||
if !deployment.active() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Caller needs to ensure that there is either a complete FTP or rsync
|
||||
/// configuration available, otherwise will panic.
|
||||
pub fn start_deploy(
|
||||
@@ -12,7 +12,9 @@ use std::thread;
|
||||
use chrono::{DateTime, Utc};
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::{Context, Site, SitePath, Translations};
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{Context, Site, SitePath};
|
||||
use crate::util::{
|
||||
nanoid,
|
||||
string_from_os_string
|
||||
@@ -10,7 +10,9 @@ use std::thread;
|
||||
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::{Context, Site, SitePath, Translations};
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{Context, Site, SitePath};
|
||||
use crate::util::nanoid;
|
||||
|
||||
const RSYNC_CONFIG_PROBE_FILE_NAME: &str = ".hyper8_rsync_config_probe.txt";
|
||||
@@ -179,8 +181,6 @@ pub fn start_deploy(
|
||||
}
|
||||
}
|
||||
|
||||
let context = Arc::clone(context_ref);
|
||||
|
||||
let remote_deploy_path = format!(
|
||||
"{user}@{server}:{path}",
|
||||
path = Path::new(site.ssh_config.path_unchecked()).join("./").to_string_lossy(),
|
||||
@@ -192,11 +192,13 @@ pub fn start_deploy(
|
||||
|
||||
command.arg("-avP");
|
||||
command.arg("--delete");
|
||||
command.arg(context.build_dir.join("./"));
|
||||
command.arg(context_ref.build_dir.join("./"));
|
||||
command.arg(remote_deploy_path);
|
||||
|
||||
command.stdout(Stdio::piped());
|
||||
|
||||
let context = Arc::clone(context_ref);
|
||||
|
||||
thread::spawn(move || {
|
||||
match command.spawn() {
|
||||
Ok(mut child) => {
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Simon Repp
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::fs;
|
||||
@@ -5,6 +5,8 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::Context;
|
||||
|
||||
mod encode;
|
||||
@@ -21,18 +23,31 @@ pub use worker::Worker;
|
||||
|
||||
use encode::FFmpegEncodingStats;
|
||||
|
||||
pub fn start_workers(count: usize, context_ref: &Arc<Context>) {
|
||||
// TODO: This currently "bakes" the language into the worker, i.e. whatever the
|
||||
// passed language when the worker is started, remains the language in which
|
||||
// it produces messages throughout the session (no matter if the language has
|
||||
// then changed). I.e. we need to move the passing of language closer to the
|
||||
// invocation (e.g. passing it in the Job/JobDesc as a next step, or giving
|
||||
// a ref-counted language handle to the worker that it shares with context
|
||||
// itself). (In an ideal scenario the language would only be brought into
|
||||
// the equation at display time (i.e. all errors are just semantic markers
|
||||
// that are then rendered into the given language when they are displayed),
|
||||
// but this can be done someday (TM) because it's so complex and has little
|
||||
// reward in 99% of real life cases).
|
||||
pub fn start_workers(
|
||||
context_ref: &Arc<Context>,
|
||||
count: usize,
|
||||
translations: &Translations
|
||||
) {
|
||||
for worker_number in 1..=count {
|
||||
let context = Arc::clone(context_ref);
|
||||
let translations = translations.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let high_priority_only = worker_number == 1;
|
||||
|
||||
let worker = Worker::new(high_priority_only, worker_number);
|
||||
|
||||
let language = context.get_editor_language();
|
||||
let translations = &language.translations;
|
||||
|
||||
loop {
|
||||
let job = match context.take_next_job(&worker) {
|
||||
Some(job) => job,
|
||||
@@ -48,14 +63,14 @@ pub fn start_workers(count: usize, context_ref: &Arc<Context>) {
|
||||
format,
|
||||
&context,
|
||||
job.id.clone(),
|
||||
translations,
|
||||
&translations,
|
||||
video
|
||||
).err()
|
||||
}
|
||||
JobDesc::AudioMeta { video } => {
|
||||
Worker::compute_audio_metadata(
|
||||
&context,
|
||||
translations,
|
||||
&translations,
|
||||
video
|
||||
).err()
|
||||
}
|
||||
@@ -63,21 +78,21 @@ pub fn start_workers(count: usize, context_ref: &Arc<Context>) {
|
||||
Worker::compute_collection_fast_tasks(
|
||||
collection,
|
||||
&context,
|
||||
translations
|
||||
&translations
|
||||
).err()
|
||||
}
|
||||
JobDesc::PlaylistFastTasks { playlist } => {
|
||||
Worker::compute_playlist_fast_tasks(
|
||||
&context,
|
||||
playlist,
|
||||
translations
|
||||
&translations
|
||||
).err()
|
||||
}
|
||||
JobDesc::VideoFastTasks { poster_aspect, video } => {
|
||||
Worker::compute_video_fast_tasks(
|
||||
&context,
|
||||
poster_aspect,
|
||||
translations,
|
||||
&translations,
|
||||
video
|
||||
).err()
|
||||
}
|
||||
@@ -85,7 +100,7 @@ pub fn start_workers(count: usize, context_ref: &Arc<Context>) {
|
||||
Worker::render_video_format(
|
||||
&context,
|
||||
job.id.clone(),
|
||||
translations,
|
||||
&translations,
|
||||
video,
|
||||
format
|
||||
).err()
|
||||
@@ -95,7 +110,7 @@ pub fn start_workers(count: usize, context_ref: &Arc<Context>) {
|
||||
&context,
|
||||
poster_aspect,
|
||||
timecode,
|
||||
translations,
|
||||
&translations,
|
||||
video
|
||||
).err()
|
||||
}
|
||||
@@ -8,6 +8,8 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
AudioFile,
|
||||
AudioFormat,
|
||||
@@ -16,7 +18,6 @@ use crate::{
|
||||
EncodingApproach,
|
||||
FileMeta,
|
||||
JobProgress,
|
||||
Translations,
|
||||
Video,
|
||||
VideoFile,
|
||||
VideoFormat
|
||||
@@ -3,15 +3,15 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
AudioFormat,
|
||||
Collection,
|
||||
Playlist,
|
||||
Translations,
|
||||
Video,
|
||||
VideoFormat
|
||||
};
|
||||
use crate::editor::routes;
|
||||
|
||||
/// The job description specifies what needs to be done in a processing job,
|
||||
/// and to which resource (collection, playlist or video) it applies.
|
||||
@@ -105,26 +105,6 @@ impl JobDesc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The url for editing the collection, playlist or video which is part of
|
||||
/// this job description in the editor.
|
||||
pub fn target_edit_url(&self) -> String {
|
||||
match self {
|
||||
JobDesc::CollectionFastTasks { collection } => {
|
||||
routes::collection_edit(collection)
|
||||
}
|
||||
JobDesc::PlaylistFastTasks { playlist } => {
|
||||
routes::playlist_edit(playlist)
|
||||
}
|
||||
JobDesc::AudioFormat { video, .. } |
|
||||
JobDesc::AudioMeta { video } |
|
||||
JobDesc::VideoFastTasks { video, .. } |
|
||||
JobDesc::VideoFormat { video, .. } |
|
||||
JobDesc::VideoPickPoster { video, .. } => {
|
||||
routes::video_edit(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for JobDesc {
|
||||
@@ -7,11 +7,9 @@ use std::process::{Command};
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use crate::{
|
||||
AudioMeta,
|
||||
Translations,
|
||||
VideoMeta
|
||||
};
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{AudioMeta, VideoMeta};
|
||||
|
||||
pub const FFPROBE: &str = if cfg!(windows) { "ffprobe.exe" } else { "ffprobe" };
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
AudioFormat,
|
||||
AudioMetaCached,
|
||||
@@ -21,7 +23,6 @@ use crate::{
|
||||
PosterFile,
|
||||
PosterSourceCached,
|
||||
ResizeMode,
|
||||
Translations,
|
||||
Video,
|
||||
VideoFormat,
|
||||
VideoMetaCached,
|
||||
@@ -8,7 +8,7 @@ use icu_locale::Locale as IcuLocale;
|
||||
use icu_locale::locale as icu_locale;
|
||||
use log::warn;
|
||||
|
||||
use crate::Translations;
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Language {
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
mod aspect_ratio;
|
||||
mod audio_file;
|
||||
mod audio_format;
|
||||
mod audio_meta;
|
||||
mod banner;
|
||||
mod build;
|
||||
mod cache;
|
||||
mod collection;
|
||||
mod container;
|
||||
mod container_order;
|
||||
mod context;
|
||||
mod deploy;
|
||||
mod file_meta;
|
||||
mod font;
|
||||
mod hyper_dir;
|
||||
mod image_processor;
|
||||
mod inherited_options;
|
||||
mod jobs;
|
||||
mod language;
|
||||
mod link;
|
||||
mod lqip;
|
||||
mod manifest;
|
||||
mod markdown_subset;
|
||||
mod platform_integration;
|
||||
mod playlist;
|
||||
mod poster_assets;
|
||||
mod poster_file;
|
||||
mod poster_meta;
|
||||
mod preview_server;
|
||||
mod site;
|
||||
mod site_path;
|
||||
mod site_url;
|
||||
mod sort;
|
||||
mod subtitles;
|
||||
mod theme;
|
||||
mod video;
|
||||
mod video_file;
|
||||
mod video_format;
|
||||
mod video_meta;
|
||||
mod video_order;
|
||||
|
||||
pub mod browser_support;
|
||||
pub mod util;
|
||||
pub mod watcher;
|
||||
|
||||
use audio_meta::AudioMeta;
|
||||
use cache::{AudioMetaCached, PosterAssetsCached, PosterSourceCached, VideoMetaCached};
|
||||
use deploy::{FtpConfig, SshConfig};
|
||||
use image_processor::{ImageProcessor, ResizeMode};
|
||||
use inherited_options::InheritedOptions;
|
||||
use jobs::{CompletedJob, QueuedJob, Worker};
|
||||
use lqip::Lqip;
|
||||
use markdown_subset::MarkdownSubset;
|
||||
use platform_integration::{PlatformIntegrationImage, PlatformIntegrationMeta};
|
||||
use poster_assets::{PosterAsset, PosterAssets};
|
||||
use poster_meta::PosterMeta;
|
||||
use sort::SortDirection;
|
||||
use video_format::{Codec, EncodingApproach};
|
||||
use video_meta::VideoMeta;
|
||||
|
||||
pub use aspect_ratio::AspectRatio;
|
||||
pub use audio_file::AudioFile;
|
||||
pub use audio_format::AudioFormat;
|
||||
pub use banner::Banner;
|
||||
pub use build::perform_build;
|
||||
pub use cache::Cache;
|
||||
pub use collection::Collection;
|
||||
pub use container::Container;
|
||||
pub use container_order::ContainerOrder;
|
||||
pub use context::{
|
||||
CollectionUpdate,
|
||||
Context,
|
||||
GetAnyResult,
|
||||
PlaylistUpdate,
|
||||
SiteUpdate,
|
||||
VideoParent,
|
||||
VideoUpdate
|
||||
};
|
||||
pub use deploy::DeploymentState;
|
||||
// TODO: Rename "config_probe" to something clearer? (e.g. test_deployment_config or such)
|
||||
pub use deploy::{config_probe, start_deploy};
|
||||
pub use file_meta::FileMeta;
|
||||
pub use font::Font;
|
||||
pub use hyper_dir::HyperDir;
|
||||
pub use jobs::{JobDesc, JobProgress};
|
||||
pub use jobs::start_workers;
|
||||
pub use language::Language;
|
||||
pub use link::Link;
|
||||
pub use manifest::MAX_SYNOPSIS_CHARS;
|
||||
pub use platform_integration::PlatformIntegration;
|
||||
pub use playlist::Playlist;
|
||||
pub use poster_file::PosterFile;
|
||||
pub use preview_server::start as start_preview_server;
|
||||
pub use site::{Site, SiteContent};
|
||||
pub use site_path::SitePath;
|
||||
pub use site_url::SiteUrl;
|
||||
pub use sort::{Asc, Comparison, Desc};
|
||||
pub use subtitles::SubtitleFile;
|
||||
pub use theme::{Theme, ThemeKind};
|
||||
pub use video::Video;
|
||||
pub use video_file::VideoFile;
|
||||
pub use video_format::VideoFormat;
|
||||
pub use video_order::VideoOrder;
|
||||
@@ -9,7 +9,9 @@
|
||||
//! - https://ogp.me/
|
||||
//! - https://developers.facebook.com/docs/sharing/webmasters/#video
|
||||
|
||||
use crate::{Site, Translations};
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::Site;
|
||||
use crate::util::html_escape_inside_attribute;
|
||||
|
||||
pub struct Embed {
|
||||
@@ -137,7 +139,7 @@ impl PlatformIntegrationMeta {
|
||||
|
||||
// Open Graph metadata (for link previews and content embeds)
|
||||
|
||||
// TODO: Sketched out for usage when we have audio(-only) content too
|
||||
// TODO: Uncomment and put the new audio(-only) content to use here
|
||||
// if let Some(audio) = &self.audio {
|
||||
// tags.push(format!(r#"<meta property="og:audio" content="{audio}"/>"#));
|
||||
// }
|
||||
@@ -9,12 +9,11 @@ use std::sync::Arc;
|
||||
use chrono::NaiveDate;
|
||||
use url::Url;
|
||||
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
use crate::{
|
||||
Asc,
|
||||
Banner,
|
||||
Comparison,
|
||||
Context,
|
||||
Desc,
|
||||
FileMeta,
|
||||
GetAnyResult,
|
||||
HyperDir,
|
||||
@@ -24,8 +23,6 @@ use crate::{
|
||||
PlatformIntegration,
|
||||
Site,
|
||||
SitePath,
|
||||
Translations,
|
||||
TreeViewOrder,
|
||||
Video,
|
||||
VideoOrder,
|
||||
VideoUpdate
|
||||
@@ -91,14 +88,6 @@ impl Playlist {
|
||||
fn apply_manifest(&mut self, context: &Context, manifest_path: &Path) {
|
||||
let content = fs::read_to_string(manifest_path).unwrap();
|
||||
|
||||
// TODO: Tricky manifest problem/question in general: If there is anything wrong
|
||||
// with the manifest (especially: syntax error rendering the entire manifest useless),
|
||||
// we can potentially erase everything contained in it by doing an update via the browser
|
||||
// editor (it persists what has been read - nothing!). Consequently we should e.g. disable
|
||||
// the edit form/updates via browser editor while there are issues with the manifest, or
|
||||
// try to read in some things in a "broken state", e.g. registering a poster although the
|
||||
// image referenced was not found.
|
||||
|
||||
let document = match enolib::parse(&content) {
|
||||
Ok(document) => document,
|
||||
Err(err) => {
|
||||
@@ -1070,25 +1059,6 @@ impl Playlist {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn videos_in_tree_view_order(
|
||||
&self,
|
||||
language: &Language,
|
||||
tree_view_order: &TreeViewOrder
|
||||
) -> Vec<Arc<Video>> {
|
||||
let mut videos: Vec<Arc<Video>> = self.videos.clone();
|
||||
|
||||
let (comparison, direction) = match tree_view_order {
|
||||
TreeViewOrder::Default |
|
||||
TreeViewOrder::SiteOrder => Video::video_order_comparison_and_direction(&self.order),
|
||||
TreeViewOrder::TitleAsc => (Video::cmp_slug_then_title as Comparison<Video>, Asc),
|
||||
TreeViewOrder::TitleDesc => (Video::cmp_slug_then_title as Comparison<Video>, Desc)
|
||||
};
|
||||
|
||||
videos.sort_unstable_by(|a, b| comparison(a, b, &language.icu_collator, direction));
|
||||
|
||||
videos
|
||||
}
|
||||
|
||||
pub fn write_manifest(&self, context: &Context) {
|
||||
let mut eno = String::new();
|
||||
|
||||
@@ -19,8 +19,8 @@ const MAX_PORT_ATTEMPTS: u16 = 10;
|
||||
#[actix_web::main]
|
||||
pub async fn start(
|
||||
build_dir: &Path,
|
||||
ip_requested: &Option<IpAddr>,
|
||||
port_requested: &Option<u16>
|
||||
ip_requested: Option<&IpAddr>,
|
||||
port_requested: Option<&u16>
|
||||
) -> Result<(), ()> {
|
||||
let bind_server = |build_dir_moving: PathBuf, ip: IpAddr, port: u16| {
|
||||
HttpServer::new(move || {
|
||||
@@ -35,10 +35,10 @@ pub async fn start(
|
||||
.bind((ip, port))
|
||||
};
|
||||
|
||||
let ip = ip_requested.unwrap_or(DEFAULT_PREVIEW_IP);
|
||||
let ip = ip_requested.unwrap_or(&DEFAULT_PREVIEW_IP);
|
||||
|
||||
let (server, port_bound) = if let Some(port) = port_requested {
|
||||
match bind_server(build_dir.to_path_buf(), ip, *port) {
|
||||
match bind_server(build_dir.to_path_buf(), *ip, *port) {
|
||||
Ok(server) => (server, *port),
|
||||
Err(err) => {
|
||||
eprintln!("Could not bind preview server to {ip}:{port} ({err})");
|
||||
@@ -49,7 +49,7 @@ pub async fn start(
|
||||
let mut port = DEFAULT_PREVIEW_PORT;
|
||||
|
||||
loop {
|
||||
match bind_server(build_dir.to_path_buf(), ip, port) {
|
||||
match bind_server(build_dir.to_path_buf(), *ip, port) {
|
||||
Ok(server) => break (server, port),
|
||||
Err(err) => {
|
||||
if port > DEFAULT_PREVIEW_PORT + MAX_PORT_ATTEMPTS {
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2024 Simon Repp
|
||||
// SPDX-FileCopyrightText: 2024-2025 Simon Repp
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use indoc::formatdoc;
|
||||
|
||||
use crate::Translations;
|
||||
use hyper8_translations::Translations;
|
||||
|
||||
mod color;
|
||||
mod dark_cool;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user