feat: inline code, resource links

fixes Text::is_empty() and allows formatted
text in the context section of quotes (so you can link to /ref)

slight changes to the css of the html generator,
this should fix document preview on link hovering
and might improve nesting if you edit the generated
html in weird ways (should be more robust now).
This commit is contained in:
Mark
2026-08-03 17:53:07 +02:00
parent 8492d0b636
commit b53e6e7bb6
5 changed files with 151 additions and 95 deletions

View File

@@ -11,7 +11,8 @@ pub enum Section<'a> {
Paragraph(Text<'a>),
List(Vec<Text<'a>>),
Code(&'a str, Text<'a>),
Quote(&'a str, &'a str, Text<'a>),
Quote(&'a str, Text<'a>, Text<'a>),
Link(&'a str, &'a str, Text<'a>),
Ext(&'a str, &'a str),
Document(Document<'a>),
}
@@ -25,34 +26,32 @@ pub enum Component<'a> {
Text(&'a str),
Note(Text<'a>),
Wrong(Text<'a>),
Name(Text<'a>),
Code(Text<'a>),
Special(Text<'a>),
Emphasis(Text<'a>),
Important(Text<'a>),
Name(Text<'a>),
Code(Text<'a>),
Link(&'a str, Text<'a>),
Ext(&'a str, &'a str),
}
impl<'a> Text<'a> {
pub fn is_empty(&self) -> bool {
self.0.iter().any(|c| c.is_empty())
self.0.iter().all(|c| c.is_empty())
}
}
impl<'a> Component<'a> {
pub fn is_empty(&self) -> bool {
match self {
Component::Char(_) => false,
Component::Char(_) | Component::Link(_, _) | Component::Ext(_, _) => false,
Component::Text(t) => t.is_empty(),
Component::Note(t)
| Component::Wrong(t)
| Component::Name(t)
| Component::Code(t)
| Component::Special(t)
| Component::Emphasis(t)
| Component::Important(t)
| Component::Name(t)
| Component::Code(t) => t.is_empty(),
Component::Link(target, t) => target.is_empty() && t.is_empty(),
Component::Ext(_, _) => false,
| Component::Important(t) => t.is_empty(),
}
}
}

View File

@@ -225,6 +225,12 @@ pub fn parse_text_impl<'a>(
'~' if opt.is_empty() => {
inner = Ok(text(inner, Component::Wrong));
}
'@' if opt.is_empty() => {
inner = Ok(text(inner, Component::Name));
}
'!' if opt.is_empty() => {
inner = Ok(text(inner, Component::Code));
}
'_' if opt.is_empty() => {
inner = Ok(text(inner, Component::Special));
}
@@ -234,12 +240,6 @@ pub fn parse_text_impl<'a>(
'*' if opt.is_empty() => {
inner = Ok(text(inner, Component::Important));
}
'@' if opt.is_empty() => {
inner = Ok(text(inner, Component::Name));
}
'!' if opt.is_empty() => {
inner = Ok(text(inner, Component::Code));
}
'/' if !opt.is_empty() => {
inner = Ok(text(inner, |v| Component::Link(opt, v)));
}
@@ -254,11 +254,11 @@ pub fn parse_text_impl<'a>(
Component::Link(_, text)
| Component::Note(text)
| Component::Wrong(text)
| Component::Name(text)
| Component::Code(text)
| Component::Special(text)
| Component::Emphasis(text)
| Component::Important(text)
| Component::Name(text)
| Component::Code(text) => inner = &mut text.0[0],
| Component::Important(text) => inner = &mut text.0[0],
Component::Char(_) | Component::Ext(_, _) => {
unreachable!()
}
@@ -426,7 +426,31 @@ fn parse_sections<'a>(
attributed.rsplit_once('@').unwrap_or((attributed, ""));
sections.push(Section::Quote(
attributed.strip_suffix(' ').unwrap_or(attributed),
context.strip_prefix(' ').unwrap_or(context),
parse_text(&mut context.strip_prefix(' ').unwrap_or(context), |o| {
o.remove_linebreaks()
})
.map_err(|ParseError(e, i)| ParseError(e, i + pos))?,
parse_text(&mut inner, |o| o.remove_linebreaks())
.map_err(|ParseError(e, i)| ParseError(e, i + pos))?,
));
}
Some(_)
if posthash
.trim_start_matches(|c: char| !(c == ':' || c.is_whitespace()))
.starts_with(":") =>
{
let i = posthash.find(':').unwrap();
let (mut link, mut inner) = if let Some(j) = posthash.find('\n') {
(&posthash[0..j], &posthash[j + 1..])
} else {
(posthash, "")
};
if i == 0 {
link = &link[1..];
}
sections.push(Section::Link(
&posthash[0..i],
link,
parse_text(&mut inner, |o| o.remove_linebreaks())
.map_err(|ParseError(e, i)| ParseError(e, i + pos))?,
));

View File

@@ -40,7 +40,7 @@ pub fn page_to<'a>(
<style>
@media (prefers-color-scheme: light) {{ :root {{ --bg: #FFF; --bg2: #FFF4F4; --fg: #000; }} }}
@media (prefers-color-scheme: dark) {{ :root {{ --bg: #070002; --bg2: #070010; --fg: #E0B8A0; }} }}
:root {{ color: var(--fg); background: var(--bg); font-family: sans-serif;
:root {{ background: var(--bg);
--fgl: color-mix(var(--fg), var(--bg) 50%); --fgd: color-mix(var(--fg), var(--bg) 65%); --fgt: color-mix(var(--fg), var(--bg) 80%);
--fsmall: 66%; --fless: 77%; --fmore: 110%; --ftitle: 132%; }}
/*
@@ -54,6 +54,11 @@ pub fn page_to<'a>(
max-width: 90%;
width: fit-content;
}}
.doc {{
color: var(--fg); font-family: sans-serif;
text-align: left; text-decoration: none;
}}
a {{ color: inherit; text-decoration: none; }}
.z {{
width: fit-content;
max-width: 100%;
@@ -108,64 +113,67 @@ pub fn page_to<'a>(
padding-left: 1em;
}}
.y.code .head {{
.y.code > .head {{
color: var(--fgl);
font-size: var(--fsmall);
}}
.y.code .head::before {{
.y.code > .head::before {{
content: "#! ";
font-size: var(--fless);
}}
.y.quote .head {{
.y.quote > .head {{
color: var(--fgl);
font-size: var(--fless);
}}
.y.quote .tail {{
.y.quote > .tail {{
color: var(--fgl);
font-size: var(--fsmall);
text-align: right;
}}
.y.quote .head::before {{
.y.quote > .head::before {{
content: "@ ";
font-size: var(--fless);
}}
.y.quote:not(.c-) .tail::before {{
.y.quote > .tail::before {{
content: " (";
font-size: var(--fless);
}}
.y.quote:not(.c-) .tail::after {{
.y.quote > .tail::after {{
content: ") ";
font-size: var(--fless);
}}
.y.quote.c- .x::after {{
color: var(--fgl);
.y.rlink > .head {{
font-size: var(--fsmall);
content: "”";
> * {{ text-decoration: underline var(--fgl); }}
}}
.y.rlink > .head::before {{
content: "➤ ";
color: var(--fgl);
}}
.y.ext.spec .head {{
.y.ext.spec > .head {{
color: var(--fgl);
}}
.y.ext.nospec .head {{
.y.ext.nospec > .head {{
color: red;
}}
.y.ext .head {{
.y.ext > .head {{
font-size: var(--fsmall);
}}
.y.ext .head::before {{
.y.ext > .head::before {{
content: "#/ ";
font-size: var(--fless);
}}
.y.ext.spec.e-html .head {{
.y.ext.spec.e-html > .head {{
display: none;
}}
.y.ext.spec.e-texm .head {{
.y.ext.spec.e-texm > .head {{
display: none;
}}
.link {{
color: var(--fg);
text-decoration: underline var(--fgl);
position: relative;
}}
@@ -303,6 +311,8 @@ fn doc_to_impl<'a>(
Section::List(..) => "y list",
Section::Code(..) => "y code",
Section::Quote(..) => "y quote",
Section::Link("", ..) => "y rlink path",
Section::Link(..) => "y rlink uri",
Section::Ext(..) => "y ext",
Section::Document(..) => "sub",
},
@@ -333,16 +343,27 @@ fn doc_to_impl<'a>(
}
Section::Quote(attributed, context, text) => {
out.push_str(&format!(
r#" a-{} c-{}"><div class="head">{}</div><div class="x">"#,
r#" a-{}"><div class="head">{}</div><div class="x">"#,
escape_quot(&attributed.replace(char::is_whitespace, "-")),
escape_quot(&context.replace(char::is_whitespace, "-")),
escape(attributed),
));
text_to(text, specs, out, doc_args);
out.push_str("</div>");
if !context.is_empty() {
out.push_str(r#"<div class="x tail">"#);
text_to(context, specs, out, doc_args);
out.push_str("</div>");
}
}
Section::Link(scheme, link, text) => {
out.push_str(&format!(
r#"</div><div class="tail">{}</div>"#,
escape(context),
r#" ls-{}"><div class="head"><a href="{}">{}</a></div><div class="x">"#,
escape_quot(scheme),
escape_quot(link),
escape(link),
));
text_to(text, specs, out, doc_args);
out.push_str("</div>");
}
Section::Ext(spec @ "html", source) if specs.has(spec) => {
out.push_str(&format!(
@@ -417,6 +438,16 @@ fn text_to<'a: 'b, 'b>(
text_to(text, specs, out, args);
out.push_str("</del>");
}
Component::Name(text) => {
out.push_str(r#"<span class="tname">"#);
text_to(text, specs, out, args);
out.push_str("</span>");
}
Component::Code(text) => {
out.push_str(r#"<pre class="tcode">"#);
text_to(text, specs, out, args);
out.push_str("</pre>");
}
Component::Special(text) => {
out.push_str(r#"<u class="special">"#);
text_to(text, specs, out, args);
@@ -432,16 +463,6 @@ fn text_to<'a: 'b, 'b>(
text_to(text, specs, out, args);
out.push_str("</strong>");
}
Component::Name(text) => {
out.push_str(r#"<span class="tname">"#);
text_to(text, specs, out, args);
out.push_str("</span>");
}
Component::Code(text) => {
out.push_str(r#"<pre class="tcode">"#);
text_to(text, specs, out, args);
out.push_str("</pre>");
}
Component::Link(target, text) => {
let link_target: Option<&'a Document<'a>> = args
.max_len

View File

@@ -35,6 +35,18 @@ fn text_to(text: &Text<'_>, complete: bool, specs: &[impl Spec], out: &mut Strin
out.push('~');
}
}
Component::Name(text) => {
text_to(text, complete, specs, out);
}
Component::Code(text) => {
if complete {
out.push('`');
}
text_to(text, complete, specs, out);
if complete {
out.push('`');
}
}
Component::Special(text) => {
if complete {
out.push('_');
@@ -66,18 +78,6 @@ fn text_to(text: &Text<'_>, complete: bool, specs: &[impl Spec], out: &mut Strin
out.push('*');
}
}
Component::Name(text) => {
text_to(text, complete, specs, out);
}
Component::Code(text) => {
if complete {
out.push('`');
}
text_to(text, complete, specs, out);
if complete {
out.push('`');
}
}
Component::Link(target, text) => {
if complete {
out.push('_');