Skip to content

Commit

Permalink
feat: supports readonly and norepl options for codeblock
Browse files Browse the repository at this point in the history
  • Loading branch information
MR-Addict committed Feb 18, 2024
1 parent 8b038d2 commit 43946d7
Show file tree
Hide file tree
Showing 17 changed files with 112 additions and 49 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ jobs:
name: ${{ matrix.target }}
path: backend/target/release/mdbook-repl.exe

# - name: Publish crate
# if: matrix.target == 'x86_64-unknown-linux-gnu'
# run: |
# cd backend
# cargo login ${{ secrets.CARGO_TOKEN }}
# cargo publish
- name: Publish crate
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
cd backend
cargo login ${{ secrets.CARGO_TOKEN }}
cargo publish
release:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions backend/assets/repl.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="repl" data-id="{id}" data-lang="{lang}">
<div class="repl" data-id="{id}" data-readonly="{readonly}" data-lang="{lang}">
<iframe src="https://mr-addict.github.io/mdbook-repl/embed" allow="clipboard-write"></iframe>
</div>

{code}
{codeblock}
3 changes: 2 additions & 1 deletion backend/assets/script.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
const id = replElement.getAttribute("data-id");
const lang = replElement.getAttribute("data-lang");
const code = replElement.nextElementSibling.innerText.trim();
const readonly = replElement.getAttribute("data-readonly") === "true";
let theme = mapTheme(localStorage.getItem("mdbook-theme"));

const postmessage = (msg) => iframeElement.contentWindow.postMessage({ repl: msg }, "*");
Expand All @@ -20,7 +21,7 @@

const repl = event.data.repl;
// if id is empty means iframe is first loaded, so we send configurations to it
if (repl.id === "") postmessage({ id, editor: { theme, lang, code, defaultCode: code } });
if (repl.id === "") postmessage({ id, editor: { theme, lang, code, defaultCode: code, readonly } });
// if id is not empty and it's the same as the current repl, we can receive the data
else if (repl.id === id) {
if (repl.editor.theme !== theme) postmessage({ editor: { theme } });
Expand Down
1 change: 1 addition & 0 deletions backend/example/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Basics

- [Intro](intro.md)
- [Installation](installation.md)
- [Usage](usage.md)
- [For Developers](for-developers.md)

Expand Down
4 changes: 3 additions & 1 deletion backend/example/src/for-developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ When the iframe is loaded, it will send a message to the parent window. The mess
"height": 600
},
"editor": {
"readonly": false,
"theme": "light",
"language": "python",
"code": "# This is a default python code\n\nprint('Hello world')",
Expand Down Expand Up @@ -48,6 +49,7 @@ Here is an example of how to use the **mdbook-repl** in your own project.
const id = "ac2f5a2";
const lang = "python";
const theme = "light";
const readonly = false;
const code = "print('Hello world')";
const iframe = document.querySelector("iframe");
Expand All @@ -61,7 +63,7 @@ Here is an example of how to use the **mdbook-repl** in your own project.
// update the iframe height when new dimensions updated
if (replData.id === id) replElement.style.height = replData.dimensions.height + "px";
// if id is empty, it means the iframe is just loaded
else if (replData.id === "") postmessage({ id, editor: { theme, language: lang, code } });
else if (replData.id === "") postmessage({ id, editor: { theme, language: lang, code, readonly } });
});
</script>
```
Expand Down
17 changes: 17 additions & 0 deletions backend/example/src/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Installation

There two ways to install this preprocessor.

You can install with **cargo** if you have [rust](https://www.rust-lang.org) installed:

```sh
cargo install mdbook-repl
```

Or you can download the **binary** from [release page](https://github.com/MR-Addict/mdbook-repl/releases). Then you can put the binary in your **PATH**.

You can check if the installation is successful by running:

```sh
mdbook-repl --version
```
2 changes: 1 addition & 1 deletion backend/example/src/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is a [mdbook](https://rust-lang.github.io/mdBook) playground which uses **w

This is inspired by [mdbook rust playground](https://rust-lang.github.io/mdBook/format/mdbook.html#rust-playground), but it is not limited to rust. Recent years, webassembly is very powerful and able to so many things. So I want to learned it and try to make a playground for multiple languages for mdbook.

```python
```python, readonly
# Python codeblock
print("Hello, world!")
Expand Down
68 changes: 41 additions & 27 deletions backend/example/src/usage.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,63 @@
# Usage

## Installation
## Basics

There two ways to install this preprocessor.

You can install with **cargo** if you have [rust](https://www.rust-lang.org) installed:

```sh
cargo install mdbook-repl
```

Or you can download the **binary** from [release page](https://github.com/MR-Addict/mdbook-repl/releases). Then you can put the binary in your **PATH**.

You can check if the installation is successful by running:

```sh
mdbook-repl --version
```

## Usage

After you have installed the preprocessor, you can use it in your **mdbook** project. You need to add the following code to your **book.toml**, so that **mdbook** can use this preprocessor to preprocess your markdown files.
After you installed the preprocessor, you can use it in your **mdbook** project. You need to add the following code to your **book.toml**, so that **mdbook** can use this preprocessor to preprocess your markdown files.

```toml
[preprocessor.mdbook-repl]
[preprocessor.repl]
```

After that, all your markdown files that contain python codeblock will be processed by this preprocessor. It's just like magic.

For example, you can write a python codeblock in your markdown file:

```python
````markdown
```python,norepl
# Python codeblock

print("Hello, world!")
```
````

After you build your **mdbook**, the codeblock will be replaced by the output of the code.

```python
# Python codeblock

print("Hello, world!")
```

## Extensions

This preprocessor only recongnizes specific extensions for sepecific language. For example, you can only use `python` or `py` codeblock for python code.
This preprocessor only recongnizes specific extensions for sepecific language. For example, you can only use **python** or **py** codeblock for python code.

## Options

You can also specific some options for the codeblock. For example, you can specify the **readonly** for the codeblock, so that the codeblock will not be eidtable.

<pre><code>
&#96;&#96;&#96;python, readonly
print("This is a readonly python codeblock")
&#96;&#96;&#96;
</code></pre>

Below is a list of supported extensions for each language:
You can test below codeblock to see the result.

| Language | Codeblock |
| :------- | :------------- |
| Python | `python`, `py` |
```python, readonly
print("This is a readonly python codeblock")
```

And if you put **norepl** in the codeblock, the codeblock will not be rendered by this preprocessor.

<pre><code>
&#96;&#96;&#96;python, norepl
print("This is codeblock will not be rendered")
&#96;&#96;&#96;
</code></pre>

You can test below codeblock to see the result.

```python, norepl
print("This is codeblock will not be rendered")
```
25 changes: 22 additions & 3 deletions backend/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ fn get_asset(name: &str) -> String {
std::str::from_utf8(file.data.as_ref()).unwrap().to_string()
}

fn parse_options(options_str: &str) -> Vec<String> {
options_str
.split(',')
.map(|s| s.trim().to_string())
.collect()
}

fn render_repls(content: &str) -> (bool, String) {
// \r? is for windows line endings
let re = Regex::new(r"(?s)```(py|python)\r?\n(.*?)```").unwrap();
let re = Regex::new(r"(?s)```(py|python),?(.*?)\r?\n(.*?)```").unwrap();

// if there are no matches, return the content as is
if !re.is_match(content) {
Expand All @@ -39,12 +46,24 @@ fn render_repls(content: &str) -> (bool, String) {
.replace_all(content, |caps: &regex::Captures| {
let lang = "python";
let id = Uuid::new_v4().to_string();
let code = caps.get(0).unwrap().as_str().trim();
let code = caps.get(3).map(|m| m.as_str()).unwrap_or("").trim();

let codeblock = format!("```{}\n{}\n```", lang, code);
let options_str = caps.get(2).map(|m| m.as_str()).unwrap_or("");
let options = parse_options(options_str);

// if norepl is in the options, return the code block as is
if options.contains(&"norepl".to_string()) {
return codeblock;
}

let readonly = options.contains(&"readonly".to_string());

get_asset("repl.html")
.replace("{id}", &id)
.replace("{lang}", lang)
.replace("{code}", &code)
.replace("{codeblock}", &codeblock)
.replace("{readonly}", if readonly { "true" } else { "false" })
})
.to_string();

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Buttons/Buttons.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.wrapper {
@apply sm:opacity-0 duration-300 absolute right-2 top-2 flex flex-row items-center gap-2;
@apply sm:opacity-0 duration-300 absolute right-2 top-1 flex flex-row items-center gap-2;
}

.wrapper button {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Buttons/Buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function Buttons() {

return (
<div className={clsx(style.wrapper, "buttons")}>
<Button name="reset" Icon={FaHistory} onClick={handleReset} />
{!editor.readonly && <Button name="reset" Icon={FaHistory} onClick={handleReset} />}
<Button name="copy" Icon={copied ? IoCheckmark : FaRegCopy} onClick={handleCopy} />
<Button
name="play"
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const defaultOptions = {
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
tabSize: 4,
minLines: 2,
minLines: 1,
editorProps: { $blockScrolling: true }
};

Expand All @@ -32,6 +32,7 @@ export default function Editor() {
mode={editor.lang}
value={editor.code}
className="bg-transparent"
readOnly={editor.readonly}
defaultValue={editor.defaultCode}
theme={editor.theme === "light" ? "tomorrow" : "tomorrow_night"}
onChange={(code) => setEditor({ ...editor, code })}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Output/Output.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

/* clear button */
.clear-button {
@apply absolute right-2 top-1 sm:opacity-0 duration-300;
@apply absolute right-2 top-0.5 sm:opacity-0 duration-300;
}

.clear-button:disabled {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Output/Output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function Output() {

return (
<div data-status={output.status} className={clsx(style.wrapper, "dark:text-gray-300")}>
<p className={clsx(style.output, "dark:bg-zinc-800")}>{output.msg}</p>
<p className={clsx(style.output, "dark:bg-zinc-800")}>{output.msg || "Sorry, there is no output"}</p>
<button type="button" className={style["clear-button"]} onClick={() => setOutput({ status: "idle", msg: "" })}>
clear
</button>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/contexts/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const defaultEditorOptions: EditorType = {
lang: "python",
theme: "light",
code: defaultCode,
defaultCode
defaultCode,
readonly: false
};

interface AppContextProps {
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ body {
@apply overflow-y-hidden;
}

/* ace editor releated */
.ace-editor:hover ~ .buttons,
.buttons:hover {
@apply opacity-100;
}

.ace_mobile-menu {
display: none;
@apply hidden;
}

.ace_editor textarea:read-only ~ .ace_scroller .ace_cursor-layer,
.ace_editor textarea:read-only ~ .ace_scroller .ace_marker-layer .ace_bracket {
@apply hidden;
}
3 changes: 2 additions & 1 deletion frontend/src/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const Editor = z.object({
lang: z.literal("python"),
code: z.string(),
theme: z.union([z.literal("light"), z.literal("dark")]),
defaultCode: z.string()
defaultCode: z.string(),
readonly: z.boolean()
});

type EditorType = z.infer<typeof Editor>;
Expand Down

0 comments on commit 43946d7

Please sign in to comment.