doc: extensions: Add Doxygen tooltips to main documentation
Add Doxygen tooltips in the main documentation. The tooltips are triggered when hovering over C symbols in the documentation and display a preview of the Doxygen documentation for the symbol. Some shadow DOM magic is needed to style the tooltips correctly, without affecting the rest of the page. Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
This commit is contained in:
parent
22bde52b37
commit
bd29c5c410
6 changed files with 221 additions and 0 deletions
36
doc/_extensions/zephyr/doxytooltip/__init__.py
Normal file
36
doc/_extensions/zephyr/doxytooltip/__init__.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Doxygen Tooltip Extension
|
||||
#########################
|
||||
|
||||
Copyright (c) 2024 The Linux Foundation
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
A simple Sphinx extension that adds JS and CSS resources to the app
|
||||
to enable tooltips for C domain links.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RESOURCES_DIR = Path(__file__).parent / "static"
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.config.html_static_path.append(RESOURCES_DIR.as_posix())
|
||||
|
||||
app.add_js_file("tippy/popper.min.js")
|
||||
app.add_js_file("tippy/tippy-bundle.umd.min.js")
|
||||
|
||||
app.add_js_file("doxytooltip.js")
|
||||
app.add_css_file("doxytooltip.css")
|
||||
|
||||
return {
|
||||
"version": "0.1",
|
||||
"parallel_read_safe": True,
|
||||
"parallel_write_safe": True,
|
||||
}
|
||||
36
doc/_extensions/zephyr/doxytooltip/static/doxytooltip.css
Normal file
36
doc/_extensions/zephyr/doxytooltip/static/doxytooltip.css
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
.tippy-box[data-theme~='doxytooltip'] {
|
||||
/* provide minimal definition of the CSS variables used by Doxygen Awesome so that we have a
|
||||
look and feel that's consistent with the rest of the documentation without necessarily aiming
|
||||
for full fidelity */
|
||||
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', Courier, monospace;
|
||||
--primary-color: var(--navbar-heading-color);
|
||||
--param-color: var(--code-literal-color);
|
||||
--border-radius-small: 4px;
|
||||
--border-radius-medium: 6px;
|
||||
--border-radius-large: 8px;
|
||||
|
||||
background-color: var(--content-wrap-background-color);
|
||||
color: var(--body-color);
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~='doxytooltip'][data-placement^='top']>.tippy-arrow::before {
|
||||
border-top-color: var(--content-wrap-background-color);
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~='doxytooltip'][data-placement^='bottom']>.tippy-arrow::before {
|
||||
border-bottom-color: var(--content-wrap-background-color);
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~='doxytooltip'][data-placement^='left']>.tippy-arrow::before {
|
||||
border-left-color: var(--content-wrap-background-color);
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~='doxytooltip'][data-placement^='right']>.tippy-arrow::before {
|
||||
border-right-color: var(--content-wrap-background-color);
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~='doxytooltip'] .tippy-content {
|
||||
font-size: 0.9em;
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
133
doc/_extensions/zephyr/doxytooltip/static/doxytooltip.js
Normal file
133
doc/_extensions/zephyr/doxytooltip/static/doxytooltip.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Copyright (c) 2024, Benjamin Cabé <benjamin@zephyrproject.org>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* This script adds mouse hover hooks to the current page to display
|
||||
* Doxygen documentation as tooltips when hovering over a symbol in the
|
||||
* documentation.
|
||||
*/
|
||||
|
||||
registerDoxygenTooltip = function () {
|
||||
const parser = new DOMParser();
|
||||
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.id = 'doxytooltip';
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
const tooltipShadow = document.createElement('div');
|
||||
tooltipShadow.id = 'doxytooltip-shadow';
|
||||
tooltip.attachShadow({ mode: 'open' }).appendChild(tooltipShadow);
|
||||
|
||||
/* tippy's JS code automatically adds a <style> tag to the document's <head> with the
|
||||
* styles for the tooltips. We need to copy it to the tooltip's shadow DOM for it to apply to the
|
||||
* tooltip's content.
|
||||
*/
|
||||
const tippyStyle = document.querySelector('style[data-tippy-stylesheet]');
|
||||
if (tippyStyle) {
|
||||
tooltipShadow.appendChild(tippyStyle.cloneNode(true));
|
||||
tippyStyle.remove();
|
||||
}
|
||||
|
||||
/*
|
||||
* similarly, doxytooltip.css is added to the document's <head> by the Sphinx extension, but we
|
||||
* need it in the shadow DOM.
|
||||
*/
|
||||
const doxytooltipStyle = document.querySelector('link[href*="doxytooltip.css"]');
|
||||
if (doxytooltipStyle) {
|
||||
tooltipShadow.appendChild(doxytooltipStyle.cloneNode(true));
|
||||
doxytooltipStyle.remove();
|
||||
}
|
||||
|
||||
let firstTimeShowingTooltip = true;
|
||||
let links = Array.from(document.querySelectorAll('a.reference.internal')).filter((a) =>
|
||||
a.querySelector('code.c')
|
||||
);
|
||||
links.forEach((link) => {
|
||||
tippy(link, {
|
||||
content: "Loading...",
|
||||
allowHTML: true,
|
||||
maxWidth: '500px',
|
||||
placement: 'auto',
|
||||
trigger: 'mouseenter',
|
||||
touch: false,
|
||||
theme: 'doxytooltip',
|
||||
interactive: true,
|
||||
appendTo: () =>
|
||||
document
|
||||
.querySelector('#doxytooltip')
|
||||
.shadowRoot.getElementById('doxytooltip-shadow'),
|
||||
|
||||
onShow(instance) {
|
||||
const url = link.getAttribute('href');
|
||||
const targetId = link.getAttribute('href').split('#')[1];
|
||||
|
||||
fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then((data) => {
|
||||
const parsedDoc = parser.parseFromString(data, 'text/html');
|
||||
|
||||
if (firstTimeShowingTooltip) {
|
||||
/* Grab the main stylesheets from the Doxygen page and inject them in the tooltip's
|
||||
shadow DOM */
|
||||
const doxygenCSSNames = ['doxygen-awesome.css', 'doxygen-awesome-zephyr.css'];
|
||||
for (const cssName of doxygenCSSNames) {
|
||||
const cssLink = parsedDoc.querySelector(`link[href$="${cssName}"]`);
|
||||
if (cssLink) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = new URL(
|
||||
cssLink.getAttribute('href'),
|
||||
new URL(url, window.location.href)
|
||||
);
|
||||
tooltipShadow.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
firstTimeShowingTooltip = false;
|
||||
}
|
||||
|
||||
const codeClasses = link.getElementsByTagName('code')[0].classList;
|
||||
let content;
|
||||
if (codeClasses.contains('c-enumerator')) {
|
||||
const target = parsedDoc.querySelector(`tr td a[id="${targetId}"]`);
|
||||
if (target) {
|
||||
content = target.parentElement.nextElementSibling;
|
||||
}
|
||||
} else if (codeClasses.contains('c-struct')) {
|
||||
content = parsedDoc.querySelector(`#details ~ div.textblock`);
|
||||
} else {
|
||||
/* c-function, c-type, etc. */
|
||||
const target = parsedDoc.querySelector(
|
||||
`h2.memtitle span.permalink a[href="#${targetId}"]`
|
||||
);
|
||||
if (target) {
|
||||
content = target.closest('h2').nextElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
if (content) {
|
||||
// rewrite all relative links as they are originally relative to the Doxygen page
|
||||
content.querySelectorAll('a').forEach((a) => {
|
||||
const href = a.getAttribute('href');
|
||||
if (href && !href.startsWith('http')) {
|
||||
a.href = new URL(href, new URL(url, window.location.href));
|
||||
}
|
||||
});
|
||||
|
||||
// set the tooltip content
|
||||
instance.setContent(content.cloneNode(true).innerHTML);
|
||||
} else {
|
||||
// don't show tooltip if no meaningful content was found (ex. no brief description)
|
||||
instance.setContent(null);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch the documentation:', err);
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', registerDoxygenTooltip);
|
||||
8
doc/_extensions/zephyr/doxytooltip/static/tippy/popper.min.js
vendored
Normal file
8
doc/_extensions/zephyr/doxytooltip/static/tippy/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
doc/_extensions/zephyr/doxytooltip/static/tippy/tippy-bundle.umd.min.js
vendored
Normal file
7
doc/_extensions/zephyr/doxytooltip/static/tippy/tippy-bundle.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -85,6 +85,7 @@ extensions = [
|
|||
"sphinx_sitemap",
|
||||
"zephyr.doxyrunner",
|
||||
"zephyr.doxybridge",
|
||||
"zephyr.doxytooltip",
|
||||
"zephyr.gh_utils",
|
||||
"zephyr.manifest_projects_table",
|
||||
"notfound.extension",
|
||||
|
|
|
|||
Loading…
Reference in a new issue