@@ -285,6 +285,45 @@ impl SiteGenerator {
285285 completed_books. sort_by ( |a, b| a. epub_info . title . cmp ( & b. epub_info . title ) ) ;
286286 unread_books. sort_by ( |a, b| a. epub_info . title . cmp ( & b. epub_info . title ) ) ;
287287
288+ // ------------------------------------------------------------------
289+ // Generate books manifest JSON categorized by reading status.
290+ // NOTE: This manifest is not consumed by the frontend code – it is
291+ // generated purely for the convenience of users who may want a
292+ // machine-readable list of all books and their export paths.
293+ // ------------------------------------------------------------------
294+
295+ use serde_json:: json;
296+
297+ let to_manifest_entry = |b : & Book | {
298+ json ! ( {
299+ "id" : b. id. clone( ) ,
300+ "title" : b. epub_info. title. clone( ) ,
301+ "authors" : b. epub_info. authors. clone( ) ,
302+ "json_path" : format!( "/books/{}/details.json" , b. id) ,
303+ "html_path" : format!( "/books/{}/index.html" , b. id) ,
304+ } )
305+ } ;
306+
307+ let reading_json: Vec < _ > = reading_books. iter ( ) . map ( to_manifest_entry) . collect ( ) ;
308+ let completed_json: Vec < _ > = completed_books. iter ( ) . map ( to_manifest_entry) . collect ( ) ;
309+ let new_json: Vec < _ > = unread_books. iter ( ) . map ( to_manifest_entry) . collect ( ) ;
310+
311+ let manifest = json ! ( {
312+ "reading" : reading_json,
313+ "completed" : completed_json,
314+ "new" : new_json,
315+ "generated_at" : self . get_last_updated( ) ,
316+ } ) ;
317+
318+ fs:: write (
319+ self . books_dir ( ) . join ( "list.json" ) ,
320+ serde_json:: to_string_pretty ( & manifest) ?,
321+ ) ?;
322+
323+ // ------------------------------------------------------------------
324+ // Render book list HTML
325+ // ------------------------------------------------------------------
326+
288327 let template = IndexTemplate {
289328 site_title : self . site_title . clone ( ) ,
290329 reading_books,
@@ -294,7 +333,7 @@ impl SiteGenerator {
294333 last_updated : self . get_last_updated ( ) ,
295334 navbar_items : self . create_navbar_items ( "books" ) ,
296335 } ;
297-
336+
298337 let html = template. render ( ) ?;
299338 fs:: write ( self . output_dir . join ( "index.html" ) , html) ?;
300339
@@ -327,8 +366,8 @@ impl SiteGenerator {
327366 let template = BookTemplate {
328367 site_title : self . site_title . clone ( ) ,
329368 book : book. clone ( ) ,
330- book_stats,
331- session_stats,
369+ book_stats : book_stats . clone ( ) ,
370+ session_stats : session_stats . clone ( ) ,
332371 version : self . get_version ( ) ,
333372 last_updated : self . get_last_updated ( ) ,
334373 navbar_items : self . create_navbar_items ( "books" ) ,
@@ -339,6 +378,54 @@ impl SiteGenerator {
339378 fs:: create_dir_all ( & book_dir) ?;
340379 let book_path = book_dir. join ( "index.html" ) ;
341380 fs:: write ( book_path, html) ?;
381+
382+ // Generate Markdown export
383+ let md_template = BookMarkdownTemplate {
384+ book : book. clone ( ) ,
385+ book_stats : book_stats. clone ( ) ,
386+ session_stats : session_stats. clone ( ) ,
387+ version : self . get_version ( ) ,
388+ last_updated : self . get_last_updated ( ) ,
389+ } ;
390+ let markdown = md_template. render ( ) ?;
391+ fs:: write ( book_dir. join ( "details.md" ) , markdown) ?;
392+
393+ // Generate JSON export / not used by the frontend code - only for the user's convenience
394+ let json_data = serde_json:: json!( {
395+ "book" : {
396+ "title" : book. epub_info. title,
397+ "authors" : book. epub_info. authors,
398+ "series" : book. series_display( ) ,
399+ "language" : book. language( ) ,
400+ "publisher" : book. publisher( ) ,
401+ "description" : book. epub_info. sanitized_description( ) ,
402+ "rating" : book. rating( ) ,
403+ "review_note" : book. review_note( ) ,
404+ "status" : book. status( ) . to_string( ) ,
405+ "progress_percentage" : book. progress_percentage( ) ,
406+ "subjects" : book. subjects( ) ,
407+ "identifiers" : book. identifiers( ) . iter( ) . map( |id| {
408+ serde_json:: json!( {
409+ "scheme" : id. scheme,
410+ "value" : id. value,
411+ "display_scheme" : id. display_scheme( ) ,
412+ "url" : id. url( )
413+ } )
414+ } ) . collect:: <Vec <_>>( )
415+ } ,
416+ "annotations" : book. koreader_metadata. as_ref( ) . map( |m| & m. annotations) . unwrap_or( & vec![ ] ) ,
417+ "statistics" : {
418+ "book_stats" : book_stats,
419+ "session_stats" : session_stats
420+ } ,
421+ "export_info" : {
422+ "generated_by" : "KoShelf" ,
423+ "version" : self . get_version( ) ,
424+ "generated_at" : self . get_last_updated( )
425+ }
426+ } ) ;
427+ let json_str = serde_json:: to_string_pretty ( & json_data) ?;
428+ fs:: write ( book_dir. join ( "details.json" ) , json_str) ?;
342429 }
343430
344431 Ok ( ( ) )
0 commit comments