fix: maybe preview works now
This commit is contained in:
@@ -614,7 +614,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn loads_empty_defaults() {
|
fn loads_empty_defaults() {
|
||||||
clear_keyring(AiEndpointKind::Online);
|
|
||||||
let db = setup();
|
let db = setup();
|
||||||
let settings = load_ai_settings(db.conn(), false).unwrap();
|
let settings = load_ai_settings(db.conn(), false).unwrap();
|
||||||
assert!(!settings.offline_mode);
|
assert!(!settings.offline_mode);
|
||||||
@@ -624,6 +623,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore = "touches system keychain; run explicitly when validating keychain integration"]
|
||||||
fn saves_online_endpoint_with_keychain_secret() {
|
fn saves_online_endpoint_with_keychain_secret() {
|
||||||
clear_keyring(AiEndpointKind::Online);
|
clear_keyring(AiEndpointKind::Online);
|
||||||
let db = setup();
|
let db = setup();
|
||||||
@@ -684,6 +684,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore = "touches system keychain; run explicitly when validating keychain integration"]
|
||||||
fn run_one_shot_uses_active_endpoint_and_parses_response() {
|
fn run_one_shot_uses_active_endpoint_and_parses_response() {
|
||||||
clear_keyring(AiEndpointKind::Online);
|
clear_keyring(AiEndpointKind::Online);
|
||||||
let server = spawn_test_server(|request| {
|
let server = spawn_test_server(|request| {
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
|
use crate::db::queries;
|
||||||
use crate::engine::{EngineError, EngineResult};
|
use crate::engine::{EngineError, EngineResult};
|
||||||
use crate::model::{Post, ProjectMetadata};
|
use crate::model::{Post, ProjectMetadata};
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
GeneratedWriteOutcome, build_calendar_json, build_canonical_post_path,
|
GeneratedWriteOutcome, build_calendar_json, build_canonical_post_path,
|
||||||
render_markdown_to_html, render_starter_list_page, render_starter_single_post_page,
|
render_markdown_to_html, render_starter_list_page_with_media_map, render_starter_single_post_page_with_media_map,
|
||||||
write_generated_file,
|
write_generated_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,17 +34,29 @@ pub fn generate_starter_site(
|
|||||||
language: &str,
|
language: &str,
|
||||||
) -> EngineResult<GenerationReport> {
|
) -> EngineResult<GenerationReport> {
|
||||||
let mut report = GenerationReport::default();
|
let mut report = GenerationReport::default();
|
||||||
|
let media_rewrite_map = build_media_rewrite_map(conn, project_id)?;
|
||||||
|
|
||||||
let list_input = posts
|
let list_input = posts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|source| (source.post.clone(), source.body_markdown.clone()))
|
.map(|source| (source.post.clone(), source.body_markdown.clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let index_page = render_starter_list_page(&list_input, metadata, language)
|
let index_page = render_starter_list_page_with_media_map(
|
||||||
|
&list_input,
|
||||||
|
metadata,
|
||||||
|
language,
|
||||||
|
media_rewrite_map.clone(),
|
||||||
|
)
|
||||||
.map_err(|error| EngineError::Parse(error.to_string()))?;
|
.map_err(|error| EngineError::Parse(error.to_string()))?;
|
||||||
write_out(conn, output_dir, project_id, &index_page.relative_path, &index_page.html, &mut report)?;
|
write_out(conn, output_dir, project_id, &index_page.relative_path, &index_page.html, &mut report)?;
|
||||||
|
|
||||||
for source in posts {
|
for source in posts {
|
||||||
let rendered = render_starter_single_post_page(&source.post, &source.body_markdown, metadata, language)
|
let rendered = render_starter_single_post_page_with_media_map(
|
||||||
|
&source.post,
|
||||||
|
&source.body_markdown,
|
||||||
|
metadata,
|
||||||
|
language,
|
||||||
|
media_rewrite_map.clone(),
|
||||||
|
)
|
||||||
.map_err(|error| EngineError::Parse(error.to_string()))?;
|
.map_err(|error| EngineError::Parse(error.to_string()))?;
|
||||||
write_out(conn, output_dir, project_id, &rendered.relative_path, &rendered.html, &mut report)?;
|
write_out(conn, output_dir, project_id, &rendered.relative_path, &rendered.html, &mut report)?;
|
||||||
}
|
}
|
||||||
@@ -65,6 +79,28 @@ pub fn generate_starter_site(
|
|||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_media_rewrite_map(
|
||||||
|
conn: &Connection,
|
||||||
|
project_id: &str,
|
||||||
|
) -> EngineResult<HashMap<String, String>> {
|
||||||
|
let media_items = queries::media::list_media_by_project(conn, project_id)?;
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for media in media_items {
|
||||||
|
let canonical_path = if media.file_path.starts_with('/') {
|
||||||
|
media.file_path.clone()
|
||||||
|
} else {
|
||||||
|
format!("/{}", media.file_path.trim_start_matches('/'))
|
||||||
|
};
|
||||||
|
map.insert(format!("bds-media://{}", media.id), canonical_path.clone());
|
||||||
|
|
||||||
|
let relative_key = media.file_path.trim_start_matches('/').to_lowercase();
|
||||||
|
map.insert(relative_key, canonical_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
fn write_out(
|
fn write_out(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
output_dir: &Path,
|
output_dir: &Path,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use axum::extract::{Path as AxumPath, Query, State};
|
use axum::extract::{Path as AxumPath, Query, State};
|
||||||
use axum::http::{StatusCode, Uri, header};
|
use axum::http::{StatusCode, Uri, header};
|
||||||
@@ -14,7 +15,7 @@ use crate::db::{Database, queries};
|
|||||||
use crate::engine::generation::PublishedPostSource;
|
use crate::engine::generation::PublishedPostSource;
|
||||||
use crate::engine::{EngineError, EngineResult};
|
use crate::engine::{EngineError, EngineResult};
|
||||||
use crate::model::{Post, PostStatus, ProjectMetadata};
|
use crate::model::{Post, PostStatus, ProjectMetadata};
|
||||||
use crate::render::{build_canonical_post_path, render_starter_list_page, render_starter_single_post_page};
|
use crate::render::{build_canonical_post_path, render_starter_list_page_with_media_map, render_starter_single_post_page_with_media_map};
|
||||||
use crate::util::frontmatter::{read_post_file, read_translation_file};
|
use crate::util::frontmatter::{read_post_file, read_translation_file};
|
||||||
|
|
||||||
pub const PREVIEW_HOST: &str = "127.0.0.1";
|
pub const PREVIEW_HOST: &str = "127.0.0.1";
|
||||||
@@ -110,6 +111,7 @@ pub fn render_preview_path(
|
|||||||
path: &str,
|
path: &str,
|
||||||
metadata: &ProjectMetadata,
|
metadata: &ProjectMetadata,
|
||||||
posts: &[PublishedPostSource],
|
posts: &[PublishedPostSource],
|
||||||
|
canonical_media_path_by_source_path: &HashMap<String, String>,
|
||||||
) -> EngineResult<Option<String>> {
|
) -> EngineResult<Option<String>> {
|
||||||
let normalized = if path.is_empty() { "/" } else { path };
|
let normalized = if path.is_empty() { "/" } else { path };
|
||||||
let main_language = metadata.main_language.as_deref().unwrap_or("en");
|
let main_language = metadata.main_language.as_deref().unwrap_or("en");
|
||||||
@@ -119,7 +121,12 @@ pub fn render_preview_path(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|source| (source.post.clone(), source.body_markdown.clone()))
|
.map(|source| (source.post.clone(), source.body_markdown.clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
return render_starter_list_page(&list_posts, metadata, main_language)
|
return render_starter_list_page_with_media_map(
|
||||||
|
&list_posts,
|
||||||
|
metadata,
|
||||||
|
main_language,
|
||||||
|
canonical_media_path_by_source_path.clone(),
|
||||||
|
)
|
||||||
.map(|page| Some(page.html))
|
.map(|page| Some(page.html))
|
||||||
.map_err(|error| EngineError::Parse(error.to_string()));
|
.map_err(|error| EngineError::Parse(error.to_string()));
|
||||||
}
|
}
|
||||||
@@ -128,7 +135,13 @@ pub fn render_preview_path(
|
|||||||
if let Some(source) = posts.iter().find(|source| {
|
if let Some(source) = posts.iter().find(|source| {
|
||||||
build_canonical_post_path(&source.post, &language, main_language) == route_path
|
build_canonical_post_path(&source.post, &language, main_language) == route_path
|
||||||
}) {
|
}) {
|
||||||
return render_starter_single_post_page(&source.post, &source.body_markdown, metadata, &language)
|
return render_starter_single_post_page_with_media_map(
|
||||||
|
&source.post,
|
||||||
|
&source.body_markdown,
|
||||||
|
metadata,
|
||||||
|
&language,
|
||||||
|
canonical_media_path_by_source_path.clone(),
|
||||||
|
)
|
||||||
.map(|page| Some(page.html))
|
.map(|page| Some(page.html))
|
||||||
.map_err(|error| EngineError::Parse(error.to_string()));
|
.map_err(|error| EngineError::Parse(error.to_string()));
|
||||||
}
|
}
|
||||||
@@ -172,8 +185,10 @@ fn render_preview_response(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let metadata = crate::engine::meta::read_project_json(&state.data_dir)?;
|
let metadata = crate::engine::meta::read_project_json(&state.data_dir)?;
|
||||||
|
let db = Database::open(&state.db_path)?;
|
||||||
|
let media_rewrite_map = build_media_rewrite_map(db.conn(), &state.project_id)?;
|
||||||
let published_posts = collect_published_posts(state, &metadata)?;
|
let published_posts = collect_published_posts(state, &metadata)?;
|
||||||
match render_preview_path(path, &metadata, &published_posts)? {
|
match render_preview_path(path, &metadata, &published_posts, &media_rewrite_map)? {
|
||||||
Some(html) => Ok(Html(html).into_response()),
|
Some(html) => Ok(Html(html).into_response()),
|
||||||
None => Ok(error_response(StatusCode::NOT_FOUND, "preview not found")),
|
None => Ok(error_response(StatusCode::NOT_FOUND, "preview not found")),
|
||||||
}
|
}
|
||||||
@@ -196,6 +211,7 @@ fn render_draft_preview(
|
|||||||
post_id,
|
post_id,
|
||||||
target_language,
|
target_language,
|
||||||
) {
|
) {
|
||||||
|
let media_rewrite_map = build_media_rewrite_map(db.conn(), &post.project_id)?;
|
||||||
let mut translated_post = post.clone();
|
let mut translated_post = post.clone();
|
||||||
translated_post.title = translation.title.clone();
|
translated_post.title = translation.title.clone();
|
||||||
translated_post.excerpt = translation.excerpt.clone();
|
translated_post.excerpt = translation.excerpt.clone();
|
||||||
@@ -204,18 +220,53 @@ fn render_draft_preview(
|
|||||||
translated_post.file_path = translation.file_path.clone();
|
translated_post.file_path = translation.file_path.clone();
|
||||||
translated_post.published_at = translation.published_at.or(post.published_at);
|
translated_post.published_at = translation.published_at.or(post.published_at);
|
||||||
let body = load_translation_body(&state.data_dir, &translation)?;
|
let body = load_translation_body(&state.data_dir, &translation)?;
|
||||||
return render_starter_single_post_page(&translated_post, &body, &metadata, target_language)
|
return render_starter_single_post_page_with_media_map(
|
||||||
|
&translated_post,
|
||||||
|
&body,
|
||||||
|
&metadata,
|
||||||
|
target_language,
|
||||||
|
media_rewrite_map,
|
||||||
|
)
|
||||||
.map(|page| page.html)
|
.map(|page| page.html)
|
||||||
.map_err(|error| EngineError::Parse(error.to_string()));
|
.map_err(|error| EngineError::Parse(error.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let media_rewrite_map = build_media_rewrite_map(db.conn(), &post.project_id)?;
|
||||||
let body = load_post_body(&state.data_dir, &post)?;
|
let body = load_post_body(&state.data_dir, &post)?;
|
||||||
render_starter_single_post_page(&post, &body, &metadata, canonical_language)
|
render_starter_single_post_page_with_media_map(
|
||||||
|
&post,
|
||||||
|
&body,
|
||||||
|
&metadata,
|
||||||
|
canonical_language,
|
||||||
|
media_rewrite_map,
|
||||||
|
)
|
||||||
.map(|page| page.html)
|
.map(|page| page.html)
|
||||||
.map_err(|error| EngineError::Parse(error.to_string()))
|
.map_err(|error| EngineError::Parse(error.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_media_rewrite_map(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
project_id: &str,
|
||||||
|
) -> EngineResult<HashMap<String, String>> {
|
||||||
|
let media_items = queries::media::list_media_by_project(conn, project_id)?;
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for media in media_items {
|
||||||
|
let canonical_path = if media.file_path.starts_with('/') {
|
||||||
|
media.file_path.clone()
|
||||||
|
} else {
|
||||||
|
format!("/{}", media.file_path.trim_start_matches('/'))
|
||||||
|
};
|
||||||
|
map.insert(format!("bds-media://{}", media.id), canonical_path.clone());
|
||||||
|
|
||||||
|
let relative_key = media.file_path.trim_start_matches('/').to_lowercase();
|
||||||
|
map.insert(relative_key, canonical_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_published_posts(
|
fn collect_published_posts(
|
||||||
state: &PreviewServerState,
|
state: &PreviewServerState,
|
||||||
metadata: &ProjectMetadata,
|
metadata: &ProjectMetadata,
|
||||||
@@ -454,7 +505,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn root_preview_renders_index_page() {
|
fn root_preview_renders_index_page() {
|
||||||
let html = render_preview_path("/", &make_metadata(), &[make_post()])
|
let html = render_preview_path("/", &make_metadata(), &[make_post()], &HashMap::new())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(html.contains("post-list"));
|
assert!(html.contains("post-list"));
|
||||||
@@ -462,7 +513,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preview_renders_single_post_for_canonical_path() {
|
fn preview_renders_single_post_for_canonical_path() {
|
||||||
let html = render_preview_path("/2024/03/09/hello", &make_metadata(), &[make_post()])
|
let html = render_preview_path(
|
||||||
|
"/2024/03/09/hello",
|
||||||
|
&make_metadata(),
|
||||||
|
&[make_post()],
|
||||||
|
&HashMap::new(),
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(html.contains("<h1>Hello</h1>"));
|
assert!(html.contains("<h1>Hello</h1>"));
|
||||||
@@ -471,7 +527,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preview_renders_language_prefixed_single_post() {
|
fn preview_renders_language_prefixed_single_post() {
|
||||||
let html = render_preview_path("/de/2024/03/09/hello", &make_metadata(), &[make_post()])
|
let html = render_preview_path(
|
||||||
|
"/de/2024/03/09/hello",
|
||||||
|
&make_metadata(),
|
||||||
|
&[make_post()],
|
||||||
|
&HashMap::new(),
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(html.contains("lang=\"de\""));
|
assert!(html.contains("lang=\"de\""));
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ pub use markdown::render_markdown_to_html;
|
|||||||
pub use page_renderer::{RenderError, render_liquid_template};
|
pub use page_renderer::{RenderError, render_liquid_template};
|
||||||
pub use routes::{
|
pub use routes::{
|
||||||
RenderedPage, build_canonical_post_path, render_starter_list_page,
|
RenderedPage, build_canonical_post_path, render_starter_list_page,
|
||||||
render_starter_single_post_page,
|
render_starter_list_page_with_media_map, render_starter_single_post_page,
|
||||||
|
render_starter_single_post_page_with_media_map,
|
||||||
};
|
};
|
||||||
pub use template_lookup::{
|
pub use template_lookup::{
|
||||||
RenderCategorySettings, RenderTemplateLookup, TemplateLookupError,
|
RenderCategorySettings, RenderTemplateLookup, TemplateLookupError,
|
||||||
|
|||||||
@@ -208,11 +208,24 @@ fn rewrite_attribute_urls(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_preview_href(raw_href: &str, rewrite_context: &impl RewriteContextView) -> String {
|
fn normalize_preview_href(raw_href: &str, rewrite_context: &impl RewriteContextView) -> String {
|
||||||
if raw_href.is_empty() || is_external_or_special_url(raw_href) {
|
if raw_href.is_empty() {
|
||||||
return raw_href.to_string();
|
return raw_href.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (path_part, suffix) = split_path_suffix(raw_href.trim());
|
let (path_part, suffix) = split_path_suffix(raw_href.trim());
|
||||||
|
if let Some(media_lookup_key) = extract_media_lookup_key(path_part) {
|
||||||
|
let canonical = rewrite_context
|
||||||
|
.canonical_media_path_by_source_path()
|
||||||
|
.get(&media_lookup_key)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| format!("/{media_lookup_key}"));
|
||||||
|
return format!("{canonical}{suffix}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_external_or_special_url(raw_href) {
|
||||||
|
return raw_href.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(normalized) = normalize_day_route(path_part) {
|
if let Some(normalized) = normalize_day_route(path_part) {
|
||||||
return format!("{normalized}{suffix}");
|
return format!("{normalized}{suffix}");
|
||||||
}
|
}
|
||||||
@@ -226,7 +239,7 @@ fn normalize_preview_href(raw_href: &str, rewrite_context: &impl RewriteContextV
|
|||||||
return format!("{canonical}{suffix}");
|
return format!("{canonical}{suffix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(media_source_key) = extract_media_source_key(path_part) {
|
if let Some(media_source_key) = extract_media_lookup_key(path_part) {
|
||||||
let canonical = rewrite_context
|
let canonical = rewrite_context
|
||||||
.canonical_media_path_by_source_path()
|
.canonical_media_path_by_source_path()
|
||||||
.get(&media_source_key)
|
.get(&media_source_key)
|
||||||
@@ -239,12 +252,12 @@ fn normalize_preview_href(raw_href: &str, rewrite_context: &impl RewriteContextV
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_preview_src(raw_src: &str, rewrite_context: &impl RewriteContextView) -> String {
|
fn normalize_preview_src(raw_src: &str, rewrite_context: &impl RewriteContextView) -> String {
|
||||||
if raw_src.is_empty() || is_external_or_special_url(raw_src) {
|
if raw_src.is_empty() {
|
||||||
return raw_src.to_string();
|
return raw_src.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (path_part, suffix) = split_path_suffix(raw_src.trim());
|
let (path_part, suffix) = split_path_suffix(raw_src.trim());
|
||||||
if let Some(media_source_key) = extract_media_source_key(path_part) {
|
if let Some(media_source_key) = extract_media_lookup_key(path_part) {
|
||||||
let canonical = rewrite_context
|
let canonical = rewrite_context
|
||||||
.canonical_media_path_by_source_path()
|
.canonical_media_path_by_source_path()
|
||||||
.get(&media_source_key)
|
.get(&media_source_key)
|
||||||
@@ -253,6 +266,10 @@ fn normalize_preview_src(raw_src: &str, rewrite_context: &impl RewriteContextVie
|
|||||||
return format!("{canonical}{suffix}");
|
return format!("{canonical}{suffix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_external_or_special_url(raw_src) {
|
||||||
|
return raw_src.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
raw_src.to_string()
|
raw_src.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +333,15 @@ fn extract_post_slug(path: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_media_source_key(path: &str) -> Option<String> {
|
fn extract_media_lookup_key(path: &str) -> Option<String> {
|
||||||
|
if let Some(media_id) = path.trim().strip_prefix("bds-media://") {
|
||||||
|
let media_id = media_id.trim();
|
||||||
|
if media_id.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(format!("bds-media://{media_id}"));
|
||||||
|
}
|
||||||
|
|
||||||
let trimmed = path.trim_start_matches('/');
|
let trimmed = path.trim_start_matches('/');
|
||||||
let segments: Vec<_> = trimmed.split('/').collect();
|
let segments: Vec<_> = trimmed.split('/').collect();
|
||||||
match segments.as_slice() {
|
match segments.as_slice() {
|
||||||
@@ -332,4 +357,43 @@ fn trim_html_suffix(value: &str) -> String {
|
|||||||
.trim_end_matches(".html")
|
.trim_end_matches(".html")
|
||||||
.trim_end_matches(".htm")
|
.trim_end_matches(".htm")
|
||||||
.to_string()
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{rewrite_rendered_html_urls, RewriteContextView};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
struct TestRewriteContext {
|
||||||
|
canonical_post_path_by_slug: HashMap<String, String>,
|
||||||
|
canonical_media_path_by_source_path: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RewriteContextView for TestRewriteContext {
|
||||||
|
fn canonical_post_path_by_slug(&self) -> &HashMap<String, String> {
|
||||||
|
&self.canonical_post_path_by_slug
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical_media_path_by_source_path(&self) -> &HashMap<String, String> {
|
||||||
|
&self.canonical_media_path_by_source_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rewrites_bds_media_image_src_to_canonical_media_path() {
|
||||||
|
let context = TestRewriteContext {
|
||||||
|
canonical_post_path_by_slug: HashMap::new(),
|
||||||
|
canonical_media_path_by_source_path: HashMap::from([(
|
||||||
|
"bds-media://media-1".to_string(),
|
||||||
|
"/media/2026/04/media-1.png".to_string(),
|
||||||
|
)]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = rewrite_rendered_html_urls(
|
||||||
|
"<p><img src=\"bds-media://media-1\" alt=\"\" /></p>",
|
||||||
|
&context,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(html.contains("src=\"/media/2026/04/media-1.png\""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +119,22 @@ pub fn render_starter_single_post_page(
|
|||||||
body_markdown: &str,
|
body_markdown: &str,
|
||||||
metadata: &ProjectMetadata,
|
metadata: &ProjectMetadata,
|
||||||
language: &str,
|
language: &str,
|
||||||
|
) -> Result<RenderedPage, RenderError> {
|
||||||
|
render_starter_single_post_page_with_media_map(
|
||||||
|
post,
|
||||||
|
body_markdown,
|
||||||
|
metadata,
|
||||||
|
language,
|
||||||
|
HashMap::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_starter_single_post_page_with_media_map(
|
||||||
|
post: &Post,
|
||||||
|
body_markdown: &str,
|
||||||
|
metadata: &ProjectMetadata,
|
||||||
|
language: &str,
|
||||||
|
canonical_media_path_by_source_path: HashMap<String, String>,
|
||||||
) -> Result<RenderedPage, RenderError> {
|
) -> Result<RenderedPage, RenderError> {
|
||||||
let relative_path = format!("{}/index.html", build_canonical_post_path(post, language, main_language(metadata)).trim_start_matches('/'));
|
let relative_path = format!("{}/index.html", build_canonical_post_path(post, language, main_language(metadata)).trim_start_matches('/'));
|
||||||
let canonical_path = build_canonical_post_path(post, language, main_language(metadata));
|
let canonical_path = build_canonical_post_path(post, language, main_language(metadata));
|
||||||
@@ -151,7 +167,7 @@ pub fn render_starter_single_post_page(
|
|||||||
.collect(),
|
.collect(),
|
||||||
backlinks: vec![],
|
backlinks: vec![],
|
||||||
canonical_post_path_by_slug: HashMap::from([(post.slug.clone(), canonical_path)]),
|
canonical_post_path_by_slug: HashMap::from([(post.slug.clone(), canonical_path)]),
|
||||||
canonical_media_path_by_source_path: HashMap::new(),
|
canonical_media_path_by_source_path,
|
||||||
post_data_json_by_id: HashMap::new(),
|
post_data_json_by_id: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,6 +184,15 @@ pub fn render_starter_list_page(
|
|||||||
posts: &[(Post, String)],
|
posts: &[(Post, String)],
|
||||||
metadata: &ProjectMetadata,
|
metadata: &ProjectMetadata,
|
||||||
language: &str,
|
language: &str,
|
||||||
|
) -> Result<RenderedPage, RenderError> {
|
||||||
|
render_starter_list_page_with_media_map(posts, metadata, language, HashMap::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_starter_list_page_with_media_map(
|
||||||
|
posts: &[(Post, String)],
|
||||||
|
metadata: &ProjectMetadata,
|
||||||
|
language: &str,
|
||||||
|
canonical_media_path_by_source_path: HashMap<String, String>,
|
||||||
) -> Result<RenderedPage, RenderError> {
|
) -> Result<RenderedPage, RenderError> {
|
||||||
let relative_path = if language.eq_ignore_ascii_case(main_language(metadata)) {
|
let relative_path = if language.eq_ignore_ascii_case(main_language(metadata)) {
|
||||||
"index.html".to_string()
|
"index.html".to_string()
|
||||||
@@ -217,7 +242,7 @@ pub fn render_starter_list_page(
|
|||||||
prev_page_href: None,
|
prev_page_href: None,
|
||||||
next_page_href: None,
|
next_page_href: None,
|
||||||
canonical_post_path_by_slug: canonical_paths,
|
canonical_post_path_by_slug: canonical_paths,
|
||||||
canonical_media_path_by_source_path: HashMap::new(),
|
canonical_media_path_by_source_path,
|
||||||
post_data_json_by_id: HashMap::new(),
|
post_data_json_by_id: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -447,6 +472,25 @@ mod tests {
|
|||||||
assert!(rendered.html.contains("href=\"/2024/03/09/hello\""));
|
assert!(rendered.html.contains("href=\"/2024/03/09/hello\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn starter_single_post_renderer_rewrites_bds_media_image_links() {
|
||||||
|
let post = make_post();
|
||||||
|
let metadata = make_metadata();
|
||||||
|
let rendered = render_starter_single_post_page_with_media_map(
|
||||||
|
&post,
|
||||||
|
"",
|
||||||
|
&metadata,
|
||||||
|
"en",
|
||||||
|
HashMap::from([(
|
||||||
|
"bds-media://media-1".to_string(),
|
||||||
|
"/media/2026/04/media-1.png".to_string(),
|
||||||
|
)]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(rendered.html.contains("src=\"/media/2026/04/media-1.png\""));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn starter_list_renderer_groups_posts_and_uses_language_specific_index_path() {
|
fn starter_list_renderer_groups_posts_and_uses_language_specific_index_path() {
|
||||||
let metadata = make_metadata();
|
let metadata = make_metadata();
|
||||||
|
|||||||
Reference in New Issue
Block a user