Skip to content

Improve templating mechanism #266

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Fixed relative path to favicon in vitepress config [#265](https://github.com/LuxDL/DocumenterVitepress.jl/pull/265).
- Changed internal templating mechanism which will require synchronizing custom `config.mts` files to use the new format [#266](https://github.com/LuxDL/DocumenterVitepress.jl/pull/266).

## v0.2.1 - 2025-05-15
Bug fix release after v0.2.0 - now, namespacing deploydocs as `DocumenterVitepress.deploydocs` should "just work".
Expand Down
14 changes: 8 additions & 6 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@ DocInventories = "43dc2714-ed3b-44b5-b226-857eda1aa7de"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
IOCapture = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
NodeJS_20_jll = "c7aee132-11e1-519c-8219-0a43005e73c2"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[weakdeps]
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"

[extensions]
DocumenterVitepressDocumenterCitationsExt = "DocumenterCitations"

[compat]
ANSIColoredPrinters = "0.0.1"
DocInventories = "1.0"
DocStringExtensions = "0.9"
Documenter = "1.11"
DocumenterCitations = "1"
IOCapture = "0.2"
JSON = "0.21.4"
NodeJS_20_jll = "20"
TOML = "1.0.3"
julia = "1.6"

[weakdeps]
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"

[extensions]
DocumenterVitepressDocumenterCitationsExt = "DocumenterCitations"
67 changes: 36 additions & 31 deletions docs/src/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,49 +1,51 @@
import { defineConfig } from 'vitepress'
import { defineConfig, DefaultTheme, HeadConfig } from 'vitepress'
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
import mathjax3 from "markdown-it-mathjax3";
import footnote from "markdown-it-footnote";
import path from 'path'

// console.log(process.env)

function getBaseRepository(base: string): string {
if (!base || base === '/') return '/';
const parts = base.split('/').filter(Boolean);
return parts.length > 0 ? `/${parts[0]}/` : '/';
}

const baseTemp = {
base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs!
type DocumenterOptions = {
base: string;
deployAbspath: string;
description: string;
editLink?: DefaultTheme.EditLink;
favicon?: string;
githubLink: string;
logo?: string;
outDir: string;
nav: Array<any>;
sidebar?: DefaultTheme.Sidebar;
title: string;
}

const navTemp = {
nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
}
// this will be filled in by DocumenterVitepress at render time
const DOCUMENTER = {} as DocumenterOptions;

const faviconConfig: HeadConfig = ['link', { rel: 'icon', href: `${DOCUMENTER.base}${DOCUMENTER.favicon}` }]

const nav = [
...navTemp.nav,
{
component: 'VersionPicker',
}
]
// https://vitepress.dev/reference/site-config
export default defineConfig({
base: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // TODO: replace this in makedocs!
title: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
description: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
base: DOCUMENTER.base,
title: DOCUMENTER.title,
description: DOCUMENTER.description,
lastUpdated: true,
cleanUrls: true,
outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly...

outDir: DOCUMENTER.outDir,
head: [
['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }],
['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}],
// ['script', {src: '/versions.js'], for custom domains, I guess if deploy_url is available.
['script', {src: `${baseTemp.base}siteinfo.js`}]
...(DOCUMENTER.favicon ? [faviconConfig] : []),
['script', {src: `${getBaseRepository(DOCUMENTER.base)}versions.js`}],
['script', {src: `${DOCUMENTER.base}siteinfo.js`}]
],

vite: {
define: {
__DEPLOY_ABSPATH__: JSON.stringify('REPLACE_ME_DOCUMENTER_VITEPRESS_DEPLOY_ABSPATH'),
__DEPLOY_ABSPATH__: JSON.stringify(DOCUMENTER.deployAbspath),
},
resolve: {
alias: {
Expand All @@ -68,7 +70,6 @@ export default defineConfig({
],
},
},

markdown: {
math: true,
config(md) {
Expand All @@ -83,23 +84,27 @@ export default defineConfig({
},
themeConfig: {
outline: 'deep',
// https://vitepress.dev/reference/default-theme-config
logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
...(DOCUMENTER.logo ? { src: DOCUMENTER.logo, width: 24, height: 24} : {}),
search: {
provider: 'local',
options: {
detailedView: true
}
},
nav,
sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
nav: [
...DOCUMENTER.nav,
{
component: 'VersionPicker'
}
],
sidebar: DOCUMENTER.sidebar,
editLink: DOCUMENTER.editLink,
socialLinks: [
{ icon: 'slack', link: 'https://julialang.org/slack/' }
],
footer: {
message: 'Made with <a href="https://documenter.juliadocs.org/stable/" target="_blank"><strong>Documenter.jl</strong></a>, <a href="https://vitepress.dev" target="_blank"><strong>VitePress</strong></a> and <a href="https://luxdl.github.io/DocumenterVitepress.jl/stable/" target="_blank"><strong>DocumenterVitepress.jl</strong></a> <br>',
copyright: `© Copyright ${new Date().getUTCFullYear()}.`
},
}
}
})
1 change: 1 addition & 0 deletions src/DocumenterVitepress.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using Documenter: Documenter, Selectors

using DocStringExtensions
using NodeJS_20_jll: node, npm
import JSON

const ASSETS = normpath(joinpath(@__DIR__, "..", "assets"))

Expand Down
158 changes: 69 additions & 89 deletions src/vitepress_config.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,8 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base)
@info "DocumenterVitepress: Did not detect `docs/src/.vitepress/theme/docstrings.css` file. Substituting in the default file."
write(joinpath(build_vitepress_dir, "theme", "docstrings.css"), read(joinpath(template_vitepress_dir, "theme", "docstrings.css"), String))
end
# We have already rewritten the config file, so we can't get burned by clean=false
# again.
vitepress_config_file = joinpath(build_vitepress_dir, "config.mts")
config = read(vitepress_config_file, String)
replacers = Vector{Pair{<: Union{AbstractString, Regex}, <: AbstractString}}()

json_config = Dict()

# # Vitepress base path

Expand All @@ -96,61 +92,56 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base)
isempty(s) ? "/" : "/$(s)"
end

base_str = deploy_abspath == "/" ? "base: '$(deploy_abspath)$(deploy_relpath)'" : "base: '$(deploy_abspath)/$(deploy_relpath)'"
base_str = deploy_abspath == "/" ? "$(deploy_abspath)$(deploy_relpath)" : "$(deploy_abspath)/$(deploy_relpath)"

json_config["deployAbspath"] = deploy_abspath
json_config["base"] = base_str

push!(replacers, "REPLACE_ME_DOCUMENTER_VITEPRESS_DEPLOY_ABSPATH" => deploy_abspath)
push!(replacers, "base: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => base_str)
# Vitepress output path
json_config["outDir"] = "../$(i_folder)"

# # Vitepress output path
push!(replacers, "outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "outDir: '../$(i_folder)'")
# # Vitepress navbar and sidebar
# Vitepress navbar and sidebar

provided_page_list = doc.user.pages
sidebar_navbar_info = pagelist2str.((doc,), provided_page_list)
sidebar_navbar_string = join(sidebar_navbar_info, ",\n")
push!(replacers, "sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "sidebar: [\n$sidebar_navbar_string\n]\n")
push!(replacers, "nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "nav: [\n$sidebar_navbar_string\n]\n")
sidebar_navbar_info = sidebar(doc) # pagelist2str.((doc,), provided_page_list)
json_config["sidebar"] = sidebar_navbar_info
json_config["nav"] = sidebar_navbar_info

# # Title
push!(replacers, "title: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "title: '$(doc.user.sitename)'")
# Title
json_config["title"] = doc.user.sitename

# # Description
push!(replacers, "description: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "description: '$(replace(settings.description, "'" => "\\'"))'")
# Description
json_config["description"] = settings.description

# # Edit link
push!(replacers, "editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "editLink: { pattern: \"https://$(settings.repo)$(endswith(settings.repo, "/") ? "" : "/")edit/$(settings.devbranch)/docs/src/:path\" }")
# Edit link
json_config["editLink"] = Dict("pattern" => "https://$(settings.repo)$(endswith(settings.repo, "/") ? "" : "/")edit/$(settings.devbranch)/docs/src/:path")

# # Github repo
# Github repo
full_repo = startswith(settings.repo, r"https?:\/\/") ? settings.repo : "https://" * settings.repo
push!(replacers, """{ icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' }""" => """{ icon: 'github', link: '$full_repo' }""")

# # Logo
json_config["githubLink"] = full_repo

if occursin("logo:", config)
if isfile(joinpath(doc.user.build, settings.md_output_path, "public", "logo.png"))
push!(replacers, "logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "logo: { src: '/logo.png', width: 24, height: 24}")
elseif isfile(joinpath(doc.user.build, settings.md_output_path, "public", "logo.svg"))
push!(replacers, "logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "logo: { src: '/logo.svg', width: 24, height: 24}")
else
@warn "DocumenterVitepress: No logo.png file found in `docs/src/assets`. Skipping logo replacement."
push!(replacers, "logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS'," => "")
end
# Logo
if isfile(joinpath(doc.user.build, settings.md_output_path, "public", "logo.png"))
json_config["logo"] = "/logo.png"
elseif isfile(joinpath(doc.user.build, settings.md_output_path, "public", "logo.svg"))
json_config["logo"] = "/logo.svg"
else
@warn "DocumenterVitepress: No logo.png file found in `docs/src/assets`."
end

# # Favicon

if occursin("rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON'", config)
if isfile(joinpath(doc.user.build, settings.md_output_path, "public", "favicon.ico"))
push!(replacers, "rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON'" => "rel: 'icon', href: `\${baseTemp.base}favicon.ico`")
else
@warn "DocumenterVitepress: No favicon.ico file found in `docs/src/assets`. Skipping favicon replacement."
push!(replacers, "['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }]," => "")
end
# Favicon
if isfile(joinpath(doc.user.build, settings.md_output_path, "public", "favicon.ico"))
json_config["favicon"] = "favicon.ico"
else
@warn "DocumenterVitepress: No favicon.ico file found in `docs/src/assets`."
end

# Finally, run all the replacers and write the new config file

new_config = replace(config, replacers...)
# We have already rewritten the config file, so we can't get burned by clean=false
# again.
vitepress_config_file = joinpath(build_vitepress_dir, "config.mts")
config = read(vitepress_config_file, String)
json_config_string = strip(JSON.json(json_config, 2))
new_config = replace(config, "const DOCUMENTER = {}" => "const DOCUMENTER = $json_config_string")
write(vitepress_config_file, new_config)
yield()
touch(vitepress_config_file)
Expand All @@ -161,59 +152,48 @@ end
function _get_raw_text(element)
end

function pagelist2str(doc, page::String)
function sidebar(doc)
return map(p -> sidebar_page_entry(doc, p), doc.user.pages)
end

sidebar_page_entry(doc, name_any::Pair{String, <: Any}) = sidebar_page_entry(doc, first(name_any) => last(name_any))

function sidebar_page_entry(doc, page::String)
return Dict(
"text" => get_page_name(doc, page),
"link" => splitext(page)[1],
)
end

function sidebar_page_entry(doc, name_page::Pair{String, String})
name, page = name_page
return Dict(
"text" => name,
"link" => splitext(page)[1],
)
end

function sidebar_page_entry(doc, name_contents::Pair{String, <: AbstractVector})
name, contents = name_contents
return Dict(
"text" => name,
"items" => map(p -> sidebar_page_entry(doc, p), contents),
"collapsed" => false,
)
end

function get_page_name(doc, page::String)
# If no name is given, find the first header in the page,
# and use that as the name.
elements = collect(doc.blueprint.pages[page].mdast.children)
# elements is a vector of Markdown.jl objects,
# you can get the MarkdownAST stuff via `page.mdast`.
# I f``
idx = findfirst(x -> x.element isa Union{MarkdownAST.Heading, Documenter.AnchoredHeader}, elements)
name = if isnothing(idx)
return if isnothing(idx)
splitext(page)[1]
else
Documenter.MDFlatten.mdflatten(elements[idx])
end
return "{ text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])' }" # , $(sidebar_items(doc, page)) }"
end

pagelist2str(doc, name_any::Pair{String, <: Any}) = pagelist2str(doc, first(name_any) => last(name_any))

function pagelist2str(doc, name_page::Pair{String, String})
name, page = name_page
# This is the simplest and easiest case.
return "{ text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])' }" # , $(sidebar_items(doc, page)) }"
end

function pagelist2str(doc, name_contents::Pair{String, <: AbstractVector})
name, contents = name_contents
# This is for nested stuff. Should work automatically but you never know...
rendered_contents = pagelist2str.((doc,), contents)
return "{ text: '$(replace(name, "'" => "\\'"))', collapsed: false, items: [\n$(join(rendered_contents, ",\n"))]\n }" # TODO: add a link here if the name is the same name as a file?
end

function sidebar_items(doc, page::String)
# We look at the page elements, and obtain all level 1 and 2 headers.
elements = doc.blueprint.pages[page].elements
headers = filter(x -> x isa Union{MarkdownAST.Heading{1}, MarkdownAST.Heading{2}}, elements)
# If nothing is found, move on in life
if length(headers) ≤ 1
return ""
end
# Otherwise, we return a collapsible tree of headers for each level 1 and 2 header.
items = headers
return "collapsed: true, items: [\n $(join(_item_link.((page,), items), ",\n"))\n]"
end

function _item_link(page, item)
return "{ text: '$(replace(Documenter.MDFlatten.mdflatten(item), "'" => "\\'"))', link: '/$(splitext(page)[1])#$(replace(item, " " => "-"))' }"

end

function _get_first_or_string(x::String)
return x
end

function _get_first_or_string(x)
return first(x)
end
Loading
Loading