diff --git a/src/main.rs b/src/main.rs
index bb5844c..803c627 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,20 +20,19 @@ use data_stream::{
from_stream, to_stream,
};
use header_config::parse_config;
-use indexmap::IndexMap;
use maud::html;
use percent_encoding::percent_decode_str;
-use pukram2html::{Settings, convert, convert_extended, convert_subheader};
+use pukram2html::convert;
use threadpool::ThreadPool;
mod chara;
mod dialog;
mod request;
+mod tabs;
mod vn;
-use chara::render_character;
use request::Request;
-use vn::render_novel;
+use tabs::{Tab, TabInfo, write_tab_styles};
#[derive(Clone)]
struct Comment {
@@ -593,230 +592,6 @@ impl<'a> RequestData<'a> {
}
}
-static GENERAL_TAB_STYLE: &str = r"
-.tab-system {
- margin: 20px;
-}
-
-.tab-radio {
- display: none;
-}
-
-.tab-nav {
- display: flex;
- gap: 5px;
- margin-bottom: -1px;
-}
-
-.tab-button {
- padding: 8px 15px;
- background: #f0f0f0;
- border: 1px solid #ddd;
- border-bottom: 1px solid transparent;
- cursor: pointer;
- border-radius: 4px 4px 0 0;
- position: relative;
-}
-
-.tab-button:hover {
- background: #e0e0e0;
-}
-
-.tab-radio:checked + .tab-button {
- background: white;
- border-color: #ddd;
- border-bottom-color: white;
- z-index: 1;
-}
-
-.tab-content {
- display: none;
- padding: 15px;
- border: 1px solid #ddd;
-}";
-
-fn write_tab_styles(stream: &mut TcpStream, count: usize) {
- let _ = writeln!(stream, "");
-}
-
-fn render_comments(stream: &mut TcpStream, comments: Vec) {
- let html = html! {
- h2 { "Comments" }
- form method="POST" {
- input type="text" name="name" value="anon" placeholder="Name";
- br;
- textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
- br;
- input type="submit" value="Send!";
- }
- form method="POST" {
- input type="submit" value="💖️" name="up";
- input type="submit" value="💔️" name="down";
- }
- @for comment in comments {
- fieldset {
- legend { (comment.name) }
- (maud::PreEscaped(comment.text))
- }
- }
- };
- let _ = stream.write_all(html.into_string().as_bytes());
-}
-
-enum TabInfo {
- Lines(Vec),
- Chara(Vec),
- Game(IndexMap, Box>),
- Description,
- Comment(Vec),
-}
-
-impl TabInfo {
- fn render_tab_content(
- self,
- stream: &mut TcpStream,
- path: &Path,
- relative_path: &str,
- censored: bool,
- choice: usize,
- progress: &str,
- mlc_path: Option<&Path>,
- file_paths: &DocumentPaths,
- ) {
- match self {
- Self::Lines(lines) => {
- convert_extended(
- lines,
- stream,
- Settings::default()
- .with_handler(entry_handler(path, relative_path, censored))
- .with_start_level(1)
- .with_use_textboxes(true),
- );
- }
- Self::Chara(lines) => {
- convert_extended(
- lines,
- stream,
- Settings::default()
- .with_handler(chara_handler(path, relative_path))
- .with_start_level(1)
- .with_use_textboxes(true),
- );
- }
- Self::Game(config_map) => {
- let _ = render_novel(
- config_map,
- file_paths.pk,
- mlc_path,
- file_paths.mld,
- relative_path,
- stream,
- choice,
- progress,
- );
- }
- Self::Description => {
- let Ok(pki_file) = File::open(file_paths.pki.unwrap()) else {
- return;
- };
-
- let _ = writeln!(stream, "Description
");
-
- if let Some(audio_path) = &file_paths.audio {
- if audio_path.is_file() {
- let _ = writeln!(
- stream,
- ""
- );
- }
- }
-
- let lines = BufReader::new(pki_file).lines().map_while(Result::ok);
- convert_subheader(lines, stream, 1);
- }
- Self::Comment(comments) => {
- render_comments(stream, comments);
- }
- }
- }
-}
-
-struct Tab {
- title: Box,
- info: TabInfo,
-}
-
-fn entry_handler(
- path: &Path,
- relative_path: &str,
- censored: bool,
-) -> impl Fn(&str, &mut W, usize) {
- move |mut entry, output, level| {
- let level = level + 1;
- let mut pki_path = path.to_path_buf();
- let mut audio_path = path.to_path_buf();
- if let Some((real_entry, _)) = entry.split_once(':') {
- entry = real_entry
- }
- let pki_extension = if censored { "pkc" } else { "pki" };
- if relative_path.is_empty() {
- pki_path.push(format!("{entry}.{pki_extension}"));
- audio_path.push(format!("{entry}.mp3"));
- } else {
- pki_path.push(format!("{relative_path}/{entry}.{pki_extension}"));
- audio_path.push(format!("{relative_path}/{entry}.mp3"));
- }
-
- let Ok(file) = File::open(pki_path) else {
- return;
- };
-
- let _ = writeln!(
- output,
- "{entry}"
- );
-
- if Path::is_file(&audio_path) {
- let _ = writeln!(
- output,
- ""
- );
- }
-
- convert_subheader(
- BufReader::new(file).lines().map(Result::unwrap_or_default),
- output,
- level,
- );
- }
-}
-
-fn chara_handler(path: &Path, relative_path: &str) -> impl Fn(&str, &mut W, usize) {
- move |entry, output, _level| {
- let mut chara_path = path.to_path_buf();
- chara_path.push(relative_path);
- chara_path.push(format!("{entry}.chara"));
-
- let Some(def) = load_character_file(&chara_path) else {
- return;
- };
-
- let html = render_character(entry, &def, relative_path);
- let _ = output.write_all(html.into_string().as_bytes());
- }
-}
-
fn handle_relative_connection(
info: Arc,
mut stream: TcpStream,
diff --git a/src/tabs.rs b/src/tabs.rs
new file mode 100644
index 0000000..467b150
--- /dev/null
+++ b/src/tabs.rs
@@ -0,0 +1,238 @@
+use std::{
+ fs::File,
+ io::{BufRead, BufReader, Write},
+ net::TcpStream,
+ path::Path,
+};
+
+use indexmap::IndexMap;
+use maud::html;
+use pukram2html::{Settings, convert_extended, convert_subheader};
+
+use crate::{
+ Comment, DocumentPaths, chara::render_character, load_character_file, vn::render_novel,
+};
+
+static GENERAL_TAB_STYLE: &str = r"
+.tab-system {
+ margin: 20px;
+}
+
+.tab-radio {
+ display: none;
+}
+
+.tab-nav {
+ display: flex;
+ gap: 5px;
+ margin-bottom: -1px;
+}
+
+.tab-button {
+ padding: 8px 15px;
+ background: #f0f0f0;
+ border: 1px solid #ddd;
+ border-bottom: 1px solid transparent;
+ cursor: pointer;
+ border-radius: 4px 4px 0 0;
+ position: relative;
+}
+
+.tab-button:hover {
+ background: #e0e0e0;
+}
+
+.tab-radio:checked + .tab-button {
+ background: white;
+ border-color: #ddd;
+ border-bottom-color: white;
+ z-index: 1;
+}
+
+.tab-content {
+ display: none;
+ padding: 15px;
+ border: 1px solid #ddd;
+}";
+
+pub fn write_tab_styles(stream: &mut TcpStream, count: usize) {
+ let _ = writeln!(stream, "");
+}
+
+pub enum TabInfo {
+ Lines(Vec),
+ Chara(Vec),
+ Game(IndexMap, Box>),
+ Description,
+ Comment(Vec),
+}
+
+impl TabInfo {
+ pub fn render_tab_content(
+ self,
+ stream: &mut TcpStream,
+ path: &Path,
+ relative_path: &str,
+ censored: bool,
+ choice: usize,
+ progress: &str,
+ mlc_path: Option<&Path>,
+ file_paths: &DocumentPaths,
+ ) {
+ match self {
+ Self::Lines(lines) => {
+ convert_extended(
+ lines,
+ stream,
+ Settings::default()
+ .with_handler(entry_handler(path, relative_path, censored))
+ .with_start_level(1)
+ .with_use_textboxes(true),
+ );
+ }
+ Self::Chara(lines) => {
+ convert_extended(
+ lines,
+ stream,
+ Settings::default()
+ .with_handler(chara_handler(path, relative_path))
+ .with_start_level(1)
+ .with_use_textboxes(true),
+ );
+ }
+ Self::Game(config_map) => {
+ let _ = render_novel(
+ config_map,
+ file_paths.pk,
+ mlc_path,
+ file_paths.mld,
+ relative_path,
+ stream,
+ choice,
+ progress,
+ );
+ }
+ Self::Description => {
+ let Ok(pki_file) = File::open(file_paths.pki.unwrap()) else {
+ return;
+ };
+
+ let _ = writeln!(stream, "Description
");
+
+ if let Some(audio_path) = &file_paths.audio {
+ if audio_path.is_file() {
+ let _ = writeln!(
+ stream,
+ ""
+ );
+ }
+ }
+
+ let lines = BufReader::new(pki_file).lines().map_while(Result::ok);
+ convert_subheader(lines, stream, 1);
+ }
+ Self::Comment(comments) => {
+ render_comments(stream, comments);
+ }
+ }
+ }
+}
+
+fn render_comments(stream: &mut TcpStream, comments: Vec) {
+ let html = html! {
+ h2 { "Comments" }
+ form method="POST" {
+ input type="text" name="name" value="anon" placeholder="Name";
+ br;
+ textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
+ br;
+ input type="submit" value="Send!";
+ }
+ form method="POST" {
+ input type="submit" value="💖️" name="up";
+ input type="submit" value="💔️" name="down";
+ }
+ @for comment in comments {
+ fieldset {
+ legend { (comment.name) }
+ (maud::PreEscaped(comment.text))
+ }
+ }
+ };
+ let _ = stream.write_all(html.into_string().as_bytes());
+}
+
+pub struct Tab {
+ pub title: Box,
+ pub info: TabInfo,
+}
+
+fn entry_handler(
+ path: &Path,
+ relative_path: &str,
+ censored: bool,
+) -> impl Fn(&str, &mut W, usize) {
+ move |mut entry, output, level| {
+ let level = level + 1;
+ let mut pki_path = path.to_path_buf();
+ let mut audio_path = path.to_path_buf();
+ if let Some((real_entry, _)) = entry.split_once(':') {
+ entry = real_entry
+ }
+ let pki_extension = if censored { "pkc" } else { "pki" };
+ if relative_path.is_empty() {
+ pki_path.push(format!("{entry}.{pki_extension}"));
+ audio_path.push(format!("{entry}.mp3"));
+ } else {
+ pki_path.push(format!("{relative_path}/{entry}.{pki_extension}"));
+ audio_path.push(format!("{relative_path}/{entry}.mp3"));
+ }
+
+ let Ok(file) = File::open(pki_path) else {
+ return;
+ };
+
+ let _ = writeln!(
+ output,
+ "{entry}"
+ );
+
+ if Path::is_file(&audio_path) {
+ let _ = writeln!(
+ output,
+ ""
+ );
+ }
+
+ convert_subheader(
+ BufReader::new(file).lines().map(Result::unwrap_or_default),
+ output,
+ level,
+ );
+ }
+}
+
+fn chara_handler(path: &Path, relative_path: &str) -> impl Fn(&str, &mut W, usize) {
+ move |entry, output, _level| {
+ let mut chara_path = path.to_path_buf();
+ chara_path.push(relative_path);
+ chara_path.push(format!("{entry}.chara"));
+
+ let Some(def) = load_character_file(&chara_path) else {
+ return;
+ };
+
+ let html = render_character(entry, &def, relative_path);
+ let _ = output.write_all(html.into_string().as_bytes());
+ }
+}