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
9 changes: 9 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,12 @@ jobs:

- name: Run tests
run: uv run pytest

js-test:
name: JS Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Run Bifrost theme JS tests
run: node --test tests/javascripts/*.test.js
97 changes: 97 additions & 0 deletions src/intility_bifrost_mkdocs/overrides/javascripts/bifrost-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Bifrost theme sync.
*
* Mirrors Material for MkDocs' palette state onto the Bifrost CSS framework:
* data-md-color-scheme -> .bf-lightmode / .bf-darkmode on <html>
* data-md-color-primary -> .bf-theme-{teal,purple,pink,yellow}
*
* Also inserts an optional version badge into the header when a
* <meta name="bifrost-version" content="..."> tag is present.
*
* Exposes pure functions on `module.exports` for unit testing under
* Node's built-in test runner.
*/
(function (global) {
var BIFROST_THEMES = ['teal', 'purple', 'pink', 'yellow'];
var DARK_SCHEMES = ['dark', 'slate'];
var DEFAULT_THEME = 'teal';

function syncBifrostTheme(html, body) {
if (!html || !body) return;

var scheme = body.getAttribute('data-md-color-scheme');
var primary = body.getAttribute('data-md-color-primary');

if (DARK_SCHEMES.indexOf(scheme) !== -1) {
html.classList.add('bf-darkmode');
html.classList.remove('bf-lightmode');
} else {
html.classList.add('bf-lightmode');
html.classList.remove('bf-darkmode');
}

BIFROST_THEMES.forEach(function (theme) {
html.classList.remove('bf-theme-' + theme);
});
var resolved = BIFROST_THEMES.indexOf(primary) !== -1 ? primary : DEFAULT_THEME;
html.classList.add('bf-theme-' + resolved);
}

function readVersion(doc) {
if (!doc || !doc.querySelector) return null;
var meta = doc.querySelector('meta[name="bifrost-version"]');
if (!meta) return null;
var content = meta.getAttribute('content');
return content && content.length > 0 ? content : null;
}

function insertVersionBadge(doc, headerTopic, version) {
if (!doc || !headerTopic || !version) return null;
if (headerTopic.querySelector && headerTopic.querySelector('.bf-header-version')) {
return null;
}
var badge = doc.createElement('span');
badge.className = 'bf-badge bf-badge-pill bfc-theme-fade-bg bf-header-version';
badge.textContent = 'v' + version;
headerTopic.appendChild(badge);
return badge;
}

function init() {
var html = document.documentElement;
var body = document.body;
syncBifrostTheme(html, body);

var version = readVersion(document);
if (version) {
insertVersionBadge(document, document.querySelector('.md-header__topic'), version);
}

var observer = new MutationObserver(function () {
syncBifrostTheme(html, body);
});
observer.observe(body, {
attributes: true,
attributeFilter: ['data-md-color-scheme', 'data-md-color-primary'],
});
}

if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
}

if (typeof module !== 'undefined' && module.exports) {
module.exports = {
syncBifrostTheme: syncBifrostTheme,
readVersion: readVersion,
insertVersionBadge: insertVersionBadge,
BIFROST_THEMES: BIFROST_THEMES,
DARK_SCHEMES: DARK_SCHEMES,
DEFAULT_THEME: DEFAULT_THEME,
};
}
})(typeof window !== 'undefined' ? window : globalThis);
92 changes: 5 additions & 87 deletions src/intility_bifrost_mkdocs/overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Bifrost MkDocs Theme

A custom theme that extends Material for MkDocs while implementing
Intility's Bifrost design system.
Intility's Bifrost design system. Theme-sync behaviour lives in
`assets/javascripts/bifrost-theme.js`, injected via the plugin.
#}

{% block htmltitle %}
Expand All @@ -17,90 +18,7 @@
{% endblock %}

{% block extrahead %}
{# Apply Bifrost classes based on Material's palette configuration #}
<script>
(function() {
var html = document.documentElement;
var bifrostThemes = ['teal', 'purple', 'pink', 'yellow'];
{% if config.extra.version %}
var siteVersion = '{{ config.extra.version }}';
{% endif %}

// Function to sync Bifrost classes with Material's palette
function syncBifrostTheme() {
var body = document.body;
if (!body) return; // Body might not be ready yet

// Get scheme (light/dark) from data-md-color-scheme
var scheme = body.getAttribute('data-md-color-scheme');

// Get Bifrost theme from data-md-color-primary
var primary = body.getAttribute('data-md-color-primary');

// Apply light/dark mode
// Material now uses 'light' and 'dark' instead of 'default' and 'slate'
if (scheme === 'dark') {
html.classList.add('bf-darkmode');
html.classList.remove('bf-lightmode');
} else {
html.classList.add('bf-lightmode');
html.classList.remove('bf-darkmode');
}

// Apply Bifrost theme color
// Remove all existing theme classes
bifrostThemes.forEach(function(theme) {
html.classList.remove('bf-theme-' + theme);
});

// Add the appropriate theme class if valid
if (primary && bifrostThemes.indexOf(primary) !== -1) {
html.classList.add('bf-theme-' + primary);
} else {
// Default to teal if not specified or invalid
html.classList.add('bf-theme-teal');
}
}

// Insert version badge next to site name in the header
function insertVersionBadge() {
if (typeof siteVersion === 'undefined') return;
var topic = document.querySelector('.md-header__topic');
if (!topic || document.querySelector('.bf-header-version')) return;
var badge = document.createElement('span');
badge.className = 'bf-badge bf-badge-pill bfc-theme-fade-bg bf-header-version';
badge.textContent = 'v' + siteVersion;
topic.appendChild(badge);
}

// Apply when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
syncBifrostTheme();
insertVersionBadge();

// Watch for changes to palette attributes on body
var observer = new MutationObserver(syncBifrostTheme);
observer.observe(document.body, {
attributes: true,
attributeFilter: ['data-md-color-scheme', 'data-md-color-primary']
});
});
} else {
syncBifrostTheme();
insertVersionBadge();

// Watch for changes to palette attributes on body
var observer = new MutationObserver(syncBifrostTheme);
observer.observe(document.body, {
attributes: true,
attributeFilter: ['data-md-color-scheme', 'data-md-color-primary']
});
}
})();
</script>
{%- if config.extra.version -%}
<meta name="bifrost-version" content="{{ config.extra.version }}">
{%- endif -%}
{% endblock %}

{% block scripts %}
{{ super() }}
{% endblock %}
3 changes: 2 additions & 1 deletion src/intility_bifrost_mkdocs/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@
}

# ---------------------------------------------------------------------------
# Default extra JavaScript (MathJax).
# Default extra JavaScript.
# ---------------------------------------------------------------------------
DEFAULT_EXTRA_JS: list[str] = [
"javascripts/bifrost-theme.js",
"javascripts/mathjax.js",
"https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js",
]
Expand Down
Loading