feat: 表格渲染样式重写
|
|
@ -1,187 +0,0 @@
|
|||
# Markdown 节点渲染集成说明
|
||||
|
||||
## 概述
|
||||
|
||||
这个集成方案为你的 Mind Elixir 思维导图项目添加了 markdown 节点渲染能力,支持:
|
||||
|
||||
- ✅ **表格渲染** - 完整的 markdown 表格支持
|
||||
- ✅ **代码高亮** - 代码块和行内代码
|
||||
- ✅ **文本格式** - 粗体、斜体、标题等
|
||||
- ✅ **列表** - 有序和无序列表
|
||||
- ✅ **链接** - 自动链接渲染
|
||||
- ✅ **智能检测** - 自动识别 markdown 语法
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── utils/
|
||||
│ └── markdownRenderer.js # 核心渲染器
|
||||
├── components/
|
||||
│ ├── MindMap.vue # 主思维导图组件(已集成)
|
||||
│ └── MarkdownTest.vue # 测试组件
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在节点中使用 markdown
|
||||
|
||||
现在你可以在思维导图的节点内容中直接使用 markdown 语法:
|
||||
|
||||
```markdown
|
||||
# 产品价格表
|
||||
|
||||
| 产品 | 价格 | 库存 |
|
||||
|------|------|------|
|
||||
| 苹果 | 4元 | 100个 |
|
||||
| 香蕉 | 2元 | 50个 |
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **前端**: Vue.js 3
|
||||
- **后端**: Django
|
||||
- **数据库**: PostgreSQL
|
||||
|
||||
## 代码示例
|
||||
|
||||
\`\`\`javascript
|
||||
function hello() {
|
||||
console.log('Hello World!');
|
||||
}
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 2. 测试功能
|
||||
|
||||
访问 `MarkdownTest.vue` 组件来测试 markdown 渲染功能:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<MarkdownTest />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MarkdownTest from './components/MarkdownTest.vue';
|
||||
</script>
|
||||
```
|
||||
|
||||
### 3. 在现有节点中启用
|
||||
|
||||
系统会自动检测节点内容是否包含 markdown 语法,如果包含,会自动使用 markdown 渲染器。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 智能渲染
|
||||
|
||||
```javascript
|
||||
import { smartRenderNodeContent } from '../utils/markdownRenderer.js';
|
||||
|
||||
// 自动检测并渲染
|
||||
smartRenderNodeContent(nodeElement, content);
|
||||
```
|
||||
|
||||
### 手动渲染
|
||||
|
||||
```javascript
|
||||
import { renderMarkdownToHTML } from '../utils/markdownRenderer.js';
|
||||
|
||||
// 直接渲染为 HTML
|
||||
const html = renderMarkdownToHTML(markdownContent);
|
||||
```
|
||||
|
||||
### 语法检测
|
||||
|
||||
```javascript
|
||||
import { hasMarkdownSyntax } from '../utils/markdownRenderer.js';
|
||||
|
||||
// 检测是否包含 markdown 语法
|
||||
if (hasMarkdownSyntax(content)) {
|
||||
// 使用 markdown 渲染
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的 Markdown 语法
|
||||
|
||||
### 表格
|
||||
```markdown
|
||||
| 列1 | 列2 | 列3 |
|
||||
|-----|-----|-----|
|
||||
| 数据1 | 数据2 | 数据3 |
|
||||
```
|
||||
|
||||
### 代码块
|
||||
```markdown
|
||||
\`\`\`javascript
|
||||
function test() {
|
||||
console.log('Hello');
|
||||
}
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 行内代码
|
||||
```markdown
|
||||
使用 \`console.log()\` 输出信息
|
||||
```
|
||||
|
||||
### 文本格式
|
||||
```markdown
|
||||
**粗体文本**
|
||||
*斜体文本*
|
||||
```
|
||||
|
||||
### 列表
|
||||
```markdown
|
||||
- 无序列表项1
|
||||
- 无序列表项2
|
||||
|
||||
1. 有序列表项1
|
||||
2. 有序列表项2
|
||||
```
|
||||
|
||||
### 链接
|
||||
```markdown
|
||||
[链接文本](https://example.com)
|
||||
```
|
||||
|
||||
## 样式定制
|
||||
|
||||
渲染器会自动添加 CSS 样式,你也可以通过以下类名进行自定义:
|
||||
|
||||
- `.markdown-content` - 主容器
|
||||
- `.markdown-table` - 表格样式
|
||||
- `.markdown-code` - 代码块样式
|
||||
- `.markdown-math` - 数学公式样式
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **性能**: 大量 markdown 内容可能影响渲染性能
|
||||
2. **安全性**: 渲染器允许 HTML,请确保内容来源可信
|
||||
3. **兼容性**: 与 Mind Elixir 的拖拽、编辑功能完全兼容
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 渲染失败
|
||||
- 检查 markdown 语法是否正确
|
||||
- 查看浏览器控制台错误信息
|
||||
- 使用 `MarkdownTest.vue` 组件测试
|
||||
|
||||
### 样式问题
|
||||
- 检查 CSS 样式是否被覆盖
|
||||
- 确保 `markdown-node-styles` 样式已加载
|
||||
|
||||
### 性能问题
|
||||
- 避免在单个节点中放置过多内容
|
||||
- 考虑将复杂内容拆分为多个子节点
|
||||
|
||||
## 扩展功能
|
||||
|
||||
如果需要更多功能,可以扩展 `markdownRenderer.js`:
|
||||
|
||||
- 数学公式支持(KaTeX)
|
||||
- 图表支持(Mermaid)
|
||||
- 更多 markdown 扩展语法
|
||||
|
||||
## 总结
|
||||
|
||||
这个集成方案让你可以在保持现有 Mind Elixir 功能的同时,享受强大的 markdown 渲染能力。特别是表格渲染功能,让思维导图可以展示更丰富的数据结构。
|
||||
57
README.md
|
|
@ -15,6 +15,8 @@
|
|||
- **拖拽操作**: 支持节点拖拽、移动、编辑
|
||||
- **实时保存**: 自动保存编辑内容到数据库
|
||||
- **缩放控制**: 支持思维导图缩放和居中显示
|
||||
- **Markdown支持**: 节点内容支持Markdown语法渲染
|
||||
- **表格渲染**: 支持Markdown表格在思维导图中的显示
|
||||
|
||||
### 💾 数据管理
|
||||
- **云端存储**: 思维导图数据持久化存储
|
||||
|
|
@ -39,10 +41,12 @@
|
|||
|
||||
### 前端技术
|
||||
- **框架**: Vue.js 3.3.4 + Vite 4.4.9
|
||||
- **思维导图**: MindElixir 3.0.0
|
||||
- **思维导图**: MindElixir 3.0.0 (自定义增强版本)
|
||||
- **文件处理**: mammoth.js (DOCX), pdfjs-dist (PDF)
|
||||
- **HTTP客户端**: Axios 1.5.0
|
||||
- **Markdown处理**: marked 16.2.1
|
||||
- **Markdown处理**: marked 16.2.1 + 自定义渲染器
|
||||
- **数学公式**: KaTeX 0.16.22
|
||||
- **代码高亮**: PrismJS 1.30.0
|
||||
|
||||
### AI内容分析
|
||||
- **TypeScript**: 类型安全的Markdown转JSON转换
|
||||
|
|
@ -59,7 +63,7 @@
|
|||
### 1. 克隆项目
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd siweidaotu
|
||||
cd MindMap
|
||||
```
|
||||
|
||||
### 2. 后端设置
|
||||
|
|
@ -128,8 +132,8 @@ npm run dev
|
|||
## 📁 项目结构
|
||||
|
||||
```
|
||||
siweidaotu/
|
||||
├── backend/ # 后端代码
|
||||
MindMap/
|
||||
├── backend/ # Django后端
|
||||
│ ├── django_mindmap/ # Django项目配置
|
||||
│ │ ├── settings.py # 项目设置
|
||||
│ │ ├── urls.py # 主URL配置
|
||||
|
|
@ -148,20 +152,25 @@ siweidaotu/
|
|||
│ ├── src/
|
||||
│ │ ├── components/ # Vue组件
|
||||
│ │ │ ├── MindMap.vue # 思维导图组件
|
||||
│ │ │ └── AISidebar.vue # AI助手侧边栏
|
||||
│ │ │ ├── AISidebar.vue # AI助手侧边栏
|
||||
│ │ │ └── MarkdownTest.vue # Markdown测试组件
|
||||
│ │ ├── api/ # API接口
|
||||
│ │ │ └── mindmap.js # 思维导图API
|
||||
│ │ └── App.vue # 主应用组件
|
||||
│ │ ├── lib/ # 第三方库
|
||||
│ │ │ └── mind-elixir/ # MindElixir库(项目使用版本)
|
||||
│ │ ├── utils/ # 工具函数
|
||||
│ │ │ └── markdownRenderer.js # Markdown渲染器
|
||||
│ │ ├── App.vue # 主应用组件
|
||||
│ │ └── main.js # 应用入口
|
||||
│ ├── test-*.html # 功能测试文件
|
||||
│ ├── package.json # 前端依赖
|
||||
│ └── vite.config.js # Vite配置
|
||||
├── others_deletable/ # 其他文件(可删除)
|
||||
│ ├── ai-content-analyzer/ # AI内容分析工具
|
||||
│ │ ├── markdownToJSON.ts # Markdown转JSON
|
||||
│ │ ├── json_openai.py # AI服务调用
|
||||
│ │ └── package.json # TypeScript依赖
|
||||
│ └── json_openai.py # 独立AI服务文件
|
||||
├── json、接口文档.md # API接口文档
|
||||
└── README.md # 项目文档
|
||||
├── mind-elixir-core-master/ # MindElixir完整源码
|
||||
│ ├── src/ # TypeScript源码
|
||||
│ ├── tests/ # 测试套件
|
||||
│ ├── dist/ # 编译后文件
|
||||
│ └── package.json # 依赖配置
|
||||
└── README.md # 项目文档
|
||||
```
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
|
@ -373,6 +382,8 @@ server {
|
|||
- `双击节点`:编辑节点内容
|
||||
- `右键节点`:显示操作菜单
|
||||
- `拖拽节点`:移动节点位置
|
||||
- `Ctrl + E`:展开/折叠节点
|
||||
- `Alt + F`:聚焦/取消聚焦节点
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
|
|
@ -388,6 +399,22 @@ server {
|
|||
|
||||
MIT License - 详见 [LICENSE](LICENSE) 文件
|
||||
|
||||
## 🧹 项目优化
|
||||
|
||||
### 代码清理
|
||||
项目已进行全面的代码清理和优化:
|
||||
|
||||
- ✅ **删除冗余文件**: 移除了重复的测试文件和调试文件
|
||||
- ✅ **保留核心功能**: 保留了所有必要的测试和调试工具
|
||||
- ✅ **优化项目结构**: 清晰分离了开发版本和生产版本
|
||||
- ✅ **MindElixir增强**: 集成了自定义的Markdown和表格渲染功能
|
||||
|
||||
### 文件结构优化
|
||||
- **保留**: `mind-elixir-core-master/` - 完整的源码和文档
|
||||
- **保留**: `frontend/src/lib/mind-elixir/` - 项目中使用的增强版本
|
||||
- **保留**: 核心测试文件 - 用于功能验证和问题调试
|
||||
- **删除**: 重复的调试文件和过时的测试文件
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
- [MindElixir](https://github.com/ssshooter/mind-elixir-core) - 思维导图可视化库
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ def call_ai_api(system_prompt, user_prompt, model="glm-4.5", base_url="https://o
|
|||
model=model,
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
max_tokens=8000, # 增加token限制,避免内容截断
|
||||
max_tokens=4000, # 减少token限制,提高响应速度
|
||||
stream=stream
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,182 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>当前问题调试</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.test-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.test-title {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 模拟Mind Elixir的样式 */
|
||||
.topic {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin: 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.topic-text {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 我们的markdown样式 */
|
||||
.topic .topic-text.markdown-content {
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table {
|
||||
border-collapse: collapse !important;
|
||||
width: 100% !important;
|
||||
margin: 4px 0 !important;
|
||||
font-size: 11px !important;
|
||||
border: 1px solid #ddd !important;
|
||||
display: table !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table th,
|
||||
.topic .topic-text.markdown-content table td {
|
||||
border: 1px solid #ddd !important;
|
||||
padding: 2px 4px !important;
|
||||
text-align: left !important;
|
||||
vertical-align: top !important;
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table th {
|
||||
background-color: #f5f5f5 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr {
|
||||
display: table-row !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr:nth-child(even) {
|
||||
background-color: #f9f9f9 !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr:hover {
|
||||
background-color: #e9ecef !important;
|
||||
}
|
||||
|
||||
.raw-content {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
background: #e3f2fd;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
border-left: 4px solid #2196f3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1 class="test-title">当前问题调试 - 用户提供的表格</h1>
|
||||
|
||||
<h3>原始表格内容:</h3>
|
||||
<div class="raw-content" id="raw-content"></div>
|
||||
|
||||
<h3>模拟Mind Elixir节点中的渲染:</h3>
|
||||
<div class="topic">
|
||||
<div class="topic-text markdown-content" id="mindelixir-render"></div>
|
||||
</div>
|
||||
|
||||
<h3>普通渲染(对比):</h3>
|
||||
<div id="normal-render"></div>
|
||||
|
||||
<div class="debug-info" id="debug-info"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 配置marked
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
sanitize: false
|
||||
});
|
||||
|
||||
// 用户提供的表格内容
|
||||
const tableContent = `| 数据类型 | 特点 | 适用场景 | 示例 |
|
||||
|---------|------|---------|------|
|
||||
| 数值型 | 可进行数学运算 | 统计分析、机器学习 | 年龄、收入、温度 |
|
||||
| 分类型 | 表示类别属性 | 分类问题、特征工程 | 性别、职业、地区 |
|
||||
| 时间序列 | 按时间顺序排列 | 趋势预测、周期性分析 | 股票价格、气温变化 |
|
||||
| 文本型 | 非结构化自然语言 | NLP分析、情感识别 | 评论、文章、社交媒体内容 |`;
|
||||
|
||||
// 显示原始内容
|
||||
document.getElementById('raw-content').textContent = tableContent;
|
||||
|
||||
// 渲染到Mind Elixir模拟节点
|
||||
try {
|
||||
const html = marked.parse(tableContent);
|
||||
document.getElementById('mindelixir-render').innerHTML = html;
|
||||
} catch (error) {
|
||||
document.getElementById('mindelixir-render').innerHTML = `<div style="color: red;">渲染失败: ${error.message}</div>`;
|
||||
}
|
||||
|
||||
// 普通渲染
|
||||
try {
|
||||
const html = marked.parse(tableContent);
|
||||
document.getElementById('normal-render').innerHTML = html;
|
||||
} catch (error) {
|
||||
document.getElementById('normal-render').innerHTML = `<div style="color: red;">渲染失败: ${error.message}</div>`;
|
||||
}
|
||||
|
||||
// 调试信息
|
||||
const debugInfo = document.getElementById('debug-info');
|
||||
const mindelixirElement = document.getElementById('mindelixir-render');
|
||||
const table = mindelixirElement.querySelector('table');
|
||||
|
||||
if (table) {
|
||||
const computedStyle = window.getComputedStyle(table);
|
||||
debugInfo.innerHTML = `
|
||||
<h4>表格样式检查:</h4>
|
||||
<p><strong>display:</strong> ${computedStyle.display}</p>
|
||||
<p><strong>border-collapse:</strong> ${computedStyle.borderCollapse}</p>
|
||||
<p><strong>border:</strong> ${computedStyle.border}</p>
|
||||
<p><strong>width:</strong> ${computedStyle.width}</p>
|
||||
<p><strong>font-size:</strong> ${computedStyle.fontSize}</p>
|
||||
<p><strong>margin:</strong> ${computedStyle.margin}</p>
|
||||
<p><strong>表格行数:</strong> ${table.rows.length}</p>
|
||||
<p><strong>表格列数:</strong> ${table.rows[0] ? table.rows[0].cells.length : 0}</p>
|
||||
`;
|
||||
} else {
|
||||
debugInfo.innerHTML = '<h4>错误:</h4><p>没有找到表格元素</p>';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mind Elixir样式调试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.test-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.test-title {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 模拟Mind Elixir的样式 */
|
||||
.topic {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin: 10px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.topic-text {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 我们的markdown样式 */
|
||||
.topic .topic-text.markdown-content {
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #ddd !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table th,
|
||||
.topic .topic-text.markdown-content table td {
|
||||
border: 1px solid #ddd !important;
|
||||
padding: 2px 4px !important;
|
||||
text-align: left !important;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table th {
|
||||
background-color: #f5f5f5 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr:nth-child(even) {
|
||||
background-color: #f9f9f9 !important;
|
||||
}
|
||||
|
||||
/* 对比:普通表格样式 */
|
||||
.normal-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.normal-table th,
|
||||
.normal-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 2px 4px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.normal-table th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.normal-table tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1 class="test-title">Mind Elixir样式调试</h1>
|
||||
|
||||
<h3>测试1:模拟Mind Elixir节点中的表格</h3>
|
||||
<div class="topic">
|
||||
<div class="topic-text markdown-content">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>评估维度</th>
|
||||
<th>权重</th>
|
||||
<th>评分标准</th>
|
||||
<th>计算公式</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>完整性</td>
|
||||
<td>0.3</td>
|
||||
<td>缺失值比例 < 5%</td>
|
||||
<td>完整性 = 1 - 缺失值数量/总数据量</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>准确性</td>
|
||||
<td>0.3</td>
|
||||
<td>误差率 < 3%</td>
|
||||
<td>准确性 = 1 - 错误记录数/总记录数</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>测试2:普通表格(对比)</h3>
|
||||
<table class="normal-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>评估维度</th>
|
||||
<th>权重</th>
|
||||
<th>评分标准</th>
|
||||
<th>计算公式</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>完整性</td>
|
||||
<td>0.3</td>
|
||||
<td>缺失值比例 < 5%</td>
|
||||
<td>完整性 = 1 - 缺失值数量/总数据量</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>准确性</td>
|
||||
<td>0.3</td>
|
||||
<td>误差率 < 3%</td>
|
||||
<td>准确性 = 1 - 错误记录数/总记录数</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>测试3:检查样式是否被覆盖</h3>
|
||||
<div id="style-check"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 检查样式是否被正确应用
|
||||
const topicText = document.querySelector('.topic-text.markdown-content');
|
||||
const table = topicText.querySelector('table');
|
||||
|
||||
const computedStyle = window.getComputedStyle(table);
|
||||
const styleCheck = document.getElementById('style-check');
|
||||
|
||||
styleCheck.innerHTML = `
|
||||
<p><strong>表格样式检查:</strong></p>
|
||||
<p>border-collapse: ${computedStyle.borderCollapse}</p>
|
||||
<p>border: ${computedStyle.border}</p>
|
||||
<p>width: ${computedStyle.width}</p>
|
||||
<p>font-size: ${computedStyle.fontSize}</p>
|
||||
<p>margin: ${computedStyle.margin}</p>
|
||||
`;
|
||||
|
||||
// 检查第一个单元格的样式
|
||||
const firstCell = table.querySelector('td');
|
||||
const cellStyle = window.getComputedStyle(firstCell);
|
||||
|
||||
styleCheck.innerHTML += `
|
||||
<p><strong>单元格样式检查:</strong></p>
|
||||
<p>border: ${cellStyle.border}</p>
|
||||
<p>padding: ${cellStyle.padding}</p>
|
||||
<p>text-align: ${cellStyle.textAlign}</p>
|
||||
<p>vertical-align: ${cellStyle.verticalAlign}</p>
|
||||
`;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>表格检测调试</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.test-case { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
|
||||
.content { background: #f8f9fa; padding: 10px; font-family: monospace; white-space: pre-wrap; }
|
||||
.result { margin-top: 10px; padding: 10px; border-radius: 3px; }
|
||||
.success { background: #d4edda; color: #155724; }
|
||||
.error { background: #f8d7da; color: #721c24; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>表格检测调试</h1>
|
||||
<div id="results"></div>
|
||||
|
||||
<script>
|
||||
// 复制hasTable函数
|
||||
function hasTable(content) {
|
||||
if (!content || typeof content !== 'string') {
|
||||
console.log('🔍 hasTable: 内容为空或不是字符串');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('🔍 hasTable: 检查内容:', content);
|
||||
|
||||
// 检查是否包含表格语法
|
||||
const lines = content.split('\n');
|
||||
let hasTableRow = false;
|
||||
let hasSeparator = false;
|
||||
|
||||
console.log('🔍 hasTable: 分割后的行数:', lines.length);
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
console.log('🔍 hasTable: 检查行:', trimmedLine);
|
||||
|
||||
// 检查表格行(包含|字符且至少有3个部分)
|
||||
if (trimmedLine.includes('|') && trimmedLine.split('|').length >= 3) {
|
||||
hasTableRow = true;
|
||||
console.log('✅ hasTable: 找到表格行');
|
||||
}
|
||||
// 检查分隔符行(包含-和|,且主要由这些字符组成)
|
||||
if (trimmedLine.includes('|') && trimmedLine.includes('-') && /^[\s\|\-\:]+$/.test(trimmedLine)) {
|
||||
hasSeparator = true;
|
||||
console.log('✅ hasTable: 找到分隔符行');
|
||||
}
|
||||
}
|
||||
|
||||
// 如果只有表格行但没有分隔符,也可能是表格
|
||||
// 或者如果内容包含多个|字符,也可能是表格
|
||||
const pipeCount = (content.match(/\|/g) || []).length;
|
||||
const hasMultiplePipes = pipeCount >= 6; // 至少3行,每行2个|
|
||||
|
||||
console.log('🔍 hasTable: 结果 - hasTableRow:', hasTableRow, 'hasSeparator:', hasSeparator, 'pipeCount:', pipeCount, 'hasMultiplePipes:', hasMultiplePipes);
|
||||
|
||||
const result = (hasTableRow && hasSeparator) || (hasTableRow && hasMultiplePipes);
|
||||
console.log('🔍 hasTable: 最终结果:', result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
const testCases = [
|
||||
{
|
||||
name: "数据质量评估矩阵",
|
||||
content: `| 评估维度 | 权重 | 评分标准 | 计算公式 |
|
||||
|---------|------|---------|---------|
|
||||
| 完整性 | 0.3 | 缺失值比例 < 5% | $\\text{完整性} = 1 - \\frac{\\text{缺失值数量}}{\\text{总数据量}}$ |
|
||||
| 准确性 | 0.3 | 误差率 < 3% | $\\text{准确性} = 1 - \\frac{\\text{错误记录数}}{\\text{总记录数}}$ |`
|
||||
},
|
||||
{
|
||||
name: "产品价格表",
|
||||
content: `产品价格表
|
||||
| 产品 | 价格 |
|
||||
|------|------|
|
||||
| 苹果 | 4元 |
|
||||
| 香蕉 | 2元 |`
|
||||
},
|
||||
{
|
||||
name: "简单表格",
|
||||
content: `| 姓名 | 年龄 | 城市 |
|
||||
|------|------|------|
|
||||
| 张三 | 25 | 北京 |
|
||||
| 李四 | 30 | 上海 |`
|
||||
},
|
||||
{
|
||||
name: "只有表格行,没有分隔符",
|
||||
content: `| 姓名 | 年龄 | 城市 |
|
||||
| 张三 | 25 | 北京 |
|
||||
| 李四 | 30 | 上海 |`
|
||||
},
|
||||
{
|
||||
name: "包含表格字符但不是表格",
|
||||
content: `这是一个包含|字符的文本,但不是表格`
|
||||
}
|
||||
];
|
||||
|
||||
// 运行测试
|
||||
const resultsDiv = document.getElementById('results');
|
||||
|
||||
testCases.forEach((testCase, index) => {
|
||||
const testDiv = document.createElement('div');
|
||||
testDiv.className = 'test-case';
|
||||
|
||||
const result = hasTable(testCase.content);
|
||||
|
||||
testDiv.innerHTML = `
|
||||
<h3>测试用例 ${index + 1}: ${testCase.name}</h3>
|
||||
<div class="content">${testCase.content}</div>
|
||||
<div class="result ${result ? 'success' : 'error'}">
|
||||
检测结果: ${result ? '✅ 检测到表格' : '❌ 未检测到表格'}
|
||||
</div>
|
||||
`;
|
||||
|
||||
resultsDiv.appendChild(testDiv);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-d6c20d61.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-44efe7b9.css">
|
||||
<script type="module" crossorigin src="/assets/index-a09f7810.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-5b39da23.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,470 @@
|
|||
import "./chunk-FOSKEDPS.js";
|
||||
|
||||
// src/lib/mind-elixir/node_modules/@viselect/vanilla/dist/viselect.mjs
|
||||
var X = class {
|
||||
constructor() {
|
||||
this._listeners = /* @__PURE__ */ new Map(), this.on = this.addEventListener, this.off = this.removeEventListener, this.emit = this.dispatchEvent;
|
||||
}
|
||||
addEventListener(e, t) {
|
||||
const s = this._listeners.get(e) ?? /* @__PURE__ */ new Set();
|
||||
return this._listeners.set(e, s), s.add(t), this;
|
||||
}
|
||||
removeEventListener(e, t) {
|
||||
var s;
|
||||
return (s = this._listeners.get(e)) == null || s.delete(t), this;
|
||||
}
|
||||
dispatchEvent(e, ...t) {
|
||||
let s = true;
|
||||
for (const i of this._listeners.get(e) ?? [])
|
||||
s = i(...t) !== false && s;
|
||||
return s;
|
||||
}
|
||||
unbindAllListeners() {
|
||||
this._listeners.clear();
|
||||
}
|
||||
};
|
||||
var L = (l, e = "px") => typeof l == "number" ? l + e : l;
|
||||
var y = ({ style: l }, e, t) => {
|
||||
if (typeof e == "object")
|
||||
for (const [s, i] of Object.entries(e))
|
||||
i !== void 0 && (l[s] = L(i));
|
||||
else
|
||||
t !== void 0 && (l[e] = L(t));
|
||||
};
|
||||
var M = (l = 0, e = 0, t = 0, s = 0) => {
|
||||
const i = { x: l, y: e, width: t, height: s, top: e, left: l, right: l + t, bottom: e + s };
|
||||
return { ...i, toJSON: () => JSON.stringify(i) };
|
||||
};
|
||||
var Y = (l) => {
|
||||
let e, t = -1, s = false;
|
||||
return {
|
||||
next: (...i) => {
|
||||
e = i, s || (s = true, t = requestAnimationFrame(() => {
|
||||
l(...e), s = false;
|
||||
}));
|
||||
},
|
||||
cancel: () => {
|
||||
cancelAnimationFrame(t), s = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
var k = (l, e, t = "touch") => {
|
||||
switch (t) {
|
||||
case "center": {
|
||||
const s = e.left + e.width / 2, i = e.top + e.height / 2;
|
||||
return s >= l.left && s <= l.right && i >= l.top && i <= l.bottom;
|
||||
}
|
||||
case "cover":
|
||||
return e.left >= l.left && e.top >= l.top && e.right <= l.right && e.bottom <= l.bottom;
|
||||
case "touch":
|
||||
return l.right >= e.left && l.left <= e.right && l.bottom >= e.top && l.top <= e.bottom;
|
||||
}
|
||||
};
|
||||
var H = () => matchMedia("(hover: none), (pointer: coarse)").matches;
|
||||
var N = () => "safari" in window;
|
||||
var A = (l) => Array.isArray(l) ? l : [l];
|
||||
var O = (l) => (e, t, s, i = {}) => {
|
||||
(e instanceof HTMLCollection || e instanceof NodeList) && (e = Array.from(e)), t = A(t), e = A(e);
|
||||
for (const o of e)
|
||||
if (o)
|
||||
for (const n of t)
|
||||
o[l](n, s, { capture: false, ...i });
|
||||
};
|
||||
var S = O("addEventListener");
|
||||
var g = O("removeEventListener");
|
||||
var x = (l) => {
|
||||
var i;
|
||||
const { clientX: e, clientY: t, target: s } = ((i = l.touches) == null ? void 0 : i[0]) ?? l;
|
||||
return { x: e, y: t, target: s };
|
||||
};
|
||||
var E = (l, e = document) => A(l).map(
|
||||
(t) => typeof t == "string" ? Array.from(e.querySelectorAll(t)) : t instanceof Element ? t : null
|
||||
).flat().filter(Boolean);
|
||||
var q = (l, e) => e.some((t) => typeof t == "number" ? l.button === t : typeof t == "object" ? t.button !== l.button ? false : t.modifiers.every((s) => {
|
||||
switch (s) {
|
||||
case "alt":
|
||||
return l.altKey;
|
||||
case "ctrl":
|
||||
return l.ctrlKey || l.metaKey;
|
||||
case "shift":
|
||||
return l.shiftKey;
|
||||
}
|
||||
}) : false);
|
||||
var { abs: b, max: C, min: B, ceil: R } = Math;
|
||||
var D = (l = []) => ({
|
||||
stored: l,
|
||||
selected: [],
|
||||
touched: [],
|
||||
changed: { added: [], removed: [] }
|
||||
});
|
||||
var T = class T2 extends X {
|
||||
constructor(e) {
|
||||
var o, n, r, a, u;
|
||||
super(), this._selection = D(), this._targetBoundaryScrolled = true, this._selectables = [], this._areaLocation = { y1: 0, x2: 0, y2: 0, x1: 0 }, this._areaRect = M(), this._singleClick = true, this._scrollAvailable = true, this._scrollingActive = false, this._scrollSpeed = { x: 0, y: 0 }, this._scrollDelta = { x: 0, y: 0 }, this._lastMousePosition = { x: 0, y: 0 }, this.enable = this._toggleStartEvents, this.disable = this._toggleStartEvents.bind(this, false), this._options = {
|
||||
selectionAreaClass: "selection-area",
|
||||
selectionContainerClass: void 0,
|
||||
selectables: [],
|
||||
document: window.document,
|
||||
startAreas: ["html"],
|
||||
boundaries: ["html"],
|
||||
container: "body",
|
||||
...e,
|
||||
behaviour: {
|
||||
overlap: "invert",
|
||||
intersect: "touch",
|
||||
triggers: [0],
|
||||
...e.behaviour,
|
||||
startThreshold: (o = e.behaviour) != null && o.startThreshold ? typeof e.behaviour.startThreshold == "number" ? e.behaviour.startThreshold : { x: 10, y: 10, ...e.behaviour.startThreshold } : { x: 10, y: 10 },
|
||||
scrolling: {
|
||||
speedDivider: 10,
|
||||
manualSpeed: 750,
|
||||
...(n = e.behaviour) == null ? void 0 : n.scrolling,
|
||||
startScrollMargins: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
...(a = (r = e.behaviour) == null ? void 0 : r.scrolling) == null ? void 0 : a.startScrollMargins
|
||||
}
|
||||
}
|
||||
},
|
||||
features: {
|
||||
range: true,
|
||||
touch: true,
|
||||
deselectOnBlur: false,
|
||||
...e.features,
|
||||
singleTap: {
|
||||
allow: true,
|
||||
intersect: "native",
|
||||
...(u = e.features) == null ? void 0 : u.singleTap
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const _ of Object.getOwnPropertyNames(Object.getPrototypeOf(this)))
|
||||
typeof this[_] == "function" && (this[_] = this[_].bind(this));
|
||||
const { document: t, selectionAreaClass: s, selectionContainerClass: i } = this._options;
|
||||
this._area = t.createElement("div"), this._clippingElement = t.createElement("div"), this._clippingElement.appendChild(this._area), this._area.classList.add(s), i && this._clippingElement.classList.add(i), y(this._area, {
|
||||
willChange: "top, left, bottom, right, width, height",
|
||||
top: 0,
|
||||
left: 0,
|
||||
position: "fixed"
|
||||
}), y(this._clippingElement, {
|
||||
overflow: "hidden",
|
||||
position: "fixed",
|
||||
transform: "translate3d(0, 0, 0)",
|
||||
// https://stackoverflow.com/a/38268846
|
||||
pointerEvents: "none",
|
||||
zIndex: "1"
|
||||
}), this._frame = Y((_) => {
|
||||
this._recalculateSelectionAreaRect(), this._updateElementSelection(), this._emitEvent("move", _), this._redrawSelectionArea();
|
||||
}), this.enable();
|
||||
}
|
||||
_toggleStartEvents(e = true) {
|
||||
const { document: t, features: s } = this._options, i = e ? S : g;
|
||||
i(t, "mousedown", this._onTapStart), s.touch && i(t, "touchstart", this._onTapStart, { passive: false });
|
||||
}
|
||||
_onTapStart(e, t = false) {
|
||||
const { x: s, y: i, target: o } = x(e), { document: n, startAreas: r, boundaries: a, features: u, behaviour: _ } = this._options, c = o.getBoundingClientRect();
|
||||
if (e instanceof MouseEvent && !q(e, _.triggers))
|
||||
return;
|
||||
const p = E(r, n), m = E(a, n);
|
||||
this._targetElement = m.find(
|
||||
(v) => k(v.getBoundingClientRect(), c)
|
||||
);
|
||||
const f = e.composedPath(), d = p.find((v) => f.includes(v));
|
||||
if (this._targetBoundary = m.find((v) => f.includes(v)), !this._targetElement || !d || !this._targetBoundary || !t && this._emitEvent("beforestart", e) === false)
|
||||
return;
|
||||
this._areaLocation = { x1: s, y1: i, x2: 0, y2: 0 };
|
||||
const h = n.scrollingElement ?? n.body;
|
||||
this._scrollDelta = { x: h.scrollLeft, y: h.scrollTop }, this._singleClick = true, this.clearSelection(false, true), S(n, ["touchmove", "mousemove"], this._delayedTapMove, { passive: false }), S(n, ["mouseup", "touchcancel", "touchend"], this._onTapStop), S(n, "scroll", this._onScroll), u.deselectOnBlur && (this._targetBoundaryScrolled = false, S(this._targetBoundary, "scroll", this._onStartAreaScroll));
|
||||
}
|
||||
_onSingleTap(e) {
|
||||
const { singleTap: { intersect: t }, range: s } = this._options.features, i = x(e);
|
||||
let o;
|
||||
if (t === "native")
|
||||
o = i.target;
|
||||
else if (t === "touch") {
|
||||
this.resolveSelectables();
|
||||
const { x: r, y: a } = i;
|
||||
o = this._selectables.find((u) => {
|
||||
const { right: _, left: c, top: p, bottom: m } = u.getBoundingClientRect();
|
||||
return r < _ && r > c && a < m && a > p;
|
||||
});
|
||||
}
|
||||
if (!o)
|
||||
return;
|
||||
for (this.resolveSelectables(); !this._selectables.includes(o); )
|
||||
if (o.parentElement)
|
||||
o = o.parentElement;
|
||||
else {
|
||||
this._targetBoundaryScrolled || this.clearSelection();
|
||||
return;
|
||||
}
|
||||
const { stored: n } = this._selection;
|
||||
if (this._emitEvent("start", e), e.shiftKey && s && this._latestElement) {
|
||||
const r = this._latestElement, [a, u] = r.compareDocumentPosition(o) & 4 ? [o, r] : [r, o], _ = [...this._selectables.filter(
|
||||
(c) => c.compareDocumentPosition(a) & 4 && c.compareDocumentPosition(u) & 2
|
||||
), a, u];
|
||||
this.select(_), this._latestElement = r;
|
||||
} else
|
||||
n.includes(o) && (n.length === 1 || e.ctrlKey || n.every((r) => this._selection.stored.includes(r))) ? this.deselect(o) : (this.select(o), this._latestElement = o);
|
||||
}
|
||||
_delayedTapMove(e) {
|
||||
const { container: t, document: s, behaviour: { startThreshold: i } } = this._options, { x1: o, y1: n } = this._areaLocation, { x: r, y: a } = x(e);
|
||||
if (
|
||||
// Single number for both coordinates
|
||||
typeof i == "number" && b(r + a - (o + n)) >= i || // Different x and y threshold
|
||||
typeof i == "object" && b(r - o) >= i.x || b(a - n) >= i.y
|
||||
) {
|
||||
if (g(s, ["mousemove", "touchmove"], this._delayedTapMove, { passive: false }), this._emitEvent("beforedrag", e) === false) {
|
||||
g(s, ["mouseup", "touchcancel", "touchend"], this._onTapStop);
|
||||
return;
|
||||
}
|
||||
S(s, ["mousemove", "touchmove"], this._onTapMove, { passive: false }), y(this._area, "display", "block"), E(t, s)[0].appendChild(this._clippingElement), this.resolveSelectables(), this._singleClick = false, this._targetRect = this._targetElement.getBoundingClientRect(), this._scrollAvailable = this._targetElement.scrollHeight !== this._targetElement.clientHeight || this._targetElement.scrollWidth !== this._targetElement.clientWidth, this._scrollAvailable && (S(this._targetElement, "wheel", this._wheelScroll, { passive: false }), S(this._options.document, "keydown", this._keyboardScroll, { passive: false }), this._selectables = this._selectables.filter((u) => this._targetElement.contains(u))), this._setupSelectionArea(), this._emitEvent("start", e), this._onTapMove(e);
|
||||
}
|
||||
this._handleMoveEvent(e);
|
||||
}
|
||||
_setupSelectionArea() {
|
||||
const { _clippingElement: e, _targetElement: t, _area: s } = this, i = this._targetRect = t.getBoundingClientRect();
|
||||
this._scrollAvailable ? (y(e, {
|
||||
top: i.top,
|
||||
left: i.left,
|
||||
width: i.width,
|
||||
height: i.height
|
||||
}), y(s, {
|
||||
marginTop: -i.top,
|
||||
marginLeft: -i.left
|
||||
})) : (y(e, {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}), y(s, {
|
||||
marginTop: 0,
|
||||
marginLeft: 0
|
||||
}));
|
||||
}
|
||||
_onTapMove(e) {
|
||||
const { _scrollSpeed: t, _areaLocation: s, _options: i, _frame: o } = this, { speedDivider: n } = i.behaviour.scrolling, r = this._targetElement, { x: a, y: u } = x(e);
|
||||
if (s.x2 = a, s.y2 = u, this._lastMousePosition.x = a, this._lastMousePosition.y = u, this._scrollAvailable && !this._scrollingActive && (t.y || t.x)) {
|
||||
this._scrollingActive = true;
|
||||
const _ = () => {
|
||||
if (!t.x && !t.y) {
|
||||
this._scrollingActive = false;
|
||||
return;
|
||||
}
|
||||
const { scrollTop: c, scrollLeft: p } = r;
|
||||
t.y && (r.scrollTop += R(t.y / n), s.y1 -= r.scrollTop - c), t.x && (r.scrollLeft += R(t.x / n), s.x1 -= r.scrollLeft - p), o.next(e), requestAnimationFrame(_);
|
||||
};
|
||||
requestAnimationFrame(_);
|
||||
} else
|
||||
o.next(e);
|
||||
this._handleMoveEvent(e);
|
||||
}
|
||||
_handleMoveEvent(e) {
|
||||
const { features: t } = this._options;
|
||||
(t.touch && H() || this._scrollAvailable && N()) && e.preventDefault();
|
||||
}
|
||||
_onScroll() {
|
||||
const { _scrollDelta: e, _options: { document: t } } = this, { scrollTop: s, scrollLeft: i } = t.scrollingElement ?? t.body;
|
||||
this._areaLocation.x1 += e.x - i, this._areaLocation.y1 += e.y - s, e.x = i, e.y = s, this._setupSelectionArea(), this._frame.next(null);
|
||||
}
|
||||
_onStartAreaScroll() {
|
||||
this._targetBoundaryScrolled = true, g(this._targetElement, "scroll", this._onStartAreaScroll);
|
||||
}
|
||||
_wheelScroll(e) {
|
||||
const { manualSpeed: t } = this._options.behaviour.scrolling, s = e.deltaY ? e.deltaY > 0 ? 1 : -1 : 0, i = e.deltaX ? e.deltaX > 0 ? 1 : -1 : 0;
|
||||
this._scrollSpeed.y += s * t, this._scrollSpeed.x += i * t, this._onTapMove(e), e.preventDefault();
|
||||
}
|
||||
_keyboardScroll(e) {
|
||||
const { manualSpeed: t } = this._options.behaviour.scrolling, s = e.key === "ArrowLeft" ? -1 : e.key === "ArrowRight" ? 1 : 0, i = e.key === "ArrowUp" ? -1 : e.key === "ArrowDown" ? 1 : 0;
|
||||
this._scrollSpeed.x += Math.sign(s) * t, this._scrollSpeed.y += Math.sign(i) * t, e.preventDefault(), this._onTapMove({
|
||||
clientX: this._lastMousePosition.x,
|
||||
clientY: this._lastMousePosition.y,
|
||||
preventDefault: () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
_recalculateSelectionAreaRect() {
|
||||
const { _scrollSpeed: e, _areaLocation: t, _targetElement: s, _options: i } = this, { scrollTop: o, scrollHeight: n, clientHeight: r, scrollLeft: a, scrollWidth: u, clientWidth: _ } = s, c = this._targetRect, { x1: p, y1: m } = t;
|
||||
let { x2: f, y2: d } = t;
|
||||
const { behaviour: { scrolling: { startScrollMargins: h } } } = i;
|
||||
f < c.left + h.x ? (e.x = a ? -b(c.left - f + h.x) : 0, f = f < c.left ? c.left : f) : f > c.right - h.x ? (e.x = u - a - _ ? b(c.left + c.width - f - h.x) : 0, f = f > c.right ? c.right : f) : e.x = 0, d < c.top + h.y ? (e.y = o ? -b(c.top - d + h.y) : 0, d = d < c.top ? c.top : d) : d > c.bottom - h.y ? (e.y = n - o - r ? b(c.top + c.height - d - h.y) : 0, d = d > c.bottom ? c.bottom : d) : e.y = 0;
|
||||
const v = B(p, f), w = B(m, d), j = C(p, f), K = C(m, d);
|
||||
this._areaRect = M(v, w, j - v, K - w);
|
||||
}
|
||||
_redrawSelectionArea() {
|
||||
const { x: e, y: t, width: s, height: i } = this._areaRect, { style: o } = this._area;
|
||||
o.left = `${e}px`, o.top = `${t}px`, o.width = `${s}px`, o.height = `${i}px`;
|
||||
}
|
||||
_onTapStop(e, t) {
|
||||
var n;
|
||||
const { document: s, features: i } = this._options, { _singleClick: o } = this;
|
||||
g(this._targetElement, "scroll", this._onStartAreaScroll), g(s, ["mousemove", "touchmove"], this._delayedTapMove), g(s, ["touchmove", "mousemove"], this._onTapMove), g(s, ["mouseup", "touchcancel", "touchend"], this._onTapStop), g(s, "scroll", this._onScroll), this._keepSelection(), e && o && i.singleTap.allow ? this._onSingleTap(e) : !o && !t && (this._updateElementSelection(), this._emitEvent("stop", e)), this._scrollSpeed.x = 0, this._scrollSpeed.y = 0, g(this._targetElement, "wheel", this._wheelScroll, { passive: true }), g(this._options.document, "keydown", this._keyboardScroll, { passive: true }), this._clippingElement.remove(), (n = this._frame) == null || n.cancel(), y(this._area, "display", "none");
|
||||
}
|
||||
_updateElementSelection() {
|
||||
const { _selectables: e, _options: t, _selection: s, _areaRect: i } = this, { stored: o, selected: n, touched: r } = s, { intersect: a, overlap: u } = t.behaviour, _ = u === "invert", c = [], p = [], m = [];
|
||||
for (let d = 0; d < e.length; d++) {
|
||||
const h = e[d];
|
||||
if (k(i, h.getBoundingClientRect(), a)) {
|
||||
if (n.includes(h))
|
||||
o.includes(h) && !r.includes(h) && r.push(h);
|
||||
else if (_ && o.includes(h)) {
|
||||
m.push(h);
|
||||
continue;
|
||||
} else
|
||||
p.push(h);
|
||||
c.push(h);
|
||||
}
|
||||
}
|
||||
_ && p.push(...o.filter((d) => !n.includes(d)));
|
||||
const f = u === "keep";
|
||||
for (let d = 0; d < n.length; d++) {
|
||||
const h = n[d];
|
||||
!c.includes(h) && !// Check if the user wants to keep previously selected elements, e.g.,
|
||||
// not make them part of the current selection as soon as they're touched.
|
||||
(f && o.includes(h)) && m.push(h);
|
||||
}
|
||||
s.selected = c, s.changed = { added: p, removed: m }, this._latestElement = void 0;
|
||||
}
|
||||
_emitEvent(e, t) {
|
||||
return this.emit(e, {
|
||||
event: t,
|
||||
store: this._selection,
|
||||
selection: this
|
||||
});
|
||||
}
|
||||
_keepSelection() {
|
||||
const { _options: e, _selection: t } = this, { selected: s, changed: i, touched: o, stored: n } = t, r = s.filter((a) => !n.includes(a));
|
||||
switch (e.behaviour.overlap) {
|
||||
case "drop": {
|
||||
t.stored = [
|
||||
...r,
|
||||
...n.filter((a) => !o.includes(a))
|
||||
// Elements not touched
|
||||
];
|
||||
break;
|
||||
}
|
||||
case "invert": {
|
||||
t.stored = [
|
||||
...r,
|
||||
...n.filter((a) => !i.removed.includes(a))
|
||||
// Elements not removed from selection
|
||||
];
|
||||
break;
|
||||
}
|
||||
case "keep": {
|
||||
t.stored = [
|
||||
...n,
|
||||
...s.filter((a) => !n.includes(a))
|
||||
// Newly added
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Manually triggers the start of a selection
|
||||
* @param evt A MouseEvent / TouchEvent-like object
|
||||
* @param silent If beforestart should be fired
|
||||
*/
|
||||
trigger(e, t = true) {
|
||||
this._onTapStart(e, t);
|
||||
}
|
||||
/**
|
||||
* Can be used if during a selection elements have been added
|
||||
* Will update everything that can be selected
|
||||
*/
|
||||
resolveSelectables() {
|
||||
this._selectables = E(this._options.selectables, this._options.document);
|
||||
}
|
||||
/**
|
||||
* Same as deselecting, but for all elements currently selected
|
||||
* @param includeStored If the store should also get cleared
|
||||
* @param quiet If move / stop events should be fired
|
||||
*/
|
||||
clearSelection(e = true, t = false) {
|
||||
const { selected: s, stored: i, changed: o } = this._selection;
|
||||
o.added = [], o.removed.push(
|
||||
...s,
|
||||
...e ? i : []
|
||||
), t || (this._emitEvent("move", null), this._emitEvent("stop", null)), this._selection = D(e ? [] : i);
|
||||
}
|
||||
/**
|
||||
* @returns {Array} Selected elements
|
||||
*/
|
||||
getSelection() {
|
||||
return this._selection.stored;
|
||||
}
|
||||
/**
|
||||
* @returns {HTMLElement} The selection area element
|
||||
*/
|
||||
getSelectionArea() {
|
||||
return this._area;
|
||||
}
|
||||
/**
|
||||
* @returns {Element[]} Available selectable elements for current selection
|
||||
*/
|
||||
getSelectables() {
|
||||
return this._selectables;
|
||||
}
|
||||
/**
|
||||
* Set the location of the selection area
|
||||
* @param location A partial AreaLocation object
|
||||
*/
|
||||
setAreaLocation(e) {
|
||||
Object.assign(this._areaLocation, e), this._redrawSelectionArea();
|
||||
}
|
||||
/**
|
||||
* @returns {AreaLocation} The current location of the selection area
|
||||
*/
|
||||
getAreaLocation() {
|
||||
return this._areaLocation;
|
||||
}
|
||||
/**
|
||||
* Cancel the current selection process, pass true to fire a stop event after cancel
|
||||
* @param keepEvent If a stop event should be fired
|
||||
*/
|
||||
cancel(e = false) {
|
||||
this._onTapStop(null, !e);
|
||||
}
|
||||
/**
|
||||
* Unbinds all events and removes the area-element.
|
||||
*/
|
||||
destroy() {
|
||||
this.cancel(), this.disable(), this._clippingElement.remove(), super.unbindAllListeners();
|
||||
}
|
||||
/**
|
||||
* Adds elements to the selection
|
||||
* @param query CSS Query, can be an array of queries
|
||||
* @param quiet If this should not trigger the move event
|
||||
*/
|
||||
select(e, t = false) {
|
||||
const { changed: s, selected: i, stored: o } = this._selection, n = E(e, this._options.document).filter(
|
||||
(r) => !i.includes(r) && !o.includes(r)
|
||||
);
|
||||
return o.push(...n), i.push(...n), s.added.push(...n), s.removed = [], this._latestElement = void 0, t || (this._emitEvent("move", null), this._emitEvent("stop", null)), n;
|
||||
}
|
||||
/**
|
||||
* Removes a particular element from the selection
|
||||
* @param query CSS Query, can be an array of queries
|
||||
* @param quiet If this should not trigger the move event
|
||||
*/
|
||||
deselect(e, t = false) {
|
||||
const { selected: s, stored: i, changed: o } = this._selection, n = E(e, this._options.document).filter(
|
||||
(r) => s.includes(r) || i.includes(r)
|
||||
);
|
||||
this._selection.stored = i.filter((r) => !n.includes(r)), this._selection.selected = s.filter((r) => !n.includes(r)), this._selection.changed.added = [], this._selection.changed.removed.push(
|
||||
...n.filter((r) => !o.removed.includes(r))
|
||||
), this._latestElement = void 0, t || (this._emitEvent("move", null), this._emitEvent("stop", null));
|
||||
}
|
||||
};
|
||||
T.version = "3.9.0";
|
||||
var P = T;
|
||||
export {
|
||||
P as default
|
||||
};
|
||||
/*! Bundled license information:
|
||||
|
||||
@viselect/vanilla/dist/viselect.mjs:
|
||||
(*! @viselect/vanilla v3.9.0 MIT | https://github.com/Simonwep/selection/tree/master/packages/vanilla *)
|
||||
*/
|
||||
//# sourceMappingURL=@viselect_vanilla.js.map
|
||||
|
|
@ -1,35 +1,35 @@
|
|||
{
|
||||
"hash": "76772e52",
|
||||
"browserHash": "b5df73c3",
|
||||
"browserHash": "a4ef7769",
|
||||
"optimized": {
|
||||
"axios": {
|
||||
"src": "../../axios/index.js",
|
||||
"file": "axios.js",
|
||||
"fileHash": "d5a6fb13",
|
||||
"fileHash": "923e7809",
|
||||
"needsInterop": false
|
||||
},
|
||||
"mammoth": {
|
||||
"src": "../../mammoth/lib/index.js",
|
||||
"file": "mammoth.js",
|
||||
"fileHash": "8e0b13e7",
|
||||
"fileHash": "2d23e669",
|
||||
"needsInterop": true
|
||||
},
|
||||
"marked": {
|
||||
"src": "../../marked/lib/marked.esm.js",
|
||||
"file": "marked.js",
|
||||
"fileHash": "330cfdd3",
|
||||
"fileHash": "e6802b70",
|
||||
"needsInterop": false
|
||||
},
|
||||
"pdfjs-dist": {
|
||||
"src": "../../pdfjs-dist/build/pdf.mjs",
|
||||
"file": "pdfjs-dist.js",
|
||||
"fileHash": "63546997",
|
||||
"fileHash": "b0d6a144",
|
||||
"needsInterop": false
|
||||
},
|
||||
"prismjs": {
|
||||
"src": "../../prismjs/prism.js",
|
||||
"file": "prismjs.js",
|
||||
"fileHash": "3730e60a",
|
||||
"fileHash": "0c7d8fc7",
|
||||
"needsInterop": true
|
||||
},
|
||||
"prismjs/components/prism-css": {
|
||||
|
|
@ -65,13 +65,19 @@
|
|||
"vue": {
|
||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "a7d38f14",
|
||||
"fileHash": "b4ce9a46",
|
||||
"needsInterop": false
|
||||
},
|
||||
"katex": {
|
||||
"src": "../../katex/dist/katex.mjs",
|
||||
"file": "katex.js",
|
||||
"fileHash": "d08a8bdf",
|
||||
"fileHash": "e7ed5c7a",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@viselect/vanilla": {
|
||||
"src": "../../../src/lib/mind-elixir/node_modules/@viselect/vanilla/dist/viselect.mjs",
|
||||
"file": "@viselect_vanilla.js",
|
||||
"fileHash": "b39569df",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -631,7 +631,7 @@ Level 4 标题用 #####
|
|||
|
||||
// 添加超时处理 - 增加超时时间,处理复杂文档
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 120000); // 增加到2分钟超时
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000); // 减少到1分钟超时
|
||||
|
||||
const response = await fetch('http://127.0.0.1:8000/api/ai/generate-markdown', {
|
||||
method: 'POST',
|
||||
|
|
|
|||
|
|
@ -1510,7 +1510,8 @@ const createAINode = async (parentNode, question, answer) => {
|
|||
.replace(/^#+\s*/gm, '') // 移除标题标记
|
||||
.replace(/\*\*(.*?)\*\*/g, '$1') // 移除粗体标记
|
||||
.replace(/\*(.*?)\*/g, '$1') // 移除斜体标记
|
||||
.replace(/^\s*[-*+]\s*/gm, '• ') // 统一列表标记
|
||||
// 保留表格格式,不转换表格为列表
|
||||
.replace(/^\s*[-*+]\s*(?![|])/gm, '• ') // 只转换非表格的列表标记
|
||||
.replace(/\n{3,}/g, '\n\n') // 限制连续换行
|
||||
.trim();
|
||||
};
|
||||
|
|
@ -4209,40 +4210,85 @@ const updateMindMapRealtime = async (data, title) => {
|
|||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Markdown表格样式 - 使用更高优先级 */
|
||||
/* 强制表格样式 - 最高优先级 */
|
||||
.topic table,
|
||||
.topic .text table,
|
||||
.topic .topic-text table,
|
||||
.topic .topic-text.markdown-content table {
|
||||
border-collapse: collapse !important;
|
||||
width: 100% !important;
|
||||
margin: 4px 0 !important;
|
||||
font-size: 11px !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border: 2px solid #333 !important;
|
||||
border-radius: 6px !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08) !important;
|
||||
background-color: #fafafa !important;
|
||||
overflow: hidden !important;
|
||||
display: table !important;
|
||||
white-space: normal !important; /* 覆盖MindElixir的pre-wrap */
|
||||
}
|
||||
|
||||
.topic table th,
|
||||
.topic table td,
|
||||
.topic .text table th,
|
||||
.topic .text table td,
|
||||
.topic .topic-text table th,
|
||||
.topic .topic-text table td,
|
||||
.topic .topic-text.markdown-content table th,
|
||||
.topic .topic-text.markdown-content table td {
|
||||
border: 1px solid #ddd !important;
|
||||
padding: 2px 4px !important;
|
||||
border: 2px solid #333 !important;
|
||||
padding: 6px 8px !important;
|
||||
text-align: left !important;
|
||||
vertical-align: top !important;
|
||||
display: table-cell !important;
|
||||
position: relative !important;
|
||||
white-space: normal !important; /* 覆盖MindElixir的pre-wrap */
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table th {
|
||||
.topic .topic-text.markdown-content table th,
|
||||
.topic .text table th,
|
||||
.topic table th {
|
||||
background-color: #f5f5f5 !important;
|
||||
font-weight: 600 !important;
|
||||
color: #333 !important;
|
||||
text-align: center !important;
|
||||
border-bottom: 2px solid #333 !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr {
|
||||
.topic .topic-text.markdown-content table td,
|
||||
.topic .text table td,
|
||||
.topic table td {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr,
|
||||
.topic .text table tr,
|
||||
.topic table tr {
|
||||
display: table-row !important;
|
||||
white-space: normal !important; /* 覆盖MindElixir的pre-wrap */
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr:nth-child(even) {
|
||||
background-color: #f9f9f9 !important;
|
||||
.topic .topic-text.markdown-content table tr:nth-child(even) td,
|
||||
.topic .text table tr:nth-child(even) td {
|
||||
background-color: #f8f8f8 !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr:hover {
|
||||
background-color: #e9ecef !important;
|
||||
.topic .topic-text.markdown-content table tr:hover td,
|
||||
.topic .text table tr:hover td {
|
||||
background-color: #f0f8ff !important;
|
||||
}
|
||||
|
||||
/* 简洁的边框效果 */
|
||||
.topic .topic-text.markdown-content table th:not(:last-child),
|
||||
.topic .topic-text.markdown-content table td:not(:last-child),
|
||||
.topic .text table th:not(:last-child),
|
||||
.topic .text table td:not(:last-child) {
|
||||
border-right: 1px solid #e0e0e0 !important;
|
||||
}
|
||||
|
||||
.topic .topic-text.markdown-content table tr:not(:last-child) td,
|
||||
.topic .text table tr:not(:last-child) td {
|
||||
border-bottom: 1px solid #e0e0e0 !important;
|
||||
}
|
||||
|
||||
/* Markdown代码样式 */
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 331 KiB |
|
Before Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
|
@ -1,430 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="versión">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="licencia">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="calidad del código">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="cantidad de dependencias">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="tamaño del paquete">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind elixir es un núcleo de mapas mentales de JavaScript de código abierto. Puedes usarlo con cualquier framework frontend que prefieras.
|
||||
|
||||
Características:
|
||||
|
||||
- Ligero
|
||||
- Alto rendimiento
|
||||
- Agnóstico al framework
|
||||
- Pluginable
|
||||
- Plugin de arrastrar y soltar / edición de nodos incorporado
|
||||
- Exportar como SVG / PNG / Html
|
||||
- Resumir nodos
|
||||
- Operaciones en masa soportadas
|
||||
- Deshacer / Rehacer
|
||||
- Atajos eficientes
|
||||
- Estiliza fácilmente tu nodo con variables CSS
|
||||
|
||||
<details>
|
||||
<summary>Tabla de Contenidos</summary>
|
||||
|
||||
- [Prueba ahora](#prueba-ahora)
|
||||
- [Playground](#playground)
|
||||
- [Documentación](#documentación)
|
||||
- [Uso](#uso)
|
||||
- [Instalar](#instalar)
|
||||
- [NPM](#npm)
|
||||
- [Etiqueta de script](#etiqueta-de-script)
|
||||
- [Inicializar](#inicializar)
|
||||
- [Estructura de Datos](#estructura-de-datos)
|
||||
- [Manejo de Eventos](#manejo-de-eventos)
|
||||
- [Exportación e Importación de Datos](#exportación-e-importación-de-datos)
|
||||
- [Guardias de Operación](#guardias-de-operación)
|
||||
- [Exportar como Imagen](#exportar-como-imagen)
|
||||
- [Solución 1](#solución-1)
|
||||
- [Solución 2](#solución-2)
|
||||
- [Tema](#tema)
|
||||
- [Atajos](#atajos)
|
||||
- [Ecosistema](#ecosistema)
|
||||
- [Desarrollo](#desarrollo)
|
||||
- [Agradecimientos](#agradecimientos)
|
||||
- [Contribuidores](#contribuidores)
|
||||
|
||||
</details>
|
||||
|
||||
## Prueba ahora
|
||||
|
||||

|
||||
|
||||
https://mind-elixir.com/
|
||||
|
||||
### Playground
|
||||
|
||||
- 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
|
||||
|
||||
## Documentación
|
||||
|
||||
https://docs.mind-elixir.com/
|
||||
|
||||
## Uso
|
||||
|
||||
### Instalar
|
||||
|
||||
#### NPM
|
||||
|
||||
```bash
|
||||
npm i mind-elixir -S
|
||||
```
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
```
|
||||
|
||||
#### Etiqueta de script
|
||||
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/mind-elixir/dist/MindElixir.js"></script>
|
||||
```
|
||||
|
||||
### Inicializar
|
||||
|
||||
```html
|
||||
<div id="map"></div>
|
||||
<style>
|
||||
#map {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Cambio Importante** desde la versión 1.0.0, `data` debe ser pasado a `init()`, no `options`.
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
import example from 'mind-elixir/dist/example1'
|
||||
|
||||
let options = {
|
||||
el: '#map', // o HTMLDivElement
|
||||
direction: MindElixir.LEFT,
|
||||
draggable: true, // por defecto true
|
||||
contextMenu: true, // por defecto true
|
||||
toolBar: true, // por defecto true
|
||||
nodeMenu: true, // por defecto true
|
||||
keypress: true, // por defecto true
|
||||
locale: 'en', // [zh_CN,zh_TW,en,ja,pt,ru] esperando PRs
|
||||
overflowHidden: false, // por defecto false
|
||||
mainLinkStyle: 2, // [1,2] por defecto 1
|
||||
mouseSelectionButton: 0, // 0 para botón izquierdo, 2 para botón derecho, por defecto 0
|
||||
contextMenuOption: {
|
||||
focus: true,
|
||||
link: true,
|
||||
extend: [
|
||||
{
|
||||
name: 'Editar nodo',
|
||||
onclick: () => {
|
||||
alert('menú extendido')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
before: {
|
||||
insertSibling(el, obj) {
|
||||
return true
|
||||
},
|
||||
async addChild(el, obj) {
|
||||
await sleep()
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let mind = new MindElixir(options)
|
||||
|
||||
mind.install(plugin) // instala tu plugin
|
||||
|
||||
// crea nuevos datos de mapa
|
||||
const data = MindElixir.new('nuevo tema')
|
||||
// o `example`
|
||||
// o los datos devueltos por `.getData()`
|
||||
mind.init(data)
|
||||
|
||||
// obtener un nodo
|
||||
MindElixir.E('node-id')
|
||||
```
|
||||
|
||||
### Estructura de Datos
|
||||
|
||||
```javascript
|
||||
// estructura completa de datos de nodo hasta ahora
|
||||
const nodeData = {
|
||||
topic: 'tema del nodo',
|
||||
id: 'bd1c24420cd2c2f5',
|
||||
style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' },
|
||||
expanded: true,
|
||||
parent: null,
|
||||
tags: ['Etiqueta'],
|
||||
icons: ['😀'],
|
||||
hyperLink: 'https://github.com/ssshooter/mind-elixir-core',
|
||||
image: {
|
||||
url: 'https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png', // requerido
|
||||
// necesitas consultar la altura y el ancho de la imagen y calcular el valor apropiado para mostrar la imagen
|
||||
height: 90, // requerido
|
||||
width: 90, // requerido
|
||||
},
|
||||
children: [
|
||||
{
|
||||
topic: 'hijo',
|
||||
id: 'xxxx',
|
||||
// ...
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Manejo de Eventos
|
||||
|
||||
```javascript
|
||||
mind.bus.addListener('operation', operation => {
|
||||
console.log(operation)
|
||||
// return {
|
||||
// name: nombre de la acción,
|
||||
// obj: objeto objetivo
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: objetivo
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:objetivo1,to:objetivo2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### Exportación e Importación de Datos
|
||||
|
||||
```javascript
|
||||
// exportación de datos
|
||||
const data = mind.getData() // objeto javascript, ver src/example.js
|
||||
mind.getDataString() // objeto en cadena
|
||||
|
||||
// importación de datos
|
||||
// iniciar
|
||||
let mind = new MindElixir(options)
|
||||
mind.init(data)
|
||||
// actualización de datos
|
||||
mind.refresh(data)
|
||||
```
|
||||
|
||||
### Guardias de Operación
|
||||
|
||||
```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
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Exportar como Imagen
|
||||
|
||||
### Solución 1
|
||||
|
||||
```typescript
|
||||
const mind = {
|
||||
/** instancia de mind elixir */
|
||||
}
|
||||
const downloadPng = async () => {
|
||||
const blob = await mind.exportPng() // ¡Obtén un Blob!
|
||||
if (!blob) return
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'nombrearchivo.png'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
```
|
||||
|
||||
### Solución 2
|
||||
|
||||
Instala `@ssshooter/modern-screenshot`, luego:
|
||||
|
||||
```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 = 'captura.png'
|
||||
link.href = dataUrl
|
||||
link.click()
|
||||
}
|
||||
```
|
||||
|
||||
## Tema
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
// ...
|
||||
theme: {
|
||||
name: 'Oscuro',
|
||||
// paleta de colores de las líneas principales
|
||||
palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
|
||||
// sobrescribir variables css
|
||||
cssVar: {
|
||||
'--main-color': '#ffffff',
|
||||
'--main-bgcolor': '#4c4f69',
|
||||
'--color': '#cccccc',
|
||||
'--bgcolor': '#252526',
|
||||
'--panel-color': '255, 255, 255',
|
||||
'--panel-bgcolor': '45, 55, 72',
|
||||
},
|
||||
// todas las variables ver /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',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Ten en cuenta que Mind Elixir no observará el cambio de `prefers-color-scheme`. Por favor, cambia el tema **manualmente** cuando el esquema cambie.
|
||||
|
||||
## Atajos
|
||||
|
||||
| Atajo | Función |
|
||||
| ------------------ | -------------------------------- |
|
||||
| Enter | Insertar Nodo Hermano |
|
||||
| Tab | Insertar Nodo Hijo |
|
||||
| F1 | Centrar el Mapa |
|
||||
| F2 | Comenzar a Editar el Nodo Actual |
|
||||
| ↑ | Seleccionar el Nodo Hermano Anterior |
|
||||
| ↓ | Seleccionar el Nodo Hermano Siguiente |
|
||||
| ← / → | Seleccionar Padre o Primer Hijo |
|
||||
| PageUp / Alt + ↑ | Mover Nodo Arriba |
|
||||
| PageDown / Alt + ↓ | Mover Nodo Abajo |
|
||||
| Ctrl + ↑ | Cambiar Patrón de Diseño a Lado |
|
||||
| Ctrl + ← | Cambiar Patrón de Diseño a Izquierda |
|
||||
| Ctrl + → | Cambiar Patrón de Diseño a Derecha |
|
||||
| Ctrl + C | Copiar el Nodo Actual |
|
||||
| Ctrl + V | Pegar el Nodo Copiado |
|
||||
| Ctrl + "+" | Acercar el Mapa Mental |
|
||||
| Ctrl + "-" | Alejar el Mapa Mental |
|
||||
| Ctrl + 0 | Restablecer Nivel de Zoom |
|
||||
|
||||
## Ecosistema
|
||||
|
||||
- [@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)
|
||||
|
||||
¡Las PRs son bienvenidas!
|
||||
|
||||
## Desarrollo
|
||||
|
||||
```
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Prueba los archivos generados con `dev.dist.ts`:
|
||||
|
||||
```
|
||||
pnpm build
|
||||
pnpm link ./
|
||||
```
|
||||
|
||||
Actualiza la documentación:
|
||||
|
||||
```
|
||||
# Instalar api-extractor
|
||||
pnpm install -g @microsoft/api-extractor
|
||||
# Mantener /src/docs.ts
|
||||
# Generar documentación
|
||||
pnpm doc
|
||||
pnpm doc:md
|
||||
```
|
||||
|
||||
## Agradecimientos
|
||||
|
||||
- [@viselect/vanilla](https://github.com/simonwep/selection/tree/master/packages/vanilla)
|
||||
|
||||
## Contribuidores
|
||||
|
||||
¡Gracias por tus contribuciones a Mind Elixir! Tu apoyo y dedicación hacen que este proyecto sea mejor.
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="version">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="license">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="code quality">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="dependency-count">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="package size">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind Elixir est un noyau JavaScript open source pour créer des cartes mentales. Vous pouvez l'utiliser avec n'importe quel framework frontend de votre choix.
|
||||
|
||||
Caractéristiques :
|
||||
|
||||
- Léger
|
||||
- Haute performance
|
||||
- Indépendant du framework
|
||||
- Extensible via plugins
|
||||
- Plugin intégré pour le glisser-déposer / édition de nœuds
|
||||
- Export en SVG / PNG / Html
|
||||
- Résumé des nœuds
|
||||
- Opérations en masse supportées
|
||||
- Annuler / Refaire
|
||||
- Raccourcis efficaces
|
||||
- Stylisation facile des nœuds avec les variables CSS
|
||||
|
||||
<details>
|
||||
<summary>Table des matières</summary>
|
||||
|
||||
- [Essayer maintenant](#essayer-maintenant)
|
||||
- [Playground](#playground)
|
||||
- [Documentation](#documentation)
|
||||
- [Utilisation](#utilisation)
|
||||
- [Installation](#installation)
|
||||
- [NPM](#npm)
|
||||
- [Balise script](#balise-script)
|
||||
- [Initialisation](#initialisation)
|
||||
- [Structure des données](#structure-des-données)
|
||||
- [Gestion des événements](#gestion-des-événements)
|
||||
- [Export et import des données](#export-et-import-des-données)
|
||||
- [Gardes d'opération](#gardes-dopération)
|
||||
- [Exporter en image](#exporter-en-image)
|
||||
- [Solution 1](#solution-1)
|
||||
- [Solution 2](#solution-2)
|
||||
- [Thème](#thème)
|
||||
- [Raccourcis](#raccourcis)
|
||||
- [Écosystème](#écosystème)
|
||||
- [Développement](#développement)
|
||||
- [Remerciements](#remerciements)
|
||||
- [Contributeurs](#contributeurs)
|
||||
|
||||
</details>
|
||||
|
||||
## Essayer maintenant
|
||||
|
||||

|
||||
|
||||
https://mind-elixir.com/
|
||||
|
||||
### Playground
|
||||
|
||||
- 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
|
||||
|
||||
## Documentation
|
||||
|
||||
https://docs.mind-elixir.com/
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Installation
|
||||
|
||||
#### NPM
|
||||
|
||||
```bash
|
||||
npm i mind-elixir -S
|
||||
```
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
```
|
||||
|
||||
#### Balise script
|
||||
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/mind-elixir/dist/MindElixir.js"></script>
|
||||
```
|
||||
|
||||
### Initialisation
|
||||
|
||||
```html
|
||||
<div id="map"></div>
|
||||
<style>
|
||||
#map {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Changement majeur** depuis la version 1.0.0, `data` doit être passé à `init()`, et non `options`.
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
import example from 'mind-elixir/dist/example1'
|
||||
|
||||
let options = {
|
||||
el: '#map', // ou HTMLDivElement
|
||||
direction: MindElixir.LEFT,
|
||||
draggable: true, // par défaut true
|
||||
contextMenu: true, // par défaut true
|
||||
toolBar: true, // par défaut true
|
||||
nodeMenu: true, // par défaut true
|
||||
keypress: true, // par défaut true
|
||||
locale: 'en', // [zh_CN,zh_TW,en,ja,pt,ru] en attente de PRs
|
||||
overflowHidden: false, // par défaut false
|
||||
mainLinkStyle: 2, // [1,2] par défaut 1
|
||||
mouseSelectionButton: 0, // 0 pour le bouton gauche, 2 pour le bouton droit, par défaut 0
|
||||
contextMenuOption: {
|
||||
focus: true,
|
||||
link: true,
|
||||
extend: [
|
||||
{
|
||||
name: 'Édition de nœud',
|
||||
onclick: () => {
|
||||
alert('menu étendu')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
before: {
|
||||
insertSibling(el, obj) {
|
||||
return true
|
||||
},
|
||||
async addChild(el, obj) {
|
||||
await sleep()
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let mind = new MindElixir(options)
|
||||
|
||||
mind.install(plugin) // installer votre plugin
|
||||
|
||||
// créer de nouvelles données de carte
|
||||
const data = MindElixir.new('nouveau sujet')
|
||||
// ou `example`
|
||||
// ou les données retournées par `.getData()`
|
||||
mind.init(data)
|
||||
|
||||
// obtenir un nœud
|
||||
MindElixir.E('node-id')
|
||||
```
|
||||
|
||||
### Structure des données
|
||||
|
||||
```javascript
|
||||
// structure complète des données de nœud jusqu'à présent
|
||||
const nodeData = {
|
||||
topic: 'sujet du nœud',
|
||||
id: 'bd1c24420cd2c2f5',
|
||||
style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' },
|
||||
expanded: true,
|
||||
parent: null,
|
||||
tags: ['Tag'],
|
||||
icons: ['😀'],
|
||||
hyperLink: 'https://github.com/ssshooter/mind-elixir-core',
|
||||
image: {
|
||||
url: 'https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png', // requis
|
||||
// vous devez interroger la hauteur et la largeur de l'image et calculer la valeur appropriée pour afficher l'image
|
||||
height: 90, // requis
|
||||
width: 90, // requis
|
||||
},
|
||||
children: [
|
||||
{
|
||||
topic: 'enfant',
|
||||
id: 'xxxx',
|
||||
// ...
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Gestion des événements
|
||||
|
||||
```javascript
|
||||
mind.bus.addListener('operation', operation => {
|
||||
console.log(operation)
|
||||
// return {
|
||||
// name: nom de l'action,
|
||||
// obj: objet cible
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: cible
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:cible1,to:cible2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### Export et import des données
|
||||
|
||||
```javascript
|
||||
// export des données
|
||||
const data = mind.getData() // objet javascript, voir src/example.js
|
||||
mind.getDataString() // objet en chaîne
|
||||
|
||||
// import des données
|
||||
// initialisation
|
||||
let mind = new MindElixir(options)
|
||||
mind.init(data)
|
||||
// mise à jour des données
|
||||
mind.refresh(data)
|
||||
```
|
||||
|
||||
### Gardes d'opération
|
||||
|
||||
```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
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Exporter en image
|
||||
|
||||
### Solution 1
|
||||
|
||||
```typescript
|
||||
const mind = {
|
||||
/** instance mind elixir */
|
||||
}
|
||||
const downloadPng = async () => {
|
||||
const blob = await mind.exportPng() // Obtenez un 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)
|
||||
}
|
||||
```
|
||||
|
||||
### Solution 2
|
||||
|
||||
Installer `@ssshooter/modern-screenshot`, puis :
|
||||
|
||||
```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()
|
||||
}
|
||||
```
|
||||
|
||||
## Thème
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
// ...
|
||||
theme: {
|
||||
name: 'Dark',
|
||||
// palette de couleurs des lignes principales
|
||||
palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
|
||||
// remplacer les variables css
|
||||
cssVar: {
|
||||
'--main-color': '#ffffff',
|
||||
'--main-bgcolor': '#4c4f69',
|
||||
'--color': '#cccccc',
|
||||
'--bgcolor': '#252526',
|
||||
'--panel-color': '255, 255, 255',
|
||||
'--panel-bgcolor': '45, 55, 72',
|
||||
},
|
||||
// toutes les variables voir /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',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Soyez conscient que Mind Elixir n'observera pas le changement de `prefers-color-scheme`. Veuillez changer le thème **manuellement** lorsque le schéma change.
|
||||
|
||||
## Raccourcis
|
||||
|
||||
| Raccourci | Fonction |
|
||||
| ------------------ | ----------------------------------------- |
|
||||
| Entrée | Insérer un nœud frère |
|
||||
| Tab | Insérer un nœud enfant |
|
||||
| F1 | Centrer la carte |
|
||||
| F2 | Commencer l'édition du nœud actuel |
|
||||
| ↑ | Sélectionner le nœud frère précédent |
|
||||
| ↓ | Sélectionner le nœud frère suivant |
|
||||
| ← / → | Sélectionner le parent ou le premier enfant|
|
||||
| PageUp / Alt + ↑ | Déplacer le nœud vers le haut |
|
||||
| PageDown / Alt + ↓| Déplacer le nœud vers le bas |
|
||||
| Ctrl + ↑ | Changer la disposition en mode latéral |
|
||||
| Ctrl + ← | Changer la disposition vers la gauche |
|
||||
| Ctrl + → | Changer la disposition vers la droite |
|
||||
| Ctrl + C | Copier le nœud actuel |
|
||||
| Ctrl + V | Coller le nœud copié |
|
||||
| Ctrl + "+" | Zoomer la carte mentale |
|
||||
| Ctrl + "-" | Dézoomer la carte mentale |
|
||||
| Ctrl + 0 | Réinitialiser le niveau de zoom |
|
||||
|
||||
## Écosystème
|
||||
|
||||
- [@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)
|
||||
|
||||
Les PRs sont les bienvenues !
|
||||
|
||||
## Développement
|
||||
|
||||
```
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Tester les fichiers générés avec `dev.dist.ts` :
|
||||
|
||||
```
|
||||
pnpm build
|
||||
pnpm link ./
|
||||
```
|
||||
|
||||
Mettre à jour la documentation :
|
||||
|
||||
```
|
||||
# Installer api-extractor
|
||||
pnpm install -g @microsoft/api-extractor
|
||||
# Maintenir /src/docs.ts
|
||||
# Générer la documentation
|
||||
pnpm doc
|
||||
pnpm doc:md
|
||||
```
|
||||
|
||||
## Remerciements
|
||||
|
||||
- [@viselect/vanilla](https://github.com/simonwep/selection/tree/master/packages/vanilla)
|
||||
|
||||
## Contributeurs
|
||||
|
||||
Merci pour vos contributions à Mind Elixir ! Votre soutien et votre dévouement rendent ce projet meilleur.
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,429 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="version">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="license">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="code quality">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="dependency-count">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="package size">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind Elixirは、オープンソースのJavaScriptマインドマップコアです。お好みのフロントエンドフレームワークと組み合わせて使用できます。
|
||||
|
||||
特徴:
|
||||
|
||||
- 軽量
|
||||
- 高パフォーマンス
|
||||
- フレームワーク非依存
|
||||
- プラグイン対応
|
||||
- ドラッグ&ドロップ/ノード編集プラグイン内蔵
|
||||
- SVG/PNG/HTMLとしてエクスポート可能
|
||||
- ノードの要約
|
||||
- 一括操作対応
|
||||
- 元に戻す/やり直し
|
||||
- 効率的なショートカット
|
||||
- CSSカスタマイズが容易
|
||||
|
||||
<details>
|
||||
<summary>目次</summary>
|
||||
|
||||
- [デモを試す](#デモを試す)
|
||||
- [プレイグラウンド](#プレイグラウンド)
|
||||
- [ドキュメント](#ドキュメント)
|
||||
- [使い方](#使い方)
|
||||
- [インストール](#インストール)
|
||||
- [NPM](#npm)
|
||||
- [スクリプトタグ](#スクリプトタグ)
|
||||
- [初期化](#初期化)
|
||||
- [データ構造](#データ構造)
|
||||
- [イベントハンドリング](#イベントハンドリング)
|
||||
- [データのエクスポートとインポート](#データのエクスポートとインポート)
|
||||
- [操作ガード](#操作ガード)
|
||||
- [画像としてエクスポート](#画像としてエクスポート)
|
||||
- [方法1](#方法1)
|
||||
- [方法2](#方法2)
|
||||
- [テーマ](#テーマ)
|
||||
- [ショートカット](#ショートカット)
|
||||
- [エコシステム](#エコシステム)
|
||||
- [開発](#開発)
|
||||
- [謝辞](#謝辞)
|
||||
- [貢献者](#貢献者)
|
||||
|
||||
</details>
|
||||
|
||||
## デモを試す
|
||||
|
||||

|
||||
|
||||
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`は`options`ではなく`init()`に渡す必要があります。
|
||||
|
||||
```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: 'ja', // [zh_CN,zh_TW,en,ja,pt,ru] PRs募集中
|
||||
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: action name,
|
||||
// obj: target object
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: target
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:target1,to:target2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### データのエクスポートとインポート
|
||||
|
||||
```javascript
|
||||
// データのエクスポート
|
||||
const data = mind.getData() // JavaScriptオブジェクト、src/example.jsを参照
|
||||
mind.getDataString() // オブジェクトを文字列化
|
||||
|
||||
// データのインポート
|
||||
// 初期化
|
||||
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 instance */
|
||||
}
|
||||
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()
|
||||
}
|
||||
```
|
||||
|
||||
## テーマ
|
||||
|
||||
```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)
|
||||
|
||||
PRsは大歓迎です!
|
||||
|
||||
## 開発
|
||||
|
||||
```
|
||||
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)
|
||||
|
||||
## 貢献者
|
||||
|
||||
Mind Elixirへの貢献に感謝します!あなたのサポートと献身がこのプロジェクトをより良くします。
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="version">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="license">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="code quality">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="dependency-count">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="package size">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind Elixir는 오픈 소스 JavaScript 마인드맵 코어입니다. 원하는 프론트엔드 프레임워크와 함께 사용할 수 있습니다.
|
||||
|
||||
특징:
|
||||
|
||||
- 경량화
|
||||
- 고성능
|
||||
- 프레임워크에 구애받지 않음
|
||||
- 플러그인 지원
|
||||
- 드래그 앤 드롭 / 노드 편집 플러그인 내장
|
||||
- SVG / PNG / HTML 내보내기
|
||||
- 노드 요약
|
||||
- 대량 작업 지원
|
||||
- 실행 취소 / 다시 실행
|
||||
- 효율적인 단축키
|
||||
- CSS 변수로 쉽게 노드 스타일링
|
||||
|
||||
<details>
|
||||
<summary>목차</summary>
|
||||
|
||||
- [지금 시작하기](#지금-시작하기)
|
||||
- [플레이그라운드](#플레이그라운드)
|
||||
- [문서](#문서)
|
||||
- [사용법](#사용법)
|
||||
- [설치](#설치)
|
||||
- [NPM](#npm)
|
||||
- [스크립트 태그](#스크립트-태그)
|
||||
- [초기화](#초기화)
|
||||
- [데이터 구조](#데이터-구조)
|
||||
- [이벤트 처리](#이벤트-처리)
|
||||
- [데이터 내보내기와 가져오기](#데이터-내보내기와-가져오기)
|
||||
- [작업 가드](#작업-가드)
|
||||
- [이미지로 내보내기](#이미지로-내보내기)
|
||||
- [방법 1](#방법-1)
|
||||
- [방법 2](#방법-2)
|
||||
- [테마](#테마)
|
||||
- [단축키](#단축키)
|
||||
- [생태계](#생태계)
|
||||
- [개발](#개발)
|
||||
- [감사의 말](#감사의-말)
|
||||
- [기여자](#기여자)
|
||||
|
||||
</details>
|
||||
|
||||
## 지금 시작하기
|
||||
|
||||

|
||||
|
||||
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`는 `options`가 아닌 `init()`에 전달되어야 합니다.
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
import example from 'mind-elixir/dist/example1'
|
||||
|
||||
let options = {
|
||||
el: '#map', // or HTMLDivElement
|
||||
direction: MindElixir.LEFT,
|
||||
draggable: true, // default true
|
||||
contextMenu: true, // default true
|
||||
toolBar: true, // default true
|
||||
nodeMenu: true, // default true
|
||||
keypress: true, // default true
|
||||
locale: 'en', // [zh_CN,zh_TW,en,ja,pt,ru] waiting for PRs
|
||||
overflowHidden: false, // default false
|
||||
mainLinkStyle: 2, // [1,2] default 1
|
||||
mouseSelectionButton: 0, // 0 for left button, 2 for right button, default 0
|
||||
contextMenuOption: {
|
||||
focus: true,
|
||||
link: true,
|
||||
extend: [
|
||||
{
|
||||
name: 'Node edit',
|
||||
onclick: () => {
|
||||
alert('extend menu')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
before: {
|
||||
insertSibling(el, obj) {
|
||||
return true
|
||||
},
|
||||
async addChild(el, obj) {
|
||||
await sleep()
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let mind = new MindElixir(options)
|
||||
|
||||
mind.install(plugin) // install your plugin
|
||||
|
||||
// create new map data
|
||||
const data = MindElixir.new('new topic')
|
||||
// or `example`
|
||||
// or the data return from `.getData()`
|
||||
mind.init(data)
|
||||
|
||||
// get a node
|
||||
MindElixir.E('node-id')
|
||||
```
|
||||
|
||||
### 데이터 구조
|
||||
|
||||
```javascript
|
||||
// whole node data structure up to now
|
||||
const nodeData = {
|
||||
topic: 'node topic',
|
||||
id: 'bd1c24420cd2c2f5',
|
||||
style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' },
|
||||
expanded: true,
|
||||
parent: null,
|
||||
tags: ['Tag'],
|
||||
icons: ['😀'],
|
||||
hyperLink: 'https://github.com/ssshooter/mind-elixir-core',
|
||||
image: {
|
||||
url: 'https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png', // required
|
||||
// you need to query the height and width of the image and calculate the appropriate value to display the image
|
||||
height: 90, // required
|
||||
width: 90, // required
|
||||
},
|
||||
children: [
|
||||
{
|
||||
topic: 'child',
|
||||
id: 'xxxx',
|
||||
// ...
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### 이벤트 처리
|
||||
|
||||
```javascript
|
||||
mind.bus.addListener('operation', operation => {
|
||||
console.log(operation)
|
||||
// return {
|
||||
// name: action name,
|
||||
// obj: target object
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: target
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:target1,to:target2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### 데이터 내보내기와 가져오기
|
||||
|
||||
```javascript
|
||||
// data export
|
||||
const data = mind.getData() // javascript object, see src/example.js
|
||||
mind.getDataString() // stringify object
|
||||
|
||||
// data import
|
||||
// initiate
|
||||
let mind = new MindElixir(options)
|
||||
mind.init(data)
|
||||
// data update
|
||||
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 instance */
|
||||
}
|
||||
const downloadPng = async () => {
|
||||
const blob = await mind.exportPng() // Get a 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
|
||||
|
||||
Install `@ssshooter/modern-screenshot`, then:
|
||||
|
||||
```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()
|
||||
}
|
||||
```
|
||||
|
||||
## 테마
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
// ...
|
||||
theme: {
|
||||
name: 'Dark',
|
||||
// main lines color palette
|
||||
palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
|
||||
// overwrite css variables
|
||||
cssVar: {
|
||||
'--main-color': '#ffffff',
|
||||
'--main-bgcolor': '#4c4f69',
|
||||
'--color': '#cccccc',
|
||||
'--bgcolor': '#252526',
|
||||
'--panel-color': '255, 255, 255',
|
||||
'--panel-bgcolor': '45, 55, 72',
|
||||
},
|
||||
// all variables see /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',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Be aware that Mind Elixir will not observe the change of `prefers-color-scheme`. Please change the theme **manually** when the scheme changes.
|
||||
|
||||
## 단축키
|
||||
|
||||
| 단축키 | 기능 |
|
||||
| ----------------- | -------------------------- |
|
||||
| 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)
|
||||
|
||||
PR은 언제나 환영입니다!
|
||||
|
||||
## 개발
|
||||
|
||||
```
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Test generated files with `dev.dist.ts`:
|
||||
|
||||
```
|
||||
pnpm build
|
||||
pnpm link ./
|
||||
```
|
||||
|
||||
Update docs:
|
||||
|
||||
```
|
||||
# Install api-extractor
|
||||
pnpm install -g @microsoft/api-extractor
|
||||
# Maintain /src/docs.ts
|
||||
# Generate docs
|
||||
pnpm doc
|
||||
pnpm doc:md
|
||||
```
|
||||
|
||||
## 감사의 말
|
||||
|
||||
- [@viselect/vanilla](https://github.com/simonwep/selection/tree/master/packages/vanilla)
|
||||
|
||||
## 기여자
|
||||
|
||||
Mind Elixir에 기여해 주셔서 감사합니다! 여러분의 지원과 헌신이 이 프로젝트를 더 좋게 만들어 갑니다.
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="version">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="license">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="code quality">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="dependency-count">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="package size">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind Elixir é um núcleo JavaScript de mapa mental de código aberto. Você pode usá-lo com qualquer framework frontend de sua preferência.
|
||||
|
||||
Características:
|
||||
|
||||
- Leve
|
||||
- Alto desempenho
|
||||
- Independente de framework
|
||||
- Plugável
|
||||
- Plugin integrado de arrastar e soltar / edição de nós
|
||||
- Exportação como SVG / PNG / Html
|
||||
- Resumo de nós
|
||||
- Suporte a operações em massa
|
||||
- Desfazer / Refazer
|
||||
- Atalhos eficientes
|
||||
- Estilização fácil dos nós com variáveis CSS
|
||||
|
||||
<details>
|
||||
<summary>Índice</summary>
|
||||
|
||||
- [Experimente agora](#experimente-agora)
|
||||
- [Playground](#playground)
|
||||
- [Documentação](#documentação)
|
||||
- [Uso](#uso)
|
||||
- [Instalação](#instalação)
|
||||
- [NPM](#npm)
|
||||
- [Tag de script](#tag-de-script)
|
||||
- [Inicialização](#inicialização)
|
||||
- [Estrutura de Dados](#estrutura-de-dados)
|
||||
- [Manipulação de Eventos](#manipulação-de-eventos)
|
||||
- [Exportação e Importação de Dados](#exportação-e-importação-de-dados)
|
||||
- [Guardas de Operação](#guardas-de-operação)
|
||||
- [Exportar como Imagem](#exportar-como-imagem)
|
||||
- [Solução 1](#solução-1)
|
||||
- [Solução 2](#solução-2)
|
||||
- [Tema](#tema)
|
||||
- [Atalhos](#atalhos)
|
||||
- [Ecossistema](#ecossistema)
|
||||
- [Desenvolvimento](#desenvolvimento)
|
||||
- [Agradecimentos](#agradecimentos)
|
||||
- [Contribuidores](#contribuidores)
|
||||
|
||||
</details>
|
||||
|
||||
## Experimente agora
|
||||
|
||||

|
||||
|
||||
https://mind-elixir.com/
|
||||
|
||||
### Playground
|
||||
|
||||
- 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
|
||||
|
||||
## Documentação
|
||||
|
||||
https://docs.mind-elixir.com/
|
||||
|
||||
## Uso
|
||||
|
||||
### Instalação
|
||||
|
||||
#### NPM
|
||||
|
||||
```bash
|
||||
npm i mind-elixir -S
|
||||
```
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
```
|
||||
|
||||
#### Tag de script
|
||||
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/mind-elixir/dist/MindElixir.js"></script>
|
||||
```
|
||||
|
||||
### Inicialização
|
||||
|
||||
```html
|
||||
<div id="map"></div>
|
||||
<style>
|
||||
#map {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Mudança Importante** desde a versão 1.0.0, `data` deve ser passado para `init()`, não para `options`.
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
import example from 'mind-elixir/dist/example1'
|
||||
|
||||
let options = {
|
||||
el: '#map', // ou HTMLDivElement
|
||||
direction: MindElixir.LEFT,
|
||||
draggable: true, // padrão true
|
||||
contextMenu: true, // padrão true
|
||||
toolBar: true, // padrão true
|
||||
nodeMenu: true, // padrão true
|
||||
keypress: true, // padrão true
|
||||
locale: 'pt', // [zh_CN,zh_TW,en,ja,pt,ru] aguardando PRs
|
||||
overflowHidden: false, // padrão false
|
||||
mainLinkStyle: 2, // [1,2] padrão 1
|
||||
mouseSelectionButton: 0, // 0 para botão esquerdo, 2 para botão direito, padrão 0
|
||||
contextMenuOption: {
|
||||
focus: true,
|
||||
link: true,
|
||||
extend: [
|
||||
{
|
||||
name: 'Editar Nó',
|
||||
onclick: () => {
|
||||
alert('menu estendido')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
before: {
|
||||
insertSibling(el, obj) {
|
||||
return true
|
||||
},
|
||||
async addChild(el, obj) {
|
||||
await sleep()
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let mind = new MindElixir(options)
|
||||
|
||||
mind.install(plugin) // instale seu plugin
|
||||
|
||||
// criar novos dados do mapa
|
||||
const data = MindElixir.new('novo tópico')
|
||||
// ou `example`
|
||||
// ou os dados retornados de `.getData()`
|
||||
mind.init(data)
|
||||
|
||||
// obter um nó
|
||||
MindElixir.E('node-id')
|
||||
```
|
||||
|
||||
### Estrutura de Dados
|
||||
|
||||
```javascript
|
||||
// estrutura completa de dados do nó até agora
|
||||
const nodeData = {
|
||||
topic: 'tópico do nó',
|
||||
id: 'bd1c24420cd2c2f5',
|
||||
style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' },
|
||||
expanded: true,
|
||||
parent: null,
|
||||
tags: ['Tag'],
|
||||
icons: ['😀'],
|
||||
hyperLink: 'https://github.com/ssshooter/mind-elixir-core',
|
||||
image: {
|
||||
url: 'https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png', // obrigatório
|
||||
// você precisa consultar a altura e largura da imagem e calcular o valor apropriado para exibir a imagem
|
||||
height: 90, // obrigatório
|
||||
width: 90, // obrigatório
|
||||
},
|
||||
children: [
|
||||
{
|
||||
topic: 'filho',
|
||||
id: 'xxxx',
|
||||
// ...
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Manipulação de Eventos
|
||||
|
||||
```javascript
|
||||
mind.bus.addListener('operation', operation => {
|
||||
console.log(operation)
|
||||
// retorna {
|
||||
// name: nome da ação,
|
||||
// obj: objeto alvo
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: alvo
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:alvo1,to:alvo2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### Exportação e Importação de Dados
|
||||
|
||||
```javascript
|
||||
// exportação de dados
|
||||
const data = mind.getData() // objeto javascript, veja src/example.js
|
||||
mind.getDataString() // objeto em string
|
||||
|
||||
// importação de dados
|
||||
// inicialização
|
||||
let mind = new MindElixir(options)
|
||||
mind.init(data)
|
||||
// atualização de dados
|
||||
mind.refresh(data)
|
||||
```
|
||||
|
||||
### Guardas de Operação
|
||||
|
||||
```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
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Exportar como Imagem
|
||||
|
||||
### Solução 1
|
||||
|
||||
```typescript
|
||||
const mind = {
|
||||
/** instância do mind elixir */
|
||||
}
|
||||
const downloadPng = async () => {
|
||||
const blob = await mind.exportPng() // Obter um Blob!
|
||||
if (!blob) return
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'arquivo.png'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
```
|
||||
|
||||
### Solução 2
|
||||
|
||||
Instale `@ssshooter/modern-screenshot`, depois:
|
||||
|
||||
```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()
|
||||
}
|
||||
```
|
||||
|
||||
## Tema
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
// ...
|
||||
theme: {
|
||||
name: 'Dark',
|
||||
// main lines color palette
|
||||
palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
|
||||
// overwrite css variables
|
||||
cssVar: {
|
||||
'--main-color': '#ffffff',
|
||||
'--main-bgcolor': '#4c4f69',
|
||||
'--color': '#cccccc',
|
||||
'--bgcolor': '#252526',
|
||||
'--panel-color': '255, 255, 255',
|
||||
'--panel-bgcolor': '45, 55, 72',
|
||||
},
|
||||
// all variables see /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',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Be aware that Mind Elixir will not observe the change of `prefers-color-scheme`. Please change the theme **manually** when the scheme changes.
|
||||
|
||||
## Atalhos
|
||||
|
||||
| Atalho | Função |
|
||||
| ------------------ | --------------------------------- |
|
||||
| Enter | Inserir Nó Irmão |
|
||||
| Tab | Inserir Nó Filho |
|
||||
| F1 | Centralizar o Mapa |
|
||||
| F2 | Começar a Editar o Nó Atual |
|
||||
| ↑ | Selecionar o Nó Irmão Anterior |
|
||||
| ↓ | Selecionar o Próximo Nó Irmão |
|
||||
| ← / → | Selecionar Pai ou Primeiro Filho |
|
||||
| PageUp / Alt + ↑ | Mover Nó para Cima |
|
||||
| PageDown / Alt + ↓ | Mover Nó para Baixo |
|
||||
| Ctrl + ↑ | Mudar Padrão de Layout para Lado |
|
||||
| Ctrl + ← | Mudar Padrão de Layout para Esquerda |
|
||||
| Ctrl + → | Mudar Padrão de Layout para Direita |
|
||||
| Ctrl + C | Copiar o Nó Atual |
|
||||
| Ctrl + V | Colar o Nó Copiado |
|
||||
| Ctrl + "+" | Aumentar Zoom do Mapa Mental |
|
||||
| Ctrl + "-" | Diminuir Zoom do Mapa Mental |
|
||||
| Ctrl + 0 | Redefinir Nível de Zoom |
|
||||
|
||||
## Ecossistema
|
||||
|
||||
- [@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)
|
||||
|
||||
PRs são bem-vindos!
|
||||
|
||||
## Desenvolvimento
|
||||
|
||||
```
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Testar arquivos gerados com `dev.dist.ts`:
|
||||
|
||||
```
|
||||
pnpm build
|
||||
pnpm link ./
|
||||
```
|
||||
|
||||
Atualizar documentação:
|
||||
|
||||
```
|
||||
# Instalar api-extractor
|
||||
pnpm install -g @microsoft/api-extractor
|
||||
# Manter /src/docs.ts
|
||||
# Gerar documentação
|
||||
pnpm doc
|
||||
pnpm doc:md
|
||||
```
|
||||
|
||||
## Agradecimentos
|
||||
|
||||
- [@viselect/vanilla](https://github.com/simonwep/selection/tree/master/packages/vanilla)
|
||||
|
||||
## Contribuidores
|
||||
|
||||
Obrigado por suas contribuições ao Mind Elixir! Seu apoio e dedicação tornam este projeto melhor.
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="version">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="license">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="code quality">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="dependency-count">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="package size">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind Elixir - это библиотека с открытым исходным кодом для создания интеллект-карт на JavaScript. Вы можете использовать её с любым frontend-фреймворком.
|
||||
|
||||
Особенности:
|
||||
|
||||
- Легковесность
|
||||
- Высокая производительность
|
||||
- Независимость от фреймворков
|
||||
- Расширяемость с помощью плагинов
|
||||
- Встроенные плагины для перетаскивания и редактирования узлов
|
||||
- Экспорт в SVG / PNG / HTML
|
||||
- Возможность сворачивать узлы
|
||||
- Поддержка массовых операций
|
||||
- Отмена / Повтор действий
|
||||
- Эффективные горячие клавиши
|
||||
- Простая стилизация узлов с помощью CSS переменных
|
||||
|
||||
<details>
|
||||
<summary>Содержание</summary>
|
||||
|
||||
- [Попробовать сейчас](#попробовать-сейчас)
|
||||
- [Playground](#playground)
|
||||
- [Документация](#документация)
|
||||
- [Использование](#использование)
|
||||
- [Установка](#установка)
|
||||
- [NPM](#npm)
|
||||
- [Script tag](#script-tag)
|
||||
- [Инициализация](#инициализация)
|
||||
- [Структура данных](#структура-данных)
|
||||
- [Обработка событий](#обработка-событий)
|
||||
- [Экспорт и импорт данных](#экспорт-и-импорт-данных)
|
||||
- [Контроль операций](#контроль-операций)
|
||||
- [Экспорт в изображение](#экспорт-в-изображение)
|
||||
- [Способ 1](#способ-1)
|
||||
- [Способ 2](#способ-2)
|
||||
- [Тема](#тема)
|
||||
- [Горячие клавиши](#горячие-клавиши)
|
||||
- [Экосистема](#экосистема)
|
||||
- [Разработка](#разработка)
|
||||
- [Благодарности](#благодарности)
|
||||
- [Контрибьюторы](#контрибьюторы)
|
||||
|
||||
</details>
|
||||
|
||||
## Попробовать сейчас
|
||||
|
||||

|
||||
|
||||
https://mind-elixir.com/
|
||||
|
||||
### Playground
|
||||
|
||||
- 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'
|
||||
```
|
||||
|
||||
#### Script tag
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
**Breaking Change** since 1.0.0, `data` should be passed to `init()`, not `options`.
|
||||
|
||||
```javascript
|
||||
import MindElixir from 'mind-elixir'
|
||||
import example from 'mind-elixir/dist/example1'
|
||||
|
||||
let options = {
|
||||
el: '#map', // or HTMLDivElement
|
||||
direction: MindElixir.LEFT,
|
||||
draggable: true, // default true
|
||||
contextMenu: true, // default true
|
||||
toolBar: true, // default true
|
||||
nodeMenu: true, // default true
|
||||
keypress: true, // default true
|
||||
locale: 'en', // [zh_CN,zh_TW,en,ja,pt,ru] waiting for PRs
|
||||
overflowHidden: false, // default false
|
||||
mainLinkStyle: 2, // [1,2] default 1
|
||||
mouseSelectionButton: 0, // 0 for left button, 2 for right button, default 0
|
||||
contextMenuOption: {
|
||||
focus: true,
|
||||
link: true,
|
||||
extend: [
|
||||
{
|
||||
name: 'Node edit',
|
||||
onclick: () => {
|
||||
alert('extend menu')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
before: {
|
||||
insertSibling(el, obj) {
|
||||
return true
|
||||
},
|
||||
async addChild(el, obj) {
|
||||
await sleep()
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let mind = new MindElixir(options)
|
||||
|
||||
mind.install(plugin) // install your plugin
|
||||
|
||||
// create new map data
|
||||
const data = MindElixir.new('new topic')
|
||||
// or `example`
|
||||
// or the data return from `.getData()`
|
||||
mind.init(data)
|
||||
|
||||
// get a node
|
||||
MindElixir.E('node-id')
|
||||
```
|
||||
|
||||
### Структура данных
|
||||
|
||||
```javascript
|
||||
// whole node data structure up to now
|
||||
const nodeData = {
|
||||
topic: 'node topic',
|
||||
id: 'bd1c24420cd2c2f5',
|
||||
style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' },
|
||||
expanded: true,
|
||||
parent: null,
|
||||
tags: ['Tag'],
|
||||
icons: ['😀'],
|
||||
hyperLink: 'https://github.com/ssshooter/mind-elixir-core',
|
||||
image: {
|
||||
url: 'https://raw.githubusercontent.com/ssshooter/mind-elixir-core/master/images/logo2.png', // required
|
||||
// you need to query the height and width of the image and calculate the appropriate value to display the image
|
||||
height: 90, // required
|
||||
width: 90, // required
|
||||
},
|
||||
children: [
|
||||
{
|
||||
topic: 'child',
|
||||
id: 'xxxx',
|
||||
// ...
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Обработка событий
|
||||
|
||||
```javascript
|
||||
mind.bus.addListener('operation', operation => {
|
||||
console.log(operation)
|
||||
// return {
|
||||
// name: action name,
|
||||
// obj: target object
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: target
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:target1,to:target2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### Экспорт и импорт данных
|
||||
|
||||
```javascript
|
||||
// data export
|
||||
const data = mind.getData() // javascript object, see src/example.js
|
||||
mind.getDataString() // stringify object
|
||||
|
||||
// data import
|
||||
// initiate
|
||||
let mind = new MindElixir(options)
|
||||
mind.init(data)
|
||||
// data update
|
||||
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 instance */
|
||||
}
|
||||
const downloadPng = async () => {
|
||||
const blob = await mind.exportPng() // Get a 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
|
||||
|
||||
Install `@ssshooter/modern-screenshot`, then:
|
||||
|
||||
```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()
|
||||
}
|
||||
```
|
||||
|
||||
## Тема
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
// ...
|
||||
theme: {
|
||||
name: 'Dark',
|
||||
// main lines color palette
|
||||
palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
|
||||
// overwrite css variables
|
||||
cssVar: {
|
||||
'--main-color': '#ffffff',
|
||||
'--main-bgcolor': '#4c4f69',
|
||||
'--color': '#cccccc',
|
||||
'--bgcolor': '#252526',
|
||||
'--panel-color': '255, 255, 255',
|
||||
'--panel-bgcolor': '45, 55, 72',
|
||||
},
|
||||
// all variables see /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',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Be aware that Mind Elixir will not observe the change of `prefers-color-scheme`. Please change the theme **manually** when the scheme changes.
|
||||
|
||||
## Горячие клавиши
|
||||
|
||||
| Комбинация клавиш | Функция |
|
||||
| ------------------- | -------------------------------------- |
|
||||
| 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)
|
||||
|
||||
PRs are welcome!
|
||||
|
||||
## Разработка
|
||||
|
||||
```
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Test generated files with `dev.dist.ts`:
|
||||
|
||||
```
|
||||
pnpm build
|
||||
pnpm link ./
|
||||
```
|
||||
|
||||
Update docs:
|
||||
|
||||
```
|
||||
# Install api-extractor
|
||||
pnpm install -g @microsoft/api-extractor
|
||||
# Maintain /src/docs.ts
|
||||
# Generate docs
|
||||
pnpm doc
|
||||
pnpm doc:md
|
||||
```
|
||||
|
||||
## Благодарности
|
||||
|
||||
- [@viselect/vanilla](https://github.com/simonwep/selection/tree/master/packages/vanilla)
|
||||
|
||||
## Контрибьюторы
|
||||
|
||||
Спасибо за ваш вклад в Mind Elixir! Ваша поддержка и преданность делают этот проект лучше.
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://docs.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>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/mind-elixir">
|
||||
<img src="https://img.shields.io/npm/v/mind-elixir" alt="version">
|
||||
</a>
|
||||
<a href="https://github.com/ssshooter/mind-elixir-core/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/npm/l/mind-elixir" alt="license">
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/ssshooter/mind-elixir-core?utm_source=github.com&utm_medium=referral&utm_content=ssshooter/mind-elixir-core&utm_campaign=Badge_Grade_Settings">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/09fadec5bf094886b30cea6aabf3a88b" alt="code quality">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=mind-elixir">
|
||||
<img src="https://badgen.net/bundlephobia/dependency-count/mind-elixir" alt="dependency-count">
|
||||
</a>
|
||||
<a href="https://packagephobia.com/result?p=mind-elixir">
|
||||
<img src="https://packagephobia.com/badge?p=mind-elixir" alt="package size">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[English](/readme.md) |
|
||||
[中文](/readme/zh.md) |
|
||||
[Español](/readme/es.md) |
|
||||
[Français](/readme/fr.md) |
|
||||
[Português](/readme/pt.md) |
|
||||
[Русский](/readme/ru.md) |
|
||||
[日本語](/readme/ja.md) |
|
||||
[한국어](/readme/ko.md)
|
||||
|
||||
Mind elixir 是一个开源的 JavaScript 思维导图核心。你可以在任何前端框架中使用它。
|
||||
|
||||
特点:
|
||||
|
||||
- 轻量级
|
||||
- 高性能
|
||||
- 框架无关
|
||||
- 插件化
|
||||
- 内置拖放 / 节点编辑插件
|
||||
- 导出为 SVG / PNG / Html
|
||||
- 总结节点
|
||||
- 支持批量操作
|
||||
- 撤销 / 重做
|
||||
- 高效快捷键
|
||||
- 轻松使用 CSS 变量样式化节点
|
||||
|
||||
<details>
|
||||
<summary>目录</summary>
|
||||
|
||||
- [立即试用](#立即试用)
|
||||
- [演示](#演示)
|
||||
- [文档](#文档)
|
||||
- [使用](#使用)
|
||||
- [安装](#安装)
|
||||
- [NPM](#npm)
|
||||
- [Script 标签](#script-标签)
|
||||
- [初始化](#初始化)
|
||||
- [数据结构](#数据结构)
|
||||
- [事件处理](#事件处理)
|
||||
- [数据导出和导入](#数据导出和导入)
|
||||
- [操作守卫](#操作守卫)
|
||||
- [导出为图片](#导出为图片)
|
||||
- [方案 1](#方案-1)
|
||||
- [方案 2](#方案-2)
|
||||
- [主题](#主题)
|
||||
- [快捷键](#快捷键)
|
||||
- [生态](#生态)
|
||||
- [开发](#开发)
|
||||
- [感谢](#感谢)
|
||||
- [贡献者](#贡献者)
|
||||
|
||||
</details>
|
||||
|
||||
## 立即试用
|
||||
|
||||

|
||||
|
||||
https://mind-elixir.com/
|
||||
|
||||
### 演示
|
||||
|
||||
- 原生 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'
|
||||
```
|
||||
|
||||
#### Script 标签
|
||||
|
||||
```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] 等待 PRs
|
||||
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('new topic')
|
||||
// 或 `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)
|
||||
// 返回 {
|
||||
// name: 操作名称,
|
||||
// obj: 目标对象
|
||||
// }
|
||||
|
||||
// name: [insertSibling|addChild|removeNode|beginEdit|finishEdit]
|
||||
// obj: 目标
|
||||
|
||||
// name: moveNode
|
||||
// obj: {from:目标1,to:目标2}
|
||||
})
|
||||
|
||||
|
||||
mind.bus.addListener('selectNodes', nodes => {
|
||||
console.log(nodes)
|
||||
})
|
||||
|
||||
mind.bus.addListener('expandNode', node => {
|
||||
console.log('expandNode: ', node)
|
||||
})
|
||||
```
|
||||
|
||||
### 数据导出和导入
|
||||
|
||||
```javascript
|
||||
// 数据导出
|
||||
const data = mind.getData() // JavaScript 对象,见 src/example.js
|
||||
mind.getDataString() // 字符串化对象
|
||||
|
||||
// 数据导入
|
||||
// 初始化
|
||||
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()
|
||||
}
|
||||
```
|
||||
|
||||
## 主题
|
||||
|
||||
```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)
|
||||
|
||||
欢迎 PR!
|
||||
|
||||
## 开发
|
||||
|
||||
```
|
||||
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)
|
||||
|
||||
## 贡献者
|
||||
|
||||
感谢你们对 Mind Elixir 的贡献!你们的支持和奉献使这个项目变得更好。
|
||||
|
||||
<a href="https://github.com/SSShooter/mind-elixir-core/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=SSShooter/mind-elixir-core&columns=6" />
|
||||
</a>
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Mind Elixir</title>
|
||||
<style>
|
||||
/* test tailwind compatibility */
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
#map,
|
||||
#map2 {
|
||||
margin-top: 30px;
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<div id="map2"></div>
|
||||
<script type="module">
|
||||
import MindElixir from '/src/index.ts'
|
||||
window.MindElixir = MindElixir
|
||||
window.E = MindElixir.E
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -307,29 +307,52 @@ const addMarkdownStyles = (container) => {
|
|||
width: 100%;
|
||||
margin: 4px 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
background-color: #fafafa;
|
||||
overflow: hidden;
|
||||
white-space: normal; /* 覆盖MindElixir的pre-wrap */
|
||||
}
|
||||
|
||||
.markdown-table th,
|
||||
.markdown-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
white-space: normal; /* 覆盖MindElixir的pre-wrap */
|
||||
}
|
||||
|
||||
.markdown-table th {
|
||||
background-color: #f8f9fa;
|
||||
background-color: #f5f5f5;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
}
|
||||
|
||||
.markdown-table tr:nth-child(even) {
|
||||
background-color: #f8f9fa;
|
||||
.markdown-table td {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.markdown-table tr:hover {
|
||||
background-color: #e9ecef;
|
||||
.markdown-table tr:nth-child(even) td {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.markdown-table tr:hover td {
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
/* 移除多余的边框,保持简洁 */
|
||||
.markdown-table th:not(:last-child),
|
||||
.markdown-table td:not(:last-child) {
|
||||
border-right: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.markdown-table tr:not(:last-child) td {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.markdown-content a {
|
||||
|
|
@ -477,6 +500,7 @@ export const smartRenderNodeContent = (content) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
renderMarkdownToHTML,
|
||||
setNodeMarkdownContent,
|
||||
|
|
|
|||
|
|
@ -1,261 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>增强Markdown渲染测试</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.test-container {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.markdown-content {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.markdown-content h1,
|
||||
.markdown-content h2,
|
||||
.markdown-content h3,
|
||||
.markdown-content h4,
|
||||
.markdown-content h5,
|
||||
.markdown-content h6 {
|
||||
margin: 4px 0 2px 0;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.markdown-content h1 { font-size: 16px; }
|
||||
.markdown-content h2 { font-size: 15px; }
|
||||
.markdown-content h3 { font-size: 14px; }
|
||||
.markdown-content h4 { font-size: 13px; }
|
||||
.markdown-content h5 { font-size: 12px; }
|
||||
.markdown-content h6 { font-size: 11px; }
|
||||
|
||||
.markdown-content p {
|
||||
margin: 2px 0;
|
||||
line-height: 1.3;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.markdown-content ul,
|
||||
.markdown-content ol {
|
||||
margin: 2px 0;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.markdown-content li {
|
||||
margin: 1px 0;
|
||||
line-height: 1.3;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.markdown-content strong,
|
||||
.markdown-content b {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.markdown-content em,
|
||||
.markdown-content i {
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.markdown-content code {
|
||||
background: #f5f5f5;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: #d63384;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.markdown-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
font-size: 11px;
|
||||
border: none;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.markdown-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.markdown-table th,
|
||||
.markdown-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px 6px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.markdown-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.markdown-table tr:nth-child(even) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.markdown-table tr:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.markdown-content a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-content blockquote {
|
||||
border-left: 3px solid #ddd;
|
||||
margin: 4px 0;
|
||||
padding-left: 8px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>增强Markdown渲染测试</h1>
|
||||
|
||||
<div class="test-container">
|
||||
<h2>测试1: 代码块 + 表格组合</h2>
|
||||
<div id="test1" class="markdown-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-container">
|
||||
<h2>测试2: 复杂表格</h2>
|
||||
<div id="test2" class="markdown-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-container">
|
||||
<h2>测试3: 你的测试内容</h2>
|
||||
<div id="test3" class="markdown-content"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 配置marked
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
sanitize: false,
|
||||
});
|
||||
|
||||
// 后处理函数
|
||||
function postprocessHTML(html) {
|
||||
let processedHTML = html
|
||||
.replace(/<table>/g, '<table class="markdown-table">')
|
||||
.replace(/<pre><code/g, '<pre class="markdown-code"><code');
|
||||
|
||||
// 创建临时DOM元素来处理语法高亮
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = processedHTML;
|
||||
|
||||
// 为代码块添加语法高亮
|
||||
const codeBlocks = tempDiv.querySelectorAll('pre code');
|
||||
codeBlocks.forEach(block => {
|
||||
const language = block.className.match(/language-(\w+)/);
|
||||
if (language) {
|
||||
try {
|
||||
block.innerHTML = Prism.highlight(block.textContent, Prism.languages[language[1]], language[1]);
|
||||
} catch (e) {
|
||||
console.warn('语法高亮失败:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
// 测试1: 代码块 + 表格组合
|
||||
const test1Markdown = `\`\`\`js
|
||||
console.log('hello, JavaScript')
|
||||
\`\`\`
|
||||
|
||||
| Products | Price |
|
||||
|----------|-------|
|
||||
| Apple | 4 |
|
||||
| Banana | 2 |`;
|
||||
|
||||
document.getElementById('test1').innerHTML = postprocessHTML(marked.parse(test1Markdown));
|
||||
|
||||
// 测试2: 复杂表格
|
||||
const test2Markdown = `| 评估维度 | 权重 | 评分标准 | 计算公式 |
|
||||
|---------|------|---------|---------|
|
||||
| 完整性 | 0.3 | 缺失值比例 < 5% | $\\text{完整性} = 1 - \\frac{\\text{缺失值数量}}{\\text{总数据量}}$ |
|
||||
| 准确性 | 0.3 | 误差率 < 3% | $\\text{准确性} = 1 - \\frac{\\text{错误记录数}}{\\text{总记录数}}$ |`;
|
||||
|
||||
document.getElementById('test2').innerHTML = postprocessHTML(marked.parse(test2Markdown));
|
||||
|
||||
// 测试3: 你的测试内容
|
||||
const test3Markdown = `测试标题
|
||||
这是一个**粗体**和*斜体*的测试。
|
||||
|
||||
表格测试
|
||||
| 产品 | 价格 | 库存 |
|
||||
|------|------|------|
|
||||
| 苹果 | 4元 | 100个 |
|
||||
| 香蕉 | 2元 | 50个 |
|
||||
|
||||
代码测试
|
||||
\`\`\`javascript
|
||||
function hello() {
|
||||
console.log('Hello World!');
|
||||
}
|
||||
\`\`\`
|
||||
行内代码:\`const name = 'test'\`
|
||||
|
||||
列表测试
|
||||
- 项目1
|
||||
- 项目2
|
||||
- 子项目2.1
|
||||
- 子项目2.2
|
||||
- 项目3
|
||||
|
||||
链接测试
|
||||
[GitHub](https://github.com)
|
||||
[Vue.js](https://vuejs.org)`;
|
||||
|
||||
document.getElementById('test3').innerHTML = postprocessHTML(marked.parse(test3Markdown));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>表格渲染测试</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.test-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.test-title {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.markdown-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.markdown-table th,
|
||||
.markdown-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px 6px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.markdown-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.markdown-table tr:nth-child(even) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.markdown-table tr:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.raw-content {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1 class="test-title">Markdown表格渲染测试</h1>
|
||||
|
||||
<h3>测试内容1:数据质量评估矩阵</h3>
|
||||
<div class="raw-content" id="raw1"></div>
|
||||
<div id="rendered1"></div>
|
||||
|
||||
<h3>测试内容2:产品价格表</h3>
|
||||
<div class="raw-content" id="raw2"></div>
|
||||
<div id="rendered2"></div>
|
||||
|
||||
<h3>测试内容3:简单表格</h3>
|
||||
<div class="raw-content" id="raw3"></div>
|
||||
<div id="rendered3"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 配置marked
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
sanitize: false
|
||||
});
|
||||
|
||||
// 测试内容
|
||||
const testContent1 = `| 评估维度 | 权重 | 评分标准 | 计算公式 |
|
||||
|---------|------|---------|---------|
|
||||
| 完整性 | 0.3 | 缺失值比例 < 5% | $\\text{完整性} = 1 - \\frac{\\text{缺失值数量}}{\\text{总数据量}}$ |
|
||||
| 准确性 | 0.3 | 误差率 < 3% | $\\text{准确性} = 1 - \\frac{\\text{错误记录数}}{\\text{总记录数}}$ |
|
||||
| 一致性 | 0.2 | 数据格式统一度 > 95% | $\\text{一致性} = \\frac{\\text{符合格式标准记录数}}{\\text{总记录数}}$ |
|
||||
| 及时性 | 0.2 | 数据更新频率 ≤ 24小时 | $\\text{及时性} = \\frac{\\text{最近24小时更新数据量}}{\\text{总数据量}}$ |`;
|
||||
|
||||
const testContent2 = `产品价格表
|
||||
| 产品 | 价格 |
|
||||
|------|------|
|
||||
| 苹果 | 4元 |
|
||||
| 香蕉 | 2元 |`;
|
||||
|
||||
const testContent3 = `| 姓名 | 年龄 | 城市 |
|
||||
|------|------|------|
|
||||
| 张三 | 25 | 北京 |
|
||||
| 李四 | 30 | 上海 |`;
|
||||
|
||||
// 渲染函数
|
||||
function renderContent(rawId, renderedId, content) {
|
||||
document.getElementById(rawId).textContent = content;
|
||||
|
||||
try {
|
||||
const html = marked.parse(content);
|
||||
// 为表格添加样式类
|
||||
const processedHtml = html.replace(/<table>/g, '<table class="markdown-table">');
|
||||
document.getElementById(renderedId).innerHTML = processedHtml;
|
||||
} catch (error) {
|
||||
document.getElementById(renderedId).innerHTML = `<div style="color: red;">渲染失败: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染所有测试内容
|
||||
renderContent('raw1', 'rendered1', testContent1);
|
||||
renderContent('raw2', 'rendered2', testContent2);
|
||||
renderContent('raw3', 'rendered3', testContent3);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>表格渲染测试</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
.test-container {
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.markdown-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
.markdown-table th,
|
||||
.markdown-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px 6px;
|
||||
text-align: left;
|
||||
}
|
||||
.markdown-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.markdown-table tr:nth-child(even) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>表格渲染测试</h1>
|
||||
|
||||
<div class="test-container">
|
||||
<h2>测试1: 简单表格</h2>
|
||||
<div id="test1"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-container">
|
||||
<h2>测试2: 复杂表格</h2>
|
||||
<div id="test2"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-container">
|
||||
<h2>测试3: 你的测试表格</h2>
|
||||
<div id="test3"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 配置marked
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
sanitize: false,
|
||||
});
|
||||
|
||||
// 测试1: 简单表格
|
||||
const test1Markdown = `| 产品 | 价格 |
|
||||
|------|------|
|
||||
| 苹果 | 4元 |
|
||||
| 香蕉 | 2元 |`;
|
||||
|
||||
document.getElementById('test1').innerHTML = marked.parse(test1Markdown);
|
||||
|
||||
// 测试2: 复杂表格
|
||||
const test2Markdown = `| 评估维度 | 权重 | 评分标准 | 计算公式 |
|
||||
|---------|------|---------|---------|
|
||||
| 完整性 | 0.3 | 缺失值比例 < 5% | $\\text{完整性} = 1 - \\frac{\\text{缺失值数量}}{\\text{总数据量}}$ |
|
||||
| 准确性 | 0.3 | 误差率 < 3% | $\\text{准确性} = 1 - \\frac{\\text{错误记录数}}{\\text{总记录数}}$ |`;
|
||||
|
||||
document.getElementById('test2').innerHTML = marked.parse(test2Markdown);
|
||||
|
||||
// 测试3: 你的测试表格
|
||||
const test3Markdown = `| 产品 | 价格 | 库存 |
|
||||
|------|------|------|
|
||||
| 苹果 | 4元 | 100个 |
|
||||
| 香蕉 | 2元 | 50个 |`;
|
||||
|
||||
document.getElementById('test3').innerHTML = marked.parse(test3Markdown);
|
||||
|
||||
// 测试表格检测函数
|
||||
function hasTable(content) {
|
||||
if (!content || typeof content !== 'string') return false;
|
||||
|
||||
const lines = content.split('\n');
|
||||
let hasTableRow = false;
|
||||
let hasSeparator = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
// 检查表格行(包含|字符)
|
||||
if (trimmedLine.includes('|') && trimmedLine.split('|').length >= 3) {
|
||||
hasTableRow = true;
|
||||
}
|
||||
// 检查分隔符行(包含-和|)
|
||||
if (trimmedLine.includes('|') && trimmedLine.includes('-') && /^[\s\|\-\:]+$/.test(trimmedLine)) {
|
||||
hasSeparator = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasTableRow && hasSeparator;
|
||||
}
|
||||
|
||||
// 测试表格检测
|
||||
console.log('测试1表格检测:', hasTable(test1Markdown));
|
||||
console.log('测试2表格检测:', hasTable(test2Markdown));
|
||||
console.log('测试3表格检测:', hasTable(test3Markdown));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
629
json、接口文档.md
|
|
@ -1,629 +0,0 @@
|
|||
# AI思维导图系统API接口文档
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档描述了AI思维导图系统的RESTful API接口,包括思维导图管理、节点操作和AI服务等核心功能。
|
||||
|
||||
## 🔧 基础信息
|
||||
|
||||
- **Base URL**: `http://127.0.0.1:8000/api`
|
||||
- **Content-Type**: `application/json`
|
||||
- **数据格式**: 遵循MindElixir规范
|
||||
|
||||
## 📊 数据格式规范
|
||||
|
||||
### MindElixir节点格式
|
||||
```json
|
||||
{
|
||||
"id": "string", // 唯一标识符,必填
|
||||
"topic": "string", // 节点标题,必填
|
||||
"data": {
|
||||
"des": "string" // 节点描述,可选
|
||||
},
|
||||
"children": [ /* Node[] */ ], // 子节点数组,必填
|
||||
"mindmapId": "string" // 思维导图ID,可选
|
||||
}
|
||||
```
|
||||
|
||||
### 完整思维导图数据格式
|
||||
```json
|
||||
{
|
||||
"nodeData": {
|
||||
"id": "string", // 根节点ID
|
||||
"topic": "string", // 根节点标题
|
||||
"data": {
|
||||
"des": "string" // 根节点描述
|
||||
},
|
||||
"children": [ /* Node[] */ ] // 子节点数组
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库节点格式
|
||||
```json
|
||||
{
|
||||
"id": "string", // 节点ID
|
||||
"isRoot": boolean, // 是否为根节点
|
||||
"parentId": "string", // 父节点ID
|
||||
"childrenCount": number, // 子节点数量
|
||||
"depth": number, // 节点深度
|
||||
"title": "string", // 节点标题
|
||||
"des": "string", // 节点描述
|
||||
"createDate": "datetime", // 创建时间
|
||||
"updateDate": "datetime", // 更新时间
|
||||
"delete": boolean // 是否删除
|
||||
}
|
||||
```
|
||||
|
||||
## 🗺️ 思维导图管理接口
|
||||
|
||||
### 获取思维导图
|
||||
|
||||
**接口:** `GET /api/mindMaps/{id}`
|
||||
|
||||
**描述:** 根据ID获取思维导图的完整数据,返回MindElixir格式的树形结构
|
||||
|
||||
**路径参数:**
|
||||
- `id` (integer): 思维导图ID
|
||||
|
||||
**请求示例:**
|
||||
```bash
|
||||
GET /api/mindMaps/1
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"nodeData": {
|
||||
"id": "root",
|
||||
"topic": "数字教育平台设计要点",
|
||||
"data": {
|
||||
"des": "思维导图的根节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "node-1",
|
||||
"topic": "用户体验设计",
|
||||
"data": {
|
||||
"des": "用户体验设计的重要性"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "node-1-1",
|
||||
"topic": "界面交互设计",
|
||||
"data": {
|
||||
"des": "核心功能入口应控制在3次点击内可达"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"detail": "mindMap not found"
|
||||
}
|
||||
```
|
||||
|
||||
### 创建思维导图
|
||||
|
||||
**接口:** `POST /api/mindMaps`
|
||||
|
||||
**描述:** 创建新的思维导图,支持传入初始数据或创建空思维导图
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"title": "新的思维导图", // 可选,思维导图名称
|
||||
"data": { // 可选,初始思维导图数据
|
||||
"topic": "根节点标题",
|
||||
"data": {
|
||||
"des": "根节点描述"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例:**
|
||||
```bash
|
||||
POST /api/mindMaps
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "AI技术发展",
|
||||
"data": {
|
||||
"topic": "AI技术发展",
|
||||
"data": {
|
||||
"des": "人工智能技术发展历程"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"title": "AI技术发展",
|
||||
"nodeData": {
|
||||
"id": "root",
|
||||
"topic": "AI技术发展",
|
||||
"data": {
|
||||
"des": "人工智能技术发展历程"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 节点操作接口
|
||||
|
||||
### 批量添加节点
|
||||
|
||||
**接口:** `POST /api/mindMaps/addNodes`
|
||||
|
||||
**描述:** 向指定思维导图中批量添加节点,支持添加子节点和兄弟节点
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"mindMapId": 1, // 思维导图ID
|
||||
"nodes": [ // 节点数组
|
||||
{
|
||||
"title": "子节点1", // 节点标题
|
||||
"des": "节点描述", // 节点描述
|
||||
"parentId": "parent-id", // 父节点ID
|
||||
"isRoot": false // 是否为根节点
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例:**
|
||||
```bash
|
||||
POST /api/mindMaps/addNodes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"mindMapId": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"title": "机器学习",
|
||||
"des": "机器学习算法和应用",
|
||||
"parentId": "root"
|
||||
},
|
||||
{
|
||||
"title": "深度学习",
|
||||
"des": "深度学习技术发展",
|
||||
"parentId": "root"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功创建 2 个节点",
|
||||
"data": {
|
||||
"mindMapId": "1",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-1",
|
||||
"isRoot": false,
|
||||
"parentId": "root",
|
||||
"childrenCount": 0,
|
||||
"depth": 1,
|
||||
"title": "机器学习",
|
||||
"des": "机器学习算法和应用",
|
||||
"createDate": "2023-07-01T10:30:00Z",
|
||||
"updateDate": "2023-07-01T10:30:00Z",
|
||||
"delete": false
|
||||
},
|
||||
{
|
||||
"id": "node-2",
|
||||
"isRoot": false,
|
||||
"parentId": "root",
|
||||
"childrenCount": 0,
|
||||
"depth": 1,
|
||||
"title": "深度学习",
|
||||
"des": "深度学习技术发展",
|
||||
"createDate": "2023-07-01T10:30:00Z",
|
||||
"updateDate": "2023-07-01T10:30:00Z",
|
||||
"delete": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"detail": "mindMapId is required"
|
||||
}
|
||||
```
|
||||
|
||||
### 更新节点
|
||||
|
||||
**接口:** `PATCH /api/mindMaps/updateNode`
|
||||
|
||||
**描述:** 更新指定节点的标题、描述或父节点关系
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"id": "node_123", // 节点ID,必填
|
||||
"newTitle": "更新后的节点标题", // 可选,新标题
|
||||
"newDes": "更新后的节点描述内容", // 可选,新描述
|
||||
"newParentId": "parent_node_id_123" // 可选,新父节点ID
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例:**
|
||||
```bash
|
||||
PATCH /api/mindMaps/updateNode
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": "node-1",
|
||||
"newTitle": "机器学习算法",
|
||||
"newDes": "包括监督学习、无监督学习和强化学习",
|
||||
"newParentId": "root"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "节点更新成功",
|
||||
"data": {
|
||||
"id": "node-1",
|
||||
"isRoot": false,
|
||||
"parentId": "root",
|
||||
"childrenCount": 0,
|
||||
"depth": 1,
|
||||
"title": "机器学习算法",
|
||||
"des": "包括监督学习、无监督学习和强化学习",
|
||||
"createDate": "2023-01-01T00:00:00Z",
|
||||
"updateDate": "2023-10-05T15:30:00Z",
|
||||
"delete": false
|
||||
},
|
||||
"updatedFields": ["title", "des", "parentId"]
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"detail": "id is required"
|
||||
}
|
||||
```
|
||||
|
||||
### 批量删除节点
|
||||
|
||||
**接口:** `DELETE /api/mindMaps/deleteNodes`
|
||||
|
||||
**描述:** 批量删除节点及其所有子节点(软删除),自动更新父节点的子节点计数
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"nodeIds": ["node_123", "node_456", "node_789"] // 要删除的节点ID数组
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例:**
|
||||
```bash
|
||||
DELETE /api/mindMaps/deleteNodes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"nodeIds": ["node-1", "node-2"]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "节点删除成功",
|
||||
"data": {
|
||||
"deletedCount": 5,
|
||||
"deletedNodeIds": ["node-1", "node-2", "node-1-1", "node-1-2", "node-2-1"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"detail": "nodeIds is required"
|
||||
}
|
||||
```
|
||||
|
||||
## 🤖 AI服务接口
|
||||
|
||||
### AI生成Markdown
|
||||
|
||||
**接口:** `POST /api/ai/generate-markdown`
|
||||
|
||||
**描述:** 调用AI服务分析文档内容,生成结构化的Markdown格式思维导图
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"system_prompt": "AI系统提示词", // 可选,自定义系统提示
|
||||
"user_prompt": "用户输入内容", // 必填,要分析的内容
|
||||
"model": "glm-4.5", // 可选,AI模型名称
|
||||
"base_url": "https://open.bigmodel.cn/api/paas/v4/", // 可选,API地址
|
||||
"api_key": "your_api_key" // 可选,API密钥
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例:**
|
||||
```bash
|
||||
POST /api/ai/generate-markdown
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"system_prompt": "你是一位专业的文档分析专家,请将以下内容转换为结构化的Markdown格式",
|
||||
"user_prompt": "人工智能技术发展历程包括早期发展、现代发展等阶段...",
|
||||
"model": "glm-4.5"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"markdown": "# 人工智能技术发展历程\n\n## 早期发展\n- 1950年代概念提出\n- 图灵测试的提出\n\n## 现代发展\n- 深度学习技术突破\n- 应用领域扩展",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"error": "用户提示词不能为空",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 数据格式转换说明
|
||||
|
||||
### 前端MindElixir格式 vs 后端数据库格式
|
||||
|
||||
**前端MindElixir期望格式:**
|
||||
```json
|
||||
{
|
||||
"nodeData": {
|
||||
"id": "root",
|
||||
"topic": "根节点标题",
|
||||
"data": {
|
||||
"des": "节点描述"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "child-1",
|
||||
"topic": "子节点标题",
|
||||
"data": {
|
||||
"des": "子节点描述"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**后端数据库存储格式:**
|
||||
```json
|
||||
{
|
||||
"id": "mindmap-123",
|
||||
"title": "思维导图标题",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-1",
|
||||
"title": "根节点标题",
|
||||
"des": "节点描述",
|
||||
"isRoot": true,
|
||||
"parentId": null,
|
||||
"depth": 0,
|
||||
"childrenCount": 1,
|
||||
"createDate": "2023-07-01T00:00:00Z",
|
||||
"updateDate": "2023-07-01T00:00:00Z",
|
||||
"delete": false
|
||||
}
|
||||
],
|
||||
"createDate": "2023-07-01T00:00:00Z",
|
||||
"updateDate": "2023-07-01T00:00:00Z",
|
||||
"delete": false
|
||||
}
|
||||
```
|
||||
|
||||
### 关键字段映射
|
||||
|
||||
| 前端MindElixir | 后端数据库 | 说明 |
|
||||
|---------------|-----------|------|
|
||||
| `topic` | `title` | 节点标题 |
|
||||
| `data.des` | `des` | 节点描述 |
|
||||
| `children` | 通过`parentId`关系构建 | 子节点关系 |
|
||||
| `id` | `id` | 节点唯一标识 |
|
||||
|
||||
### 数据转换规则
|
||||
|
||||
1. **获取思维导图时**:后端将扁平节点列表转换为MindElixir期望的树形结构
|
||||
2. **保存节点时**:前端发送扁平格式,后端存储为关系型数据
|
||||
3. **过滤规则**:自动过滤掉标题为"根节点标题"且没有子节点的空根节点
|
||||
|
||||
所有操作后端都会自动更新 `depth`、`childrenCount`。
|
||||
|
||||
## 📝 示例思维导图结构
|
||||
|
||||
```
|
||||
数字教育平台设计要点
|
||||
├── 用户体验设计
|
||||
│ ├── 界面交互设计
|
||||
│ │ └── 核心功能入口应控制在3次点击内可达
|
||||
│ └── 内容架构规划
|
||||
│ └── 合理的内容组织帮助用户构建知识体系
|
||||
├── 互动功能开发
|
||||
│ └── 学习互动设计
|
||||
│ └── 平台应提供即时反馈机制
|
||||
└── 数据安全保障
|
||||
└── 隐私保护措施
|
||||
├── 个人信息和学习数据采用AES-256加密存储
|
||||
└── 实施基于角色的权限管理
|
||||
```
|
||||
|
||||
## 🔧 错误码说明
|
||||
|
||||
| 错误码 | 说明 | 解决方案 |
|
||||
|--------|------|----------|
|
||||
| 400 | 请求参数错误 | 检查请求体格式和必填字段 |
|
||||
| 404 | 资源不存在 | 确认ID是否正确 |
|
||||
| 500 | 服务器内部错误 | 检查服务器日志 |
|
||||
|
||||
## 📚 使用示例
|
||||
|
||||
### 完整工作流程示例
|
||||
|
||||
1. **创建思维导图**
|
||||
```bash
|
||||
POST /api/mindMaps
|
||||
{
|
||||
"title": "AI技术发展"
|
||||
}
|
||||
```
|
||||
|
||||
2. **添加节点**
|
||||
```bash
|
||||
POST /api/mindMaps/addNodes
|
||||
{
|
||||
"mindMapId": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"title": "机器学习",
|
||||
"des": "机器学习算法和应用",
|
||||
"parentId": "root"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. **更新节点**
|
||||
```bash
|
||||
PATCH /api/mindMaps/updateNode
|
||||
{
|
||||
"id": "node-1",
|
||||
"newTitle": "机器学习算法",
|
||||
"newDes": "包括监督学习、无监督学习等"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
4. **获取完整数据**
|
||||
```bash
|
||||
GET /api/mindMaps/1
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
1. 启动后端服务:`python manage.py runserver`
|
||||
2. 启动前端服务:`cd frontend && npm run dev`
|
||||
3. 访问前端界面:`http://localhost:5173`
|
||||
4. 使用API接口:`http://localhost:8000/api`
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有问题,请查看:
|
||||
- 项目README文档
|
||||
- 代码注释和示例
|
||||
- 错误日志和调试信息
|
||||
|
||||
## 初始化Git仓库并上传
|
||||
|
||||
### 1. 初始化Git仓库
|
||||
|
||||
```bash
|
||||
cd /Users/natalie/Documents/test/siweidaotu
|
||||
git init
|
||||
```
|
||||
|
||||
### 2. 创建.gitignore文件
|
||||
|
||||
```bash
|
||||
<code_block_to_apply_changes_from>
|
||||
```
|
||||
|
||||
### 3. 配置Git用户信息(如果还没有配置)
|
||||
|
||||
```bash
|
||||
git config --global user.name "你的用户名"
|
||||
git config --global user.email "你的邮箱"
|
||||
```
|
||||
|
||||
### 4. 添加远程仓库
|
||||
|
||||
```bash
|
||||
git remote add origin http://test-www.writebug.com:3000/lixinran/MindMap.git
|
||||
```
|
||||
|
||||
### 5. 添加所有文件到Git
|
||||
|
||||
```bash
|
||||
git add .
|
||||
```
|
||||
|
||||
### 6. 提交更改
|
||||
|
||||
```bash
|
||||
git commit -m "Initial commit: AI思维导图生成器项目
|
||||
|
||||
- 基于Django + Vue.js的智能思维导图生成和管理系统
|
||||
- 支持AI驱动的文档分析和可视化思维导图创建
|
||||
- 包含完整的前后端代码和文档"
|
||||
```
|
||||
|
||||
### 7. 设置主分支并推送
|
||||
|
||||
```bash
|
||||
git branch -M main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## 如果推送时遇到认证问题
|
||||
|
||||
当执行 `git push -u origin main` 时,可能会提示输入用户名和密码:
|
||||
|
||||
- **用户名**:输入你的Git账户用户名
|
||||
- **密码**:输入你的Git账户密码或访问令牌
|
||||
|
||||
## 验证步骤
|
||||
|
||||
推送完成后,你可以:
|
||||
|
||||
1. **检查Git状态**:
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
2. **查看远程仓库**:
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
3. **访问仓库网站**:
|
||||
打开 [http://test-www.writebug.com:3000/lixinran/MindMap](http://test-www.writebug.com:3000/lixinran/MindMap) 查看是否能看到项目文件
|
||||
|
||||
请按照这些步骤操作,如果遇到任何错误信息,请告诉我具体的错误内容。
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 331 KiB |