make tairu work better with noscript

This commit is contained in:
りき萌 2024-02-20 23:30:36 +01:00
parent 90de54c359
commit a92ae02454
22 changed files with 404 additions and 238 deletions

View file

@ -13,9 +13,10 @@ clap = { version = "4.3.22", features = ["derive"] }
codespan-reporting = "0.11.1"
copy_dir = "0.1.3"
env_logger = "0.10.0"
log = { workspace = true }
handlebars = "4.3.7"
http-body = "1.0.0"
image = "0.24.8"
log = { workspace = true }
pulldown-cmark = { version = "0.9.3", default-features = false }
rand = "0.8.5"
serde = { version = "1.0.183", features = ["derive"] }

View file

@ -18,7 +18,7 @@ use walkdir::WalkDir;
use crate::{
cli::parse::parse_tree_with_diagnostics,
config::Config,
config::{Config, ConfigDerivedData},
html::{
breadcrumbs::breadcrumbs_to_html,
navmap::{build_navigation_map, NavigationMap},
@ -175,6 +175,7 @@ impl Generator {
parsed_trees: impl IntoIterator<Item = ParsedTree>,
) -> anyhow::Result<()> {
let mut handlebars = Handlebars::new();
let mut config_derived_data = ConfigDerivedData::default();
let mut template_file_ids = HashMap::new();
for entry in WalkDir::new(paths.template_dir) {
@ -232,6 +233,7 @@ impl Generator {
&mut tree,
treehouse,
config,
&mut config_derived_data,
parsed_tree.file_id,
&roots.branches,
);

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, ffi::OsStr, path::Path};
use std::{collections::HashMap, ffi::OsStr, fs::File, io::BufReader, path::Path};
use anyhow::Context;
use serde::{Deserialize, Serialize};
@ -108,3 +108,36 @@ impl Config {
)
}
}
/// Data derived from the config.
#[derive(Debug, Clone, Default)]
pub struct ConfigDerivedData {
pub pic_sizes: HashMap<String, Option<PicSize>>,
}
/// Picture size. This is useful for emitting <img> elements with a specific size to eliminate layout shifting.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PicSize {
pub width: u32,
pub height: u32,
}
impl ConfigDerivedData {
fn read_pic_size(config: &Config, pic_id: &str) -> Option<PicSize> {
let pic_filename = config.pics.get(pic_id)?;
let (width, height) = image::io::Reader::new(BufReader::new(
File::open(format!("static/pic/{pic_filename}")).ok()?,
))
.into_dimensions()
.ok()?;
Some(PicSize { width, height })
}
pub fn pic_size(&mut self, config: &Config, pic_id: &str) -> Option<PicSize> {
if !self.pic_sizes.contains_key(pic_id) {
self.pic_sizes
.insert(pic_id.to_owned(), Self::read_pic_size(config, pic_id));
}
self.pic_sizes.get(pic_id).copied().flatten()
}
}

View file

@ -30,7 +30,7 @@ use pulldown_cmark::escape::{escape_href, escape_html, StrWrite};
use pulldown_cmark::{Alignment, CodeBlockKind, Event, LinkType, Tag};
use pulldown_cmark::{CowStr, Event::*};
use crate::config::Config;
use crate::config::{Config, ConfigDerivedData, PicSize};
use crate::state::Treehouse;
enum TableState {
@ -41,6 +41,7 @@ enum TableState {
struct HtmlWriter<'a, I, W> {
treehouse: &'a Treehouse,
config: &'a Config,
config_derived_data: &'a mut ConfigDerivedData,
page_id: &'a str,
/// Iterator supplying events.
@ -68,6 +69,7 @@ where
fn new(
treehouse: &'a Treehouse,
config: &'a Config,
config_derived_data: &'a mut ConfigDerivedData,
page_id: &'a str,
iter: I,
writer: W,
@ -75,7 +77,9 @@ where
Self {
treehouse,
config,
config_derived_data,
page_id,
iter,
writer,
end_newline: true,
@ -247,11 +251,11 @@ where
kind,
program_name,
} => {
self.write(match kind {
self.write(match &kind {
LiterateCodeKind::Input => {
"<th-literate-program data-mode=\"input\" "
}
LiterateCodeKind::Output => {
LiterateCodeKind::Output { .. } => {
"<th-literate-program data-mode=\"output\" "
}
})?;
@ -261,7 +265,30 @@ where
escape_html(&mut self.writer, program_name)?;
self.write("\" data-language=\"")?;
escape_html(&mut self.writer, language)?;
self.write("\" role=\"code\">")
self.write("\" role=\"code\">")?;
if let LiterateCodeKind::Output { placeholder_pic_id } = kind {
if !placeholder_pic_id.is_empty() {
self.write(
"<img class=\"placeholder\" loading=\"lazy\" src=\"",
)?;
escape_html(
&mut self.writer,
&self.config.pic_url(placeholder_pic_id),
)?;
self.write("\"")?;
if let Some(PicSize { width, height }) = self
.config_derived_data
.pic_size(self.config, placeholder_pic_id)
{
self.write(&format!(
" width=\"{width}\" height=\"{height}\""
))?;
}
self.write(">")?;
}
}
Ok(())
}
},
CodeBlockKind::Indented => self.write("<pre><code>"),
@ -556,9 +583,9 @@ where
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LiterateCodeKind {
enum LiterateCodeKind<'a> {
Input,
Output,
Output { placeholder_pic_id: &'a str },
}
enum CodeBlockMode<'a> {
@ -568,7 +595,7 @@ enum CodeBlockMode<'a> {
},
LiterateProgram {
language: &'a str,
kind: LiterateCodeKind,
kind: LiterateCodeKind<'a>,
program_name: &'a str,
},
}
@ -578,14 +605,16 @@ impl<'a> CodeBlockMode<'a> {
if language.is_empty() {
CodeBlockMode::PlainText
} else if let Some((language, program_name)) = language.split_once(' ') {
let (program_name, placeholder_pic_id) =
program_name.split_once(' ').unwrap_or((program_name, ""));
CodeBlockMode::LiterateProgram {
language,
kind: if language == "output" {
LiterateCodeKind::Output
LiterateCodeKind::Output { placeholder_pic_id }
} else {
LiterateCodeKind::Input
},
program_name,
program_name: program_name.split(' ').next().unwrap(),
}
} else {
CodeBlockMode::SyntaxHighlightOnly { language }
@ -624,12 +653,13 @@ pub fn push_html<'a, I>(
s: &mut String,
treehouse: &'a Treehouse,
config: &'a Config,
config_derived_data: &'a mut ConfigDerivedData,
page_id: &'a str,
iter: I,
) where
I: Iterator<Item = Event<'a>>,
{
HtmlWriter::new(treehouse, config, page_id, iter, s)
HtmlWriter::new(treehouse, config, config_derived_data, page_id, iter, s)
.run()
.unwrap();
}

View file

@ -4,7 +4,7 @@ use pulldown_cmark::{BrokenLink, LinkType};
use treehouse_format::pull::BranchKind;
use crate::{
config::Config,
config::{Config, ConfigDerivedData},
html::EscapeAttribute,
state::{FileId, Treehouse},
tree::{
@ -19,6 +19,7 @@ pub fn branch_to_html(
s: &mut String,
treehouse: &mut Treehouse,
config: &Config,
config_derived_data: &mut ConfigDerivedData,
file_id: FileId,
branch_id: SemaBranchId,
) {
@ -145,6 +146,7 @@ pub fn branch_to_html(
s,
treehouse,
config,
config_derived_data,
treehouse.tree_path(file_id).expect(".tree file expected"),
markdown_parser,
);
@ -197,7 +199,7 @@ pub fn branch_to_html(
let num_children = branch.children.len();
for i in 0..num_children {
let child_id = treehouse.tree.branch(branch_id).children[i];
branch_to_html(s, treehouse, config, file_id, child_id);
branch_to_html(s, treehouse, config, config_derived_data, file_id, child_id);
}
s.push_str("</ul>");
}
@ -213,12 +215,13 @@ pub fn branches_to_html(
s: &mut String,
treehouse: &mut Treehouse,
config: &Config,
config_derived_data: &mut ConfigDerivedData,
file_id: FileId,
branches: &[SemaBranchId],
) {
s.push_str("<ul>");
for &child in branches {
branch_to_html(s, treehouse, config, file_id, child);
branch_to_html(s, treehouse, config, config_derived_data, file_id, child);
}
s.push_str("</ul>");
}