Implement post editor image drops.
This commit is contained in:
@@ -152,32 +152,14 @@ fn import_gallery_image(
|
||||
offline_mode: bool,
|
||||
translation_targets: &[String],
|
||||
) -> Result<ImportedGalleryImage, String> {
|
||||
let original_name = path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "image".to_string());
|
||||
let db = Database::open(db_path).map_err(|error| error.to_string())?;
|
||||
let imported = media::import_media(
|
||||
db.conn(),
|
||||
data_dir,
|
||||
project_id,
|
||||
path,
|
||||
&original_name,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(source_language),
|
||||
Vec::new(),
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
post_media::link_media_to_post(
|
||||
let imported = import_and_link_image(
|
||||
db.conn(),
|
||||
data_dir,
|
||||
project_id,
|
||||
post_id,
|
||||
&imported.id,
|
||||
path,
|
||||
source_language,
|
||||
sort_order,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
@@ -190,7 +172,7 @@ fn import_gallery_image(
|
||||
offline_mode,
|
||||
translation_targets,
|
||||
)
|
||||
.unwrap_or_else(|| imported.original_name.clone())
|
||||
.unwrap_or_else(|_| imported.original_name.clone())
|
||||
} else {
|
||||
imported.original_name.clone()
|
||||
};
|
||||
@@ -201,6 +183,43 @@ fn import_gallery_image(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn import_and_link_image(
|
||||
conn: &crate::db::DbConnection,
|
||||
data_dir: &Path,
|
||||
project_id: &str,
|
||||
post_id: &str,
|
||||
path: &Path,
|
||||
source_language: &str,
|
||||
sort_order: i32,
|
||||
) -> crate::engine::EngineResult<crate::model::Media> {
|
||||
let original_name = path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "image".to_string());
|
||||
let imported = media::import_media(
|
||||
conn,
|
||||
data_dir,
|
||||
project_id,
|
||||
path,
|
||||
&original_name,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(source_language),
|
||||
Vec::new(),
|
||||
)?;
|
||||
post_media::link_media_to_post(
|
||||
conn,
|
||||
data_dir,
|
||||
project_id,
|
||||
post_id,
|
||||
&imported.id,
|
||||
sort_order,
|
||||
)?;
|
||||
Ok(imported)
|
||||
}
|
||||
|
||||
/// Apply the shared gallery AI enrichment and translation pipeline to one
|
||||
/// already-imported image. Returns the generated title when AI was available.
|
||||
pub fn enrich_imported_image(
|
||||
@@ -209,14 +228,13 @@ pub fn enrich_imported_image(
|
||||
imported: &crate::model::Media,
|
||||
offline_mode: bool,
|
||||
translation_targets: &[String],
|
||||
) -> Option<String> {
|
||||
) -> Result<String, String> {
|
||||
let image_data_url = build_ai_image_data_url(
|
||||
data_dir,
|
||||
&imported.id,
|
||||
&imported.file_path,
|
||||
&imported.mime_type,
|
||||
)
|
||||
.ok()?;
|
||||
)?;
|
||||
let response = ai::run_one_shot(
|
||||
conn,
|
||||
offline_mode,
|
||||
@@ -232,9 +250,9 @@ pub fn enrich_imported_image(
|
||||
}),
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
.map_err(|error| error.to_string())?;
|
||||
let ai::OneShotResponse::ImageAnalysis(analysis) = response.0 else {
|
||||
return None;
|
||||
return Err("AI returned an unexpected response".to_string());
|
||||
};
|
||||
media::update_media(
|
||||
conn,
|
||||
@@ -247,7 +265,7 @@ pub fn enrich_imported_image(
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.ok()?;
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
for target in translation_targets {
|
||||
let Ok((ai::OneShotResponse::MediaTranslation(translation), _)) = ai::run_one_shot(
|
||||
@@ -277,7 +295,7 @@ pub fn enrich_imported_image(
|
||||
);
|
||||
}
|
||||
|
||||
Some(if analysis.title.is_empty() {
|
||||
Ok(if analysis.title.is_empty() {
|
||||
imported.original_name.clone()
|
||||
} else {
|
||||
analysis.title
|
||||
|
||||
@@ -156,6 +156,13 @@ const SUPPORTED_IMAGE_TYPES: &[&str] = &[
|
||||
"image/heif",
|
||||
];
|
||||
|
||||
pub fn is_supported_image_path(path: &Path) -> bool {
|
||||
path.extension()
|
||||
.and_then(|extension| extension.to_str())
|
||||
.map(mime_from_extension)
|
||||
.is_some_and(|mime_type| SUPPORTED_IMAGE_TYPES.contains(&mime_type))
|
||||
}
|
||||
|
||||
/// Import a media file (image, etc.) into the project.
|
||||
#[expect(
|
||||
clippy::too_many_arguments,
|
||||
@@ -217,7 +224,7 @@ pub(crate) fn import_media_at(
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("bin");
|
||||
let mime_type = mime_from_extension(ext).to_string();
|
||||
if !SUPPORTED_IMAGE_TYPES.contains(&mime_type.as_str()) {
|
||||
if !is_supported_image_path(Path::new(original_name)) {
|
||||
return Err(EngineError::Validation(format!(
|
||||
"unsupported file type: {mime_type} (file: {original_name})"
|
||||
)));
|
||||
|
||||
@@ -1119,12 +1119,13 @@ pub fn post_insert_link(slug: &str) -> String {
|
||||
}
|
||||
|
||||
/// Insert a media reference in the editor buffer.
|
||||
/// Returns the Markdown syntax:  or [name](bds-media://id)
|
||||
pub fn post_insert_media(media_id: &str, is_image: bool, original_name: &str) -> String {
|
||||
/// Returns Markdown with a host-absolute media URL suitable for rendered HTML.
|
||||
pub fn post_insert_media(media_path: &str, is_image: bool, original_name: &str) -> String {
|
||||
let url = format!("/{}", media_path.trim_start_matches('/'));
|
||||
if is_image {
|
||||
format!("")
|
||||
format!("")
|
||||
} else {
|
||||
format!("[{original_name}](bds-media://{media_id})")
|
||||
format!("[{original_name}]({url})")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1177,6 +1178,18 @@ mod tests {
|
||||
assert_eq!(fetched.status, PostStatus::Draft);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserted_media_uses_host_absolute_renderable_paths() {
|
||||
assert_eq!(
|
||||
post_insert_media("media/2026/07/image.png", true, "image.png"),
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
post_insert_media("/media/2026/07/file.pdf", false, "file.pdf"),
|
||||
"[file.pdf](/media/2026/07/file.pdf)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_post_empty_title_uses_untitled() {
|
||||
let (db, dir) = setup();
|
||||
|
||||
Reference in New Issue
Block a user