Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 11 additions & 35 deletions src/components/MemoryGraphBlock/index.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,24 @@
import React, { useMemo } from 'react';
import React from 'react';
import MDXComponents from '@theme-original/MDXComponents';
import MemoryGraphHeader from '@site/src/components/MemoryGraphHeader';
import styles from './styles.module.css';

const MEMORY_GRAPH_BASE_URL = 'https://memory-graph.com/';

/**
* Wraps a ```lang debug code block with a header bar linking out to
* Bas Terwijn's Memory Graph web debugger (memory-graph.com), so students
* can step through the exact snippet from the lesson and watch variables/
* references change line by line.
*
* Unlike InteractivePython, this doesn't replace the block with an editor —
* the theme's normal <pre> still renders (syntax highlighting, copy button,
* line numbers all keep working). We just read the raw code to build a
* deep link and drop a button above it.
* Wraps a ```lang debug (debug-only) code block: a flush MemoryGraphHeader
* sitting directly on top of the theme's normal <pre>, so syntax
* highlighting and the built-in copy button keep working.
*
* Memory Graph reads code from a URL *fragment* (`#code=...`), not a query
* string — that's deliberate on their end, it means the code never gets
* sent to their server, it's only ever interpreted client-side once the
* page loads. Don't "fix" this to `?code=`.
* For ```lang interactive debug (both keywords), MDXComponents renders
* MemoryGraphHeader directly above InteractivePython instead, skipping
* this component entirely — see src/theme/MDXComponents.js.
*/
export default function MemoryGraphBlock(props) {
const { children } = props;
const rawCode = children?.props?.children?.trim() || '';

const memoryGraphUrl = useMemo(
() => `${MEMORY_GRAPH_BASE_URL}#code=${encodeURIComponent(rawCode)}`,
[rawCode]
);
const rawCode = props.children?.props?.children || '';

return (
<div className={styles.wrapper}>
<div className={styles.header}>
<span className={styles.headerText}>Debug Mode</span>
<a
href={memoryGraphUrl}
target="_blank"
rel="noopener noreferrer"
className={styles.openButton}
>
Open in Memory Graph ↗
</a>
</div>
<MemoryGraphHeader code={rawCode} flush />
<MDXComponents.pre {...props} />
</div>
);
}
}
64 changes: 0 additions & 64 deletions src/components/MemoryGraphBlock/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,75 +1,11 @@
/* ==========================================================================
Reuses the site's existing purple accent (see .alert--explore in
custom.css) so "debug" blocks read as their own category, distinct
from the blue InteractivePython header.
========================================================================== */
.wrapper {
margin: 1.5rem 0;
}

.header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
padding: 10px 16px;
background-color: rgba(124, 58, 237, 0.08);
border: 1px solid rgba(124, 58, 237, 0.35);
border-bottom: none;
border-radius: var(--ifm-global-radius) var(--ifm-global-radius) 0 0;
}

[data-theme='dark'] .header {
background-color: rgba(168, 85, 247, 0.15);
border-color: rgba(216, 180, 254, 0.3);
}

.headerText {
font-family: var(--ifm-font-family-base, sans-serif);
font-weight: 600;
font-size: 0.85rem;
letter-spacing: 0.05rem;
text-transform: uppercase;
color: #7c3aed;
}

[data-theme='dark'] .headerText {
color: #e9d5ff;
}

.openButton {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 6px;
background-color: #7c3aed;
color: #ffffff !important; /* override Docusaurus's default link color */
font-family: var(--ifm-font-family-base, sans-serif);
font-weight: 600;
font-size: 0.85rem;
text-decoration: none !important;
white-space: nowrap;
transition: background-color 0.15s ease;
}

.openButton:hover {
background-color: #6d28d9;
color: #ffffff !important;
}

/* Butt the code block directly against the header — no gap, no double border */
.wrapper :global(.theme-code-block) {
margin-top: 0 !important;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
border-top: none !important;
}

@media (max-width: 480px) {
.header {
justify-content: center;
text-align: center;
}
}
37 changes: 37 additions & 0 deletions src/components/MemoryGraphHeader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useMemo } from 'react';
import styles from './styles.module.css';

const MEMORY_GRAPH_BASE_URL = 'https://memory-graph.com/';

/**
* Purple header bar linking a code snippet out to Bas Terwijn's Memory
* Graph web debugger (memory-graph.com). This is just the bar + link —
* it doesn't render the code itself, so it can sit above either a plain
* <pre> (```lang debug, via MemoryGraphBlock) or InteractivePython's
* editable sandbox (```lang interactive debug, via MDXComponents).
*
* `code` is the raw (untrimmed is fine) source string; `flush` squares
* off the bottom edge and drops the outer margin so it butts directly
* against whatever renders right beneath it, instead of floating as its
* own rounded pill.
*/
export default function MemoryGraphHeader({ code, flush = false }) {
const memoryGraphUrl = useMemo(
() => `${MEMORY_GRAPH_BASE_URL}#code=${encodeURIComponent((code || '').trim())}`,
[code]
);

return (
<div className={`${styles.header} ${flush ? styles.flush : ''}`}>
<span className={styles.headerText}>Debug Mode</span>
<a
href={memoryGraphUrl}
target="_blank"
rel="noopener noreferrer"
className={styles.openButton}
>
View on Memory Graph ↗
</a>
</div>
);
}
72 changes: 72 additions & 0 deletions src/components/MemoryGraphHeader/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* ==========================================================================
Reuses the site's existing purple accent (see .alert--explore in
custom.css) so "debug" blocks read as their own category, distinct
from the blue InteractivePython header.
========================================================================== */
.header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin: 1.5rem 0 0.5rem;
padding: 10px 16px;
background-color: rgba(124, 58, 237, 0.08);
border: 1px solid rgba(124, 58, 237, 0.35);
border-radius: var(--ifm-global-radius);
}

[data-theme='dark'] .header {
background-color: rgba(168, 85, 247, 0.15);
border-color: rgba(216, 180, 254, 0.3);
}

/* Flush mode: sits directly on top of a following block (a plain <pre>
via MemoryGraphBlock) — squared-off bottom, no gap beneath it. The
parent wrapper supplies the outer margin instead. */
.header.flush {
margin: 0;
border-bottom: none;
border-radius: var(--ifm-global-radius) var(--ifm-global-radius) 0 0;
}

.headerText {
font-family: var(--ifm-font-family-base, sans-serif);
font-weight: 600;
font-size: 0.85rem;
letter-spacing: 0.05rem;
text-transform: uppercase;
color: #7c3aed;
}

[data-theme='dark'] .headerText {
color: #e9d5ff;
}

.openButton {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 6px;
background-color: #7c3aed;
color: #ffffff !important; /* override Docusaurus's default link color */
font-family: var(--ifm-font-family-base, sans-serif);
font-weight: 600;
font-size: 0.85rem;
text-decoration: none !important;
white-space: nowrap;
transition: background-color 0.15s ease;
}

.openButton:hover {
background-color: #6d28d9;
color: #ffffff !important;
}

@media (max-width: 480px) {
.header {
justify-content: center;
text-align: center;
}
}
16 changes: 14 additions & 2 deletions src/theme/MDXComponents.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import MDXComponents from '@theme-original/MDXComponents';
import InteractivePython from '@site/src/components/InteractivePython';
import MemoryGraphBlock from '@site/src/components/MemoryGraphBlock';
import MemoryGraphHeader from '@site/src/components/MemoryGraphHeader';

export default {
...MDXComponents,
// We wrap the standard 'pre' tag (which wraps code blocks)
pre: (props) => {
const metastring = props.children?.props?.metastring || '';
const isInteractive = metastring.includes('interactive');
const isDebug = metastring.includes('debug');

if (metastring.includes('interactive')) {
if (isInteractive && isDebug) {
const rawCode = props.children?.props?.children || '';
return (
<>
<MemoryGraphHeader code={rawCode} />
<InteractivePython {...props} />
</>
);
}
if (isInteractive) {
return <InteractivePython {...props} />;
}
if (metastring.includes('debug')) {
if (isDebug) {
return <MemoryGraphBlock {...props} />;
}
return <MDXComponents.pre {...props} />;
Expand Down
Loading