fix: quote should work as a blocks container; escape special chars in text (#1771)
Some checks are pending
tinymist::ci / build-vscode (push) Blocked by required conditions
tinymist::ci / build-vscode-others (push) Blocked by required conditions
tinymist::ci / publish-vscode (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Blocked by required conditions
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Blocked by required conditions
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-2019) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Blocked by required conditions
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / build-binary (push) Blocked by required conditions
tinymist::ci / build-vsc-assets (push) Blocked by required conditions

* feat: add protip component with markdown note block conversion

* refactor: remove ProtipNode and update related parsing and tag definitions

* fix: escape special characters in markdown output and update cmark-writer dependency
This commit is contained in:
Hong Jiarong 2025-06-03 23:49:15 +08:00 committed by GitHub
parent cecb424b1e
commit 08a104cd9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 104 additions and 40 deletions

View file

@ -29,6 +29,7 @@ pub mod md_attr {
text -> text
value -> value
caption -> caption
class -> class
}
}
@ -65,6 +66,11 @@ pub struct ListItemAttr {
pub value: Option<u32>,
}
#[derive(TypliteAttr, Default)]
pub struct AlertsAttr {
pub class: EcoString,
}
pub trait TypliteAttrsParser {
fn parse(attrs: &HtmlAttrs) -> Result<Self>
where

View file

@ -44,10 +44,7 @@ pub struct FigureNode {
impl FigureNode {
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let mut temp_writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
let mut temp_writer = CommonMarkWriter::with_options(writer.options.clone());
temp_writer.write(&self.body)?;
let content = temp_writer.into_string();
writer.write_str(&content)?;
@ -126,10 +123,7 @@ pub struct HighlightNode {
impl HighlightNode {
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let mut temp_writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
let mut temp_writer = CommonMarkWriter::with_options(writer.options.clone());
for node in &self.content {
temp_writer.write(node)?;
}
@ -174,10 +168,7 @@ impl CenterNode {
}
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let mut temp_writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
let mut temp_writer = CommonMarkWriter::with_options(writer.options.clone());
temp_writer.write(&self.node)?;
let content = temp_writer.into_string();
writer.write_str(&content)?;
@ -197,6 +188,39 @@ impl CenterNode {
}
}
/// Alert node for alert messages
#[derive(Debug, PartialEq, Clone)]
#[custom_node(block = true, html_impl = false)]
pub struct AlertNode {
/// The content of the alert
pub content: Vec<Node>,
/// The class of the alert
pub class: EcoString,
}
impl AlertNode {
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let quote = Node::BlockQuote(vec![
Node::Paragraph(vec![Node::Text(
"[!".to_string() + &self.class.clone().to_string().to_ascii_uppercase() + "]",
)]),
Node::Paragraph(vec![Node::Text("".to_string())]),
]);
let mut tmp_writer = CommonMarkWriter::with_options(WriterOptions {
escape_special_chars: false,
..writer.options.clone()
});
tmp_writer.write(&quote)?;
let mut content = tmp_writer.into_string();
let quote = Node::BlockQuote(self.content.clone());
let mut tmp_writer = CommonMarkWriter::with_options(writer.options.clone());
tmp_writer.write(&quote)?;
content += &tmp_writer.into_string();
writer.write_str(&content)?;
Ok(())
}
}
/// Common writer interface for different formats
pub trait FormatWriter {
/// Write AST document to output format

View file

@ -2,6 +2,7 @@
source: crates/typlite/src/tests.rs
expression: "conv(world, ConvKind::Md { for_docs: true })"
input_file: crates/typlite/src/fixtures/docs/nest_list.typ
snapshot_kind: text
---
<!DOCTYPE html>
<html>
@ -25,4 +26,4 @@ See show-module() for outputting the results of this function.
- <!-- typlite:begin:list-item 1 -->nested something 2<!-- typlite:end:list-item 1 -->
<!-- typlite:end:list-item 0 -->
-> string
-\> string

View file

@ -1,7 +1,8 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, true)"
expression: "conv(world, ConvKind::Md { for_docs: true })"
input_file: crates/typlite/src/fixtures/docs/tidy.typ
snapshot_kind: text
---
<!DOCTYPE html>
<html>
@ -28,4 +29,4 @@ See show-module() for outputting the results of this function.
- <!-- typlite:begin:list-item 0 -->scope (dictionary): A dictionary of definitions that are then available in all function and parameter descriptions.<!-- typlite:end:list-item 0 -->
- <!-- typlite:begin:list-item 0 -->preamble (string): Code to prepend to all code snippets shown with `#example()`. This can for instance be used to import something from the scope.<!-- typlite:end:list-item 0 -->
-> string
-\> string

View file

@ -2,6 +2,7 @@
source: crates/typlite/src/tests.rs
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/ieee.typ
snapshot_kind: text
---
<!DOCTYPE html>
<html>
@ -15,7 +16,7 @@ input_file: crates/typlite/src/fixtures/integration/ieee.typ
=====
## Introduction
Scientific writing is a crucial part of the research process, allowing researchers to share their findings with the wider scientific community. However, the process of typesetting scientific documents can often be a frustrating and time-consuming affair, particularly when using outdated tools such as LaTeX. Despite being over 30 years old, it remains a popular choice for scientific writing due to its power and flexibility. However, it also comes with a steep learning curve, complex syntax, and long compile times, leading to frustration and despair for many researchers [1] [2].
Scientific writing is a crucial part of the research process, allowing researchers to share their findings with the wider scientific community. However, the process of typesetting scientific documents can often be a frustrating and time-consuming affair, particularly when using outdated tools such as LaTeX. Despite being over 30 years old, it remains a popular choice for scientific writing due to its power and flexibility. However, it also comes with a steep learning curve, complex syntax, and long compile times, leading to frustration and despair for many researchers \[1\] \[2\].
### Paper overview
@ -53,6 +54,6 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
## Bibliography
| [1] | R. Astley | and | L. Morris | , | “At-scale impact of the Net Wok: A culinarically holistic investigation of distributed dumplings,” | | Armenian Journal of Proceedings | , vol. | 61 | , | pp. | | 192219 | , | 2020 | . |
| \[1\] | R. Astley | and | L. Morris | , | “At-scale impact of the Net Wok: A culinarically holistic investigation of distributed dumplings,” | | Armenian Journal of Proceedings | , vol. | 61 | , | pp. | | 192219 | , | 2020 | . |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [2] | L. Morris | and | R. Astley | , | “Net Wok++: Taking distributed dumplings to the cloud,” | | Armenian Journal of Proceedings | , vol. | 65 | , | pp. | | 101118 | , | 2022 | . |
| \[2\] | L. Morris | and | R. Astley | , | “Net Wok++: Taking distributed dumplings to the cloud,” | | Armenian Journal of Proceedings | , vol. | 65 | , | pp. | | 101118 | , | 2022 | . |

View file

@ -65,9 +65,9 @@
attrs: (level: str(level)),
body,
)
#let md-quote(attribution: none, body) = html.elem(
#let md-quote(/* attribution: none, */ body) = html.elem(
"m1quote",
attrs: (attribution: attribution),
// attrs: (attribution: attribution),
body,
)
#let md-table(it) = html.elem(
@ -152,7 +152,7 @@
show heading: it => if-not-paged(it, md-heading(level: it.level, it.body))
show outline: it => if-not-paged(it, md-outline(it))
show outline.entry: it => if-not-paged(it, md-outline-entry(level: it.level, it.element))
show quote: it => if-not-paged(it, md-quote(attribution: it.attribution, it.body))
show quote: it => if-not-paged(it, md-quote(it.body))
show table: it => if-not-paged(it, md-table(it))
show grid: it => if-not-paged(it, md-grid(columns: it.columns, ..it.children))

View file

@ -4,8 +4,8 @@ use cmark_writer::ast::{CustomNode, HtmlAttribute, HtmlElement as CmarkHtmlEleme
use cmark_writer::{CommonMarkWriter, WriteResult};
use typst::html::{tag, HtmlElement, HtmlNode};
use crate::attributes::{HeadingAttr, RawAttr, TypliteAttrsParser};
use crate::common::{CenterNode, ListState};
use crate::attributes::{AlertsAttr, HeadingAttr, RawAttr, TypliteAttrsParser};
use crate::common::{AlertNode, CenterNode, ListState};
use crate::tags::md_tag;
use crate::Result;
use crate::TypliteFeat;
@ -88,11 +88,15 @@ impl HtmlToAstParser {
}
md_tag::quote => {
let prev_blocks = std::mem::take(&mut self.blocks);
self.flush_inline_buffer();
self.convert_children(element)?;
self.flush_inline_buffer_as_block(|content| {
Node::BlockQuote(vec![Node::Paragraph(content)])
});
let content = Node::Paragraph(std::mem::take(&mut self.inline_buffer));
let mut quote = std::mem::take(&mut self.blocks);
quote.push(content);
self.blocks.clear();
self.blocks.extend(prev_blocks);
self.blocks.push(Node::BlockQuote(quote));
Ok(())
}
@ -146,6 +150,24 @@ impl HtmlToAstParser {
Ok(())
}
md_tag::alerts => {
self.flush_inline_buffer();
let attrs = AlertsAttr::parse(&element.attrs)?;
let prev_blocks = std::mem::take(&mut self.blocks);
self.flush_inline_buffer();
self.convert_children(element)?;
let content = Node::Paragraph(std::mem::take(&mut self.inline_buffer));
let mut quote = std::mem::take(&mut self.blocks);
quote.push(content);
self.blocks.clear();
self.blocks.extend(prev_blocks);
self.blocks.push(Node::Custom(Box::new(AlertNode {
content: quote,
class: attrs.class,
})));
Ok(())
}
_ => {
let tag_name = element.tag.resolve().to_string();

View file

@ -36,7 +36,7 @@ pub mod md_tag {
math_equation_inline -> m1eqinline
math_equation_block -> m1eqblock
alerts -> m1alerts
doc -> m1document
link -> m1link
}

View file

@ -22,6 +22,7 @@ impl FormatWriter for MarkdownWriter {
fn write_eco(&mut self, document: &Node, output: &mut EcoString) -> Result<()> {
let mut writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
escape_special_chars: true,
..Default::default()
});
writer