Mind Elixir オープンソースの JavaScript マインドマップコア

<p align="center">
  <a href="mind-elixir.com" target="_blank" rel="noopener noreferrer">
    <img width="150" src="https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png" alt="mindelixir logo2">
  </a>
  <h1 align="center">Mind Elixir</h1>
</p>

https://github.com/SSShooter/mind-elixir-core

Mind Elixir はオープンソースの JavaScript マインドマップコアです。お好きなフロントエンドフレームワークと一緒に使用できます。

特徴:

- 軽量
- 高性能
- フレームワークに依存しない
- プラグイン可能
- SVG または PNG としてエクスポート
- ドラッグ＆ドロップ/ノード編集プラグイン内蔵
- ノードの要約
- 一括操作対応
- アンドゥ/リドゥ
- 効率的なショートカット
- CSS でノードをスタイリング

<details>
<summary>目次</summary>

- [今すぐ試す](#今すぐ試す)
  - [プレイグラウンド](#プレイグラウンド)
- [ドキュメント](#ドキュメント)
- [使用方法](#使用方法)
  - [インストール](#インストール)
    - [NPM](#npm)
    - [スクリプトタグ](#スクリプトタグ)
  - [初期化](#初期化)
  - [データ構造](#データ構造)
  - [イベントハンドリング](#イベントハンドリング)
  - [データのエクスポートとインポート](#データのエクスポートとインポート)
  - [操作ガード](#操作ガード)
- [画像としてエクスポート](#画像としてエクスポート)
  - [解決策 1](#解決策-1)
  - [解決策 2](#解決策-2)
- [API](#api)
- [テーマ](#テーマ)
- [ショートカット](#ショートカット)
- [コアだけではない](#コアだけではない)
- [開発](#開発)
- [感謝](#感謝)

</details>

## 今すぐ試す

![mindelixir](https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/screenshot2.png)

https://mind-elixir.com/

### プレイグラウンド

- Vanilla JS - https://codepen.io/ssshooter/pen/OJrJowN
- React - https://codesandbox.io/s/mind-elixir-3-x-react-18-x-vy9fcq
- Vue3 - https://codesandbox.io/s/mind-elixir-3-x-vue3-lth484
- Vue2 - https://codesandbox.io/s/mind-elixir-3-x-vue-2-x-5kdfjp

## ドキュメント

https://docs.mind-elixir.com/

## 使用方法

### インストール

#### NPM

```bash
npm i mind-elixir -S
```

```javascript
import MindElixir from 'mind-elixir'
```

#### スクリプトタグ

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/mind-elixir/dist/MindElixir.js"></script>
```

### 初期化

```html
<div id="map"></div>
<style>
  #map {
    height: 500px;
    width: 100%;
  }
</style>
```

**重大な変更** 1.0.0 以降、`data`は`init()`に渡す必要があります。`options`ではありません。

```javascript
import MindElixir from 'mind-elixir'
import example from 'mind-elixir/dist/example1'

let options = {
  el: '#map', // または HTMLDivElement
  direction: MindElixir.LEFT,
  draggable: true, // デフォルトは true
  contextMenu: true, // デフォルトは true
  toolBar: true, // デフォルトは true
  nodeMenu: true, // デフォルトは true
  keypress: true, // デフォルトは true
  locale: 'en', // [zh_CN,zh_TW,en,ja,pt,ru] PRをお待ちしています
  overflowHidden: false, // デフォルトは false
  mainLinkStyle: 2, // [1,2] デフォルトは 1
  mouseSelectionButton: 0, // 0は左ボタン、2は右ボタン、デフォルトは 0
  contextMenuOption: {
    focus: true,
    link: true,
    extend: [
      {
        name: 'ノード編集',
        onclick: () => {
          alert('拡張メニュー')
        },
      },
    ],
  },
  before: {
    insertSibling(el, obj) {
      return true
    },
    async addChild(el, obj) {
      await sleep()
      return true
    },
  },
}

let mind = new MindElixir(options)

mind.install(plugin) // プラグインをインストール

// 新しいマップデータを作成
const data = MindElixir.new('新しいトピック')
// または `example`
// または `.getData()`から返されるデータ
mind.init(data)

// ノードを取得
MindElixir.E('node-id')
```

### データ構造

```javascript
// 現在のノードデータ構造
const nodeData = {
  topic: 'ノードトピック',
  id: 'bd1c24420cd2c2f5',
  style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' },
  expanded: true,
  parent: null,
  tags: ['タグ'],
  icons: ['😀'],
  hyperLink: 'https://github.com/ssshooter/mind-elixir-core',
  image: {
    url: 'https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png', // 必須
    // 画像の高さと幅を計算して適切な値を設定する必要があります
    height: 90, // 必須
    width: 90, // 必須
  },
  children: [
    {
      topic: '子',
      id: 'xxxx',
      // ...
    },
  ],
}
```

### イベントハンドリング

```javascript
mind.bus.addListener('operation', operation => {
  console.log(operation)
  // return {
  //   name: アクション名,
  //   obj: 対象オブジェクト
  // }

  // name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
  // obj: 対象

  // name: moveNode
  // obj: {from:対象1,to:対象2}
})

mind.bus.addListener('selectNode', node => {
  console.log(node)
})

mind.bus.addListener('expandNode', node => {
  console.log('expandNode: ', node)
})
```

### データのエクスポートとインポート

```javascript
// データのエクスポート
const data = mind.getData() // JavaScriptオブジェクト、src/example.jsを参照
mind.getDataString() // オブジェクトを文字列化
mind.getDataMd() // マークダウン

// データのインポート
// 初期化
let mind = new MindElixir(options)
mind.init(data)
// データの更新
mind.refresh(data)
```

### 操作ガード

```javascript
let mind = new MindElixir({
  // ...
  before: {
    insertSibling(el, obj) {
      console.log(el, obj)
      if (this.currentNode.nodeObj.parent.root) {
        return false
      }
      return true
    },
    async addChild(el, obj) {
      await sleep()
      if (this.currentNode.nodeObj.parent.root) {
        return false
      }
      return true
    },
  },
})
```

## 画像としてエクスポート

### 解決策 1

```typescript
const mind = {
  /** mind elixir インスタンス */
}
const downloadPng = async () => {
  const blob = await mind.exportPng() // Blobを取得！
  if (!blob) return
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'filename.png'
  a.click()
  URL.revokeObjectURL(url)
}
```

### 解決策 2

`@ssshooter/modern-screenshot`をインストールし、次に：

```typescript
import { domToPng } from '@ssshooter/modern-screenshot'

const download = async () => {
  const dataUrl = await domToPng(mind.nodes, {
    onCloneNode: node => {
      const n = node as HTMLDivElement
      n.style.position = ''
      n.style.top = ''
      n.style.left = ''
      n.style.bottom = ''
      n.style.right = ''
    },
    padding: 300,
    quality: 1,
  })
  const link = document.createElement('a')
  link.download = 'screenshot.png'
  link.href = dataUrl
  link.click()
}
```

## API

https://github.com/ssshooter/mind-elixir-core/blob/master/api/mind-elixir.api.md

## テーマ

```javascript
const options = {
  // ...
  theme: {
    name: 'Dark',
    // メインラインのカラーパレット
    palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
    // CSS変数の上書き
    cssVar: {
      '--main-color': '#ffffff',
      '--main-bgcolor': '#4c4f69',
      '--color': '#cccccc',
      '--bgcolor': '#252526',
      '--panel-color': '255, 255, 255',
      '--panel-bgcolor': '45, 55, 72',
    },
    // すべての変数は /src/index.less を参照
  },
  // ...
}

// ...

mind.changeTheme({
  name: 'Latte',
  palette: ['#dd7878', '#ea76cb', '#8839ef', '#e64553', '#fe640b', '#df8e1d', '#40a02b', '#209fb5', '#1e66f5', '#7287fd'],
  cssVar: {
    '--main-color': '#444446',
    '--main-bgcolor': '#ffffff',
    '--color': '#777777',
    '--bgcolor': '#f6f6f6',
  },
})
```

Mind Elixir は`prefers-color-scheme`の変更を監視しません。スキームが変更された場合は、テーマを**手動で**変更してください。

## ショートカット

| ショートカット     | 機能                             |
| ------------------ | -------------------------------- |
| Enter              | 同じレベルのノードを挿入         |
| Tab                | 子ノードを挿入                   |
| F1                 | マップを中央に配置               |
| F2                 | 現在のノードの編集を開始         |
| ↑                  | 前の同じレベルのノードを選択     |
| ↓                  | 次の同じレベルのノードを選択     |
| ← / →              | 親または最初の子ノードを選択     |
| PageUp / Alt + ↑   | ノードを上に移動                 |
| PageDown / Alt + ↓ | ノードを下に移動                 |
| Ctrl + ↑           | レイアウトパターンをサイドに変更 |
| Ctrl + ←           | レイアウトパターンを左に変更     |
| Ctrl + →           | レイアウトパターンを右に変更     |
| Ctrl + C           | 現在のノードをコピー             |
| Ctrl + V           | コピーしたノードを貼り付け       |
| Ctrl + "+"         | マインドマップをズームイン       |
| Ctrl + "-"         | マインドマップをズームアウト     |
| Ctrl + 0           | ズームレベルをリセット           |

## コアだけではない

- [@mind-elixir/node-menu](https://github.com/ssshooter/node-menu)
- [@mind-elixir/node-menu-neo](https://github.com/ssshooter/node-menu-neo)
- [@mind-elixir/export-xmind](https://github.com/ssshooter/export-xmind)
- [@mind-elixir/export-html](https://github.com/ssshooter/export-html)
- [mind-elixir-react](https://github.com/ssshooter/mind-elixir-react)

## 開発

```
pnpm i
pnpm dev
```

`dev.dist.ts`で生成されたファイルをテスト：

```
pnpm build
pnpm link ./
```

ドキュメントを更新：

```
# api-extractorをインストール
pnpm install -g @microsoft/api-extractor
# /src/docs.tsを維持
# ドキュメントを生成
pnpm doc
pnpm doc:md
```

## 感謝

- [@viselect/vanilla](https://github.com/simonwep/selection/tree/master/packages/vanilla)