fix: example impl is not correct (#1770)
Some checks are pending
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
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::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::gh_pages / build-gh-pages (push) Waiting to run

* fix: example impl is not correct

* fix(typlite): example impl (#1800)

* fix: readme generation (#1754)

* fix: readme generation

* feat: markdown-aware export

* feat: add ieee example

* fix: fix wrong behavior of list parsing and <div> elem parsing

* test: update snapshot

Co-authored-by: Hong Jiarong <me@jrhim.com>

* fix: clippy warnings

* fix: handle br tag as hard break in HTML parsing (#1769)

* fix: handle br tag as hard break in HTML parsing

* Revert "svg gen"

This reverts commit 1ff4c0af33c209a9f653c879f2f7d504bad1ff32.

---------

Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>

* feat: some md-specific impl

* test: bad changes

---------

Co-authored-by: Hong Jiarong <me@jrhim.com>

* fix(typlite): recover readme (#1759)

* fix: recover readme

* docs: rewrite readme

* fix: compile warnings (#1774)

* fix: correct link to Automattic/haper (#1748)

* fix: correct link to Automattic/haper

* build: generate readme

* fix: heading-hash is broken by readme generation (#1779)

* feat: bump typstyle to v0.13.4 and add config for hard wrap (#1737)

* feat: only scroll when selection is not adjacent (#1787)

* fix: quote should work as a blocks container; escape special chars in text (#1771)

* 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

* fix: try getting font index again (#1213, #1645) (#1790)

* feat: build theme-aware pictures (equations) (#1772)

* revert: "test: bad changes"

* feat: m1source

* fix: example impl

* fix(typlite): highlight in docx export (#1798)

* fix: highlight in docx export

* fmt

* fix: correct hover docs generated by typlite (#1761)

* fix: annotate fn

* fix(typlite): duplicate docs description (#1799)

* fix: avoid duplicate docs description

* fix: clippy error

* test: flat repr of hover snapshots

* g

* test: update snapshots

---------

Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>

* test: update snapshot

---------

Co-authored-by: Hong Jiarong <me@jrhim.com>

* feat: `expr_tooltip` should not return docs (#1801)

* fix: render example

* fix: clean code

---------

Co-authored-by: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com>
Co-authored-by: Wenzhuo Liu <mgt@oi-wiki.org>
Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>

* test: update snapshot

---------

Co-authored-by: Hong Jiarong <me@jrhim.com>
Co-authored-by: Wenzhuo Liu <mgt@oi-wiki.org>
This commit is contained in:
Myriad-Dreamin 2025-06-08 13:44:59 +08:00 committed by GitHub
parent 7a30cbdc73
commit c85247a85c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 185 additions and 13 deletions

View file

@ -21,7 +21,11 @@ Speaker notes are a way to add additional information to your slides that is not
### Example
This is a speaker note
```typ
#box[This is a speaker note]
```
<img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" />
# Positional Parameters

View file

@ -28,12 +28,19 @@ pub mod md_attr {
lang -> lang
block -> block
text -> text
mode -> mode
value -> value
caption -> caption
class -> class
}
}
#[derive(TypliteAttr, Default)]
pub struct IdocAttr {
pub src: EcoString,
pub mode: EcoString,
}
#[derive(TypliteAttr, Default)]
pub struct HeadingAttr {
pub level: usize,

View file

@ -40,27 +40,35 @@ pub use cmark_writer::ast;
pub use tinymist_project::CompileOnceArgs;
pub use tinymist_std;
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct MarkdownDocument {
pub base: HtmlDocument,
world: Arc<LspWorld>,
feat: TypliteFeat,
ast: Option<Node>,
}
impl MarkdownDocument {
/// Create a new MarkdownDocument instance
pub fn new(base: HtmlDocument, feat: TypliteFeat) -> Self {
pub fn new(base: HtmlDocument, world: Arc<LspWorld>, feat: TypliteFeat) -> Self {
Self {
base,
world,
feat,
ast: None,
}
}
/// Create a MarkdownDocument instance with pre-parsed AST
pub fn with_ast(base: HtmlDocument, feat: TypliteFeat, ast: Node) -> Self {
pub fn with_ast(
base: HtmlDocument,
world: Arc<LspWorld>,
feat: TypliteFeat,
ast: Node,
) -> Self {
Self {
base,
world,
feat,
ast: Some(ast),
}
@ -71,7 +79,7 @@ impl MarkdownDocument {
if let Some(ast) = &self.ast {
return Ok(ast.clone());
}
let parser = HtmlToAstParser::new(self.feat.clone());
let parser = HtmlToAstParser::new(self.feat.clone(), &self.world);
parser.parse(&self.base.root).context_ut("failed to parse")
}
@ -209,6 +217,7 @@ impl Typlite {
let entry = self.world.entry_state();
let main = entry.main();
let current = main.context("no main file in workspace")?;
let world_origin = self.world.clone();
let world = self.world;
if WorkspaceResolver::is_package_file(current) {
@ -273,7 +282,7 @@ impl Typlite {
let base = typst::compile(&world).output?;
let mut feat = self.feat;
feat.target = format;
Ok(MarkdownDocument::new(base, feat))
Ok(MarkdownDocument::new(base, world_origin, feat))
}
}

View file

@ -75,7 +75,7 @@ fn run(args: CompileArgs, world: Arc<LspWorld>) -> tinymist_std::Result<()> {
None => None,
};
let converter = Typlite::new(world).with_feature(TypliteFeat {
let converter = Typlite::new(world.clone()).with_feature(TypliteFeat {
assets_path: assets_path.clone(),
..Default::default()
});

View file

@ -114,7 +114,36 @@
}
}
#let example(code) = eval(code.text, mode: "markup")
#let example(code) = {
let lang = if code.has("lang") and code.lang != none { code.lang } else { "typ" }
let lines = code.text.split("\n")
let display = ""
let compile = ""
for line in lines {
if line.starts-with(">>>") {
compile += line.slice(3) + "\n"
} else if line.starts-with("<<< ") {
display += line.slice(4) + "\n"
} else {
display += (line + "\n")
compile += (line + "\n")
}
}
let result = raw(block: true, lang: lang, display)
result
if sys.inputs.at("x-remove-html", default: none) != "true" {
let mode = if lang == "typc" { "code" } else { "markup" }
html.elem(
"m1idoc",
attrs: (src: compile, mode: mode),
)
}
}
#let process-math-eq(item) = {
if type(item) == str {

View file

@ -1,7 +1,10 @@
//! HTML parser core, containing main structures and general parsing logic
use std::sync::Arc;
use cmark_writer::ast::{CustomNode, HtmlAttribute, HtmlElement as CmarkHtmlElement, Node};
use cmark_writer::{CommonMarkWriter, WriteResult};
use tinymist_project::LspWorld;
use typst::html::{tag, HtmlElement, HtmlNode};
use crate::attributes::{AlertsAttr, HeadingAttr, RawAttr, TypliteAttrsParser};
@ -16,6 +19,7 @@ use super::{list::ListParser, table::TableParser};
pub struct HtmlToAstParser {
pub asset_counter: usize,
pub feat: TypliteFeat,
pub world: Arc<LspWorld>,
pub list_state: Option<ListState>,
pub list_level: usize,
pub blocks: Vec<Node>,
@ -23,9 +27,10 @@ pub struct HtmlToAstParser {
}
impl HtmlToAstParser {
pub fn new(feat: TypliteFeat) -> Self {
pub fn new(feat: TypliteFeat, world: &Arc<LspWorld>) -> Self {
Self {
feat,
world: world.clone(),
asset_counter: 0,
list_level: 0,
list_state: None,
@ -138,6 +143,12 @@ impl HtmlToAstParser {
Ok(())
}
md_tag::idoc => {
let src = self.convert_idoc(element);
self.inline_buffer.push(src);
Ok(())
}
md_tag::math_equation_inline | md_tag::math_equation_block => {
if element.tag == md_tag::math_equation_block {
self.flush_inline_buffer();

View file

@ -2,15 +2,25 @@
use core::fmt;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock};
use base64::Engine;
use cmark_writer::ast::{HtmlAttribute, HtmlElement as CmarkHtmlElement, Node};
use ecow::eco_format;
use tinymist_project::{base::ShadowApi, EntryReader, TaskInputs, MEMORY_MAIN_ENTRY};
use typst::{
foundations::{Bytes, Dict, IntoValue},
html::{HtmlElement, HtmlNode},
layout::Frame,
layout::{Abs, Frame},
utils::LazyHash,
World,
};
use crate::{attributes::md_attr, common::ExternalFrameNode};
use crate::{
attributes::{md_attr, IdocAttr, TypliteAttrsParser},
common::ExternalFrameNode,
ColorTheme,
};
use super::core::HtmlToAstParser;
@ -119,6 +129,10 @@ impl HtmlToAstParser {
}
let svg = typst_svg::svg_frame(frame);
self.convert_svg(svg)
}
fn convert_svg(&mut self, svg: String) -> Node {
let frame_url = self.create_asset_url(&svg);
match frame_url {
@ -187,4 +201,97 @@ impl HtmlToAstParser {
fn base64_url(data: &str) -> AssetUrl {
AssetUrl::Embedded(base64::engine::general_purpose::STANDARD.encode(data.as_bytes()))
}
/// Convert Typst inline document to CommonMark node
pub fn convert_idoc(&mut self, element: &HtmlElement) -> Node {
static DARK_THEME_INPUT: LazyLock<Arc<LazyHash<Dict>>> = LazyLock::new(|| {
Arc::new(LazyHash::new(Dict::from_iter(std::iter::once((
"x-color-theme".into(),
"dark".into_value(),
)))))
});
if self.feat.remove_html {
eprintln!("Removing idoc element due to remove_html feature");
// todo: make error silent is not good.
return Node::Text(String::new());
}
let attrs = match IdocAttr::parse(&element.attrs) {
Ok(attrs) => attrs,
Err(e) => {
if self.feat.soft_error {
return Node::Text(format!("Error parsing idoc attributes: {e}"));
} else {
// Construct error node
return Node::HtmlElement(CmarkHtmlElement {
tag: "div".to_string(),
attributes: vec![HtmlAttribute {
name: "class".to_string(),
value: "error".to_string(),
}],
children: vec![Node::Text(format!("Error parsing idoc attributes: {e}"))],
self_closing: false,
});
}
}
};
let src = attrs.src;
let mode = attrs.mode;
let mut world = self.world.clone().task(TaskInputs {
entry: Some(
self.world
.entry_state()
.select_in_workspace(MEMORY_MAIN_ENTRY.vpath().as_rooted_path()),
),
inputs: match self.feat.color_theme {
Some(ColorTheme::Dark) => Some(DARK_THEME_INPUT.clone()),
None | Some(ColorTheme::Light) => None,
},
});
// todo: cost some performance.
world.take_db();
let main = world.main();
const PRELUDE: &str = r##"#set page(width: auto, height: auto, margin: (y: 0.45em, rest: 0em), fill: none);
#set text(fill: rgb("#c0caf5")) if sys.inputs.at("x-color-theme", default: none) == "dark";"##;
world
.map_shadow_by_id(
main,
Bytes::from_string(match mode.as_str() {
"code" => eco_format!("{PRELUDE}#{{{src}}}"),
"math" => eco_format!("{PRELUDE}${src}$"),
"markup" => eco_format!("{PRELUDE}#[{}]", src),
// todo check mode
// "markup" |
_ => eco_format!("{PRELUDE}#[{}]", src),
}),
)
.unwrap();
let doc = typst::compile(&world);
let doc = match doc.output {
Ok(doc) => doc,
Err(e) => {
if self.feat.soft_error {
return Node::Text(format!("Error compiling idoc: {e:?}"));
} else {
// Construct error node
return Node::HtmlElement(CmarkHtmlElement {
tag: "div".to_string(),
attributes: vec![HtmlAttribute {
name: "class".to_string(),
value: "error".to_string(),
}],
children: vec![Node::Text(format!("Error compiling idoc: {e:?}"))],
self_closing: false,
});
}
}
};
let svg = typst_svg::svg_merged(&doc, Abs::zero());
self.convert_svg(svg)
}
}

View file

@ -29,6 +29,7 @@ pub mod md_tag {
outline_entry -> m1outentry
quote -> m1quote
table -> m1table
idoc -> m1idoc
source -> m1source
// table_cell -> m1tablecell
grid -> m1grid

View file

@ -91,7 +91,8 @@ impl ConvKind {
}
fn conv(world: LspWorld, kind: ConvKind) -> String {
let converter = Typlite::new(Arc::new(world)).with_feature(TypliteFeat {
let world = Arc::new(world);
let converter = Typlite::new(world.clone()).with_feature(TypliteFeat {
annotate_elem: kind.for_docs(),
..Default::default()
});

View file

@ -35,8 +35,10 @@ import { testingDebugActivate } from "./features/testing/debug";
import { FeatureEntry, tinymistActivate, tinymistDeactivate } from "./extension.shared";
import { commandShow, exportActivate, quickExports } from "./features/export";
import { resolveCodeAction } from "./lsp.code-action";
import { HoverTmpStorage } from "./features/hover-storage.tmp";
LanguageState.Client = LanguageClient;
LanguageState.HoverTmpStorage = HoverTmpStorage;
const systemActivateTable = (): FeatureEntry[] => [
[extensionState.features.label, labelActivate],

View file

@ -31,8 +31,9 @@ export class HoverTmpStorage {
try {
await vscode.workspace.fs.createDirectory(tmpImageDir);
return new HoverStorageTmpFsHandler(Uri.joinPath(this.context.storageUri, "tmp/"));
} catch (_err) {
} catch (err) {
// todo: handle errors safely
console.error("Failed to create hover image directory:", err);
}
}