Compare commits
2 commits
d6e6dbd660
...
35408f03b1
Author | SHA1 | Date | |
---|---|---|---|
35408f03b1 | |||
2211c44ee0 |
8 changed files with 339 additions and 170 deletions
|
@ -1,3 +1,4 @@
|
|||
pub mod compiler;
|
||||
pub mod navigation;
|
||||
pub mod process;
|
||||
pub mod postprocess;
|
||||
|
|
|
@ -19,7 +19,7 @@ impl PostProcess {
|
|||
/// Applies postprocessing to a [`CompiledDocument`]
|
||||
pub fn apply(
|
||||
&self,
|
||||
target: Target,
|
||||
_target: Target,
|
||||
list: &Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>,
|
||||
doc: &RefCell<CompiledDocument>,
|
||||
) -> Result<String, String> {
|
||||
|
@ -35,7 +35,7 @@ impl PostProcess {
|
|||
if let Some(found) = doc.borrow().references.get(name) {
|
||||
// Check for duplicates
|
||||
if let Some((_, previous_doc)) = &found_ref {
|
||||
return Err(format!("Cannot use an unspecific reference for reference named: `{found}`. Found in document `{}` but also in `{}`. Specify the source of the reference to resolve the conflict.", previous_doc.borrow().input, doc.borrow().input));
|
||||
return Err(format!("Cannot use an unspecific reference for reference named: `{name}`. Found in document `{}` but also in `{}`. Specify the source of the reference to resolve the conflict.", previous_doc.borrow().input, doc.borrow().input));
|
||||
}
|
||||
|
||||
found_ref = Some((found.clone(), &doc));
|
||||
|
@ -71,7 +71,7 @@ impl PostProcess {
|
|||
"Unable to get the output. Aborting postprocessing."
|
||||
))?;
|
||||
let insert_content = format!("{found_path}#{found_ref}");
|
||||
content.insert_str(pos - offset, insert_content.as_str());
|
||||
content.insert_str(pos + offset, insert_content.as_str());
|
||||
offset += insert_content.len();
|
||||
} else {
|
||||
return Err(format!("Cannot find reference `{cross_ref}` from document `{}`. Aborting postprocessing.", doc.borrow().input));
|
||||
|
|
195
src/compiler/process.rs
Normal file
195
src/compiler/process.rs
Normal file
|
@ -0,0 +1,195 @@
|
|||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::document::document::Document;
|
||||
use crate::parser::langparser::LangParser;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::source::Source;
|
||||
use crate::parser::source::SourceFile;
|
||||
|
||||
use super::compiler::CompiledDocument;
|
||||
use super::compiler::Compiler;
|
||||
use super::compiler::Target;
|
||||
use super::postprocess::PostProcess;
|
||||
|
||||
/// Parses a source file into a document
|
||||
fn parse(
|
||||
parser: &LangParser,
|
||||
source: Rc<dyn Source>,
|
||||
debug_opts: &Vec<String>,
|
||||
) -> Result<Box<dyn Document<'static>>, String> {
|
||||
// Parse
|
||||
//let source = SourceFile::new(input.to_string(), None).unwrap();
|
||||
let (doc, _) = parser.parse(ParserState::new(parser, None), source.clone(), None);
|
||||
|
||||
if debug_opts.contains(&"ast".to_string()) {
|
||||
println!("-- BEGIN AST DEBUGGING --");
|
||||
doc.content()
|
||||
.borrow()
|
||||
.iter()
|
||||
.for_each(|elem| println!("{elem:#?}"));
|
||||
println!("-- END AST DEBUGGING --");
|
||||
}
|
||||
if debug_opts.contains(&"ref".to_string()) {
|
||||
println!("-- BEGIN REFERENCES DEBUGGING --");
|
||||
let sc = doc.scope().borrow();
|
||||
sc.referenceable.iter().for_each(|(name, reference)| {
|
||||
println!(" - {name}: `{:#?}`", doc.get_from_reference(reference));
|
||||
});
|
||||
println!("-- END REFERENCES DEBUGGING --");
|
||||
}
|
||||
if debug_opts.contains(&"var".to_string()) {
|
||||
println!("-- BEGIN VARIABLES DEBUGGING --");
|
||||
let sc = doc.scope().borrow();
|
||||
sc.variables.iter().for_each(|(_name, var)| {
|
||||
println!(" - `{:#?}`", var);
|
||||
});
|
||||
println!("-- END VARIABLES DEBUGGING --");
|
||||
}
|
||||
|
||||
if parser.has_error() {
|
||||
return Err("Parsing failed due to errors while parsing".to_string());
|
||||
}
|
||||
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// Takes a list of paths and processes it into a list of compiled documents
|
||||
pub fn process(
|
||||
target: Target,
|
||||
files: Vec<PathBuf>,
|
||||
db_path: &Option<String>,
|
||||
force_rebuild: bool,
|
||||
debug_opts: &Vec<String>,
|
||||
) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, String> {
|
||||
let mut compiled = vec![];
|
||||
|
||||
let current_dir = std::env::current_dir()
|
||||
.map_err(|err| format!("Unable to get the current working directory: {err}"))?;
|
||||
|
||||
let con = db_path
|
||||
.as_ref()
|
||||
.map_or(Connection::open_in_memory(), |path| Connection::open(path))
|
||||
.map_err(|err| format!("Unable to open connection to the database: {err}"))?;
|
||||
CompiledDocument::init_cache(&con)
|
||||
.map_err(|err| format!("Failed to initialize cached document table: {err}"))?;
|
||||
|
||||
let parser = LangParser::default();
|
||||
for file in files {
|
||||
let meta = std::fs::metadata(&file)
|
||||
.map_err(|err| format!("Failed to get metadata for `{file:#?}`: {err}"))?;
|
||||
|
||||
let modified = meta
|
||||
.modified()
|
||||
.map_err(|err| format!("Unable to query modification time for `{file:#?}`: {err}"))?;
|
||||
|
||||
// Move to file's directory
|
||||
let file_parent_path = file
|
||||
.parent()
|
||||
.ok_or(format!("Failed to get parent path for `{file:#?}`"))?;
|
||||
std::env::set_current_dir(file_parent_path)
|
||||
.map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?;
|
||||
|
||||
let parse_and_compile = || -> Result<(CompiledDocument, Option<PostProcess>), String> {
|
||||
// Parse
|
||||
let source = SourceFile::new(file.to_str().unwrap().to_string(), None).unwrap();
|
||||
println!("Parsing {}...", source.name());
|
||||
let doc = parse(&parser, Rc::new(source), debug_opts)?;
|
||||
|
||||
// Compile
|
||||
let compiler = Compiler::new(target, db_path.clone());
|
||||
let (mut compiled, postprocess) = compiler.compile(&*doc);
|
||||
|
||||
compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
|
||||
Ok((compiled, Some(postprocess)))
|
||||
};
|
||||
|
||||
let (cdoc, post) = if force_rebuild {
|
||||
parse_and_compile()?
|
||||
} else {
|
||||
match CompiledDocument::from_cache(&con, file.to_str().unwrap()) {
|
||||
Some(compiled) => {
|
||||
if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() {
|
||||
parse_and_compile()?
|
||||
} else {
|
||||
(compiled, None)
|
||||
}
|
||||
}
|
||||
None => parse_and_compile()?,
|
||||
}
|
||||
};
|
||||
|
||||
compiled.push((RefCell::new(cdoc), post));
|
||||
}
|
||||
|
||||
for (doc, postprocess) in &compiled {
|
||||
if postprocess.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Post processing
|
||||
let body = postprocess
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.apply(target, &compiled, &doc)?;
|
||||
doc.borrow_mut().body = body;
|
||||
|
||||
// Insert into cache
|
||||
doc.borrow().insert_cache(&con).map_err(|err| {
|
||||
format!(
|
||||
"Failed to insert compiled document from `{}` into cache: {err}",
|
||||
doc.borrow().input
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
std::env::set_current_dir(current_dir)
|
||||
.map_err(|err| format!("Failed to set current directory: {err}"))?;
|
||||
|
||||
Ok(compiled)
|
||||
}
|
||||
|
||||
/// Processes sources from in-memory strings
|
||||
/// This function is indented for testing
|
||||
pub fn process_from_memory(target: Target, sources: Vec<String>) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, String> {
|
||||
let mut compiled = vec![];
|
||||
|
||||
let parser = LangParser::default();
|
||||
for (idx, content) in sources.iter().enumerate() {
|
||||
let parse_and_compile = || -> Result<(CompiledDocument, Option<PostProcess>), String> {
|
||||
// Parse
|
||||
let source = SourceFile::with_content(format!("{idx}"), content.clone(), None);
|
||||
let doc = parse(&parser, Rc::new(source), &vec![])?;
|
||||
|
||||
// Compile
|
||||
let compiler = Compiler::new(target, None);
|
||||
let (compiled, postprocess) = compiler.compile(&*doc);
|
||||
|
||||
Ok((compiled, Some(postprocess)))
|
||||
};
|
||||
|
||||
let (cdoc, post) = parse_and_compile()?;
|
||||
compiled.push((RefCell::new(cdoc), post));
|
||||
}
|
||||
|
||||
for (doc, postprocess) in &compiled {
|
||||
if postprocess.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Post processing
|
||||
let body = postprocess
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.apply(target, &compiled, &doc)?;
|
||||
doc.borrow_mut().body = body;
|
||||
}
|
||||
|
||||
Ok(compiled)
|
||||
}
|
|
@ -22,7 +22,7 @@ pub enum ElemReference {
|
|||
Nested(usize, usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CrossReference {
|
||||
/// When the referenced document is unspecified
|
||||
Unspecific(String),
|
||||
|
|
|
@ -45,7 +45,7 @@ pub mod tests {
|
|||
use crate::parser::langparser::LangParser;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::source::SourceFile;
|
||||
use crate::ParserState;
|
||||
use crate::parser::parser::ParserState;
|
||||
|
||||
#[test]
|
||||
fn validate_refname_tests() {
|
||||
|
|
|
@ -91,13 +91,15 @@ impl Element for ExternalReference {
|
|||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
let mut result = "<a href=\"".to_string();
|
||||
let refname = self.caption.as_ref().unwrap_or(match &self.reference {
|
||||
CrossReference::Unspecific(name) => &name,
|
||||
CrossReference::Specific(_, name) => &name,
|
||||
});
|
||||
|
||||
compiler.insert_crossreference(cursor + result.len(), self.reference.clone());
|
||||
result += format!("\">{}</a>", Compiler::sanitize(Target::HTML, refname)).as_str();
|
||||
|
||||
if let Some(caption) = &self.caption {
|
||||
result +=
|
||||
format!("\">{}</a>", Compiler::sanitize(Target::HTML, caption)).as_str();
|
||||
} else {
|
||||
result += format!("\">{}</a>", self.reference).as_str();
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
_ => todo!(""),
|
||||
|
@ -268,10 +270,6 @@ impl RegexRule for ReferenceRule {
|
|||
.and_then(|(_, s)| Some(s));
|
||||
|
||||
if let Some(refdoc) = refdoc {
|
||||
if caption.is_none() {
|
||||
return reports;
|
||||
}
|
||||
|
||||
if refdoc.is_empty() {
|
||||
state.push(
|
||||
document,
|
||||
|
@ -305,3 +303,95 @@ impl RegexRule for ReferenceRule {
|
|||
reports
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::compiler::process::process_from_memory;
|
||||
use crate::elements::paragraph::Paragraph;
|
||||
use crate::elements::section::Section;
|
||||
use crate::parser::langparser::LangParser;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::source::SourceFile;
|
||||
use crate::validate_document;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn parse_internal() {
|
||||
let source = Rc::new(SourceFile::with_content(
|
||||
"".to_string(),
|
||||
r#"
|
||||
#{ref} Referenceable section
|
||||
|
||||
§{ref}[caption=Section]
|
||||
§{ref}[caption=Another]
|
||||
"#
|
||||
.to_string(),
|
||||
None,
|
||||
));
|
||||
let parser = LangParser::default();
|
||||
let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
|
||||
|
||||
validate_document!(doc.content().borrow(), 0,
|
||||
Section;
|
||||
Paragraph {
|
||||
InternalReference { refname == "ref", caption == Some("Section".to_string()) };
|
||||
InternalReference { refname == "ref", caption == Some("Another".to_string()) };
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn parse_external() {
|
||||
let source = Rc::new(SourceFile::with_content(
|
||||
"".to_string(),
|
||||
r#"
|
||||
§{DocA#ref}[caption=Section]
|
||||
§{DocB#ref}
|
||||
§{#ref}[caption='ref' from any document]
|
||||
"#
|
||||
.to_string(),
|
||||
None,
|
||||
));
|
||||
let parser = LangParser::default();
|
||||
let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
|
||||
|
||||
validate_document!(doc.content().borrow(), 0,
|
||||
Paragraph {
|
||||
ExternalReference { reference == CrossReference::Specific("DocA".into(), "ref".into()), caption == Some("Section".to_string()) };
|
||||
ExternalReference { reference == CrossReference::Specific("DocB".into(), "ref".into()), caption == None::<String> };
|
||||
ExternalReference { reference == CrossReference::Unspecific("ref".into()), caption == Some("'ref' from any document".to_string()) };
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_external()
|
||||
{
|
||||
let result = process_from_memory(Target::HTML, vec![
|
||||
r#"
|
||||
@html.page_title = 0
|
||||
@compiler.output = a.html
|
||||
|
||||
#{ref} Referenceable section
|
||||
"#.into(),
|
||||
r#"
|
||||
@html.page_title = 1
|
||||
@compiler.output = b.html
|
||||
|
||||
§{#ref}
|
||||
§{a#ref}
|
||||
#{ref2} Another Referenceable section
|
||||
"#.into(),
|
||||
r#"
|
||||
@html.page_title = 2
|
||||
|
||||
§{#ref}[caption=from 0]
|
||||
§{#ref2}[caption=from 1]
|
||||
"#.into(),
|
||||
]).unwrap();
|
||||
|
||||
assert!(result[1].0.borrow().body.starts_with("<div class=\"content\"><p><a href=\"a.html#Referenceable_section\">#ref</a><a href=\"a.html#Referenceable_section\">a#ref</a></p>"));
|
||||
assert!(result[2].0.borrow().body.starts_with("<div class=\"content\"><p><a href=\"a.html#Referenceable_section\">from 0</a><a href=\"b.html#Another_Referenceable_section\">from 1</a></p>"));
|
||||
}
|
||||
}
|
||||
|
|
165
src/main.rs
165
src/main.rs
|
@ -5,29 +5,16 @@ mod elements;
|
|||
mod lua;
|
||||
mod parser;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::io::BufWriter;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
use std::rc::Rc;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use compiler::compiler::CompiledDocument;
|
||||
use compiler::compiler::Compiler;
|
||||
use compiler::compiler::Target;
|
||||
use compiler::navigation::create_navigation;
|
||||
use compiler::postprocess::PostProcess;
|
||||
use document::document::Document;
|
||||
use getopts::Options;
|
||||
use parser::langparser::LangParser;
|
||||
use parser::parser::Parser;
|
||||
use parser::parser::ParserState;
|
||||
use rusqlite::Connection;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::parser::source::SourceFile;
|
||||
extern crate getopts;
|
||||
|
||||
fn print_usage(program: &str, opts: Options) {
|
||||
|
@ -49,142 +36,6 @@ NML version: 0.4\n"
|
|||
);
|
||||
}
|
||||
|
||||
fn parse(
|
||||
parser: &LangParser,
|
||||
input: &str,
|
||||
debug_opts: &Vec<String>,
|
||||
) -> Result<Box<dyn Document<'static>>, String> {
|
||||
println!("Parsing {input}...");
|
||||
|
||||
// Parse
|
||||
let source = SourceFile::new(input.to_string(), None).unwrap();
|
||||
let (doc, _) = parser.parse(ParserState::new(parser, None), Rc::new(source), None);
|
||||
|
||||
if debug_opts.contains(&"ast".to_string()) {
|
||||
println!("-- BEGIN AST DEBUGGING --");
|
||||
doc.content()
|
||||
.borrow()
|
||||
.iter()
|
||||
.for_each(|elem| println!("{elem:#?}"));
|
||||
println!("-- END AST DEBUGGING --");
|
||||
}
|
||||
if debug_opts.contains(&"ref".to_string()) {
|
||||
println!("-- BEGIN REFERENCES DEBUGGING --");
|
||||
let sc = doc.scope().borrow();
|
||||
sc.referenceable.iter().for_each(|(name, reference)| {
|
||||
println!(" - {name}: `{:#?}`", doc.get_from_reference(reference));
|
||||
});
|
||||
println!("-- END REFERENCES DEBUGGING --");
|
||||
}
|
||||
if debug_opts.contains(&"var".to_string()) {
|
||||
println!("-- BEGIN VARIABLES DEBUGGING --");
|
||||
let sc = doc.scope().borrow();
|
||||
sc.variables.iter().for_each(|(_name, var)| {
|
||||
println!(" - `{:#?}`", var);
|
||||
});
|
||||
println!("-- END VARIABLES DEBUGGING --");
|
||||
}
|
||||
|
||||
if parser.has_error() {
|
||||
return Err("Parsing failed due to errors while parsing".to_string());
|
||||
}
|
||||
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
fn process(
|
||||
target: Target,
|
||||
files: Vec<PathBuf>,
|
||||
db_path: &Option<String>,
|
||||
force_rebuild: bool,
|
||||
debug_opts: &Vec<String>,
|
||||
) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, String> {
|
||||
let mut compiled = vec![];
|
||||
|
||||
let current_dir = std::env::current_dir()
|
||||
.map_err(|err| format!("Unable to get the current working directory: {err}"))?;
|
||||
|
||||
let con = db_path
|
||||
.as_ref()
|
||||
.map_or(Connection::open_in_memory(), |path| Connection::open(path))
|
||||
.map_err(|err| format!("Unable to open connection to the database: {err}"))?;
|
||||
CompiledDocument::init_cache(&con)
|
||||
.map_err(|err| format!("Failed to initialize cached document table: {err}"))?;
|
||||
|
||||
let parser = LangParser::default();
|
||||
for file in files {
|
||||
let meta = std::fs::metadata(&file)
|
||||
.map_err(|err| format!("Failed to get metadata for `{file:#?}`: {err}"))?;
|
||||
|
||||
let modified = meta
|
||||
.modified()
|
||||
.map_err(|err| format!("Unable to query modification time for `{file:#?}`: {err}"))?;
|
||||
|
||||
// Move to file's directory
|
||||
let file_parent_path = file
|
||||
.parent()
|
||||
.ok_or(format!("Failed to get parent path for `{file:#?}`"))?;
|
||||
std::env::set_current_dir(file_parent_path)
|
||||
.map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?;
|
||||
|
||||
let parse_and_compile = || -> Result<(CompiledDocument, Option<PostProcess>), String> {
|
||||
// Parse
|
||||
let doc = parse(&parser, file.to_str().unwrap(), debug_opts)?;
|
||||
|
||||
// Compile
|
||||
let compiler = Compiler::new(target, db_path.clone());
|
||||
let (mut compiled, postprocess) = compiler.compile(&*doc);
|
||||
|
||||
compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
|
||||
Ok((compiled, Some(postprocess)))
|
||||
};
|
||||
|
||||
let (cdoc, post) = if force_rebuild {
|
||||
parse_and_compile()?
|
||||
} else {
|
||||
match CompiledDocument::from_cache(&con, file.to_str().unwrap()) {
|
||||
Some(compiled) => {
|
||||
if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() {
|
||||
parse_and_compile()?
|
||||
} else {
|
||||
(compiled, None)
|
||||
}
|
||||
}
|
||||
None => parse_and_compile()?,
|
||||
}
|
||||
};
|
||||
|
||||
compiled.push((RefCell::new(cdoc), post));
|
||||
}
|
||||
|
||||
for (doc, postprocess) in &compiled {
|
||||
if postprocess.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Post processing
|
||||
let body = postprocess
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.apply(target, &compiled, &doc)?;
|
||||
doc.borrow_mut().body = body;
|
||||
|
||||
// Insert into cache
|
||||
doc.borrow().insert_cache(&con).map_err(|err| {
|
||||
format!(
|
||||
"Failed to insert compiled document from `{}` into cache: {err}",
|
||||
doc.borrow().input
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
std::env::set_current_dir(current_dir)
|
||||
.map_err(|err| format!("Failed to set current directory: {err}"))?;
|
||||
|
||||
Ok(compiled)
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let program = args[0].clone();
|
||||
|
@ -362,13 +213,15 @@ fn main() -> ExitCode {
|
|||
}
|
||||
|
||||
// Parse, compile using the cache
|
||||
let processed = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) {
|
||||
Ok(processed) => processed,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
let processed =
|
||||
match compiler::process::process(Target::HTML, files, &db_path, force_rebuild, &debug_opts)
|
||||
{
|
||||
Ok(processed) => processed,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if input_meta.is_dir()
|
||||
// Batch mode
|
||||
|
|
30
style.css
30
style.css
|
@ -137,6 +137,11 @@ a.inline-code {
|
|||
content: "–";
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
a.section-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
div.code-block-title {
|
||||
background-color: #20202a;
|
||||
|
@ -241,3 +246,28 @@ a:hover.medium-ref img {
|
|||
|
||||
box-shadow: 0px 0px 6px 2px rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
/* Blockquote */
|
||||
blockquote {
|
||||
margin-left: 0.2em;
|
||||
padding-left: 0.6em;
|
||||
|
||||
border-left: 4px solid #0ff08b;
|
||||
}
|
||||
|
||||
blockquote p::before {
|
||||
content: '\201C';
|
||||
}
|
||||
|
||||
blockquote p::after {
|
||||
content: '\201D';
|
||||
}
|
||||
|
||||
.blockquote-author:before {
|
||||
content: '—';
|
||||
}
|
||||
|
||||
.blockquote-author {
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue