The Windows CI leg caught mixed separator styles: slash()ed include paths
were compared against native-separator ancestors, which broke the
circular-include guard (a -> b -> a expanded one extra level), and
line-map files, dead-link reports and the ignoreDeadLinks context mixed
C:/ and C:\ forms. The file identity is now slash()ed once at the
render entry point, so everything stored, compared or reported - segment
files, the ancestor chain, rebase comparisons, deadLinks[].file/.via and
filter contexts - uses posix separators on every platform.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An output line assembled from more than one source (page text on the same
line as an include directive) matches no single physical line, so any
position on it names the wrong place. Rebasing trusted such lines' file
attribution and rewrote the page's own relative links against the included
file's directory when a mid-line include came first; columns on splice
tail lines were measured against the expanded text. Links and attributes
on spliced lines now carry no location at all, and the tail line after a
mid-line include is marked spliced too.
Also from the adversarial review: omit the column when the inline text
appears more than once in its raw line (repeated table cells) instead of
guessing the first occurrence; give a fully-elided source an identity
line map instead of letting resolve() throw; store segment files
posix-style so Windows builds compare and report one separator; offset
alert paragraph_open attrs past the removed marker like their inline
content; keep the page's line map and dev attrs out of excerpt renders;
name the including page in dead-link reports for included files
(deadLinks[].via); and harden the dev click handler (relative-base
endpoint, fetch failure).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vp_github_alerts rule strips the alert marker from the first inline
token's content before inline parsing, shifting every position in the
body up by the marker line and breaking column re-alignment. The rule now
records how many lines it removed (token.meta.vpLineOffset) and the
source positions plugin adds them back.
Also re-emit the source-location attribute from the remaining hand-built
renderers that dropped it (::: v-pre and ::: raw wrappers, math blocks),
and pop it off fence tokens in preWrapper so a custom highlight falling
back to markdown-it's default fence renderer cannot emit it twice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Page renders in dev stamp block elements with data-v-inspector
attributes carrying the cwd-relative source file, line and column —
include-aware through the line map, so content pulled in via
`<!-- @include -->` points at the included file. The attribute is the
one vite-plugin-vue-inspector's overlay reads off arbitrary elements,
so the Vue DevTools component inspector jumps to the markdown source
out of the box; a ~50-line dev-only client handler additionally makes
alt+click open the editor through Vite's built-in /__open-in-editor
endpoint with no plugins installed (#4293). Fence wrappers, code
groups and GitHub alerts re-emit the attribute from their hand-built
markup; builds, the local search index and content loader output are
env-gated and stay byte-identical. Opt out with
`markdown.sourceAttrs: false`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
env.links becomes an array of { url, raw, loc } objects: the normalized
href, the destination as authored (decoded), and the exact position it was
authored at — including inside `<!-- @include -->`-ed files, which
previously reported the including page with a line in the expanded text.
Reports print the URL as written plus the resolved page path
(`(resolves to /x) in file.md:12:5`), fixing #4992 and #3774's halves of
the same complaint, and table-cell links (which had no line at all) and
links past the first line of a paragraph now carry exact positions.
Carries over the raw-URL reporting and test matrix from #5316.
BREAKING CHANGE: `env.links` entries are objects instead of strings and
`env.linkLines` is gone; `ignoreDeadLinks` strings, regexes and filter
functions now match the link as authored instead of the normalized encoded
URL, and filter functions receive a `{ file, line, column, url }` context
object instead of the source path string; `MarkdownCompileResult.deadLinks`
entries gained `resolved`/`column` and their `url` is now the authored
form. The markdown-it rule names `github-alerts`, `snippet` and
`vitepress_link_lines` are renamed/replaced by `vp_`-prefixed rules.
Co-authored-by: Bjorn Lu <34116392+bluwy@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Include expansion now emits a line map alongside the expanded source
(env.lineMap): one segment per contiguous run of lines, composed through
nested includes, region/heading/range slices (with the included file's
stripped frontmatter height folded in) and the isolation blank lines. Every
resolved source position now names the physical file a construct was
authored in.
This replaces the rebase-marker mechanism outright: relative URLs in
included markdown are rebased by resolving the token's position instead of
maintaining a marker stack scrubbed out of html_block tokens, which also
extends rebasing to lines following an inline include and makes block
isolation of own-line markdown includes unconditional (previously both were
skipped when markers could not be emitted).
BREAKING CHANGE: the `<!-- @include-start/end -->` markers no longer exist
in the expanded source (`env.src`).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every inline rule is wrapped (lazily on first parse, so user config rules
are covered) to record the source range it consumed onto the tokens it
emits; a terminal core rule converts ranges into {file, line, column} via
the parent inline map and env.lineMap, stamped as token.meta.vpLoc together
with the decoded as-authored destination (token.meta.vpRaw). Table-cell
inline tokens inherit their row's map, and links spliced in by the linkify
core rule recover at least their line. Ranges ride on token objects, so
plugins that splice children or mutate content don't disturb them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Functions in the site config (e.g. a custom miniSearch tokenize) were
embedded in the client's JSON payload as source strings and rebuilt at
runtime with new Function, which strict CSP blocks unless unsafe-eval
is allowed. Emit them as plain function expressions in the generated
script instead, with only an index marker left in the JSON, so the
deserializer just looks them up.
This also stops data strings that merely start with "_vp-fn_" from
being evaluated as code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Any element with an `id` is keyed by it regardless of attribute order, and a
`meta` without one is keyed by its first attribute other than `content`. This
stops differently named meta tags with the same content from overriding each
other and lets repeated meta tags be kept apart with unique ids.
fixes#5362closes#5363closes#5379
Co-authored-by: Lazizbek Ergashev <20501725+lazerg@users.noreply.github.com>
Co-authored-by: shamu45678 <220251922@seu.edu.cn>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Indexing runs unawaited from `configureServer`, so a page whose snippet or
include cannot be resolved rejected the scan and killed the process on
startup. Warn and skip the page instead; the error is still reported by the
markdown transform, and the build still fails on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Relative image and link urls inside included markdown files now
resolve from the included file's location instead of from the page
including it, so a partial can link to its neighbors regardless of who
includes it. This matches @mdit/plugin-include, whose resolveImagePath
and resolveLinkPath are on by default as well.
Included content is wrapped in marker comments during expansion;
hidden html_block rules maintain a per-render directory stack and the
image and link_open renderer wrappers rebase '.'-prefixed urls against
its top, so nested includes resolve correctly and urls outside
included content are untouched. The markers are separated by blank
lines so an adjacent html block cannot absorb them, and are only
emitted for directives on a line of their own outside fenced blocks,
so they can never reach the output. The include plugin registers after
the image and link plugins so that rebasing runs before their url
handling.
BREAKING CHANGE: relative urls in included markdown files resolve
against the included file rather than the including page. Partials
written for one specific location may need their links updated, or
markdown.include.rebaseRelativeUrls set to false to keep resolving
them from the including page. Absolute and external urls are
unaffected. Note that the marker comments shift the line numbers
reported for dead links following an include, which already pointed
into the include-expanded source rather than the original file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves <!-- @include: --> expansion out of markdownToVue and the local
search plugin into an include plugin registered on the renderer, which
wraps renderAsync and expands includes whenever the env carries a file
path. createMarkdownRenderer users and createContentLoader with render
or excerpt enabled now get includes expanded too (previously the
directives silently came through unexpanded), and the expansion is
reachable from the public API without exporting the helper (#4838).
Region selection uses the region engine (all same-named regions
concatenate, matched across comment styles), frontmatter of markdown
files is stripped before locating regions and headings so both share
one coordinate space, and the region suffix is anchored to the end of
the include path, so paths containing # now work.
Files reached by a failed render are still reported, so that creating
a missing snippet or include recovers the page instead of requiring an
edit of the including file.
BREAKING CHANGES:
- a missing region or heading anchor now throws instead of silently
including the whole file, and out-of-bounds or inverted ranges throw
instead of clamping; markdown.include.silent logs a warning and
expands to nothing instead
- markdown.include is a new option; false disables include processing
- custom search _render functions receive the raw source (sync
md.render does not expand includes; use renderAsync)
- the include-expanded source is exposed as env.src
Co-authored-by: Miroma <its.miroma@proton.me>
Co-authored-by: Naloam <110604855+Naloam@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the single-regex raw path parsing with a structural parser
that peels [title], {meta} and #region off the end, so meta can no
longer be silently swallowed into the file path. Inside the braces,
everything after the language is now passed through to the fence info
verbatim, allowing multiple attributes and quoted values (e.g.
{ts twoslash key="a b"}). Extensions are derived from the file name,
so dotfiles resolve, region references may contain dots, and {1, 2}
style spacing is tolerated.
Region extraction uses the new region engine, so all same-named
regions are concatenated in document order, and which marker lines are
removed from the output is configurable through stripRegionMarkers.
BREAKING CHANGES:
- a missing snippet file or region now throws instead of rendering an
error message inside the code block (or importing the whole file, in
the region case); the silent option restores non-fatal behavior by
logging a warning and rendering nothing
- the language inferred from a file name now includes uppercase
extensions, which were previously matched as [a-z0-9]+ only, so a
snippet of e.g. Foo.TS is highlighted where it used to render
without a language; suffixes that are not alphanumeric, like .c++ or
.code-snippets, are still not inferred and need the language given
in the braces
- markdown.snippet now accepts an options object in addition to the
boolean toggle
- rawPathToToken, findRegion and dedent are no longer exported from
the plugin module
Co-authored-by: Miroma <its.miroma@proton.me>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds readTextFile/readTextFileSync (CRLF/CR -> LF) next to the raw
retrying readFile and switches the markdown-source readers to them
(content loader, dynamic route templates, local search, includes).
Raw reads stay in place where bytes must be preserved (serve, init
scaffolding).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shared engine for VS Code-style region extraction, used by the snippet
and include rewrites. Compared to findRegion in the snippet plugin, it
returns all same-named regions in document order and matches marker
styles per line instead of locking onto the first style found, so
same-named regions in mixed-comment files (e.g. Vue SFCs) merge. All
open regions are tracked, and an end marker without a name closes the
innermost region opened in its own comment style, so it neither gets
captured by a region of another language nested inside it nor closes
one that was left open there.
The marker regexes follow the folding.markers definitions VS Code ships
per language, which corrects two of the previous ones: REM is
case-insensitive in bat, and #pragma allows a space after the hash.
Those definitions disagree about whether the hash is required - the
marker makes it optional for js and markdown, while html, css, sql, bat
and f# require it - so related comment styles are merged into one regex
each, keeping the hash optional only where at least one of the merged
languages makes it optional. Since a markdown renderer cannot know the
language of an imported file, all styles are tried on every file, which
makes this a superset of what an editor folds. Note that a language
service can disagree with the marker its language ships: TypeScript
requires the hash the ts/js marker makes optional, while the Lua one
accepts it hash-less where we require it. Fixtures covering the marker
forms per language live in brc-dd/region-marker-fixtures.
Quoted Visual Basic region names are matched without their quotes, and
region markers declared as JSON keys are recognized as an extension,
plain JSON having no comments to put a marker in.
Co-authored-by: Miroma <its.miroma@proton.me>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expanding an include that is its own ancestor recursed forever.
Track the ancestor chain and leave such includes unexpanded; the
same file can still be included repeatedly as a sibling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
createContentLoader called md.renderAsync without a MarkdownEnv, so
the internal link plugin fell back to defaults and always emitted
.html links in rendered content and excerpts even with cleanUrls
enabled, and plugins reading env.path/relativePath broke. Build the
same env as a normal page render and forward it to both calls.
fixes#4331closes#5299
Co-authored-by: TowyTowy <towy@airreps.link>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`markdown.codeCopyButton` now holds both the button tooltip
(`tooltipText`) and the text shown after copying (`copiedText`),
per-locale overridable via `locales.<index>.markdown.codeCopyButton`.
The copied text is emitted as a `data-copied` attribute on the button
and rendered by the theme with `content: attr(data-copied)`.
BREAKING CHANGE: `markdown.codeCopyButtonTitle` is now
`markdown.codeCopyButton.tooltipText`, and its default changed from
"Copy Code" to "Copy code". The `--vp-code-copy-copied-text-content`
CSS variable and its built-in per-language `:lang()` defaults are
removed - set `codeCopyButton.copiedText` (per locale) instead.
close#4431
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Strings baked into pages by the markdown renderer can now be overridden
per locale via `locales.<index>.markdown` in the site config: container
titles (including GitHub-flavored alerts and custom containers) and the
code copy button title. Values resolve against `env.localeIndex` at
render time and fall back to the root markdown options, so the single
shared renderer keeps working unchanged.
close#4431
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New containers can be registered via
`markdown.container.customContainers`, mapping a container name to its
default title. Registered names work as `::: name` blocks and as
GitHub-style alerts (`> [!NAME]`), with custom titles, attributes, and
no-title behaving like the built-in types. Styling is left to the theme
via `.custom-block.name`.
close#3591close#3603close#4228
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A no-title attribute (`::: tip {no-title}`) renders a container without
its title element, and the theme reduces the top padding for containers
without one. It has no effect on `details`, which always needs its
summary. The docs previously hand-rolled this with a raw div and an
inline style - those are now switched to the new syntax.
close#4928close#4932close#4929
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces markdown-it-container and merges the GitHub alerts plugin into
the containers module so both share one title map. Rendered output is
byte-compatible with the previous implementation.
This also restores fence-line attributes on containers ({open}, {.class},
etc.), which broke when markdown-it-attrs was replaced with
@mdit/plugin-attrs - its fence rule handled containers too, but had to be
disabled to keep code block meta intact. The container plugin now parses
the trailing braces itself, so the old markdown-it-attrs patch is not
needed in any form.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-module hooks (resolveId / load / transform) now declare filters,
so rolldown skips the Rust-to-JS call for modules they cannot act on
instead of invoking every hook for every module in the graph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plugin options that could be disabled with `false` (`attrs`, `emoji`,
`tasklist`, `anchor`, `toc`, `image`, `component`) now also accept
`true`, which - like leaving them unset - enables the plugin with its
default options. Only object values are forwarded to the plugins.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renders `- [ ] task` syntax via `@mdit/plugin-tasklist`, enabled by
default and configurable through the new `tasklist` markdown option
(set to `false` to disable). The default theme hides list markers for
task items and pulls checkboxes into the list gutter, GitHub-style.
close#413close#1923close#3648close#5110
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `fence` rule is disabled by default, replacing the patch we carried
to stop curly attributes from consuming code block meta (line
highlighting etc.). Drops both attrs patches and
`@types/markdown-it-attrs`.
BREAKING CHANGE: `markdown.attrs` options are now typed by
`@mdit/plugin-attrs`: `leftDelimiter`, `rightDelimiter`, and
`allowedAttributes` are renamed to `left`, `right`, and `allowed`. A
`rule` option is available for toggling individual attribute rules
(VitePress disables `fence` by default).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Captures what the markdown-it-attrs patch enforces (curly attributes
never consume fence info, preserving line-highlight syntax) plus the
supported attribute placements, as a regression baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
attrs registers its core rule at a fixed position (before linkify)
while anchor pushes to the end of the chain, so user-defined ids from
curly attributes win regardless of registration order. Remove the
comment that claimed the order matters.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- group `MarkdownOptions` into sections by concern (general, syntax
highlighting, code blocks, markdown extensions, vue integration) and
rewrite the jsdocs with a consistent voice, documenting the previously
undocumented `externalLinks` default and correcting the `container`
description (label customization, not plugin pass-through)
- register plugins in accurately-labeled groups (vitepress
customizations, community plugins, mdit-vue plugins) and note the
order-sensitive couplings inline (lineNumbers after preWrapper,
anchor after attrs)
- add a test for the `cjkFriendlyEmphasis` toggle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Allows custom themes to opt out of markup and behavior added on top of
vanilla markdown rendering:
- `anchor`, `emoji`, `toc`, `component`, `image` now also accept `false`
- new `preWrapper` and `snippet` boolean options (default true)
- `lineNumbers` is a no-op when `preWrapper` is disabled, as its markup
depends on the wrapper
close#4484close#4556
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- split __tests__/tsconfig.json into per-suite projects so unit tests
check against src (via path aliases) while e2e/init check against the
built package, avoiding mixing both type universes in one program
- explicitly include the .vitepress dir in the e2e project - dotted
directories are skipped by default, so it was never type-checked
- add a `*.vue` shim for the e2e custom theme (named env.d.ts because a
shims.d.ts would be dropped in favor of the adjacent shims.ts)
- add a tsconfig for docs, checked with vue-tsc
- wire everything into `pnpm test` as `test:types`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BREAKING CHANGE: The `markdown.image.lazyLoading` option has been renamed to `markdown.image.lazyLoad`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the standalone `useData().hash` ref (backed by its own
`hashchange` listener in `initData`) with the `hash` already tracked on
the route, so the current URL is observed in one place and behaves
consistently during SSR.
- `isActive()` no longer reads `location.hash` at call time; it takes
the current hash as an argument (plus an optional `skipHashCheck`)
and `matchPath` is now required, making it pure and SSR-safe
- nav bar menus, nav screen menus, and menu links now react to hash-only
navigation too
- sidebar active-state tracking is reworked on top of the route: items
without a link are handled, and collapsed groups reliably auto-expand
when a child link becomes active (including on hash changes)
- prev/next links now properly ignore query strings and hashes when
deduplicating sidebar candidates (via `normalize`) and when matching
the current page
- custom `i18nRouting` functions receive the whole current `Route`
instead of only the hash string; language menu links are resolved
from the route (en + ru docs updated)
- `useLangs()` / `resolveLocaleLink()` are refactored with
self-documenting, JSDoc'd options (`linkToCorrespondingPage`,
`targetLocaleLink`, `currentLocaleLink`), and `resolveLocaleLink`
gained focused unit tests covering locale-home links, corresponding
links, cleanUrls, index pages, root-locale switching, and custom
routing functions
- unavoidable hydration mismatches are silenced with
`data-allow-mismatch` (viewport-dependent inline styles, per-locale
alternate links that embed the current query/hash)
- the `Route` interface moved to the shared types so
`DefaultTheme.I18nRouting` can reference it; the `vitepress` type
export is unchanged
- misc cleanups: `uniqBy` moved to theme support utils, `smartComputed`
comparator now called as `(newValue, oldValue)`, import ordering
BREAKING CHANGE: `useData().hash` has been removed. Read the hash from
`useRoute()` instead.
BREAKING CHANGE: custom `themeConfig.i18nRouting` functions now receive
the current `Route` as their second argument instead of the hash
string: `(data, route, targetLocale) => string`. Use `route.hash` for
the previous value; `route.path`, `route.query`, and `route.data` are
available as well.