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

8
Cargo.lock generated
View file

@ -581,9 +581,9 @@ dependencies = [
[[package]]
name = "cmark-writer"
version = "0.7.3"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb6dce3a4430a75a3903f4a25729ba67b4476c39e2ed6d907ab2e5bfcce03a36"
checksum = "a132e7fc9fddc446c3930910894eab669241332af90981e53eb1b782bd70b77a"
dependencies = [
"cmark-writer-macros",
"env_logger",
@ -593,9 +593,9 @@ dependencies = [
[[package]]
name = "cmark-writer-macros"
version = "0.7.3"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ee5c1417934f17ae1605a2f5f34fbb55dea86c728c717008395add125561f7a"
checksum = "d45366269a70c39928d4fd7d95045250757be31ac93b5e85e3a8469b2690d93a"
dependencies = [
"proc-macro2",
"quote",

View file

@ -97,7 +97,7 @@ rpds = "1"
# Data/Text Format and Processing
biblatex = "0.10"
cmark-writer = { version = "0.7.3", features = ["gfm"] }
cmark-writer = { version = "0.7.5", features = ["gfm"] }
docx-rs = { git = "https://github.com/Myriad-Dreamin/docx-rs", default-features = false, rev = "db49a729f68dbdb9e8e91857fbb1c3d414209871" }
hayagriva = "0.8"
hex = "0.4.3"

View file

@ -1,7 +1,7 @@
<!-- This file is generated by scripts/link-docs.mjs from docs/tinymist/introduction.typ. Do not edit manually. -->
# Tinymist
Tinymist [ˈtaɪni mɪst] is an integrated language service for [Typst](https://typst.app/) [taɪpst]. You can also call it "微霭" [wēi ǎi] in Chinese.
Tinymist \[ˈtaɪni mɪst\] is an integrated language service for [Typst](https://typst.app/) \[taɪpst\]. You can also call it "微霭" \[wēi ǎi\] in Chinese.
It contains:

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

View file

@ -29,7 +29,9 @@
#import fletcher.shapes: diamond
#let fg-blue = main-color.mix(rgb("#0074d9"))
#let pro-tip(content) = (
#let pro-tip(content) = context if sys.inputs.at("x-target", default: none) == "md" {
html.elem("m1alerts", attrs: ("class": "note"), content)
} else {
context {
block(
width: 100%,
@ -43,7 +45,7 @@
},
)
}
)
}
#let cond-image(img) = context if shiroa-sys-target() == "html" {
html.elem("div", attrs: ("class": "pseudo-image"), html.frame(img))

View file

@ -9,7 +9,9 @@ Run and configure `tinymist` in Neovim with support for all major distros and pa
- **Code Formatting**
- **Live Web Preview** with [typst-preview.](https://github.com/chomosuke/typst-preview.nvim)
Work for full parity for all `tinymist` features is underway. This will include: exporting to different file types, template preview, and multifile support. Neovim integration is behind VS Code currently but should be caught up in the near future.
> [!NOTE]
>
> Work for full parity for all `tinymist` features is underway. This will include: exporting to different file types, template preview, and multifile support. Neovim integration is behind VS Code currently but should be caught up in the near future.
## Installation
@ -114,7 +116,9 @@ vim.api.nvim_create_user_command("OpenPdf", function()
end, {})
```
For Neovim prior to v0.9.5, `os.execute` can be used instead. This is not suggested. See [Issue #1606](https://github.com/Myriad-Dreamin/tinymist/issues/1606) for more information.
> [!NOTE]
>
> For Neovim prior to v0.9.5, `os.execute` can be used instead. This is not suggested. See [Issue #1606](https://github.com/Myriad-Dreamin/tinymist/issues/1606) for more information.
Make sure to change `exportPdf` to "onType" or "onSave".

View file

@ -111,8 +111,8 @@ For details, please check [Previews sys.inputs](https://myriad-dreamin.github
To configure path to search fonts:
1. Open settings.
- File -> Preferences -> Settings (Linux, Windows).
- Code -> Preferences -> Settings (Mac).
- File -\> Preferences -\> Settings (Linux, Windows).
- Code -\> Preferences -\> Settings (Mac).
2. Search for "Tinymist Font Paths" for providing paths to search fonts order-by-order.
3. Search for "Tinymist System Fonts" for disabling system fonts to be searched, which is useful for reproducible rendering your PDF documents.
4. Reload the window or restart the vscode editor to make the settings take effect.
@ -183,7 +183,9 @@ You can pin a main file by command.
- Use command `Typst Pin Main` (tinymist.pinMainToCurrent) to set the current file as the main file.
- Use command `Typst Unpin Main` (tinymist.unpinMain) to unset the main file.
`tinymist.pinMain` is a stateful command, and tinymist doesn't remember it between sessions (closing and opening the editor).
> [!NOTE]
>
> `tinymist.pinMain` is a stateful command, and tinymist doesn't remember it between sessions (closing and opening the editor).
### Passing Extra CLI Arguments