diff --git a/.gitignore b/.gitignore index 5d49811..8748532 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,12 @@ dmypy.json # Pyre type checker .pyre/ +# node.js / npm stuff +node_modules +package.json +package-lock.json + +# our own stuff *.swp ram/storage/ !ram/storage/.gitignore diff --git a/ram/portal/static/css/main.min.css b/ram/portal/static/css/main.min.css new file mode 100644 index 0000000..3be05d5 --- /dev/null +++ b/ram/portal/static/css/main.min.css @@ -0,0 +1 @@ +html[data-bs-theme=dark] .navbar svg{fill:#fff}.card>a>img{width:100%}td>img.logo{max-width:200px;max-height:48px}td>img.logo-xl{max-width:400px;max-height:96px}td>p:last-child{margin-bottom:0}.btn>span{display:inline-block}a.badge,a.badge:hover{text-decoration:none;color:#fff}.img-thumbnail{padding:0}.w-33{width:33%!important}.table-group-divider{border-top:calc(var(--bs-border-width) * 3) solid var(--bs-border-color)}#nav-journal ol,#nav-journal ul{padding-left:1rem}#nav-journal ol:last-child,#nav-journal p:last-child,#nav-journal ul:last-child{margin-bottom:0}#footer>p{display:inline} \ No newline at end of file diff --git a/ram/portal/static/css/src/README.md b/ram/portal/static/css/src/README.md new file mode 100644 index 0000000..59610d0 --- /dev/null +++ b/ram/portal/static/css/src/README.md @@ -0,0 +1,7 @@ +# Compile main.min.css + +```bash +$ npm install clean-css-cli +$ npx cleancss -o ../main.min.css main.css +``` + diff --git a/ram/portal/static/css/main.css b/ram/portal/static/css/src/main.css similarity index 100% rename from ram/portal/static/css/main.css rename to ram/portal/static/css/src/main.css diff --git a/ram/portal/static/js/main.min.js b/ram/portal/static/js/main.min.js new file mode 100644 index 0000000..71e2864 --- /dev/null +++ b/ram/portal/static/js/main.min.js @@ -0,0 +1,6 @@ +/*! +* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) +* Copyright 2011-2023 The Bootstrap Authors +* Licensed under the Creative Commons Attribution 3.0 Unported License. +*/ +(()=>{"use strict";const e=()=>localStorage.getItem("theme"),t=()=>{const t=e();return t||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")},a=e=>{"auto"===e&&window.matchMedia("(prefers-color-scheme: dark)").matches?document.documentElement.setAttribute("data-bs-theme","dark"):document.documentElement.setAttribute("data-bs-theme",e)};a(t());const r=(e,t=!1)=>{const a=document.querySelector("#bd-theme");if(!a)return;const r=document.querySelector(".theme-icon-active i"),o=document.querySelector(`[data-bs-theme-value="${e}"]`),s=o.querySelector(".theme-icon i").getAttribute("class");document.querySelectorAll("[data-bs-theme-value]").forEach(e=>{e.classList.remove("active"),e.setAttribute("aria-pressed","false")}),o.classList.add("active"),o.setAttribute("aria-pressed","true"),r.setAttribute("class",s),t&&a.focus()};window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{const r=e();"light"!==r&&"dark"!==r&&a(t())}),window.addEventListener("DOMContentLoaded",()=>{r(t()),document.querySelectorAll("[data-bs-theme-value]").forEach(e=>{e.addEventListener("click",()=>{const t=e.getAttribute("data-bs-theme-value");(e=>{localStorage.setItem("theme",e)})(t),a(t),r(t,!0)})})})})(),document.addEventListener("DOMContentLoaded",function(){const e=window.location.hash.substring(1);if(e){const t=document.querySelector(`[data-bs-target="#nav-${e}"]`);t&&bootstrap.Tab.getOrCreateInstance(t).show()}document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(e=>{e.addEventListener("shown.bs.tab",e=>{const t=e.target.getAttribute("data-bs-target").replace("nav-","");history.replaceState(null,null,t)})});const t=document.getElementById("tabSelector");t&&(t.addEventListener("change",function(){const e=this.value,t=document.querySelector(`[data-bs-target="#${e}"]`);if(t){bootstrap.Tab.getOrCreateInstance(t).show()}}),document.querySelectorAll('[data-bs-toggle="tab"]').forEach(e=>{e.addEventListener("shown.bs.tab",e=>{const a=e.target.getAttribute("data-bs-target");t.value=a.substring(1)})}))}); diff --git a/ram/portal/static/js/src/README.md b/ram/portal/static/js/src/README.md new file mode 100644 index 0000000..bfaeac2 --- /dev/null +++ b/ram/portal/static/js/src/README.md @@ -0,0 +1,7 @@ +# Compile main.min.js + +```bash +$ npm install terser +$ npx terser theme_selector.js tabs_selector.js -c -m -o ../main.min.js +``` + diff --git a/ram/portal/static/js/src/tabs_selector.js b/ram/portal/static/js/src/tabs_selector.js new file mode 100644 index 0000000..3616b2b --- /dev/null +++ b/ram/portal/static/js/src/tabs_selector.js @@ -0,0 +1,39 @@ +document.addEventListener("DOMContentLoaded", function () { + // code to handle tab selection and URL hash synchronization + const hash = window.location.hash.substring(1) // remove the '#' prefix + if (hash) { + const trigger = document.querySelector(`[data-bs-target="#nav-${hash}"]`); + if (trigger) { + bootstrap.Tab.getOrCreateInstance(trigger).show(); + } + } + // + // update the URL hash when a tab is shown + document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(btn => { + btn.addEventListener('shown.bs.tab', event => { + const newHash = event.target.getAttribute('data-bs-target').replace('nav-', ''); + history.replaceState(null, null, newHash); + }); + }); + + // allow tab selection via a dropdown on small screens + const selectElement = document.getElementById('tabSelector'); + if (!selectElement) return; + selectElement.addEventListener('change', function () { + const targetSelector = this.value; + const triggerEl = document.querySelector(`[data-bs-target="#${targetSelector}"]`); + if (triggerEl) { + // Use Bootstrap 5.3's API — ensures transitions + ARIA updates + const tabInstance = bootstrap.Tab.getOrCreateInstance(triggerEl); + tabInstance.show(); + } + }); + + // keep the dropdown in sync if the user clicks a tab button + document.querySelectorAll('[data-bs-toggle="tab"]').forEach(btn => { + btn.addEventListener('shown.bs.tab', event => { + const target = event.target.getAttribute('data-bs-target'); + selectElement.value = target.substring(1); // remove the '#' character + }); + }); +}); diff --git a/ram/portal/static/js/src/theme_selector.js b/ram/portal/static/js/src/theme_selector.js new file mode 100644 index 0000000..78596a5 --- /dev/null +++ b/ram/portal/static/js/src/theme_selector.js @@ -0,0 +1,76 @@ +/*! +* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) +* Copyright 2011-2023 The Bootstrap Authors +* Licensed under the Creative Commons Attribution 3.0 Unported License. +*/ + +(() => { + 'use strict' + + const getStoredTheme = () => localStorage.getItem('theme') + const setStoredTheme = theme => localStorage.setItem('theme', theme) + + const getPreferredTheme = () => { + const storedTheme = getStoredTheme() + if (storedTheme) { + return storedTheme + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + } + + const setTheme = theme => { + if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) { + document.documentElement.setAttribute('data-bs-theme', 'dark') + } else { + document.documentElement.setAttribute('data-bs-theme', theme) + } + } + + setTheme(getPreferredTheme()) + + const showActiveTheme = (theme, focus = false) => { + const themeSwitcher = document.querySelector('#bd-theme') + + if (!themeSwitcher) { + return + } + + const activeThemeIcon = document.querySelector('.theme-icon-active i') + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) + const biOfActiveBtn = btnToActive.querySelector('.theme-icon i').getAttribute('class') + + document.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active') + element.setAttribute('aria-pressed', 'false') + }) + + btnToActive.classList.add('active') + btnToActive.setAttribute('aria-pressed', 'true') + activeThemeIcon.setAttribute('class', biOfActiveBtn) + + if (focus) { + themeSwitcher.focus() + } + } + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const storedTheme = getStoredTheme() + if (storedTheme !== 'light' && storedTheme !== 'dark') { + setTheme(getPreferredTheme()) + } + }) + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()) + document.querySelectorAll('[data-bs-theme-value]') + .forEach(toggle => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value') + setStoredTheme(theme) + setTheme(theme) + showActiveTheme(theme, true) + }) + }) + }) + })() diff --git a/ram/portal/templates/base.html b/ram/portal/templates/base.html index 44d6d1a..1b88cf2 100644 --- a/ram/portal/templates/base.html +++ b/ram/portal/templates/base.html @@ -22,114 +22,8 @@ {% endif %} - - - - + + {% block extra_head %} {{ site_conf.extra_head | safe }} {% endblock %} diff --git a/ram/portal/templates/magazine.html b/ram/portal/templates/bookshelf/magazine.html similarity index 100% rename from ram/portal/templates/magazine.html rename to ram/portal/templates/bookshelf/magazine.html diff --git a/ram/portal/views.py b/ram/portal/views.py index ef1c5f8..c015165 100644 --- a/ram/portal/views.py +++ b/ram/portal/views.py @@ -773,7 +773,7 @@ class GetMagazine(View): return render( request, - "magazine.html", + "bookshelf/magazine.html", { "title": magazine, "magazine": magazine, diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py index f83e720..53f5fd8 100644 --- a/ram/ram/__init__.py +++ b/ram/ram/__init__.py @@ -1,4 +1,4 @@ from ram.utils import git_suffix -__version__ = "0.19.8" +__version__ = "0.19.9" __version__ += git_suffix(__file__)