diff --git a/docs/ci/arcgis/instructions.html b/docs/ci/arcgis/instructions.html new file mode 100644 index 0000000..d301441 --- /dev/null +++ b/docs/ci/arcgis/instructions.html @@ -0,0 +1,745 @@ + + + + + + + + + +Eli's JupyterHub notes – instructions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + +
+ + + +
+ + + + +
+

Testing

+

This was testing. Doesn’t work. Discovered that

+
    +
  • jupyter-server-proxy is needed
  • +
  • works in docker but not in jupyterhub I think due to the server app being different. I tried to change to old server but no luck.
  • +
  • doesn’t work with the juptyer/base-notebook:python-3.9. Everything I tried failed.
  • +
+
+
+

tldr;

+
cd ci/arcgis
+DOCKER_TAG="20240129b"
+docker build --platform linux/amd64 -t eeholmes/py-rocket-gis:${DOCKER_TAG} .
+docker push eeholmes/py-rocket-gis:${DOCKER_TAG}
+

Testing

+
DOCKER_TAG="$(git rev-parse --short HEAD)"
+docker build --platform linux/amd64 -t eeholmes/py-rocket-gis:${DOCKER_TAG} .
+docker push eeholmes/py-rocket-gis:${DOCKER_TAG}
+ + +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/docs/ci/iopython-tf/instructions.html b/docs/ci/iopython-tf/instructions.html index 9990997..b82e587 100644 --- a/docs/ci/iopython-tf/instructions.html +++ b/docs/ci/iopython-tf/instructions.html @@ -2,7 +2,7 @@ - + @@ -47,7 +47,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -57,7 +63,8 @@ "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } @@ -74,10 +81,10 @@ - - - @@ -110,31 +117,31 @@ @@ -163,13 +170,14 @@

On this page

  • Adding packages with newpackages.yml
  • -
    +
    +

    Indian Ocean Summer Docker Images

    https://hub.docker.com/repository/docker/eeholmes/iopython-tf/general

    @@ -324,6 +332,33 @@

    Addin } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -390,13 +425,15 @@

    Addin return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -475,10 +512,9 @@

    Addin // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -488,8 +524,17 @@

    Addin interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -503,6 +548,128 @@

    Addin const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -545,6 +712,7 @@

    Addin } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -570,6 +738,32 @@

    Addin }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -635,7 +829,9 @@

    Addin - + @@ -644,4 +840,5 @@

    Addin + \ No newline at end of file diff --git a/docs/ci/iopython/instructions.html b/docs/ci/iopython/instructions.html index 71397ae..46a27d0 100644 --- a/docs/ci/iopython/instructions.html +++ b/docs/ci/iopython/instructions.html @@ -2,7 +2,7 @@ - + @@ -47,7 +47,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -57,7 +63,8 @@ "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } @@ -74,10 +81,10 @@ - - - @@ -110,31 +117,31 @@ @@ -163,13 +170,14 @@

    On this page

  • Adding packages with newpackages.yml
  • - +
    +

    Indian Ocean Summer Docker Images: Openscapes + a few extras

    https://hub.docker.com/repository/docker/eeholmes/iopython/general

    @@ -326,6 +334,33 @@

    Addin } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -392,13 +427,15 @@

    Addin return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -477,10 +514,9 @@

    Addin // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -490,8 +526,17 @@

    Addin interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -505,6 +550,128 @@

    Addin const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -547,6 +714,7 @@

    Addin } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -572,6 +740,32 @@

    Addin }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -637,7 +831,9 @@

    Addin - + @@ -646,4 +842,5 @@

    Addin + \ No newline at end of file diff --git a/docs/ci/iorocker/instructions.html b/docs/ci/iorocker/instructions.html index 79bb4c3..53bdc18 100644 --- a/docs/ci/iorocker/instructions.html +++ b/docs/ci/iorocker/instructions.html @@ -2,7 +2,7 @@ - + @@ -47,7 +47,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -57,7 +63,8 @@ "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } @@ -74,10 +81,10 @@ - - - @@ -110,31 +117,31 @@ @@ -163,13 +170,14 @@

    On this page

  • Adding packages with newpackages.yml
  • - +
    +

    Indian Ocean Summer Docker Images

    https://hub.docker.com/repository/docker/eeholmes/iorocker/general

    @@ -324,6 +332,33 @@

    Addin } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -390,13 +425,15 @@

    Addin return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -475,10 +512,9 @@

    Addin // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -488,8 +524,17 @@

    Addin interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -503,6 +548,128 @@

    Addin const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -545,6 +712,7 @@

    Addin } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -570,6 +738,32 @@

    Addin }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -635,7 +829,9 @@

    Addin - + @@ -644,4 +840,5 @@

    Addin + \ No newline at end of file diff --git a/docs/ci/iosdmTMB/instructions.html b/docs/ci/iosdmTMB/instructions.html index d4e9b90..a48587d 100644 --- a/docs/ci/iosdmTMB/instructions.html +++ b/docs/ci/iosdmTMB/instructions.html @@ -2,7 +2,7 @@ - + @@ -47,7 +47,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -57,7 +63,8 @@ "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } @@ -74,10 +81,10 @@ - - - @@ -110,31 +117,31 @@ @@ -163,13 +170,14 @@

    On this page

  • Adding packages with newpackages.yml
  • - +
    +

    Indian Ocean Summer Docker Images

    https://hub.docker.com/repository/docker/eeholmes/iosdmTMB/general

    @@ -325,6 +333,33 @@

    Addin } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -391,13 +426,15 @@

    Addin return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -476,10 +513,9 @@

    Addin // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -489,8 +525,17 @@

    Addin interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -504,6 +549,128 @@

    Addin const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -546,6 +713,7 @@

    Addin } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -571,6 +739,32 @@

    Addin }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -636,7 +830,9 @@

    Addin - + @@ -645,4 +841,5 @@

    Addin + \ No newline at end of file diff --git a/docs/ci/py-rocker-base/instructions.html b/docs/ci/py-rocker-base/instructions.html new file mode 100644 index 0000000..f1c938b --- /dev/null +++ b/docs/ci/py-rocker-base/instructions.html @@ -0,0 +1,747 @@ + + + + + + + + + +Eli's JupyterHub notes – instructions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + + + + +
    +

    py-rocket-base

    +

    This is based on the openscapes/py-rocket but simplified.

    +

    https://hub.docker.com/repository/docker/eeholmes/py-rocket-base/general

    +

    The one to use is the dated one. The main tag doesn’t seem to always be recognized as a new tag when it changes.

    +
    +
    +

    tldr;

    +
    cd ci/py-rocket-base
    +DOCKER_TAG="20230901"
    +docker build --platform linux/amd64 -t eeholmes/py-rocket-base:${DOCKER_TAG} -t eeholmes/py-rocket-base:main .
    +docker push eeholmes/py-rocket-base:${DOCKER_TAG}
    +docker push eeholmes/py-rocket-base:main
    +

    Testing

    +
    DOCKER_TAG="$(git rev-parse --short HEAD)"
    +docker build --platform linux/amd64 -t eeholmes/py-rocket-base:${DOCKER_TAG} .
    +docker push eeholmes/py-rocket-base:${DOCKER_TAG}
    +

    Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and edit dconfig2.yaml (nano dconfig2.yaml) to update the image tag. Then run this command.

    +
    helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
    +

    Tip: if things fill up use

    +
    docker system prune --all
    + + +
    + +
    + +
    +
    + +
    + + + + + \ No newline at end of file diff --git a/docs/ci/py-rocket/instructions.html b/docs/ci/py-rocket/instructions.html new file mode 100644 index 0000000..a1a91e6 --- /dev/null +++ b/docs/ci/py-rocket/instructions.html @@ -0,0 +1,852 @@ + + + + + + + + + +Eli's JupyterHub notes – instructions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + + + + +
    +

    This is based on the openscapes/py-rocket

    +

    https://hub.docker.com/repository/docker/eeholmes/py-rocket/general

    +

    The one to use is the dated one. The main tag doesn’t seem to always be recognized as a new tag when it changes.

    +
    +
    +

    tldr;

    +
    cd ci/py-rocket
    +DOCKER_TAG="20230901"
    +docker build --platform linux/amd64 -t eeholmes/py-rocket:${DOCKER_TAG} -t eeholmes/py-rocket:main .
    +docker push eeholmes/py-rocket:${DOCKER_TAG}
    +docker push eeholmes/py-rocket:main
    +

    Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and edit dconfig2.yaml (nano dconfig2.yaml) to update the py-rocket tag. Then run this command.

    +
    helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
    +

    Tip: if things fill up use

    +
    docker system prune --all
    +
    +
    +

    Requirements

    +

    Docker installed. For example, if doing on a Mac or PC, you need Docker Desktop. On VMs, docker will already be installed.

    +

    A DockerHub user account. The instructions are using EEH’s.

    +
    +
    +

    Add new packages

    +

    Add to Dockerfile something like

    +
    RUN R -e 'install.packages("gtools", repos = "http://cran.us.r-project.org")'
    +
    +
    +

    Rebuild and push the Docker image

    +
      +
    1. Make sure Docker app is running, not just installed. So if you are on a local computer, start up the app (open it).
    2. +
    3. Go to a terminal and cd to the directory with the Dockerfile. So to the ci directory in the nmfs-jhub repo.
    4. +
    +
    cd ci/py-rocket
    +
      +
    1. Update the docker tag to the date.
    2. +
    +
    DOCKER_TAG="20230901"
    +
      +
    1. Build the image. . means current directory. eeholmes/py-rocket is the name of the repo on DockerHub. --platform is added if you are building on an Mac with Apple chip.
    2. +
    +
    docker build --platform linux/amd64 -t eeholmes/py-rocket:${DOCKER_TAG} -t eeholmes/py-rocket:main .
    +
      +
    1. Push the image up to DockerHub. Make sure you are logged into DockerHub in the Docker app otherwise you’ll get “access denied”. Open the Docker app and look that it shows that you are signed in.
    2. +
    +
    docker push eeholmes/py-rocket:${DOCKER_TAG}
    +docker push eeholmes/py-rocket:main
    +

    Notes: https://help.valohai.com/hc/en-us/articles/4421364087569-Build-your-own-Docker-image

    +
    +
    +

    Stop any running the Jupyter Lab instances

    +

    Log in. File > Hub Control > Stop my server

    +
    +
    +

    Run helm upgrade

    +

    Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and run this command. Note, this assumes that eeholmes/iorocker:main is still the image to use. If not, edit dconfig2.yaml (nano dconfig2.yaml) and then upgrade.

    +
    helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
    +
    +

    If an specific image tag is in config

    +

    The JupyterHub has a config file that specifies what images are being used. If the image is say eeholmes/iopython:hublatest, then whenever the a image with tag hublatest is pushed, the hub will use that. If on the otherhand, you config file has a specific, an unique tag that you don’t overwrite, then you’ll have to update the file in the config file on the cluster (log into Azure, go to cluster, connect to cloud shell, nano dconfig2.yaml) and upgrade the installation of the JupyterHub.

    +

    Why not eeholmes/iopython:latest? There is nothing special about latest. It is the default tag used if you don’t specify -t and : in your build call. So it is a bit too easy to accidentally update “latest” and thus update the image for you hub when you didn’t intend to do that. You just forgot to specify a tag.

    +

    To update if you are using a specific tag, like 20230615 rather than one you keep updating like hublatest or latest:

    +
    +

    Step 1

    +

    Edit the config file. Mine is called dconfig2.yaml. Yours is probably config.yaml. Name is unimportant.

    +
    nano dconfig2.yaml
    +

    Inside dconfig2.yaml is this info. This shows a fixed tag. So if I update, I need to change the 20230615 part.

    +
      singleuser:
    +    image:
    +      name: eeholmes/iopython
    +      tag: 20230615
    +

    Save the changes. In nano, it cmd-O, return, cmd-X.

    +
    +
    +

    run helm upgrade

    +
    helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
    +
    +
    +

    The helm upgrade command

    +
    helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
    +

    A helm is what runs the commands to upgrade (and install in the beginning) our JupyterHub. dask/daskhub is point to the repo with the “helm chart” (the instructions). --value dconfig2.yaml is telling it where the config file is.

    +
      +
    • upgrade upgrade an existing installation with the values in dconfig2.yaml
    • +
    • --render-subchart-notes the dask/daskhub helm chart has subcharts (jupyterhub) and you need to render these too. Not all helm charts have this.
    • +
    • dask/daskhub the name of the repo that has the helm chart. The first time you reference this, you need to tell help about the repo by giving it the url. Read how here
    • +
    • --version=2023.1.0 version of the helm chart. Update when the helm chart (instructions for installing the jupyterhub) changes.
    • +
    +
    +
    +
    +

    Adding packages with newpackages.yml

    +

    When the openscapes image is used, we are in a conda env called ‘notebook’. We want to update that with the packages in newpackages.yml but need to get that file into the container. For now, I just hard code in the pip install commands.

    +

    Add to Docker file

    +
    # it can't find new.yml in home/joyvan/.kernels
    +# need to get that into the container somehow (git clone?)
    +# RUN conda env update --file new.yml
    +
    +# Asides
    +
    +## To set docker tag to latest commit
    +
    +

    SHA7=“\((git rev-parse --short HEAD)" DOCKER_TAG=\)SHA7

    +
    I am not doing that since this repo has lots of commits unrelated to the docker image.
    +
    +## To set up your own Docker repo
    +
    +1. Make an account on DockerHub. Free is fine.
    +2. Create a repo and give it a name. For example, for this project, my account is `eeholmes` and my repo is `iopython` (Indian Ocean Python) as it is specific a particular project I am working on.
    +
    +DockerHub will want to you to buy the premium account but you only need that if you are doing continuous integration, like using a GitHub Action to autobuild your image. If you are manually building, you don't need this.
    +
    +## Why is `--platform` needed in the build command
    +
    +You won't see this on docker build tutorials. But if you are on a Mac with Apple chip, then you'll build arm64 images and that's not going to work on Ubuntu VMs. The vanilla images you see are amd64 so we want to make sure we are building for that platform. This only matters if you are on a Mac with Apple chip, but it won't break things for unix and PC so I added to make the instructions more robust.
    + + +
    +
    + +
    + +
    +
    + +
    + + + + + \ No newline at end of file diff --git a/docs/images/img1.png b/docs/images/img1.png deleted file mode 100644 index de091de..0000000 Binary files a/docs/images/img1.png and /dev/null differ diff --git a/docs/images/img10.png b/docs/images/img10.png deleted file mode 100644 index aae225e..0000000 Binary files a/docs/images/img10.png and /dev/null differ diff --git a/docs/images/img11.png b/docs/images/img11.png deleted file mode 100644 index cdebc05..0000000 Binary files a/docs/images/img11.png and /dev/null differ diff --git a/docs/images/img12.png b/docs/images/img12.png deleted file mode 100644 index def2dc7..0000000 Binary files a/docs/images/img12.png and /dev/null differ diff --git a/docs/images/img13.png b/docs/images/img13.png deleted file mode 100644 index ffed094..0000000 Binary files a/docs/images/img13.png and /dev/null differ diff --git a/docs/images/img2.png b/docs/images/img2.png deleted file mode 100644 index 6cd374a..0000000 Binary files a/docs/images/img2.png and /dev/null differ diff --git a/docs/images/img3.png b/docs/images/img3.png deleted file mode 100644 index db6571a..0000000 Binary files a/docs/images/img3.png and /dev/null differ diff --git a/docs/images/img4.png b/docs/images/img4.png deleted file mode 100644 index fff8d56..0000000 Binary files a/docs/images/img4.png and /dev/null differ diff --git a/docs/images/img5.png b/docs/images/img5.png deleted file mode 100644 index b4f8e86..0000000 Binary files a/docs/images/img5.png and /dev/null differ diff --git a/docs/images/img6.png b/docs/images/img6.png deleted file mode 100644 index c13e145..0000000 Binary files a/docs/images/img6.png and /dev/null differ diff --git a/docs/images/img7.png b/docs/images/img7.png deleted file mode 100644 index 69ec86b..0000000 Binary files a/docs/images/img7.png and /dev/null differ diff --git a/docs/images/img8.png b/docs/images/img8.png deleted file mode 100644 index b3a1ce8..0000000 Binary files a/docs/images/img8.png and /dev/null differ diff --git a/docs/images/img9.png b/docs/images/img9.png deleted file mode 100644 index cef65fc..0000000 Binary files a/docs/images/img9.png and /dev/null differ diff --git a/docs/images/user-guide/img1.png b/docs/images/user-guide/img1.png deleted file mode 100644 index 19f3137..0000000 Binary files a/docs/images/user-guide/img1.png and /dev/null differ diff --git a/docs/images/user-guide/img1b.png b/docs/images/user-guide/img1b.png deleted file mode 100644 index 3f3e982..0000000 Binary files a/docs/images/user-guide/img1b.png and /dev/null differ diff --git a/docs/images/user-guide/img2.png b/docs/images/user-guide/img2.png deleted file mode 100644 index 16f315f..0000000 Binary files a/docs/images/user-guide/img2.png and /dev/null differ diff --git a/docs/images/user-guide/img3.png b/docs/images/user-guide/img3.png deleted file mode 100644 index 13027c5..0000000 Binary files a/docs/images/user-guide/img3.png and /dev/null differ diff --git a/docs/images/user-guide/img4.png b/docs/images/user-guide/img4.png deleted file mode 100644 index 517f8f9..0000000 Binary files a/docs/images/user-guide/img4.png and /dev/null differ diff --git a/docs/images/user-guide/img5.png b/docs/images/user-guide/img5.png deleted file mode 100644 index 16777b3..0000000 Binary files a/docs/images/user-guide/img5.png and /dev/null differ diff --git a/docs/images/user-guide/img6.png b/docs/images/user-guide/img6.png deleted file mode 100644 index bfc9314..0000000 Binary files a/docs/images/user-guide/img6.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html index a638fde..ba1c856 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,7 +2,7 @@ - + @@ -29,7 +29,7 @@ - + @@ -48,7 +48,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -58,7 +64,8 @@ "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } @@ -75,10 +82,10 @@ - - - @@ -111,31 +118,31 @@ @@ -152,7 +159,7 @@

    On this page

  • Test JHub
  • Installation instructions
  • - +
    @@ -172,8 +179,10 @@

    NMFS OpenSci JupyterHub Notes

    + +

    This page shows my notes for setting up JupyterHubs on various platforms. What is a JupyterHub? Read about why cloud computing environments are great: SnowEx 2022

    Test JHub

    @@ -232,6 +241,33 @@

    Installation ins } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -298,13 +334,15 @@

    Installation ins return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -383,10 +421,9 @@

    Installation ins // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -396,8 +433,17 @@

    Installation ins interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -411,6 +457,128 @@

    Installation ins const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -453,6 +621,7 @@

    Installation ins } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -478,6 +647,32 @@

    Installation ins }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -541,7 +736,7 @@

    Installation ins @@ -552,7 +747,9 @@

    Installation ins - + @@ -561,4 +758,5 @@

    Installation ins + \ No newline at end of file diff --git a/docs/JHub-User-Guide.html b/docs/posts/JHub-User-Guide.html similarity index 69% rename from docs/JHub-User-Guide.html rename to docs/posts/JHub-User-Guide.html index c1082a0..2ffe813 100644 --- a/docs/JHub-User-Guide.html +++ b/docs/posts/JHub-User-Guide.html @@ -2,7 +2,7 @@ - + @@ -24,33 +24,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -77,10 +84,10 @@ - - - @@ -91,8 +98,8 @@ +
    @@ -183,8 +190,10 @@

    JHub User Instructions

    + +

    I have set us up a JupyterHub/RStudio cloud-computing hub on Azure. It’s on Kubernetes and will spin up VMs as needed. The VMs are not huge: 2CPU & 8 Gig RAM.

    https://jhub.opensci.live/hub/login

    Login authentication is via GitHub. Only members of the JHub GitHub team can log on. This is a testing environment. Contact Eli if you want to test it out.

    @@ -310,6 +319,33 @@

    Stop your server

    } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -376,13 +412,15 @@

    Stop your server

    return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -461,10 +499,9 @@

    Stop your server

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -474,8 +511,17 @@

    Stop your server

    interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -489,6 +535,128 @@

    Stop your server

    const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -531,6 +699,7 @@

    Stop your server

    } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -556,6 +725,32 @@

    Stop your server

    }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -617,12 +812,12 @@

    Stop your server

    @@ -215,8 +222,10 @@

    Set-up CentOS https

    + +

    Now that our basic JupyterHub is running, we want to secure it. We are going to use Let’s Encrypt. Prerequisites:

    • We need to have set up a domain (URL) that points to the public IP of our JupyterHub
    • @@ -239,7 +248,7 @@

      Create a DNS entry

    Test if the url is working

    -

    http:\\dhub.bluemountain123.live:8000 would be the url using the example domain above. Test that it is working (shows a JupyterHub login) before moving on. This is what you should see:

    +

    http://dhub.bluemountain123.live:8000 would be the url using the example domain above. Test that it is working (shows a JupyterHub login) before moving on. This is what you should see:

    @@ -333,7 +342,7 @@

    Restart and Test

    sudo systemctl start jupyterhub.service
    -

    Try https:\\dhub.bluemountain123.live and you should see the JupyterHub login without the http warning.

    +

    Try https://dhub.bluemountain123.live and you should see the JupyterHub login without the http warning.

    @@ -383,6 +392,33 @@

    Restart and Test

    } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -449,13 +485,15 @@

    Restart and Test

    return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -534,10 +572,9 @@

    Restart and Test

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -547,8 +584,17 @@

    Restart and Test

    interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -562,6 +608,128 @@

    Restart and Test

    const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -604,6 +772,7 @@

    Restart and Test

    } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -629,6 +798,32 @@

    Restart and Test

    }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -690,12 +885,12 @@

    Restart and Test

    @@ -223,8 +230,10 @@

    Centos Set-up with TLJH

    + +

    This is my notes for setting this up on a Centos 8 (Linux distribution) server. Jump to the “Summary” section to see only the instructions without explanations.

    All the commands are run in a shell (bash)

    References:

    @@ -871,6 +880,33 @@

    Summary

    } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -937,13 +973,15 @@

    Summary

    return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -1022,10 +1060,9 @@

    Summary

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -1035,8 +1072,17 @@

    Summary

    interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -1050,6 +1096,128 @@

    Summary

    const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -1092,6 +1260,7 @@

    Summary

    } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -1117,6 +1286,32 @@

    Summary

    }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1182,7 +1377,9 @@

    Summary

    - + @@ -1191,4 +1388,5 @@

    Summary

    + \ No newline at end of file diff --git a/docs/Set-up-centos.html b/docs/posts/Set-up-centos.html similarity index 84% rename from docs/Set-up-centos.html rename to docs/posts/Set-up-centos.html index a7a57b8..e6ae1a5 100644 --- a/docs/Set-up-centos.html +++ b/docs/posts/Set-up-centos.html @@ -2,7 +2,7 @@ - + @@ -23,7 +23,7 @@ } /* CSS for syntax highlighting */ pre > code.sourceCode { white-space: pre; position: relative; } -pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } +pre > code.sourceCode > span { line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } .sourceCode { overflow: visible; } code.sourceCode > span { color: inherit; text-decoration: inherit; } @@ -58,33 +58,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -111,10 +118,10 @@ - - - @@ -125,8 +132,8 @@ +
    @@ -225,8 +232,10 @@

    Centos Set-up

    + +

    This is my notes for setting this up on a Centos 8 (Linux distribution) server. Jump to the “Summary” section to see only the instructions without explanations.

    All the commands are run in a shell (bash)

    References:

    @@ -648,7 +657,7 @@

    Creating a shared

    One read-only shared volume:

    https://github.com/jupyterhub/dockerspawner/issues/172

    -
    c.DockerSpawner.volumes = {  'jupyterhub-user-{username}':'/home/jovyan',   '/path/to/shared': {"bind": '/home/jovyan/shared', "mode": "ro"} }
    +
    c.DockerSpawner.volumes = {  'jupyterhub-{username}':'/home/jovyan',   '/path/to/shared': {"bind": '/home/jovyan/shared', "mode": "ro"} }

    A volume that is read-only for some and read-write for others:

    https://github.com/jupyterhub/dockerspawner/issues/172

    @@ -659,7 +668,7 @@

    Creating a shared

    Setting up https

    If you are using a public IP address, rather than being on a private network, you need to set up https so that content (passwords and everything else) is not visible. Read how to do that here.

    -

    These instructions set up this url: https:\\dhub.bluemountain123.live

    +

    These instructions set up this url: https://dhub.bluemountain123.live

    GitHub authentication

    @@ -679,15 +688,43 @@

    C

    Create a team in your GitHub organization

    You will be added by default and add anyone else who needs access to the hub. Let’s say your GitHub organization is MyOrg and the team is called JHub. So then the allowed organization is MyOrg:JHub. You can leave off :JHub if you want to allow all members of the organization to log in.

    +
    +

    Install

    +

    Install the oauthenticator package. Make sure you are in the jupyterhub conda environment.

    +
    +
    # check what environment you are in and switch if needed
    +# conda env list
    +# conda activate jupyterhub
    +conda install -c conda-forge oauthenticator
    +
    +

    Edit the jupyterhub_config.py file

    +

    Edit with something like

    +
    +
    cd /opt/miniconda3/envs/jupyterhub/etc/jupyterhub/
    +nano jupyterhub_config.py
    +
    +

    Add these info. Replace the id, secret, url and admin user with your values. Adding an admin user is handy because then you can do some basic management of the hub. Read more here.

    c.JupyterHub.authenticator_class = "github"
    -c.OAuthenticator.oauth_callback_url = "https:\\dhub.bluemountain123.live/hub/oauth_callback"
    +c.OAuthenticator.oauth_callback_url = "https://dhub.bluemountain123.live/hub/oauth_callback"
     c.OAuthenticator.client_id = "your oauth2 application id"
     c.OAuthenticator.client_secret = "your oauth2 application secret"
    -c.GitHubOAuthenticator.allowed_organizations = "MyOrg:JHub"
    -c.GitHubOAuthenticator.scope = "read:org"
    -

    Replace the id, secret and url with your values.

    +c.GitHubOAuthenticator.allowed_organizations = {"MyOrg:JHub"} +c.GitHubOAuthenticator.scope = ["read:org"] +c.GitHubOAuthenticator.admin_users = {"eeholmes"} +
    +
    +

    Restart the hub

    +
    +
    sudo systemctl stop jupyterhub.service
    +sudo systemctl start jupyterhub.service
    +
    +

    Now any member you add to the GitHub organization team should be able to log in.

    +

    If you run into trouble, try

    +
    +
    sudo systemctl status jupyterhub.service
    +

    @@ -695,120 +732,120 @@

    Summary

    Only the instructions. Make sure you are installing as the root user. I assume you have Python and conda installed.

    Create the conda environment

    -
    sudo -i
    -
    -conda create -n jupyterhub python --yes
    -conda activate jupyterhub
    -conda install -c conda-forge jupyterhub --yes
    -conda install -c conda-forge jupyterlab notebook --yes
    -
    -JHUBENV=/opt/miniconda3/envs/jupyterhub
    -chmod 755 $JHUBENV
    +
    sudo -i
    +
    +conda create -n jupyterhub python --yes
    +conda activate jupyterhub
    +conda install -c conda-forge jupyterhub --yes
    +conda install -c conda-forge jupyterlab notebook --yes
    +
    +JHUBENV=/opt/miniconda3/envs/jupyterhub
    +chmod 755 $JHUBENV

    Create user

    -
    useradd jhub
    +
    useradd jhub

    Open the 8000 port for access to the application.

    -
    #sudo systemctl enable firewalld
    -#sudo systemctl start firewalld
    -
    -sudo firewall-cmd  --permanent --add-port 8000/tcp
    -sudo firewall-cmd --reload
    -sudo firewall-cmd --list-ports
    +
    #sudo systemctl enable firewalld
    +#sudo systemctl start firewalld
    +
    +sudo firewall-cmd  --permanent --add-port 8000/tcp
    +sudo firewall-cmd --reload
    +sudo firewall-cmd --list-ports

    Create the configuration file. Will be edited at end.

    -
    sudo mkdir -p $JHUBENV/etc/jupyterhub/
    -cd $JHUBENV/etc/jupyterhub/
    -sudo $JHUBENV/bin/jupyterhub --generate-config
    +
    sudo mkdir -p $JHUBENV/etc/jupyterhub/
    +cd $JHUBENV/etc/jupyterhub/
    +sudo $JHUBENV/bin/jupyterhub --generate-config

    Install docker if needed

    -
    sudo yum install -y yum-utils
    -sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    -
    -sudo systemctl start docker
    +
    sudo yum install -y yum-utils
    +sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    +
    +sudo systemctl start docker

    Not sure this is needed.

    -
    sudo firewall-cmd --zone=docker --add-port=8081/tcp
    -sudo firewall-cmd --reload
    -sudo systemctl restart docker
    +
    sudo firewall-cmd --zone=docker --add-port=8081/tcp
    +sudo firewall-cmd --reload
    +sudo systemctl restart docker

    Install dockerspawner

    -
    conda install -c conda-forge dockerspawner --yes
    -conda install -c conda-forge docker-py --yes
    +
    conda install -c conda-forge dockerspawner --yes
    +conda install -c conda-forge docker-py --yes

    Edit the configuration file.

    -
    cd $JHUBENV/etc/jupyterhub/
    -nano jupyterhub_config.py
    +
    cd $JHUBENV/etc/jupyterhub/
    +nano jupyterhub_config.py

    Paste this in

    -
    # Configuration file for jupyterhub.
    -
    -c = get_config()  #noqa
    -c.JupyterHub.port = 8000
    -c.JupyterHub.hub_bind_url = "http://0.0.0.0:8081"
    -c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
    -c.DockerSpawner.remove = True
    -c.Spawner.http_timeout = 3600
    -c.DockerSpawner.image_whitelist = {
    -    'iorocker': 'eeholmes/iorocker-standalone:20231003',
    -    'rocker-binder': 'eeholmes/rocker-binder:20231003',
    -    'openscapes-rocker': 'eeholmes/minimal-jhub:20231004',
    -    'datascience-r': 'jupyter/datascience-notebook:r-4.3.1',
    -    'scipy-notebook': 'jupyter/scipy-notebook:7e1a19a8427f',
    -}
    -
    -notebook_dir = '/home/jovyan'
    -c.DockerSpawner.notebook_dir = notebook_dir
    -
    -# Mount the real user's Docker volume on the host to the notebook user's
    -# notebook directory in the container
    -c.DockerSpawner.volumes = { 'jupyter-{username}': notebook_dir }
    +
    # Configuration file for jupyterhub.
    +
    +c = get_config()  #noqa
    +c.JupyterHub.port = 8000
    +c.JupyterHub.hub_bind_url = "http://0.0.0.0:8081"
    +c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
    +c.DockerSpawner.remove = True
    +c.Spawner.http_timeout = 3600
    +c.DockerSpawner.image_whitelist = {
    +    'iorocker': 'eeholmes/iorocker-standalone:20231003',
    +    'rocker-binder': 'eeholmes/rocker-binder:20231003',
    +    'openscapes-rocker': 'eeholmes/minimal-jhub:20231004',
    +    'datascience-r': 'jupyter/datascience-notebook:r-4.3.1',
    +    'scipy-notebook': 'jupyter/scipy-notebook:7e1a19a8427f',
    +}
    +
    +notebook_dir = '/home/jovyan'
    +c.DockerSpawner.notebook_dir = notebook_dir
    +
    +# Mount the real user's Docker volume on the host to the notebook user's
    +# notebook directory in the container
    +c.DockerSpawner.volumes = { 'jupyter-{username}': notebook_dir }

    Docker pull of the images. Do all.

    -
    docker pull jupyter/datascience-notebook:r-4.3.1
    -docker pull jupyter/scipy-notebook:7e1a19a8427f
    +
    docker pull jupyter/datascience-notebook:r-4.3.1
    +docker pull jupyter/scipy-notebook:7e1a19a8427f

    Make a new server service

    -
    sudo mkdir -p $JHUBENV/etc/systemd
    -cd $JHUBENV/etc/systemd
    -nano jupyterhub.service
    +
    sudo mkdir -p $JHUBENV/etc/systemd
    +cd $JHUBENV/etc/systemd
    +nano jupyterhub.service

    Paste this in

    -
    [Unit]
    -Description=JupyterHub
    -After=syslog.target network.target
    -
    -[Service]
    -User=root
    -Environment="PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/opt/miniconda3/envs/jupyterhub/bin"
    -ExecStart=/opt/miniconda3/envs/jupyterhub/bin/jupyterhub -f /opt/miniconda3/envs/jupyterhub/etc/jupyterhub/jupyterhub_config.py
    -
    -[Install]
    -WantedBy=multi-user.target
    +
    [Unit]
    +Description=JupyterHub
    +After=syslog.target network.target
    +
    +[Service]
    +User=root
    +Environment="PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/opt/miniconda3/envs/jupyterhub/bin"
    +ExecStart=/opt/miniconda3/envs/jupyterhub/bin/jupyterhub -f /opt/miniconda3/envs/jupyterhub/etc/jupyterhub/jupyterhub_config.py
    +
    +[Install]
    +WantedBy=multi-user.target

    Make sure SELinux doesn’t block our service

    -
    ls -Z $JHUBENV/etc/systemd/
    -sudo chcon system_u:object_r:systemd_unit_file_t:s0 $JHUBENV/etc/systemd/jupyterhub.service
    -sudo find $JHUBENV/bin -type f -exec chcon system_u:object_r:bin_t:s0 {} \;
    +
    ls -Z $JHUBENV/etc/systemd/
    +sudo chcon system_u:object_r:systemd_unit_file_t:s0 $JHUBENV/etc/systemd/jupyterhub.service
    +sudo find $JHUBENV/bin -type f -exec chcon system_u:object_r:bin_t:s0 {} \;

    Enable our new service

    -
    sudo ln -s $JHUBENV/etc/systemd/jupyterhub.service /etc/systemd/system/jupyterhub.service
    -sudo systemctl daemon-reload
    -sudo systemctl enable jupyterhub.service
    -sudo systemctl start jupyterhub.service
    +
    sudo ln -s $JHUBENV/etc/systemd/jupyterhub.service /etc/systemd/system/jupyterhub.service
    +sudo systemctl daemon-reload
    +sudo systemctl enable jupyterhub.service
    +sudo systemctl start jupyterhub.service

    Done! See the long instructions if anything is not working.

    Now go through the https and GitHub authentication steps if you need that.

    @@ -860,6 +897,33 @@

    Summary

    } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -926,13 +990,15 @@

    Summary

    return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -1011,10 +1077,9 @@

    Summary

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -1024,8 +1089,17 @@

    Summary

    interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -1039,6 +1113,128 @@

    Summary

    const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -1081,6 +1277,7 @@

    Summary

    } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -1106,6 +1303,32 @@

    Summary

    }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1167,12 +1390,12 @@

    Summary

    @@ -188,8 +195,10 @@

    DaskHub Set-up

    + +

    This is my notes for setting this up on Azure. Attempting to replicate the Openscapes 2i2c JupyterHub: https://github.com/2i2c-org/infrastructure/tree/master/config/clusters/openscapes

    That hub is on AWS and is designed for large workshops (100+) however the NMFS OpenSci JHub is quite similar. Main difference at the moment is that I don’t have a shared drive set-up and the user persistent volume (storage) is on the same VM as the user node for their Jupyter Notebook. This means that I cannot have multiple VM sizes. Need to fix so that user can pick a larger VM for a task if needed.

    @@ -305,7 +314,7 @@

    Update

    Test if https is working

    -

    Try https:\\dhub.bluemountain123.live and you should see the JupyterHub login without that http warning.

    +

    Try https:\\dhub.bluemountain123.live and you should see the JupyterHub login without that http warning. This might take 30 minutes to an hour.

    @@ -572,6 +581,33 @@

    S3 access

    } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -638,13 +674,15 @@

    S3 access

    return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -723,10 +761,9 @@

    S3 access

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -736,8 +773,17 @@

    S3 access

    interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -751,6 +797,128 @@

    S3 access

    const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -793,6 +961,7 @@

    S3 access

    } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -818,6 +987,32 @@

    S3 access

    }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -879,12 +1074,12 @@

    S3 access

    +

    Instructions for editing config

      @@ -229,6 +237,33 @@

      Instructions for editing config

      } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -295,13 +330,15 @@

      Instructions for editing config

      return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -380,10 +417,9 @@

      Instructions for editing config

      // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -393,8 +429,17 @@

      Instructions for editing config

      interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -408,6 +453,128 @@

      Instructions for editing config

      const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -450,6 +617,7 @@

      Instructions for editing config

      } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -475,6 +643,32 @@

      Instructions for editing config

      }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -540,7 +734,9 @@

      Instructions for editing config

      - + @@ -549,4 +745,5 @@

      Instructions for editing config

      + \ No newline at end of file diff --git a/docs/set-up-vm.html b/docs/posts/set-up-vm.html similarity index 66% rename from docs/set-up-vm.html rename to docs/posts/set-up-vm.html index 36f614f..accf160 100644 --- a/docs/set-up-vm.html +++ b/docs/posts/set-up-vm.html @@ -2,7 +2,7 @@ - + @@ -24,32 +24,38 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -76,10 +83,10 @@ - - - @@ -90,8 +97,8 @@ +
      @@ -177,8 +184,10 @@

      Set up VM

      + +

      For testing JupyterHub set-ups, I start various Linux machines. Here is how to set up a virtual machines.

      Azure

      @@ -250,6 +259,33 @@

      Azure

      } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -316,13 +352,15 @@

      Azure

      return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -401,10 +439,9 @@

      Azure

      // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -414,8 +451,17 @@

      Azure

      interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -429,6 +475,128 @@

      Azure

      const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -471,6 +639,7 @@

      Azure

      } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -496,6 +665,32 @@

      Azure

      }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -557,7 +752,7 @@

      Azure