Skip to content

Commit 13c542d

Browse files
committed
Fix notion to blog cli with the latest blogpost
1 parent 029a529 commit 13c542d

File tree

3 files changed

+92
-34
lines changed

3 files changed

+92
-34
lines changed

docs-src/blog/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
- [Dioxus 0.4 $ Release Notes $ August 1, 2023 $ Server Functions, Suspense, Enum Router, Overhauled Docs, Bundler, Android Support, and more!](release-040.md)
99
- [Dioxus 0.5 $ Release Notes $ March 21, 2024 $ A signal rewrite, zero unsafe, no lifetimes, unified launch, and more!](release-050.md)
1010
- [Dioxus 0.6 $ Release Notes $ December 9, 2024 $ Massive Tooling Improvements: Mobile Simulators, Magical Hot-Reloading, Interactive CLI, and more!](release-060.md)
11+
- [Dioxus 0.7 $ Release Notes $ August 18, 2025 $ Hot-Patching, Native Renderer, Bundle Splitting, Radix-UI, more!](release-070.md)

packages/notion-to-blog/run.sh

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/notion-to-blog/src/main.rs

Lines changed: 91 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use std::fs;
77
use std::path::{Path, PathBuf};
88
use walkdir::WalkDir;
99

10+
/// Example:
11+
/// notion-to-blog --input <input_folder> --output <output_folder> --output-name release-070
1012
#[derive(Parser)]
1113
#[command(author, version, about, long_about = None)]
1214
struct Args {
@@ -17,6 +19,10 @@ struct Args {
1719
/// Output folder for transformed markdown
1820
#[arg(short, long)]
1921
output: PathBuf,
22+
23+
/// The name of the markdown file to output
24+
#[arg(short, long, default_value = "blog")]
25+
output_name: String,
2026
}
2127

2228
#[tokio::main]
@@ -27,6 +33,13 @@ async fn main() -> Result<()> {
2733
anyhow::bail!("Input folder does not exist: {}", args.input.display());
2834
}
2935

36+
if args.input.is_file() {
37+
anyhow::bail!(
38+
"Input path must be a directory, not a file: {}",
39+
args.input.display()
40+
);
41+
}
42+
3043
// Create output directory if it doesn't exist
3144
fs::create_dir_all(&args.output).with_context(|| {
3245
format!(
@@ -42,7 +55,7 @@ async fn main() -> Result<()> {
4255

4356
if path.extension().and_then(|s| s.to_str()) == Some("md") {
4457
println!("Processing: {}", path.display());
45-
process_markdown_file(&args.input, &args.output, path).await?;
58+
process_markdown_file(&args.input, &args.output, &args.output_name, path).await?;
4659
}
4760
}
4861

@@ -53,6 +66,7 @@ async fn main() -> Result<()> {
5366
async fn process_markdown_file(
5467
input_base: &Path,
5568
output_base: &Path,
69+
output_name: &str,
5670
md_path: &Path,
5771
) -> Result<()> {
5872
// Read the markdown file
@@ -61,7 +75,7 @@ async fn process_markdown_file(
6175

6276
// Get the relative path from input base to maintain directory structure
6377
let relative_path = md_path.strip_prefix(input_base)?;
64-
let output_md_path = output_base.join(relative_path);
78+
let output_md_path = output_base.join(output_name).with_extension("md");
6579

6680
// Create parent directories if needed
6781
if let Some(parent) = output_md_path.parent() {
@@ -74,17 +88,18 @@ async fn process_markdown_file(
7488
.and_then(|s| s.to_str())
7589
.unwrap_or("assets");
7690
let input_assets_folder = md_path.parent().unwrap().join(assets_folder_name);
77-
let output_assets_folder = output_md_path.parent().unwrap().join("assets");
91+
let output_assets_folder = output_md_path.parent().unwrap().join("assets").join(output_name);
7892

7993
// Process images if assets folder exists
8094
let mut image_mapping = HashMap::new();
8195
if input_assets_folder.exists() {
8296
fs::create_dir_all(&output_assets_folder)?;
8397
image_mapping = process_images(&input_assets_folder, &output_assets_folder).await?;
8498
}
99+
println!("{:#?}", image_mapping);
85100

86101
// Transform the markdown content
87-
let transformed_content = transform_markdown(&content, &image_mapping)?;
102+
let transformed_content = transform_markdown(&content, output_name, &image_mapping)?;
88103

89104
// Write the transformed markdown
90105
fs::write(&output_md_path, transformed_content)
@@ -100,6 +115,7 @@ async fn process_images(
100115
) -> Result<HashMap<String, String>> {
101116
let mut image_mapping = HashMap::new();
102117
let mut screenshot_counter = 1;
118+
let mut screen_recording_counter = 1;
103119

104120
for entry in WalkDir::new(input_folder) {
105121
let entry = entry?;
@@ -111,14 +127,19 @@ async fn process_images(
111127
}
112128

113129
if let Some(extension) = path.extension().and_then(|s| s.to_str()) {
114-
let file_name = path.file_name().unwrap().to_str().unwrap();
130+
let file_name = path.file_name().and_then(|s| s.to_str()).unwrap();
131+
let file_stem = path.file_stem().unwrap().to_str().unwrap();
115132

116133
if matches!(
117134
extension.to_lowercase().as_str(),
118135
"png" | "jpg" | "jpeg" | "gif" | "webp"
119136
) {
120137
// Convert images to AVIF
121-
let new_name = generate_new_image_name(file_name, &mut screenshot_counter);
138+
let new_name = generate_new_image_name(
139+
file_stem,
140+
&mut screenshot_counter,
141+
&mut screen_recording_counter,
142+
);
122143
let new_name_avif = format!(
123144
"{}.avif",
124145
new_name.trim_end_matches(&format!(".{}", extension))
@@ -134,11 +155,16 @@ async fn process_images(
134155

135156
// Store mapping from original to new name (without ./assets/ prefix)
136157
image_mapping.insert(file_name.to_string(), new_name_avif.clone());
137-
println!(" ✓ Converted: {} -> {}", file_name, new_name_avif);
158+
println!(" ✓ Converted: {file_name} -> {new_name_avif}");
138159
} else {
139160
// Copy all other assets (videos, documents, etc.) as-is
140-
let cleaned_name = clean_asset_name(file_name);
141-
let output_path = output_folder.join(&cleaned_name);
161+
let cleaned_name = clean_asset_name(
162+
file_stem,
163+
&mut screenshot_counter,
164+
&mut screen_recording_counter,
165+
);
166+
let cleaned_file = format!("{}.{}", cleaned_name, extension);
167+
let output_path = output_folder.join(&cleaned_file);
142168

143169
fs::copy(path, &output_path).with_context(|| {
144170
format!(
@@ -149,24 +175,20 @@ async fn process_images(
149175
})?;
150176

151177
// Store mapping from original to cleaned name (without ./assets/ prefix)
152-
image_mapping.insert(file_name.to_string(), cleaned_name.clone());
153-
println!(" ✓ Copied: {} -> {}", file_name, cleaned_name);
178+
image_mapping.insert(file_name.to_string(), cleaned_file.clone());
179+
println!(" ✓ Copied: {file_name} -> {cleaned_file}");
154180
}
155181
}
156182
}
157183

158184
Ok(image_mapping)
159185
}
160186

161-
fn clean_asset_name(original_name: &str) -> String {
162-
// Clean up asset names by removing spaces and URL encoding
163-
original_name
164-
.replace(" ", "-")
165-
.replace("%20", "-")
166-
.to_lowercase()
167-
}
168-
169-
fn generate_new_image_name(original_name: &str, screenshot_counter: &mut i32) -> String {
187+
fn clean_asset_name(
188+
original_name: &str,
189+
screenshot_counter: &mut i32,
190+
screen_recording_counter: &mut i32,
191+
) -> String {
170192
let lower_name = original_name.to_lowercase();
171193

172194
// Check if it's a screenshot
@@ -177,11 +199,29 @@ fn generate_new_image_name(original_name: &str, screenshot_counter: &mut i32) ->
177199
return name;
178200
}
179201

180-
// For other images, just clean up the name
181-
let cleaned = original_name
202+
// Check if it's a screen recording
203+
let screen_recording_regex =
204+
Regex::new(r"screen[_\s]*recording[_\s]*\d{4}[-_]\d{2}[-_]\d{2}").unwrap();
205+
if screen_recording_regex.is_match(&lower_name) {
206+
let name = format!("screen-recording-{}", screen_recording_counter);
207+
*screen_recording_counter += 1;
208+
return name;
209+
}
210+
211+
// Clean up asset names by removing spaces and URL encoding
212+
original_name
182213
.replace(" ", "-")
183214
.replace("%20", "-")
184-
.to_lowercase();
215+
.to_lowercase()
216+
}
217+
218+
fn generate_new_image_name(
219+
original_name: &str,
220+
screenshot_counter: &mut i32,
221+
screen_recording_counter: &mut i32,
222+
) -> String {
223+
// For other images, just clean up the name
224+
let cleaned = clean_asset_name(original_name, screenshot_counter, screen_recording_counter);
185225

186226
// Remove file extension to add it back later
187227
if let Some(dot_pos) = cleaned.rfind('.') {
@@ -191,7 +231,11 @@ fn generate_new_image_name(original_name: &str, screenshot_counter: &mut i32) ->
191231
}
192232
}
193233

194-
fn transform_markdown(content: &str, image_mapping: &HashMap<String, String>) -> Result<String> {
234+
fn transform_markdown(
235+
content: &str,
236+
asset_sub_directory: &str,
237+
image_mapping: &HashMap<String, String>,
238+
) -> Result<String> {
195239
let parser = pulldown_cmark::Parser::new(content);
196240
let mut events = Vec::new();
197241
let mut skip_until_after_heading = false;
@@ -217,7 +261,8 @@ fn transform_markdown(content: &str, image_mapping: &HashMap<String, String>) ->
217261
title,
218262
id,
219263
}) => {
220-
let processed_url = process_image_url(&dest_url, image_mapping);
264+
let processed_url =
265+
process_image_url(&dest_url, asset_sub_directory, image_mapping);
221266

222267
// Check if this is a video file - if so, convert to image syntax
223268
let url_decoded = dest_url.replace("%20", " ");
@@ -239,7 +284,7 @@ fn transform_markdown(content: &str, image_mapping: &HashMap<String, String>) ->
239284
in_link = true;
240285
events.push(Event::Start(Tag::Link {
241286
link_type,
242-
dest_url: processed_url.into(),
287+
dest_url,
243288
title,
244289
id,
245290
}));
@@ -260,7 +305,8 @@ fn transform_markdown(content: &str, image_mapping: &HashMap<String, String>) ->
260305
events.push(Event::Text(text));
261306
} else {
262307
// Process image references in text only if not inside a link
263-
let processed_text = process_image_references(&text, image_mapping);
308+
let processed_text =
309+
process_image_references(&text, asset_sub_directory, image_mapping);
264310
events.push(Event::Text(processed_text.into()));
265311
}
266312
}
@@ -270,7 +316,8 @@ fn transform_markdown(content: &str, image_mapping: &HashMap<String, String>) ->
270316
title,
271317
id,
272318
}) => {
273-
let processed_url = process_image_url(&dest_url, image_mapping);
319+
let processed_url =
320+
process_image_url(&dest_url, asset_sub_directory, image_mapping);
274321
events.push(Event::Start(Tag::Image {
275322
link_type,
276323
dest_url: processed_url.into(),
@@ -414,7 +461,11 @@ fn transform_markdown(content: &str, image_mapping: &HashMap<String, String>) ->
414461
Ok(output)
415462
}
416463

417-
fn process_image_references(text: &str, image_mapping: &HashMap<String, String>) -> String {
464+
fn process_image_references(
465+
text: &str,
466+
asset_sub_directory: &str,
467+
image_mapping: &HashMap<String, String>,
468+
) -> String {
418469
let mut result = text.to_string();
419470

420471
// Only process if this text doesn't look like it's already part of a processed link
@@ -426,7 +477,7 @@ fn process_image_references(text: &str, image_mapping: &HashMap<String, String>)
426477
for (original, new) in image_mapping {
427478
// Handle URL-encoded spaces and direct references
428479
let encoded_original = original.replace(" ", "%20");
429-
let new_with_prefix = format!("./assets/{}", new);
480+
let new_with_prefix = format!("./assets/{asset_sub_directory}/{new}");
430481

431482
// Check if this is a video file that should be treated as an image
432483
let is_video = is_media_file(original);
@@ -452,18 +503,25 @@ fn process_image_references(text: &str, image_mapping: &HashMap<String, String>)
452503
result
453504
}
454505

455-
fn process_image_url(url: &str, image_mapping: &HashMap<String, String>) -> String {
506+
fn process_image_url(
507+
url: &str,
508+
asset_sub_directory: &str,
509+
image_mapping: &HashMap<String, String>,
510+
) -> String {
456511
// Extract filename from URL
457512
let url_decoded = url.replace("%20", " ");
458513

459514
if let Some(filename) = Path::new(&url_decoded).file_name().and_then(|s| s.to_str()) {
460515
if let Some(new_name) = image_mapping.get(filename) {
461-
return format!("./assets/{}", new_name);
516+
return format!("./assets/{asset_sub_directory}/{new_name}");
462517
}
463518
}
464519

465520
// Fallback: clean up the URL by removing URL encoding
466-
format!("./assets/{}", url.replace("%20", "-").to_lowercase())
521+
format!(
522+
"./assets/{asset_sub_directory}/{}",
523+
url.replace("%20", "-").to_lowercase()
524+
)
467525
}
468526

469527
fn is_media_file(filename: &str) -> bool {

0 commit comments

Comments
 (0)