From 072a47378dc8d0af8ed27cead9d6c79d670cb608 Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 13 May 2026 23:02:04 +0800 Subject: [PATCH] Define core data models for book and theme --- src/book.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/theme.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/src/book.rs b/src/book.rs index e69de29..69c27a7 100644 --- a/src/book.rs +++ b/src/book.rs @@ -0,0 +1,90 @@ +pub fn strip_html(input: &str) -> String { + let mut result = String::with_capacity(input.len()); + let mut in_tag = false; + let mut in_entity = false; + let mut entity = String::new(); + + for c in input.chars() { + match c { + '<' => in_tag = true, + '>' if in_tag => in_tag = false, + '&' if !in_tag => { + in_entity = true; + entity.clear(); + } + ';' if in_entity => { + in_entity = false; + let decoded = match entity.as_str() { + "amp" => "&", + "lt" => "<", + "gt" => ">", + "quot" => "\"", + "nbsp" => " ", + _ => "", + }; + result.push_str(decoded); + } + c if !in_tag && !in_entity => result.push(c), + c if in_entity => entity.push(c), + _ => {} + } + } + result +} + +#[derive(Debug, Clone)] +pub struct TocEntry { + pub label: String, + pub section: usize, + pub children: Vec, +} + +#[derive(Debug, Clone)] +pub struct Section { + pub title: String, + pub content: String, + pub pages: Vec, +} + +#[derive(Debug, Clone)] +pub struct Book { + pub title: String, + pub author: String, + pub cover: Option>, + pub sections: Vec
, + pub toc: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_strip_html_plain_text() { + assert_eq!(strip_html("Hello World"), "Hello World"); + } + + #[test] + fn test_strip_html_simple_tags() { + assert_eq!(strip_html("

Hello

"), "Hello"); + } + + #[test] + fn test_strip_html_nested_tags() { + assert_eq!( + strip_html("

Hello World

"), + "Hello World" + ); + } + + #[test] + fn test_strip_html_html_entities() { + assert_eq!(strip_html("Hello & World"), "Hello & World"); + assert_eq!(strip_html("Hello World"), "Hello World"); + } + + #[test] + fn test_strip_html_empty() { + assert_eq!(strip_html(""), ""); + } +} diff --git a/src/theme.rs b/src/theme.rs index e69de29..453d276 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -0,0 +1,79 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum Theme { + Light, + Dark, +} + +impl Default for Theme { + fn default() -> Self { + Theme::Light + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Settings { + pub font_size: f32, + pub theme: Theme, + pub recent_files: Vec, + pub reading_positions: std::collections::HashMap, + pub bookmarks: std::collections::HashMap>, + pub window_size: Option<(f32, f32)>, +} + +impl Default for Settings { + fn default() -> Self { + Self { + font_size: 20.0, + theme: Theme::Light, + recent_files: Vec::new(), + reading_positions: std::collections::HashMap::new(), + bookmarks: std::collections::HashMap::new(), + window_size: None, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ReadingPosition { + pub section: usize, + pub page: usize, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Bookmark { + pub section: usize, + pub page: usize, + pub label: String, + pub timestamp: i64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_settings_default() { + let s = Settings::default(); + assert_eq!(s.font_size, 20.0); + assert_eq!(s.theme, Theme::Light); + } + + #[test] + fn test_settings_round_trip() { + let s = Settings::default(); + let json = serde_json::to_string(&s).unwrap(); + let restored: Settings = serde_json::from_str(&json).unwrap(); + assert_eq!(restored.font_size, s.font_size); + assert_eq!(restored.theme, s.theme); + } + + #[test] + fn test_theme_serialize() { + let json = serde_json::to_string(&Theme::Dark).unwrap(); + assert_eq!(json, "\"Dark\""); + let restored: Theme = serde_json::from_str(&json).unwrap(); + assert_eq!(restored, Theme::Dark); + } +}