remark-mind-elixir 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -0
- package/package.json +1 -1
- package/src/client.js +0 -2
- package/src/index.js +86 -23
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# remark-mind-elixir
|
|
2
|
+
|
|
3
|
+
A [remark](https://github.com/remarkjs/remark) plugin for embedding interactive [Mind Elixir](https://mind-elixir.com/) mind maps in Markdown.
|
|
4
|
+
|
|
5
|
+
It lets you define mind maps using YAML inside `elixir-mind` fenced code blocks and transforms them into interactive Mind Elixir maps.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* 📝 Define mind maps directly in Markdown
|
|
10
|
+
* 🌳 YAML-based mind map structure
|
|
11
|
+
* ⚙️ Optional YAML configuration
|
|
12
|
+
* ⚡ Automatic client-side rendering
|
|
13
|
+
* 🌐 Optional CDN-based runtime
|
|
14
|
+
* 🔌 Works with remark and the unified ecosystem
|
|
15
|
+
* 🧩 Built on top of [ZikoJS](https://github.com/zikojs)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install remark-mind-elixir
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { remark } from 'remark'
|
|
27
|
+
import remarkMindElixir from 'remark-mind-elixir'
|
|
28
|
+
|
|
29
|
+
const markdown = `
|
|
30
|
+
# My Mind Map
|
|
31
|
+
|
|
32
|
+
\`\`\`elixir-mind
|
|
33
|
+
root:
|
|
34
|
+
topic: JavaScript
|
|
35
|
+
children:
|
|
36
|
+
- topic: Browser
|
|
37
|
+
- topic: Node.js
|
|
38
|
+
\`\`\`
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
const file = await remark()
|
|
42
|
+
.use(remarkMindElixir)
|
|
43
|
+
.process(markdown)
|
|
44
|
+
|
|
45
|
+
console.log(String(file))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Markdown Syntax
|
|
49
|
+
|
|
50
|
+
Use an `elixir-mind` fenced code block:
|
|
51
|
+
|
|
52
|
+
````markdown
|
|
53
|
+
```elixir-mind
|
|
54
|
+
root:
|
|
55
|
+
topic: JavaScript
|
|
56
|
+
children:
|
|
57
|
+
- topic: Browser
|
|
58
|
+
- topic: Node.js
|
|
59
|
+
- topic: Deno
|
|
60
|
+
```
|
|
61
|
+
````
|
|
62
|
+
|
|
63
|
+
The YAML is converted into the data structure expected by Mind Elixir.
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
A configuration document can optionally be placed before the mind-map data.
|
|
68
|
+
|
|
69
|
+
Separate the configuration and data using `---`:
|
|
70
|
+
|
|
71
|
+
````markdown
|
|
72
|
+
```elixir-mind
|
|
73
|
+
---
|
|
74
|
+
direction: right
|
|
75
|
+
---
|
|
76
|
+
root:
|
|
77
|
+
topic: JavaScript
|
|
78
|
+
children:
|
|
79
|
+
- topic: Browser
|
|
80
|
+
- topic: Node.js
|
|
81
|
+
```
|
|
82
|
+
````
|
|
83
|
+
|
|
84
|
+
The first YAML document is interpreted as plugin configuration, while the second document contains the mind-map data.
|
|
85
|
+
|
|
86
|
+
## Multiple Mind Maps
|
|
87
|
+
|
|
88
|
+
A Markdown document can contain multiple `elixir-mind` blocks:
|
|
89
|
+
|
|
90
|
+
````markdown
|
|
91
|
+
# Frontend
|
|
92
|
+
|
|
93
|
+
```elixir-mind
|
|
94
|
+
root:
|
|
95
|
+
topic: Frontend
|
|
96
|
+
children:
|
|
97
|
+
- topic: HTML
|
|
98
|
+
- topic: CSS
|
|
99
|
+
- topic: JavaScript
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
# Backend
|
|
103
|
+
|
|
104
|
+
```elixir-mind
|
|
105
|
+
root:
|
|
106
|
+
topic: Backend
|
|
107
|
+
children:
|
|
108
|
+
- topic: Node.js
|
|
109
|
+
- topic: Python
|
|
110
|
+
- topic: Rust
|
|
111
|
+
```
|
|
112
|
+
````
|
|
113
|
+
|
|
114
|
+
The client runtime is injected once when at least one mind map is present.
|
|
115
|
+
|
|
116
|
+
## Options
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
remarkMindElixir({
|
|
120
|
+
useCdn: true,
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `useCdn`
|
|
125
|
+
|
|
126
|
+
Controls whether the plugin injects the Mind Elixir browser runtime from a CDN.
|
|
127
|
+
|
|
128
|
+
**Type:** `boolean`
|
|
129
|
+
|
|
130
|
+
**Default:** `true`
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
remarkMindElixir({
|
|
134
|
+
useCdn: false,
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
When disabled, the plugin does not inject the CDN runtime. Your application must provide the client-side Mind Elixir runtime.
|
|
139
|
+
|
|
140
|
+
## Client Runtime
|
|
141
|
+
|
|
142
|
+
By default, the plugin injects the Mind Elixir runtime using `esm.sh`.
|
|
143
|
+
|
|
144
|
+
The generated Markdown contains a container similar to:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<div
|
|
148
|
+
data-elixir-mind
|
|
149
|
+
data-xmind-body="..."
|
|
150
|
+
data-xmind-config="..."
|
|
151
|
+
></div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The client runtime finds these containers and mounts a Mind Elixir map into each one.
|
|
155
|
+
|
|
156
|
+
## How It Works
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
Markdown
|
|
160
|
+
│
|
|
161
|
+
▼
|
|
162
|
+
remark
|
|
163
|
+
│
|
|
164
|
+
▼
|
|
165
|
+
remark-mind-elixir
|
|
166
|
+
│
|
|
167
|
+
├── Find `elixir-mind` blocks
|
|
168
|
+
│
|
|
169
|
+
├── Parse YAML
|
|
170
|
+
│
|
|
171
|
+
├── Convert YAML to Mind Elixir data
|
|
172
|
+
│
|
|
173
|
+
└── Generate HTML container
|
|
174
|
+
│
|
|
175
|
+
▼
|
|
176
|
+
Client runtime
|
|
177
|
+
│
|
|
178
|
+
▼
|
|
179
|
+
Mind Elixir map
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Ecosystem
|
|
183
|
+
|
|
184
|
+
`remark-mind-elixir` is designed to work with remark-compatible Markdown pipelines, including:
|
|
185
|
+
|
|
186
|
+
* [Remark](https://github.com/remarkjs/remark)
|
|
187
|
+
* [Unified](https://unifiedjs.com/)
|
|
188
|
+
* [MDX](https://mdxjs.com/)
|
|
189
|
+
* [Astro](https://astro.build/)
|
|
190
|
+
* [Vite](https://vite.dev/)
|
|
191
|
+
|
|
192
|
+
It is built on top of ZikoJS utilities while remaining a remark plugin rather than a ZikoJS-specific Markdown processor.
|
|
193
|
+
|
|
194
|
+
## Related Projects
|
|
195
|
+
|
|
196
|
+
* [ZikoJS](https://github.com/zikojs)
|
|
197
|
+
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core)
|
|
198
|
+
* [Remark](https://github.com/remarkjs/remark)
|
|
199
|
+
* [Unified](https://unifiedjs.com/)
|
|
200
|
+
|
|
201
|
+
<!-- ## Documentation -->
|
|
202
|
+
|
|
203
|
+
<!-- See the [ZikoJS Remark Plugins documentation](https://remark.zikojs.org/) for guides, syntax references, and examples. -->
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT
|
package/package.json
CHANGED
package/src/client.js
CHANGED
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { visit } from 'unist-util-visit'
|
|
2
|
-
import { parse } from
|
|
2
|
+
import { parse, parseAllDocuments } from "yaml";
|
|
3
3
|
|
|
4
4
|
import { yaml2MindElixirData } from '@zikojs/mind-elixir/utils'
|
|
5
5
|
|
|
@@ -12,25 +12,35 @@ const remarkElixirMind = ({
|
|
|
12
12
|
visit(tree, 'code', (node, index, parent) => {
|
|
13
13
|
if (node.lang !== 'elixir-mind') return
|
|
14
14
|
|
|
15
|
-
hasElixirMind = true
|
|
15
|
+
hasElixirMind = true;
|
|
16
|
+
let config = {}, body = null;
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const documents = splitDocuments(node.value.trim());
|
|
19
|
+
if(documents.length === 1) body = yaml2MindElixirData(documents[0])
|
|
20
|
+
else {
|
|
21
|
+
config = parse(documents[0])
|
|
22
|
+
body = yaml2MindElixirData(documents[1])
|
|
23
|
+
}
|
|
18
24
|
|
|
19
25
|
// parent creates circular references, so serialize a clean copy
|
|
20
|
-
const
|
|
26
|
+
const serialized_body = JSON.stringify(body, (key, value) => {
|
|
21
27
|
if (key === 'parent') return undefined
|
|
22
28
|
return value
|
|
23
29
|
})
|
|
24
30
|
|
|
31
|
+
const serialized_config = JSON.stringify(config)
|
|
32
|
+
|
|
25
33
|
// Escape the JSON for use inside an HTML attribute
|
|
26
|
-
const
|
|
34
|
+
const encoded_body = escapeHtmlAttribute(serialized_body)
|
|
35
|
+
const encoded_config = escapeHtmlAttribute(serialized_config)
|
|
27
36
|
|
|
28
37
|
parent.children[index] = {
|
|
29
38
|
type: 'html',
|
|
30
39
|
value: `
|
|
31
40
|
<div
|
|
32
41
|
data-elixir-mind
|
|
33
|
-
data-xmind-body="${
|
|
42
|
+
data-xmind-body="${encoded_body}"
|
|
43
|
+
data-xmind-config="${encoded_config}"
|
|
34
44
|
></div>
|
|
35
45
|
`
|
|
36
46
|
}
|
|
@@ -45,24 +55,20 @@ const remarkElixirMind = ({
|
|
|
45
55
|
@import url('https://esm.sh/mind-elixir/style')
|
|
46
56
|
</style>
|
|
47
57
|
|
|
48
|
-
<script type="module">
|
|
49
|
-
import 'https://esm.sh/remark-mind-elixir/client'
|
|
58
|
+
<script type="module" data-engine="zikojs, remark, mind-elixir">
|
|
50
59
|
import { MindMap } from 'https://esm.sh/@zikojs/mind-elixir@latest/src/mind/main.js'
|
|
51
|
-
|
|
52
|
-
document.querySelectorAll('[data-elixir-mind]').forEach((element) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
direction: 2
|
|
62
|
-
}, nodeData)
|
|
63
|
-
|
|
64
|
-
map.mount(element)
|
|
60
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
61
|
+
document.querySelectorAll('[data-elixir-mind]').forEach((element) => {
|
|
62
|
+
const raw = element.dataset.xmindBody
|
|
63
|
+
// const config = element.dataset?.xmindConfig
|
|
64
|
+
if (!raw) return
|
|
65
|
+
const nodeData = JSON.parse(raw)
|
|
66
|
+
// const nodeConfig = JSON.parse(config)
|
|
67
|
+
const map = MindMap({}, nodeData)
|
|
68
|
+
map.mount(element)
|
|
69
|
+
})
|
|
65
70
|
})
|
|
71
|
+
|
|
66
72
|
</script>
|
|
67
73
|
`
|
|
68
74
|
})
|
|
@@ -77,4 +83,61 @@ function escapeHtmlAttribute(value) {
|
|
|
77
83
|
.replace(/>/g, '>')
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
export default remarkElixirMind
|
|
86
|
+
export default remarkElixirMind
|
|
87
|
+
|
|
88
|
+
const splitDocuments = (text) => {
|
|
89
|
+
const documents = []
|
|
90
|
+
let current = ''
|
|
91
|
+
|
|
92
|
+
let quote = null
|
|
93
|
+
let escaped = false
|
|
94
|
+
|
|
95
|
+
const lines = text.trim().split('\n')
|
|
96
|
+
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
const isSeparator = line.match(/^---[ \t]*$/)
|
|
99
|
+
|
|
100
|
+
if (isSeparator && !quote) {
|
|
101
|
+
if (current.trim()) {
|
|
102
|
+
documents.push(current.trim())
|
|
103
|
+
}
|
|
104
|
+
current = ''
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
current += (current ? '\n' : '') + line
|
|
109
|
+
|
|
110
|
+
// Track JS string state
|
|
111
|
+
for (let i = 0; i < line.length; i++) {
|
|
112
|
+
const char = line[i]
|
|
113
|
+
|
|
114
|
+
if (escaped) {
|
|
115
|
+
escaped = false
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (char === '\\') {
|
|
120
|
+
escaped = true
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (quote) {
|
|
125
|
+
if (char === quote) {
|
|
126
|
+
quote = null
|
|
127
|
+
}
|
|
128
|
+
} else if (
|
|
129
|
+
char === "'" ||
|
|
130
|
+
char === '"' ||
|
|
131
|
+
char === '`'
|
|
132
|
+
) {
|
|
133
|
+
quote = char
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (current.trim()) {
|
|
139
|
+
documents.push(current.trim())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return documents
|
|
143
|
+
}
|