Skip to content

fix(html): unified ids and id prefixes #718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/ddoc/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ fn generate_docs_directory(
deno_doc::html::comrak::COMRAK_STYLESHEET_FILENAME
)
})),
id_prefix: None,
};
let ctx = GenerateCtx::create_basic(options, doc_nodes_by_url)?;
let html = deno_doc::html::generate(ctx)?;
Expand Down
4 changes: 4 additions & 0 deletions js/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ export interface GenerateOptions {
): string | undefined;
/** Function to strip markdown. */
markdownStripper(md: string): string;
/** Prefix for IDs of elements. */
idPrefix?: string;
}

const defaultUsageComposer: UsageComposer = {
Expand Down Expand Up @@ -331,6 +333,7 @@ export async function generateHtml(
options.markdownRenderer,
options.markdownStripper,
options.headInject,
options.idPrefix,
docNodesByUrl,
false,
);
Expand Down Expand Up @@ -368,6 +371,7 @@ export async function generateHtmlAsJSON(
options.markdownRenderer,
options.markdownStripper,
options.headInject,
options.idPrefix,
docNodesByUrl,
true,
);
Expand Down
4 changes: 4 additions & 0 deletions lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ pub fn generate_html(
markdown_renderer: js_sys::Function,
markdown_stripper: js_sys::Function,
head_inject: Option<js_sys::Function>,
id_prefix: Option<String>,

doc_nodes_by_url: JsValue,

Expand All @@ -317,6 +318,7 @@ pub fn generate_html(
markdown_renderer,
markdown_stripper,
head_inject,
id_prefix,
doc_nodes_by_url,
json,
)
Expand Down Expand Up @@ -532,6 +534,7 @@ fn generate_html_inner(
markdown_renderer: js_sys::Function,
markdown_stripper: js_sys::Function,
head_inject: Option<js_sys::Function>,
id_prefix: Option<String>,

doc_nodes_by_url: JsValue,

Expand Down Expand Up @@ -642,6 +645,7 @@ fn generate_html_inner(
markdown_renderer,
markdown_stripper,
head_inject,
id_prefix,
},
doc_nodes_by_url,
)?;
Expand Down
73 changes: 49 additions & 24 deletions src/html/jsdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ lazy_static! {
regex::Regex::new(r"^\[(\S+)\](?:\.(\S+)|\s|)$").unwrap();
}

fn parse_links<'a>(md: &'a str, ctx: &RenderContext) -> Cow<'a, str> {
fn parse_links<'a>(
md: &'a str,
ctx: &RenderContext,
strip: bool,
) -> Cow<'a, str> {
JSDOC_LINK_RE.replace_all(md, |captures: &regex::Captures| {
let code = captures
.name("modifier")
Expand Down Expand Up @@ -111,7 +115,9 @@ fn parse_links<'a>(md: &'a str, ctx: &RenderContext) -> Cow<'a, str> {
(title, link)
};

if LINK_RE.is_match(&link) {
if strip {
title
} else if LINK_RE.is_match(&link) {
if code {
format!("[`{title}`]({link})")
} else {
Expand All @@ -122,7 +128,7 @@ fn parse_links<'a>(md: &'a str, ctx: &RenderContext) -> Cow<'a, str> {
if code {
format!("`{title}`")
} else {
title.to_string()
title
}
}
})
Expand All @@ -149,7 +155,7 @@ pub struct MarkdownToHTMLOptions {
pub type MarkdownStripper = std::rc::Rc<dyn (Fn(&str) -> String)>;

pub fn strip(render_ctx: &RenderContext, md: &str) -> String {
let md = parse_links(md, render_ctx);
let md = parse_links(md, render_ctx, true);

(render_ctx.ctx.markdown_stripper)(&md)
}
Expand Down Expand Up @@ -200,7 +206,7 @@ pub fn markdown_to_html(
anchorizer.as_ref(),
);

let md = parse_links(md, render_ctx);
let md = parse_links(md, render_ctx, false);

let file = render_ctx.get_current_resolve().get_file().cloned();

Expand Down Expand Up @@ -281,7 +287,7 @@ pub(crate) fn jsdoc_examples(
#[derive(Debug, Serialize, Clone)]
pub struct ExampleCtx {
pub anchor: AnchorCtx,
pub id: String,
pub id: Id,
pub title: String,
pub markdown_title: String,
markdown_body: String,
Expand All @@ -291,7 +297,10 @@ impl ExampleCtx {
pub const TEMPLATE: &'static str = "example";

pub fn new(render_ctx: &RenderContext, example: &str, i: usize) -> Self {
let id = name_to_id("example", &i.to_string());
let id = IdBuilder::new(render_ctx.ctx)
.kind(IdKind::Example)
.index(i)
.build();

let (maybe_title, body) = split_markdown_title(example);
let title = if let Some(title) = maybe_title {
Expand All @@ -305,8 +314,8 @@ impl ExampleCtx {
render_markdown(render_ctx, body.unwrap_or_default(), true);

ExampleCtx {
anchor: AnchorCtx { id: id.to_string() },
id: id.to_string(),
anchor: AnchorCtx { id: id.clone() },
id,
title,
markdown_title,
markdown_body,
Expand Down Expand Up @@ -368,7 +377,9 @@ impl ModuleDocCtx {
render_ctx.clone(),
Some(SectionHeaderCtx {
title: title.clone(),
anchor: AnchorCtx { id: title },
anchor: AnchorCtx {
id: super::util::Id::new(title),
},
href: None,
doc: None,
}),
Expand All @@ -381,7 +392,7 @@ impl ModuleDocCtx {
Self {
deprecated,
sections: super::SymbolContentCtx {
id: "module_doc".to_string(),
id: Id::new("module_doc"),
docs: html,
sections,
},
Expand Down Expand Up @@ -490,6 +501,7 @@ mod test {
),
markdown_stripper: Rc::new(crate::html::comrak::strip),
head_inject: None,
id_prefix: None,
},
Default::default(),
Default::default(),
Expand Down Expand Up @@ -569,65 +581,78 @@ mod test {
);

assert_eq!(
parse_links("foo {@link https://example.com} bar", &render_ctx),
parse_links("foo {@link https://example.com} bar", &render_ctx, false),
"foo [https://example.com](https://example.com) bar"
);
assert_eq!(
parse_links("foo {@linkcode https://example.com} bar", &render_ctx),
parse_links(
"foo {@linkcode https://example.com} bar",
&render_ctx,
false
),
"foo [`https://example.com`](https://example.com) bar"
);

assert_eq!(
parse_links("foo {@link https://example.com Example} bar", &render_ctx),
parse_links(
"foo {@link https://example.com Example} bar",
&render_ctx,
false
),
"foo [Example](https://example.com) bar"
);
assert_eq!(
parse_links("foo {@link https://example.com|Example} bar", &render_ctx),
parse_links(
"foo {@link https://example.com|Example} bar",
&render_ctx,
false
),
"foo [Example](https://example.com) bar"
);
assert_eq!(
parse_links(
"foo {@linkcode https://example.com Example} bar",
&render_ctx
&render_ctx,
false,
),
"foo [`Example`](https://example.com) bar"
);

assert_eq!(
parse_links("foo {@link unknownSymbol} bar", &render_ctx),
parse_links("foo {@link unknownSymbol} bar", &render_ctx, false),
"foo unknownSymbol bar"
);
assert_eq!(
parse_links("foo {@linkcode unknownSymbol} bar", &render_ctx),
parse_links("foo {@linkcode unknownSymbol} bar", &render_ctx, false),
"foo `unknownSymbol` bar"
);

#[cfg(not(target_os = "windows"))]
{
assert_eq!(
parse_links("foo {@link bar} bar", &render_ctx),
parse_links("foo {@link bar} bar", &render_ctx, false),
"foo [bar](../../.././/a.ts/~/bar.html) bar"
);
assert_eq!(
parse_links("foo {@linkcode bar} bar", &render_ctx),
parse_links("foo {@linkcode bar} bar", &render_ctx, false),
"foo [`bar`](../../.././/a.ts/~/bar.html) bar"
);

assert_eq!(
parse_links("foo {@link [b.ts]} bar", &render_ctx),
parse_links("foo {@link [b.ts]} bar", &render_ctx, false),
"foo [b.ts](../../.././/b.ts/index.html) bar"
);
assert_eq!(
parse_links("foo {@linkcode [b.ts]} bar", &render_ctx),
parse_links("foo {@linkcode [b.ts]} bar", &render_ctx, false),
"foo [`b.ts`](../../.././/b.ts/index.html) bar"
);

assert_eq!(
parse_links("foo {@link [b.ts].baz} bar", &render_ctx),
parse_links("foo {@link [b.ts].baz} bar", &render_ctx, false),
"foo [b.ts baz](../../.././/b.ts/~/baz.html) bar"
);
assert_eq!(
parse_links("foo {@linkcode [b.ts].baz} bar", &render_ctx),
parse_links("foo {@linkcode [b.ts].baz} bar", &render_ctx, false),
"foo [`b.ts baz`](../../.././/b.ts/~/baz.html) bar"
);
}
Expand Down
5 changes: 4 additions & 1 deletion src/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod pages;
mod parameters;
pub mod partition;
mod render_context;
mod search;
pub mod search;
mod symbols;
mod types;
mod usage;
Expand Down Expand Up @@ -267,6 +267,7 @@ pub struct GenerateOptions {
pub markdown_renderer: jsdoc::MarkdownRenderer,
pub markdown_stripper: jsdoc::MarkdownStripper,
pub head_inject: Option<HeadInject>,
pub id_prefix: Option<String>,
}

#[non_exhaustive]
Expand All @@ -286,6 +287,7 @@ pub struct GenerateCtx {
pub markdown_renderer: jsdoc::MarkdownRenderer,
pub markdown_stripper: jsdoc::MarkdownStripper,
pub head_inject: Option<HeadInject>,
pub id_prefix: Option<String>,
}

impl GenerateCtx {
Expand Down Expand Up @@ -422,6 +424,7 @@ impl GenerateCtx {
markdown_renderer: options.markdown_renderer,
markdown_stripper: options.markdown_stripper,
head_inject: options.head_inject,
id_prefix: options.id_prefix,
})
}

Expand Down
20 changes: 13 additions & 7 deletions src/html/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ impl IndexCtx {
short_path.as_resolve_kind(),
)),
title: title.to_string(),
anchor: AnchorCtx { id: anchor },
anchor: AnchorCtx {
id: util::Id::new(anchor),
},
doc,
}),
content: util::SectionContentCtx::Empty,
Expand All @@ -300,7 +302,7 @@ impl IndexCtx {
});

Some(SymbolContentCtx {
id: String::new(),
id: util::Id::empty(),
sections,
docs: None,
})
Expand Down Expand Up @@ -336,7 +338,9 @@ impl IndexCtx {
UrlResolveKind::Category { category: &title },
)),
title,
anchor: AnchorCtx { id: anchor },
anchor: AnchorCtx {
id: util::Id::new(anchor),
},
doc,
}),
content: util::SectionContentCtx::Empty,
Expand All @@ -345,7 +349,7 @@ impl IndexCtx {
.collect::<Vec<_>>();

Some(SymbolContentCtx {
id: String::new(),
id: util::Id::empty(),
sections,
docs: None,
})
Expand Down Expand Up @@ -404,7 +408,9 @@ impl IndexCtx {
(
render_ctx.clone(),
Some(SectionHeaderCtx {
anchor: AnchorCtx { id: title.clone() },
anchor: AnchorCtx {
id: util::Id::new(title.clone()),
},
title,
href: None,
doc,
Expand Down Expand Up @@ -436,7 +442,7 @@ impl IndexCtx {
html_head_ctx,
module_doc: None,
overview: Some(SymbolContentCtx {
id: String::new(),
id: util::Id::empty(),
sections,
docs: None,
}),
Expand Down Expand Up @@ -485,7 +491,7 @@ impl AllSymbolsCtx {
AllSymbolsCtx {
html_head_ctx,
content: SymbolContentCtx {
id: String::new(),
id: util::Id::empty(),
sections,
docs: None,
},
Expand Down
3 changes: 2 additions & 1 deletion src/html/render_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ impl Default for HeadingToCAdapter {

lazy_static! {
static ref REJECTED_CHARS: regex::Regex =
regex::Regex::new(r"[^\p{L}\p{M}\p{N}\p{Pc} -]").unwrap();
regex::Regex::new(r"[^\p{L}\p{M}\p{N}\p{Pc} -_/]").unwrap();
}

impl HeadingToCAdapter {
Expand Down Expand Up @@ -603,6 +603,7 @@ mod test {
),
markdown_stripper: Rc::new(crate::html::comrak::strip),
head_inject: None,
id_prefix: None,
},
None,
Default::default(),
Expand Down
Loading