Compare commits

...

13 commits

Author SHA1 Message Date
98fd99f702 Respect toc attribute in metadata
All checks were successful
PR Check / lint-and-format (pull_request) Successful in 3m19s
2024-11-06 18:38:56 +01:00
b46b20e693 wip: quick and dumb implementation of toc 2024-11-06 18:38:56 +01:00
e54cd44714
fix code blocks 2024-11-06 18:24:49 +01:00
a3161d822d
svg theme changer 2024-11-06 18:17:29 +01:00
0f6f2f1fc4
increase cours page width 2024-11-06 17:54:02 +01:00
2a4ae9f273
centered 2024-11-06 17:41:27 +01:00
aed4fa2bff
svg update on media change 2024-11-06 16:42:45 +01:00
856770c2ae
fix table 2024-11-06 16:32:19 +01:00
29cf3e3e00
fix markdown on posts 2024-11-06 16:31:13 +01:00
b02f715c5a
default to hardbreak minus blog 2024-11-06 16:11:42 +01:00
3e5ac643a7
table support 2024-11-06 15:46:27 +01:00
1097ee5194
split markdown sheet from post 2024-11-06 15:33:09 +01:00
61170953fe
ordering 2024-11-06 15:02:07 +01:00
9 changed files with 410 additions and 249 deletions

View file

@ -1,8 +1,10 @@
use crate::misc::date::Date; use crate::misc::date::Date;
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
use comrak::nodes::{AstNode, NodeValue}; use comrak::nodes::{AstNode, NodeCode, NodeMath, NodeValue};
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options}; use comrak::{
format_html, parse_document, Anchorizer, Arena, ComrakOptions, ListStyleType, Options,
};
use lol_html::html_content::ContentType; use lol_html::html_content::ContentType;
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings}; use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
use ramhorns::Content; use ramhorns::Content;
@ -117,6 +119,7 @@ impl Metadata {
pub struct File { pub struct File {
pub metadata: Metadata, pub metadata: Metadata,
pub content: String, pub content: String,
pub toc_data: String,
} }
/// Options used for parser and compiler MD --> HTML /// Options used for parser and compiler MD --> HTML
@ -151,7 +154,7 @@ pub fn get_options<'a>() -> ComrakOptions<'a> {
// options.render.broken_link_callback = ...; // options.render.broken_link_callback = ...;
// Renderer // Renderer
options.render.hardbreaks = false; // could be true? change by metadata could be good for compatibility options.render.hardbreaks = true;
options.render.github_pre_lang = false; options.render.github_pre_lang = false;
options.render.full_info_string = true; options.render.full_info_string = true;
options.render.width = 0; // 0 mean disabled? options.render.width = 0; // 0 mean disabled?
@ -286,7 +289,7 @@ pub fn read_md(
) -> File { ) -> File {
let arena = Arena::new(); let arena = Arena::new();
let opt = options.map_or_else(get_options, |specific_opt| specific_opt); let mut opt = options.map_or_else(get_options, |specific_opt| specific_opt);
let root = parse_document(&arena, raw_text, &opt); let root = parse_document(&arena, raw_text, &opt);
// Find metadata // Find metadata
@ -295,6 +298,11 @@ pub fn read_md(
let mermaid_name = "mermaid"; let mermaid_name = "mermaid";
hljs_replace(root, mermaid_name); hljs_replace(root, mermaid_name);
if let TypeFileMetadata::Blog = metadata_type {
// Change by metadata could be good for compatibility
opt.render.hardbreaks = true;
}
// Convert to HTML // Convert to HTML
let mut html = vec![]; let mut html = vec![];
format_html(root, &opt, &mut html).unwrap(); format_html(root, &opt, &mut html).unwrap();
@ -307,6 +315,8 @@ pub fn read_md(
html_content = custom_img_size(&html_content); html_content = custom_img_size(&html_content);
(html_content, mail_obfsucated) = mail_obfuscation(&html_content); (html_content, mail_obfsucated) = mail_obfuscation(&html_content);
let toc = toc_to_html(&generate_toc(root));
let mut final_metadata = Metadata { let mut final_metadata = Metadata {
info: metadata, info: metadata,
mermaid: check_mermaid(root, mermaid_name), mermaid: check_mermaid(root, mermaid_name),
@ -319,6 +329,7 @@ pub fn read_md(
File { File {
metadata: final_metadata, metadata: final_metadata,
content: html_content, content: html_content,
toc_data: toc,
} }
} }
@ -503,3 +514,87 @@ fn mail_obfuscation(html: &str) -> (String, bool) {
(new_html, modified) (new_html, modified)
} }
} }
#[derive(Debug)]
struct TOCEntry {
id: String,
title: String,
depth: u8,
}
fn generate_toc<'a>(root: &'a AstNode<'a>) -> Vec<TOCEntry> {
/// See <https://github.com/kivikakk/comrak/blob/b67d406d3b101b93539c37a1ca75bff81ff8c149/src/html.rs#L446>
fn collect_text<'a>(node: &'a AstNode<'a>, output: &mut String) {
match node.data.borrow().value {
NodeValue::Text(ref literal)
| NodeValue::Code(NodeCode { ref literal, .. })
| NodeValue::Math(NodeMath { ref literal, .. }) => {
*output = literal.to_string();
}
_ => {
for n in node.children() {
if !output.is_empty() {
break;
}
collect_text(n, output);
}
}
}
}
let mut toc = vec![];
let mut anchorizer = Anchorizer::new();
// Collect headings first to avoid mutable borrow conflicts
let headings: Vec<_> = root
.children()
.filter_map(|node| {
if let NodeValue::Heading(ref nch) = &node.data.borrow().value {
Some((*nch, node))
} else {
None
}
})
.collect();
// Now process each heading
for (nch, node) in headings {
let mut title = String::with_capacity(20);
collect_text(node, &mut title);
toc.push(TOCEntry {
id: anchorizer.anchorize(title.clone()),
title,
depth: nch.level,
});
}
toc
}
fn toc_to_html(toc: &[TOCEntry]) -> String {
if toc.is_empty() {
return String::new();
}
let mut html = Vec::with_capacity(20 + 20 * toc.len());
html.extend_from_slice(b"<ul>");
for entry in toc {
// TODO: Use depth
html.extend_from_slice(
format!(
"<li><a href=\"{}\">{} (dbg/depth/{})</a></li>",
entry.id, entry.title, entry.depth
)
.as_bytes(),
);
}
html.extend_from_slice(b"</ul>");
String::from_utf8(html).unwrap()
}

View file

@ -81,5 +81,6 @@ fn read_pdf(data: Vec<u8>) -> File {
style="width: 100%; height: 79vh"; style="width: 100%; height: 79vh";
>"# >"#
), ),
toc_data: String::new(),
} }
} }

View file

@ -186,7 +186,6 @@ fn get_posts(location: &str) -> Vec<Post> {
struct BlogPostTemplate { struct BlogPostTemplate {
navbar: NavBar, navbar: NavBar,
post: Option<File>, post: Option<File>,
toc: String,
} }
#[get("/blog/p/{id}")] #[get("/blog/p/{id}")]
@ -199,7 +198,7 @@ pub async fn page(path: web::Path<(String,)>, config: web::Data<Config>) -> impl
fn build_post(file: &str, config: Config) -> String { fn build_post(file: &str, config: Config) -> String {
let mut post = None; let mut post = None;
let (infos, toc) = get_post( let infos = get_post(
&mut post, &mut post,
file, file,
&config.fc.name.unwrap_or_default(), &config.fc.name.unwrap_or_default(),
@ -214,18 +213,12 @@ fn build_post(file: &str, config: Config) -> String {
..NavBar::default() ..NavBar::default()
}, },
post, post,
toc,
}, },
infos, infos,
) )
} }
fn get_post( fn get_post(post: &mut Option<File>, filename: &str, name: &str, data_dir: &str) -> InfosPage {
post: &mut Option<File>,
filename: &str,
name: &str,
data_dir: &str,
) -> (InfosPage, String) {
let blog_dir = format!("{data_dir}/{BLOG_DIR}/{POST_DIR}"); let blog_dir = format!("{data_dir}/{BLOG_DIR}/{POST_DIR}");
let ext = ".md"; let ext = ".md";
@ -234,13 +227,8 @@ fn get_post(
&TypeFileMetadata::Blog, &TypeFileMetadata::Blog,
); );
let default = ( let default = (filename, &format!("Blog d'{name}"), Vec::new());
filename, let (title, desc, tags) = match post {
&format!("Blog d'{name}"),
Vec::new(),
String::new(),
);
let (title, desc, tags, toc) = match post {
Some(data) => ( Some(data) => (
match &data.metadata.info.blog.as_ref().unwrap().title { match &data.metadata.info.blog.as_ref().unwrap().title {
Some(text) => text, Some(text) => text,
@ -254,16 +242,10 @@ fn get_post(
Some(tags) => tags.clone(), Some(tags) => tags.clone(),
None => default.2, None => default.2,
}, },
match &data.metadata.info.blog.as_ref().unwrap().toc {
// TODO: Generate TOC
Some(true) => String::new(),
_ => default.3,
},
), ),
None => default, None => default,
}; };
(
InfosPage { InfosPage {
title: Some(format!("Post: {title}")), title: Some(format!("Post: {title}")),
desc: Some(desc.clone()), desc: Some(desc.clone()),
@ -273,9 +255,7 @@ fn get_post(
.chain(tags.iter().map(|t| t.name.as_str())) .chain(tags.iter().map(|t| t.name.as_str()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
)), )),
}, }
toc,
)
} }
#[routes] #[routes]

View file

@ -1,21 +1,13 @@
@import "../markdown.css";
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
--code-font-color: #333333;
--code-bg-color: #eeeeee;
--quote-border-color: #9852fa;
--quote-bg-color: #d8d6d6;
--separator-color: #cccccc;
--tag-bg-color: #d2e0f0; --tag-bg-color: #d2e0f0;
} }
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--code-font-color: #eeeeee;
--code-bg-color: #333333;
--quote-border-color: #bd93f9;
--quote-bg-color: #273341;
--separator-color: #414558;
--tag-bg-color: #242e38; --tag-bg-color: #242e38;
} }
} }
@ -24,11 +16,6 @@
--max-width: 750px; --max-width: 750px;
} }
/* Page */
html {
scroll-behavior: smooth;
}
body { body {
max-width: var(--max-width); max-width: var(--max-width);
margin: auto; margin: auto;
@ -70,49 +57,8 @@ main {
max-width: 100%; max-width: 100%;
} }
/* Anchors */
:is(h1, h2, h3, h4, h5, h6):hover a.anchor::before {
visibility: visible;
}
a.anchor::before {
content: "#";
visibility: hidden;
padding-right: 0.1em;
}
a.anchor {
text-decoration: none;
vertical-align: baseline;
}
/* Links in headers */
:is(h1, h2, h3, h4, h5, h6) a {
font-size: inherit;
}
/* Separators */
hr {
border: 0;
height: 1px;
background: var(--separator-color);
}
/* Quotes */
blockquote {
margin: 1em 0;
padding: 0.1em 10px;
border-left: 6px solid;
border-color: var(--quote-border-color);
background: var(--quote-bg-color);
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
/* Images */ /* Images */
img { img {
display: block;
margin: auto;
max-width: var(--max-width); max-width: var(--max-width);
} }
@ -122,115 +68,6 @@ code {
font-family: monospace; font-family: monospace;
} }
/* Little snippet of code (not blocks) */
kbd,
code:not(.hljs):not(:has(svg)) {
background: var(--code-bg-color);
border-radius: 3px;
color: var(--code-font-color);
box-shadow: 0 1px 1px black;
font-size: calc(var(--font-size) * 0.8);
padding: 2px 4px;
vertical-align: 1.5px;
}
/* Code blocks */
.hljs {
border-radius: 5px;
}
.hljs::-webkit-scrollbar {
width: 7px;
height: 9px;
background: var(--background);
}
.hljs::-webkit-scrollbar-thumb {
background-color: var(--font-color);
border-radius: 10px;
}
/* Marge for numbers */
.hljs-ln-n {
margin-right: 0.4em;
}
/* Numbers in codeblocks */
.hljs-ln-numbers {
text-align: right;
color: var(--font-color);
}
/* Fix scroll in codeblocks with line numbering */
table.hljs-ln {
overflow: hidden;
}
/* Background for copy code button */
.hljs-copy-button {
background-color: var(--background) !important;
}
/* Light theme for the copy code button */
@media (prefers-color-scheme: light) {
.hljs-copy-button {
background-color: var(--font-color) !important;
filter: invert(100%);
}
}
/* Hide last line in codeblocks if empty */
.hljs-ln
> tbody
> tr:last-child:has(td:last-child > span::-moz-only-whitespace) {
visibility: collapse;
}
/* Temporary fix for layout.css.has-selector.enabled available only on
* Firefox under certain circumstances */
.hljs-ln > tbody > tr:last-child {
visibility: collapse;
}
/* Reference to footnotes */
.footnote-ref a {
text-decoration: underline dotted;
font-size: calc(var(--font-size) * 0.8);
}
/* Footnote */
section.footnotes * {
font-size: calc(var(--font-size) * 0.8);
}
/* When multiple ref */
a.footnote-backref sup {
font-size: calc(var(--font-size) * 0.6);
}
a.footnote-backref sup::before {
content: "(";
}
a.footnote-backref sup::after {
content: ")";
}
/* Footnotes links */
a.footnote-backref {
font-family: "Segoe UI", "Segoe UI Symbol", system-ui;
text-decoration: underline dotted;
}
/* Footnotes block separation from article */
section.footnotes {
margin: 3px;
border-top: 2px dotted var(--separator-color);
}
/* Mermaid diagrams */
pre:has(code.language-mermaid) {
text-align: center;
}
/* Table of content */ /* Table of content */
nav#toc { nav#toc {
position: fixed; position: fixed;
@ -246,36 +83,3 @@ nav#toc {
visibility: hidden; visibility: hidden;
} }
} }
@media print {
/* Better colors for paper */
blockquote {
border-color: black;
background: var(--background);
}
.hljs {
background: var(--background);
}
/* Force line numbering to be on top */
td.hljs-ln-line {
vertical-align: top;
}
/* Break code */
code.hljs {
white-space: break-spaces;
hyphens: none;
}
/* Hide arrows of backref */
a.footnote-backref {
visibility: hidden;
}
/* No underline for footnotes */
.footnote-ref > a {
text-decoration: none;
}
}

View file

@ -1,3 +1,13 @@
@import "markdown.css";
:root {
--max-width: 900px;
}
main {
max-width: var(--max-width);
}
/* Filetree */ /* Filetree */
aside { aside {
float: left; float: left;
@ -48,6 +58,4 @@ aside li.directory {
main img { main img {
max-width: 100%; max-width: 100%;
display: block;
margin: auto;
} }

248
static/css/markdown.css Normal file
View file

@ -0,0 +1,248 @@
@media (prefers-color-scheme: light) {
:root {
--code-font-color: #333333;
--code-bg-color: #eeeeee;
--quote-border-color: #9852fa;
--quote-bg-color: #d8d6d6;
--separator-color: #cccccc;
--tag-bg-color: #d2e0f0;
}
}
@media (prefers-color-scheme: dark) {
:root {
--code-font-color: #eeeeee;
--code-bg-color: #333333;
--quote-border-color: #bd93f9;
--quote-bg-color: #273341;
--separator-color: #414558;
--tag-bg-color: #242e38;
}
}
/* Page */
html {
scroll-behavior: smooth;
}
/* Anchors */
main :is(h1, h2, h3, h4, h5, h6):hover a.anchor::before {
visibility: visible;
}
main a.anchor::before {
content: "#";
visibility: hidden;
padding-right: 0.1em;
}
main a.anchor {
text-decoration: none;
vertical-align: baseline;
}
/* Links in headers */
:is(h1, h2, h3, h4, h5, h6) a {
font-size: inherit;
}
/* Images */
main img {
display: block;
margin: auto;
}
/* Separators */
hr {
border: 0;
height: 1px;
background: var(--separator-color);
}
/* Quotes */
blockquote {
margin: 1em 0;
padding: 0.1em 10px;
border-left: 6px solid;
border-color: var(--quote-border-color);
background: var(--quote-bg-color);
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
/* Little snippet of code (not blocks) */
kbd,
code:not(.hljs):not(:has(svg)) {
background: var(--code-bg-color);
border-radius: 3px;
color: var(--code-font-color);
box-shadow: 0 1px 1px black;
font-size: calc(var(--font-size) * 0.8);
padding: 2px 4px;
vertical-align: 1.5px;
}
/* Code blocks */
.hljs {
border-radius: 5px;
}
.hljs::-webkit-scrollbar {
width: 7px;
height: 9px;
background: var(--background);
}
.hljs::-webkit-scrollbar-thumb {
background-color: var(--font-color);
border-radius: 10px;
}
/* Marge for numbers */
.hljs-ln-n {
margin-right: 0.4em;
}
/* Numbers in codeblocks */
.hljs-ln-numbers {
text-align: right;
color: var(--font-color);
}
/* Fix scroll in codeblocks with line numbering */
table.hljs-ln {
overflow: hidden;
}
/* Background for copy code button */
.hljs-copy-button {
background-color: var(--background) !important;
}
/* Light theme for the copy code button */
@media (prefers-color-scheme: light) {
.hljs-copy-button {
background-color: var(--font-color) !important;
filter: invert(100%);
}
}
/* Hide last line in codeblocks if empty */
.hljs-ln
> tbody
> tr:last-child:has(td:last-child > span::-moz-only-whitespace) {
visibility: collapse;
}
/* Temporary fix for layout.css.has-selector.enabled available only on
* Firefox under certain circumstances */
.hljs-ln > tbody > tr:last-child {
visibility: collapse;
}
/* Reference to footnotes */
.footnote-ref a {
text-decoration: underline dotted;
font-size: calc(var(--font-size) * 0.8);
}
/* Footnote */
section.footnotes * {
font-size: calc(var(--font-size) * 0.8);
}
/* When multiple ref */
a.footnote-backref sup {
font-size: calc(var(--font-size) * 0.6);
}
a.footnote-backref sup::before {
content: "(";
}
a.footnote-backref sup::after {
content: ")";
}
/* Footnotes links */
a.footnote-backref {
font-family: "Segoe UI", "Segoe UI Symbol", system-ui;
text-decoration: underline dotted;
}
/* Footnotes block separation from content */
section.footnotes {
margin: 3px;
border-top: 2px dotted var(--separator-color);
}
/* Mermaid diagrams */
pre:has(code.language-mermaid) {
text-align: center;
}
/* Tables */
table:not(.hljs-ln) {
border-collapse: collapse;
margin-inline: auto;
}
table:not(.hljs-ln) th,
table:not(.hljs-ln) td {
padding: 5px;
border: 1px solid var(--separator-color);
}
table:not(.hljs-ln)th {
border-bottom: 2px solid var(--separator-color);
}
/* No borders on the outer edges of the table */
table:not(.hljs-ln) tr:last-child td {
border-bottom: 0;
}
table:not(.hljs-ln) tr:first-child th {
border-top: 0;
}
table:not(.hljs-ln) tr td:first-child,
table:not(.hljs-ln) tr th:first-child {
border-left: 0;
}
table:not(.hljs-ln) tr td:last-child,
table:not(.hljs-ln) tr th:last-child {
border-right: 0;
}
@media print {
/* Better colors for paper */
blockquote {
border-color: black;
background: var(--background);
}
.hljs {
background: var(--background);
}
/* Force line numbering to be on top */
td.hljs-ln-line {
vertical-align: top;
}
/* Break code */
code.hljs {
white-space: break-spaces;
hyphens: none;
}
/* Hide arrows of backref */
a.footnote-backref {
visibility: hidden;
}
/* No underline for footnotes */
.footnote-ref > a {
text-decoration: none;
}
}

View file

@ -80,11 +80,16 @@ const deepestNodeOpened = (path, options) => {
} }
}; };
const svgDarkTheme = () => { const Mode = {
Light: 1,
Dark: 2,
};
const svgChangeTheme = (mode) => {
for (const item of document.getElementsByTagName("img")) { for (const item of document.getElementsByTagName("img")) {
if (!item.src.startsWith("data:image/svg+xml;base64,")) { if (!item.src.startsWith("data:image/svg+xml;base64,")) {
// Exclude image who aren't SVG and base64 encoded // Exclude image who aren't SVG and base64 encoded
break; continue;
} }
/** Convert to grayscale */ /** Convert to grayscale */
@ -129,9 +134,19 @@ const svgDarkTheme = () => {
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0); const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0);
const averageGrayscale = totalGrayscale / grayscaleValues.length; const averageGrayscale = totalGrayscale / grayscaleValues.length;
if (averageGrayscale < 128) { const treshold = 128;
if (averageGrayscale < treshold && mode === Mode.Dark) {
item.style = "filter: invert(1);"; item.style = "filter: invert(1);";
continue;
} }
if (averageGrayscale > treshold && mode === Mode.Light) {
item.style = "filter: invert(1);";
continue;
}
item.style = "";
} }
}; };
@ -160,8 +175,16 @@ window.addEventListener("load", () => {
uncollapse(last_openeded); uncollapse(last_openeded);
} }
// Fix SVG images in dark mode // Fix SVG images
if (window.matchMedia("(prefers-color-scheme: dark)").matches) { svgChangeTheme(
svgDarkTheme(); window.matchMedia("(prefers-color-scheme: dark)").matches
} ? Mode.Dark
: Mode.Light
);
}); });
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) =>
svgChangeTheme(event.matches ? Mode.Dark : Mode.Light)
);

View file

@ -2,17 +2,17 @@ window.addEventListener("load", () => {
const macros = {}; const macros = {};
for (const item of new Map( for (const item of new Map(
Object.entries({ Object.entries({
B: "mathbb{B}",
N: "mathbb{N}", N: "mathbb{N}",
R: "mathbb{R}", R: "mathbb{R}",
Z: "mathbb{Z}", Z: "mathbb{Z}",
B: "mathbb{B}",
O: "Theta", O: "Theta",
Tau: "mathrm{T}",
u: "mu",
ra: "rightarrow", ra: "rightarrow",
la: "leftarrow", la: "leftarrow",
RA: "Rightarrow", RA: "Rightarrow",
LA: "Leftarrow", LA: "Leftarrow",
u: "mu",
Tau: "mathrm{T}",
lb: "llbracket", lb: "llbracket",
rb: "rrbracket", rb: "rrbracket",
}) })

View file

@ -28,7 +28,9 @@
<main> <main>
{{^post}} {{^post}}
<p>This post doesn't exist... sorry</p> <p>This post doesn't exist... sorry</p>
{{/post}} {{#post}} {{&toc}} {{/post}} {{#post}} {{#metadata}} {{#info}} {{#blog}} {{#toc}}
<aside>{{&toc_data}}</aside>
{{/toc}} {{/blog}} {{/info}} {{/metadata}}
<article>{{&content}}</article> <article>{{&content}}</article>
{{/post}} {{/post}}
</main> </main>