From 363f4cd790f1305e23279d3d29bdedabe936aa15 Mon Sep 17 00:00:00 2001 From: dalance Date: Sat, 6 Jan 2024 19:54:05 +0900 Subject: [PATCH] Add custom playground guide --- guide/src/SUMMARY.md | 1 + guide/src/format/custom-playground.md | 196 ++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 guide/src/format/custom-playground.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 974d65fa..37440815 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -33,6 +33,7 @@ - [MathJax Support](format/mathjax.md) - [mdBook-specific features](format/mdbook.md) - [Markdown](format/markdown.md) + - [Custom Playground](format/custom-playground.md) - [Continuous Integration](continuous-integration.md) - [For Developers](for_developers/README.md) - [Preprocessors](for_developers/preprocessors.md) diff --git a/guide/src/format/custom-playground.md b/guide/src/format/custom-playground.md new file mode 100644 index 00000000..8d78980b --- /dev/null +++ b/guide/src/format/custom-playground.md @@ -0,0 +1,196 @@ +# Custom Playground + +By default, mdBook supports playground for Rust only. +If you want to add playground for another language, +you can do it by overriding script through theme customization. + +In this section, we will try to add a playground for the imaginary `example` language. + +## Add `playground` option to code blocks in the book + +`playground` option is not officially supported. +It is used to mark the code block should be translated to playground code block by JavaScript. + +````markdown +```example,playground +Example Language Code +``` +```` + +`editable` option can be used like below: + +````markdown +```example,playground,editable +Example Language Code +``` +```` + +## Modify `book.js` + +`book.js` can be modified through theme customization. + +### Remove the access to play.rust-lang.org + +If there are playgrounds in the page, `book.js` will try to fetch crates list from play.rust-lang.org. +So you shoud remove the following codes. + +```javascript + var playgrounds = Array.from(document.querySelectorAll(".playground")); + if (playgrounds.length > 0) { + fetch_with_timeout("https://play.rust-lang.org/meta/crates", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + let playground_crates = response.crates.map(item => item["id"]); + playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } +``` + +### Modify `run_rust_code` function + +If a play button is pushed, `run_rust_code` function will be called to get the result of the playground. +So you should modify the function for your language. + +```javascript + function run_rust_code(code_block) { + var result_block = code_block.querySelector(".result"); + if (!result_block) { + result_block = document.createElement('code'); + // If the result should be syntax highlighted, + // `language-bash` should be modified to the appropriate language. + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + let text = playground_text(code_block); + + // Add function to get result of the playground + result_block.innerText = example_language_run(text); + + // If the result should be syntax highlighted, enable the following code. + //hljs.highlightBlock(result_block); + } +``` + +### Add code for code block transformation + +The following code transforms code blocks which have `playground` option to playground code blocks. + +```javascript + // Add
 to playground codeblock
+    Array.from(document.querySelectorAll(".playground")).forEach((element) => {
+        let parent = element.parentNode;
+        let wrapper = document.createElement('pre');
+        wrapper.className = 'playground';
+        element.classList.remove('playground');
+        // set the wrapper as child (instead of the element)
+        parent.replaceChild(wrapper, element);
+        // set element as child of wrapper
+        wrapper.appendChild(element);
+    });
+```
+
+This should be executed before other processes of `codeSnippets` function.
+For example, the code should be inserted to the following point.
+
+```javascript
+    // Insert the above code
+
+    // Syntax highlighting Configuration
+    hljs.configure({
+        tabReplace: '    ', // 4 spaces
+        languages: [],      // Languages used for auto-detection
+    });
+
+    let code_nodes = Array
+        .from(document.querySelectorAll('code'))
+        // Don't highlight `inline code` blocks in headers.
+        .filter(function (node) {return !node.parentElement.classList.contains("header"); });
+```
+
+### Tweak for editable code blocks
+
+If the code block is editable, the syntax highlight is executed by editor.
+So the normal highlight mechanism should be disabled to avoid conflict.
+
+This is achieved by the following code.
+
+```javascript
+        // language-rust class needs to be removed for editable
+        // blocks or highlightjs will capture events
+        code_nodes
+            .filter(function (node) {return node.classList.contains("editable"); })
+            .forEach(function (block) { block.classList.remove('language-rust'); });
+```
+
+The language name shoud be changed to `language-example`.
+
+```javascript
+        // language-rust class needs to be removed for editable
+        // blocks or highlightjs will capture events
+        code_nodes
+            .filter(function (node) {return node.classList.contains("editable"); })
+            .forEach(function (block) { block.classList.remove('language-example'); });
+```
+
+## Prepare syntax highlighting
+
+[highlight.js](https://highlightjs.org) and [Ace](https://ace.c9.io) are used for syntax highlighting.
+If the code block is `editable`, `Ace` is used, and if not `highlight.js` is used.
+So a syntax definition of `example` language should be prepared for each library.
+
+You can get the following JavaScript codes by following each library's document.
+
+* highlight.js: `build/highlight.min.js`
+* Ace: `build/src-min-noconflict/mode-example.js`
+
+`highlight.js` can be overridden by theme.
+So the `build/highlight.min.js` should be moved to `theme/highlight.js` in the project.
+`mode-example.js` shoud be moved to the project root because it is not overridable.
+
+## Modify `editor.js`
+
+The language configuration of Ace editor is set at `editor.js`.
+This script can't be customized through theme.
+So you should copy `editor.js` from the generated book directory to the project root.
+The copied file should be modified like below:
+
+```javascript
+        //editor.getSession().setMode("ace/mode/rust");
+        editor.getSession().setMode("ace/mode/example");
+```
+
+## Set additional-js
+
+You should add `editor.js` and `mode-example.js` to `additional-js` field of `book.toml`.
+
+```toml
+[output.html]
+additional-js = [
+  "mode-example.js",
+  "editor.js",
+]
+```
+
+## Directory structure
+
+The final directory structure becomes below:
+
+```
+.
+|-- book.toml
+|-- editor.js
+|-- mode-example.js
+|-- src
+`-- theme
+    |-- book.js
+    `-- highlight.js
+```