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
173 changes: 173 additions & 0 deletions docs/_static/js/overwrite_links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Replaces rtd-address with new-address in links

const rtd_address = 'canonical-openstack-proxy.readthedocs-hosted.com';
const new_address = 'canonical.com/openstack/docs';
const new_path = '/' + new_address.split('/').slice(1).join('/');

function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function overwriteMatchingAnchorUrls(container) {
if (!container) return;

const rtd_addressRegex = new RegExp(escapeRegExp(rtd_address), 'g');
container.querySelectorAll('a[href], link[href]').forEach((anchor) => {
anchor.href = anchor.href.replace(rtd_addressRegex, new_address);
});
}

function prependPathToAnchorUrls(container, path) {
if (!container) return;

container.querySelectorAll('a[href], link[href]').forEach((anchor) => {
const href = anchor.getAttribute('href');
// Only prepend to root-relative URLs (e.g. "/en/stable/page.html").
// Skip absolute URLs ("https://..."), protocol-relative URLs ("//..."),
// anchors ("#..."), and other schemes ("mailto:", "tel:", ...) so they
// aren't broken by having the path prepended.
if (
href &&
href.startsWith('/') &&
!href.startsWith('//') &&
!href.startsWith(path)
) {
anchor.setAttribute('href', path + href);
}
});
}

function patchFlyout() {
const rtdFlyout = document.querySelector('readthedocs-flyout');
if (!rtdFlyout) return false;
if (rtdFlyout.dataset.urlOverwritePatched) return true;
rtdFlyout.dataset.urlOverwritePatched = 'true';

overwriteMatchingAnchorUrls(rtdFlyout);
overwriteMatchingAnchorUrls(rtdFlyout.shadowRoot);

rtdFlyout.addEventListener('click', () => {
overwriteMatchingAnchorUrls(rtdFlyout);
overwriteMatchingAnchorUrls(rtdFlyout.shadowRoot);
});

return true;
}

function patchNotification() {
const rtdNotification = document.querySelector('readthedocs-notification');
if (!rtdNotification) return false;
if (rtdNotification.dataset.urlOverwritePatched) return true;
rtdNotification.dataset.urlOverwritePatched = 'true';

const patchAll = () => {
overwriteMatchingAnchorUrls(rtdNotification);
if (rtdNotification.shadowRoot) {
overwriteMatchingAnchorUrls(rtdNotification.shadowRoot);
}
};

// Patch any content that already exists.
patchAll();

// Notification content is rendered dynamically into the element's shadow DOM
// by Lit when the config is loaded (asynchronously), so the initial patch
// above will usually find nothing. Wait for the shadow root to become
// available (the custom element may not have been upgraded yet) and then
// observe it so that links are patched as soon as they are rendered.
const observeShadowRoot = () => {
if (!rtdNotification.shadowRoot) {
requestAnimationFrame(observeShadowRoot);
return;
}

patchAll();

const observer = new MutationObserver(patchAll);
observer.observe(rtdNotification.shadowRoot, {
childList: true,
subtree: true,
});
};

observeShadowRoot();

return true;
}

function patchSearch() {
const rtdSearch = document.querySelector('readthedocs-search');
if (!rtdSearch) return false;
if (rtdSearch.dataset.urlOverwritePatched) return true;
rtdSearch.dataset.urlOverwritePatched = 'true';

const patchAll = () => {
overwriteMatchingAnchorUrls(rtdSearch);
prependPathToAnchorUrls(rtdSearch, new_path);
if (rtdSearch.shadowRoot) {
overwriteMatchingAnchorUrls(rtdSearch.shadowRoot);
prependPathToAnchorUrls(rtdSearch.shadowRoot, new_path);
}
};

// Patch any content that already exists.
patchAll();

// Search results are rendered dynamically into the element's shadow DOM by
// Lit when the user performs a search, so the initial patch above will
// usually find nothing. Wait for the shadow root to become available (the
// custom element may not have been upgraded yet) and then observe it so that
// result links are patched as soon as they are rendered.
const observeShadowRoot = () => {
if (!rtdSearch.shadowRoot) {
requestAnimationFrame(observeShadowRoot);
return;
}

patchAll();

const observer = new MutationObserver(patchAll);
observer.observe(rtdSearch.shadowRoot, {
childList: true,
subtree: true,
});
};

observeShadowRoot();

// Patch on click as well, to cover keyboard navigation (the Enter key calls
// .click() on the active result) and any case the observer might miss.
rtdSearch.addEventListener('click', patchAll);

return true;
}

function init() {
overwriteMatchingAnchorUrls(document.querySelector('header'));

// Patch each addon independently. Using `&&` short-circuit evaluation here
// would prevent later addons (e.g. search) from being patched at all if an
// earlier one (e.g. flyout or notification) is disabled or not yet loaded.
let flyoutDone = patchFlyout();
let notificationDone = patchNotification();
let searchDone = patchSearch();

if (flyoutDone && notificationDone && searchDone) return;

const observer = new MutationObserver(() => {
if (!flyoutDone) flyoutDone = patchFlyout();
if (!notificationDone) notificationDone = patchNotification();
if (!searchDone) searchDone = patchSearch();
if (flyoutDone && notificationDone && searchDone) {
observer.disconnect();
}
});

observer.observe(document.body, { childList: true, subtree: true });
}

if (document.body) {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
25 changes: 13 additions & 12 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
html_title = project + " documentation"

# Documentation website URL
ogp_site_url = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
ogp_site_url = f"https://canonical.com/openstack/docs/{os.environ.get('READTHEDOCS_VERSION', 'local')}/"

# Preview name of the documentation website
ogp_site_name = project
Expand Down Expand Up @@ -97,17 +97,18 @@
# Project slug
# TODO: If your documentation is hosted on https://documentation.ubuntu.com/,
# uncomment and set to the RTD slug.
# slug = ''
slug = "openstack/docs"

#######################
# Sitemap configuration: https://sphinx-sitemap.readthedocs.io/
#######################

# Use RTD canonical URL to ensure duplicate pages have a specific canonical URL
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
html_baseurl = f"https://canonical.com/openstack/docs/{os.environ.get('READTHEDOCS_VERSION', 'local')}/"

# sphinx-sitemap uses html_baseurl to generate the full URL for each page:
sitemap_url_scheme = "{link}"
sitemap_filename = "doc-sitemap.xml"

# Include `lastmod` dates in the sitemap:
sitemap_show_lastmod = True
Expand All @@ -125,8 +126,8 @@
# Template and asset locations #
################################

# html_static_path = ["_static"]
# templates_path = ["_templates"]
html_static_path = ["_static"]
templates_path = ["_templates"]

#############
# Redirects #
Expand All @@ -143,7 +144,6 @@
# Strips '/index.html' from destination URLs when building with 'dirhtml'
rediraffe_dir_only = True


############################
# sphinx-llm configuration #
############################
Expand Down Expand Up @@ -246,14 +246,15 @@
]

# Adds custom CSS files, located remotely or in 'html_static_path'.
# html_css_files = [
# "https://assets.ubuntu.com/v1/d86746ef-cookie_banner.css",
# ]
html_css_files = [
"https://assets.ubuntu.com/v1/d86746ef-cookie_banner.css",
]

# Adds custom JavaScript files, located remotely or in 'html_static_path'.
# html_js_files = [
# "https://assets.ubuntu.com/v1/287a5e8f-bundle.js",
# ]
html_js_files = [
"js/overwrite_links.js",
"https://assets.ubuntu.com/v1/287a5e8f-bundle.js",
]

# Appends extra markup to the end of every document written in reST
rst_epilog = """
Expand Down
Loading