Skip to content
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
4 changes: 4 additions & 0 deletions docs/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ navigation tree. Rustdoc receives the same controls through generated-HTML enhan
implementations are first-party Dockle code; ``doxygen-awesome-css`` and ``doxyconfig`` are design references, not
dependencies.

Each target publishes a ``dockle-search.html`` results page. The search field offers live suggestions and submits to
that page with a shareable ``?q=`` URL. On Read the Docs, Dockle uses the Addons project and version metadata to query
the hosted search API; elsewhere, and when a hosted index is unavailable, it uses the target's local ``search.json``.

Dockle also packages the complete pinned Highlight.js browser distribution. After a generator renders authored code,
the shared client normalizes its language identifier and replaces native Pygments, Prettify, Doxygen, or rustdoc token
markup with one Highlight.js token stream. This gives every adapter the same lexer behavior and palette without making
Expand Down
26 changes: 22 additions & 4 deletions src/dockle/jsdoc_template/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,18 @@ function searchableText(document) {

function searchDocument(filename, root) {
const document = fs.readFileSync(filename, 'utf8');
const title = /<title[^>]*>([^<]*)<\/title>/i.exec(document)?.[1]
const title = /<h1\b[^>]*class="page-title"[^>]*>([\s\S]*?)<\/h1>/i.exec(document)?.[1]
|| /<title[^>]*>([^<]*)<\/title>/i.exec(document)?.[1]
|| path.basename(filename, '.html');
const content = /<div id="main">([\s\S]*?)<footer\b/i.exec(document)?.[1]
|| document;
const body = content
.replace(/<div class="dockle-page-actions"[\s\S]*?<\/div>/i, ' ')
.replace(/<h1\b[^>]*>[\s\S]*?<\/h1>/i, ' ');
return {
location: path.relative(root, filename).split(path.sep).join('/'),
text: searchableText(document).slice(0, 4000),
title: decodeEntities(title).trim(),
text: searchableText(body).slice(0, 4000),
title: decodeEntities(title).trim().split(' — ')[0].split(' – ')[0],
};
}

Expand All @@ -180,8 +186,20 @@ function finishSite(destination, dockle) {
fs.writeFileSync(path.join(root, 'dockle.css'), stylesheet, 'utf8');
copyConfiguredAsset(dockle.logo, path.join(root, dockle.logoFile));
copyConfiguredAsset(dockle.favicon, path.join(root, dockle.faviconFile));
const searchPage = fs.readFileSync(path.join(__dirname, 'tmpl', 'dockle-search.html'), 'utf8')
.replaceAll('{{FRAMEWORK}}', 'jsdoc')
.replaceAll('{{PROJECT}}', escapeAttribute(dockle.projectName))
.replaceAll('{{ASSETS}}', '')
.replaceAll('{{INDEX}}', 'search.json')
.replaceAll('{{LOGO}}', escapeAttribute(dockle.logoFile))
.replaceAll('{{FAVICON_LINK}}', dockle.faviconFile
? `<link rel="icon" href="${escapeAttribute(dockle.faviconFile)}" data-dockle-favicon>`
: '');
fs.writeFileSync(path.join(root, 'dockle-search.html'), searchPage, 'utf8');
installExtraAssets(root, dockle);
const docs = htmlFiles(root).map((filename) => searchDocument(filename, root));
const docs = htmlFiles(root)
.filter((filename) => path.basename(filename) !== 'dockle-search.html')
.map((filename) => searchDocument(filename, root));
fs.writeFileSync(
path.join(root, 'search.json'),
JSON.stringify({ docs }),
Expand Down
58 changes: 58 additions & 0 deletions src/dockle/jsdoc_template/tmpl/dockle-search.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!doctype html>
<html lang="en" data-dockle-framework="{{FRAMEWORK}}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="readthedocs-addons-api-version" content="1">
<title>Search — {{PROJECT}}</title>
{{FAVICON_LINK}}
<link rel="stylesheet" href="{{ASSETS}}dockle.css" data-dockle-theme="{{FRAMEWORK}}">
<script defer src="{{ASSETS}}lucide.min.js" data-dockle-lucide></script>
<script defer src="{{ASSETS}}dockle.js" data-dockle-script></script>
</head>
<body>
<div class="dockle-toolbar">
<form class="dockle-search dockle-universal-search" data-dockle-universal-search
data-dockle-logo-url="{{LOGO}}"
action="dockle-search.html" method="get" role="search">
<label class="visually-hidden" for="dockle-search-input">Search documentation</label>
<input id="dockle-search-input" type="search" name="q" required
placeholder="Search documentation" autocomplete="off"
data-dockle-search="{{INDEX}}" data-dockle-root=".">
<button class="dockle-search-submit" type="submit" aria-label="Search documentation"
title="Search documentation"><i data-lucide="search" aria-hidden="true"></i></button>
<ul class="dockle-search-results" data-dockle-search-results="dockle-search-input"
aria-live="polite" hidden></ul>
</form>
</div>
<div class="dockle-shell">
<aside class="dockle-sidebar" id="dockle-sidebar" aria-label="Primary navigation">
<div class="dockle-brand"><a href="index.html">{{PROJECT}}</a></div>
<nav class="dockle-tree" aria-label="Documentation">
<ul><li><a href="index.html">Documentation</a></li></ul>
</nav>
</aside>
<div class="dockle-page">
<header class="dockle-mobile-header">
<button type="button" data-dockle-menu-toggle aria-controls="dockle-sidebar" aria-expanded="false">
<i data-lucide="menu" aria-hidden="true"></i><span class="visually-hidden">Menu</span>
</button>
<a href="index.html">{{PROJECT}}</a>
</header>
<main class="dockle-main dockle-search-main">
<article class="dockle-article">
<div class="dockle-page-actions" aria-label="Page actions">
<button type="button" class="dockle-theme-toggle"
data-dockle-theme-toggle aria-label="Color scheme: auto">
<i data-lucide="sun-moon" aria-hidden="true"></i>
</button>
</div>
<h1>Search results</h1>
<p data-dockle-search-summary aria-live="polite"></p>
<ol class="dockle-search-page-results" data-dockle-search-page></ol>
</article>
</main>
</div>
</div>
</body>
</html>
13 changes: 8 additions & 5 deletions src/dockle/jsdoc_template/tmpl/layout.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const repositoryService = normalizedRepositoryUrl.includes('github')
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="readthedocs-addons-api-version" content="1">
<meta name="generator" content="JSDoc <?js= safe(env.version.number) ?> with Dockle <?js= safe(dockle.dockleVersion) ?>">
<title><?js= safe(pageTitle) ?></title>
<?js if (dockle.faviconFile) { ?>
Expand All @@ -33,19 +34,21 @@ const repositoryService = normalizedRepositoryUrl.includes('github')
<?js if (dockle.projectVersion) { ?><span><?js= safe(dockle.projectVersion) ?></span><?js } ?>
</a>
<div class="dockle-toolbar">
<div class="dockle-search dockle-universal-search"
<form class="dockle-search dockle-universal-search"
data-dockle-universal-search
data-dockle-logo-url="<?js= safe(dockle.logoFile) ?>"
data-dockle-target-title="<?js= safe(dockle.targetTitle) ?>">
<i class="dockle-search-icon" data-lucide="search" aria-hidden="true"></i>
data-dockle-target-title="<?js= safe(dockle.targetTitle) ?>"
action="dockle-search.html" method="get" role="search">
<label class="visually-hidden" for="dockle-search-input">Search documentation</label>
<input id="dockle-search-input" type="search"
<input id="dockle-search-input" type="search" name="q" required
placeholder="Search documentation" autocomplete="off"
data-dockle-search="search.json" data-dockle-root=".">
<button class="dockle-search-submit" type="submit" aria-label="Search documentation"
title="Search documentation"><i data-lucide="search" aria-hidden="true"></i></button>
<ul class="dockle-search-results"
data-dockle-search-results="dockle-search-input"
aria-live="polite" hidden></ul>
</div>
</form>
</div>

<div id="main">
Expand Down
73 changes: 67 additions & 6 deletions src/dockle/sphinx/themes/dockle/static/dockle.css
Original file line number Diff line number Diff line change
Expand Up @@ -925,19 +925,32 @@ html[data-dockle-framework="rustdoc"] .sidebar::before {
color: var(--dockle-content);
font: inherit;
height: 2.6rem;
padding: 0.55rem 0.7rem 0.55rem 2.25rem;
padding: 0.55rem 3rem 0.55rem 0.7rem;
width: 100%;
}

.dockle-search-icon {
.dockle-search-submit {
align-items: center;
background: transparent;
border: 0;
border-radius: var(--dockle-radius);
color: var(--dockle-muted);
left: 0.7rem;
pointer-events: none;
cursor: pointer;
display: flex;
height: 2.6rem;
justify-content: center;
position: absolute;
top: 0.8rem;
right: 0;
top: 0;
width: 2.6rem;
z-index: 1;
}

.dockle-search-submit:hover,
.dockle-search-submit:focus-visible {
color: var(--dockle-primary);
}

.dockle-search input[type="search"]:focus-visible {
border-color: var(--dockle-primary);
outline: 2px solid color-mix(in srgb, var(--dockle-primary) 35%, transparent);
Expand All @@ -950,7 +963,7 @@ html[data-dockle-framework="rustdoc"] .sidebar::before {
border-radius: var(--dockle-radius);
list-style: none;
margin: 0.35rem 0 0;
max-height: 16rem;
max-height: 24rem;
overflow-y: auto;
padding: 0.25rem;
position: absolute;
Expand All @@ -967,15 +980,63 @@ html[data-dockle-framework="rustdoc"] .sidebar::before {
.dockle-search-results a {
color: var(--dockle-content);
display: flex;
flex-direction: column;
gap: 0.35rem;
text-decoration: none;
}

.dockle-live-search-snippet {
color: var(--dockle-muted);
display: block;
line-height: 1.4;
}

.dockle-search-results mark,
.dockle-search-page-results mark {
background: color-mix(in srgb, var(--dockle-primary) 25%, transparent);
border-radius: 0.15rem;
color: inherit;
}

.dockle-search-results a:hover,
.dockle-search-results a:focus-visible {
color: var(--dockle-primary);
}

.dockle-search-main {
display: block;
}

.dockle-search-page-results {
list-style: none;
padding: 0;
}

.dockle-search-page-results li {
border-bottom: 1px solid var(--dockle-border);
padding: 0.8rem 0;
}

.dockle-search-page-results a {
font-size: 1.1rem;
font-weight: 600;
}

.dockle-search-page-results p {
color: var(--dockle-muted);
margin: 0.35rem 0 0;
}

.dockle-search-more {
background: var(--dockle-background);
border: 1px solid var(--dockle-border);
border-radius: var(--dockle-radius);
color: var(--dockle-content);
cursor: pointer;
font: inherit;
padding: 0.6rem 1rem;
}

.dockle-sidebar .dockle-tree {
flex: 1 1 auto;
margin: 0;
Expand Down
Loading
Loading