diff --git a/.gitignore b/.gitignore
index 13fb01a..f622d69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,7 @@ x86-ubuntu-gpu-ml-isca.gz
build/
*.DS_Store
+
+# node_modules
+node_modules/
+package-lock.json
diff --git a/marp-custom-engine.js b/marp-custom-engine.js
new file mode 100644
index 0000000..c0dcc97
--- /dev/null
+++ b/marp-custom-engine.js
@@ -0,0 +1,35 @@
+const { Marp } = require('@marp-team/marp-core');
+
+// Define your variables here
+const variables = {
+ BOOTCAMP_WEBSITE: "https://gem5bootcamp.gem5.org/",
+ BOOTCAMP_ARCHIVE: "https://gem5bootcamp.github.io/2024",
+ GITHUB_REPO: "https://github.com/gem5bootcamp/2024",
+ GITHUB_CLASSROOM: "https://classroom.github.com/a/gCcXlgBs",
+ GEM5_CODE: "https://github.com/gem5/gem5",
+ GEM5_WEBSITE: "https://www.gem5.org/",
+ GEM5_YOUTUBE: "https://youtube.com/@gem5",
+ GEM5_SLACK: "https://gem5-workspace.slack.com/join/shared_invite/zt-2e2nfln38-xsIkN1aRmofRlAHOIkZaEA"
+};
+
+// Function to replace placeholders with actual values in the markdown content
+function replaceVariables(content) {
+ let updatedContent = content.toString(); // Ensure content is a string
+ for (const [key, value] of Object.entries(variables)) {
+ const placeholder = `{{${key}}}`;
+ const regex = new RegExp(placeholder, 'g');
+ updatedContent = updatedContent.replace(regex, value);
+ }
+ return updatedContent;
+}
+
+// Custom Marp engine class
+class CustomMarpEngine extends Marp {
+ // Override the render method to preprocess Markdown content
+ render(markdown, options = {}) {
+ const processedMarkdown = replaceVariables(markdown); // Replace placeholders
+ return super.render(processedMarkdown, options); // Call Marp's render method
+ }
+}
+
+module.exports = CustomMarpEngine;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d265396
--- /dev/null
+++ b/package.json
@@ -0,0 +1,6 @@
+{
+ "dependencies": {
+ "@marp-team/marp-cli": "^4.0.3",
+ "fs": "^0.0.1-security"
+ }
+}
diff --git a/slides/01-Introduction/00-introduction-to-bootcamp.md b/slides/01-Introduction/00-introduction-to-bootcamp.md
index 4a8b217..a28e270 100644
--- a/slides/01-Introduction/00-introduction-to-bootcamp.md
+++ b/slides/01-Introduction/00-introduction-to-bootcamp.md
@@ -14,9 +14,9 @@ author: Jason Lowe-Power
## About the overall structure of the bootcamp
-These slides and are available at for you to follow along.
+These slides and are available at {{BOOTCAMP_WEBSITE}} for you to follow along.
-(Note: They will be archived at )
+(Note: They will be archived at {{GITHUB_REPO}})
The source for the slides, and what you'll be using throughout the bootcamp can be found on github at
diff --git a/web/app.js b/web/app.js
index 8fac595..96a0404 100644
--- a/web/app.js
+++ b/web/app.js
@@ -5,6 +5,10 @@ const pdf_url_base = './pdf/'
const pdf_url_ext = '.pdf'
const slide_link_height = 25;
+/* Button Names variables */
+SHOW_SOLUTION_BUTTON_TEXT = "Show Solution";
+HIDE_SOLUTION_BUTTON_TEXT = "Hide Solution";
+
/* Global references and variables */
let slide_list = [];
let slide_id = -1;
@@ -119,6 +123,138 @@ function sidebar_category_onclick(e) {
slides.style.height = `${height}px`;
}
+/* ========================================================================= *
+ * Copy functionality
+ * ========================================================================= */
+function addCopyButtonsInIframe() {
+ let frame = document.querySelector('#slideFrame');
+ if (!frame) {
+ console.error("Error: #slideFrame element not found.");
+ return;
+ }
+
+ // Function to insert copy buttons in blocks inside the iframe
+ const insertCopyButtons = () => {
+ const frame = document.querySelector('#slideFrame');
+ const codeBlocks = frame.contentDocument.querySelectorAll('marp-pre code');
+
+ codeBlocks.forEach((block) => {
+ if (block.parentElement.querySelector('.copy-button')) return; // Avoid duplicates
+
+ // Create the copy button element
+ let copyButton = document.createElement('button');
+ copyButton.classList.add('copy-button');
+ copyButton.innerHTML = ``;
+
+ // Set the CSS position directly with JavaScript
+ copyButton.style.position = 'absolute';
+ copyButton.style.top = '2px';
+ copyButton.style.right = '2px';
+ copyButton.style.background = 'none';
+ copyButton.style.border = 'none';
+ copyButton.style.padding = '4px';
+ copyButton.style.cursor = 'pointer';
+ copyButton.style.opacity = '0.7';
+
+ // Apply position to the container
+ block.parentElement.style.position = 'relative';
+
+ // Insert the copy button at the beginning of each element
+ block.parentElement.insertBefore(copyButton, block);
+
+ // Add the copy functionality
+ copyButton.addEventListener('click', () => {
+ navigator.clipboard.writeText(block.innerText);
+ });
+ });
+ };
+
+
+ // MutationObserver to detect changes within the iframe's content
+ const observer = new MutationObserver(insertCopyButtons);
+
+ // Listen for when the iframe content loads
+ frame.addEventListener('load', () => {
+ // Observe the iframe's document for added blocks
+ observer.observe(frame.contentDocument.body, { childList: true, subtree: true });
+ insertCopyButtons(); // Initial call for existing code blocks
+ });
+}
+
+// Run addCopyButtonsInIframe after the main DOM is fully loaded
+document.addEventListener('DOMContentLoaded', addCopyButtonsInIframe);
+
+/* ========================================================================= *
+ * Hidden slides logic handeling
+ * ========================================================================= */
+function setupToggleButtons() {
+ const frame = document.querySelector('#slideFrame');
+
+ // Check if iframe exists and load content
+ if (frame) {
+ frame.addEventListener('load', () => {
+ const contentDoc = frame.contentDocument || frame.contentWindow.document;
+
+ // Select all toggle buttons and toggle-content sections in the iframe
+ const toggleButtons = contentDoc.querySelectorAll('.toggle-button');
+ const toggleContents = contentDoc.querySelectorAll('.toggle-content');
+
+ // Force initial hidden state for all toggle-content elements
+ toggleContents.forEach(content => {
+ content.style.display = 'none'; // Ensure they are hidden at the start
+ });
+
+ // Initialize each button's text to "Show Content" and add inline styles
+ toggleButtons.forEach(button => {
+ button.textContent = SHOW_SOLUTION_BUTTON_TEXT;
+
+ // Apply inline styles to the toggle button
+ button.style.cursor = 'pointer';
+ button.style.backgroundColor = '#007bff'; // Blue color
+ button.style.color = 'white';
+ button.style.padding = '10px';
+ button.style.border = 'none';
+ button.style.borderRadius = '5px';
+ button.style.fontSize = '16px';
+ button.style.marginTop = '10px';
+
+ // Hover effect
+ button.addEventListener('mouseover', () => {
+ button.style.backgroundColor = '#0056b3'; // Darker blue on hover
+ });
+ button.addEventListener('mouseout', () => {
+ button.style.backgroundColor = '#007bff'; // Original blue
+ });
+ });
+
+ // Add click event listener to each toggle button
+ toggleButtons.forEach((button, index) => {
+ button.addEventListener('click', () => {
+ const content = toggleContents[index];
+ if (content) {
+ // Toggle the display of the content
+ if (content.style.display === 'none') {
+ content.style.display = 'block';
+ button.textContent = HIDE_SOLUTION_BUTTON_TEXT;
+ } else {
+ content.style.display = 'none';
+ button.textContent = SHOW_SOLUTION_BUTTON_TEXT;
+ }
+ }
+ });
+ });
+ });
+ } else {
+ console.error("Error: #slideFrame element not found.");
+ }
+}
+
+// Run setupToggleButtons after the main DOM is fully loaded
+document.addEventListener('DOMContentLoaded', setupToggleButtons);
+
/* ========================================================================= *
* Relative Link to Git Translation !!TEMPORARY!!
* ========================================================================= */