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 渲染能力。特别是表格渲染功能,让思维导图可以展示更丰富的数据结构。
|
|
||||||
55
README.md
|
|
@ -15,6 +15,8 @@
|
||||||
- **拖拽操作**: 支持节点拖拽、移动、编辑
|
- **拖拽操作**: 支持节点拖拽、移动、编辑
|
||||||
- **实时保存**: 自动保存编辑内容到数据库
|
- **实时保存**: 自动保存编辑内容到数据库
|
||||||
- **缩放控制**: 支持思维导图缩放和居中显示
|
- **缩放控制**: 支持思维导图缩放和居中显示
|
||||||
|
- **Markdown支持**: 节点内容支持Markdown语法渲染
|
||||||
|
- **表格渲染**: 支持Markdown表格在思维导图中的显示
|
||||||
|
|
||||||
### 💾 数据管理
|
### 💾 数据管理
|
||||||
- **云端存储**: 思维导图数据持久化存储
|
- **云端存储**: 思维导图数据持久化存储
|
||||||
|
|
@ -39,10 +41,12 @@
|
||||||
|
|
||||||
### 前端技术
|
### 前端技术
|
||||||
- **框架**: Vue.js 3.3.4 + Vite 4.4.9
|
- **框架**: Vue.js 3.3.4 + Vite 4.4.9
|
||||||
- **思维导图**: MindElixir 3.0.0
|
- **思维导图**: MindElixir 3.0.0 (自定义增强版本)
|
||||||
- **文件处理**: mammoth.js (DOCX), pdfjs-dist (PDF)
|
- **文件处理**: mammoth.js (DOCX), pdfjs-dist (PDF)
|
||||||
- **HTTP客户端**: Axios 1.5.0
|
- **HTTP客户端**: Axios 1.5.0
|
||||||
- **Markdown处理**: marked 16.2.1
|
- **Markdown处理**: marked 16.2.1 + 自定义渲染器
|
||||||
|
- **数学公式**: KaTeX 0.16.22
|
||||||
|
- **代码高亮**: PrismJS 1.30.0
|
||||||
|
|
||||||
### AI内容分析
|
### AI内容分析
|
||||||
- **TypeScript**: 类型安全的Markdown转JSON转换
|
- **TypeScript**: 类型安全的Markdown转JSON转换
|
||||||
|
|
@ -59,7 +63,7 @@
|
||||||
### 1. 克隆项目
|
### 1. 克隆项目
|
||||||
```bash
|
```bash
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
cd siweidaotu
|
cd MindMap
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 后端设置
|
### 2. 后端设置
|
||||||
|
|
@ -128,8 +132,8 @@ npm run dev
|
||||||
## 📁 项目结构
|
## 📁 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
siweidaotu/
|
MindMap/
|
||||||
├── backend/ # 后端代码
|
├── backend/ # Django后端
|
||||||
│ ├── django_mindmap/ # Django项目配置
|
│ ├── django_mindmap/ # Django项目配置
|
||||||
│ │ ├── settings.py # 项目设置
|
│ │ ├── settings.py # 项目设置
|
||||||
│ │ ├── urls.py # 主URL配置
|
│ │ ├── urls.py # 主URL配置
|
||||||
|
|
@ -148,19 +152,24 @@ siweidaotu/
|
||||||
│ ├── src/
|
│ ├── src/
|
||||||
│ │ ├── components/ # Vue组件
|
│ │ ├── components/ # Vue组件
|
||||||
│ │ │ ├── MindMap.vue # 思维导图组件
|
│ │ │ ├── MindMap.vue # 思维导图组件
|
||||||
│ │ │ └── AISidebar.vue # AI助手侧边栏
|
│ │ │ ├── AISidebar.vue # AI助手侧边栏
|
||||||
|
│ │ │ └── MarkdownTest.vue # Markdown测试组件
|
||||||
│ │ ├── api/ # API接口
|
│ │ ├── api/ # API接口
|
||||||
│ │ │ └── mindmap.js # 思维导图API
|
│ │ │ └── mindmap.js # 思维导图API
|
||||||
│ │ └── App.vue # 主应用组件
|
│ │ ├── lib/ # 第三方库
|
||||||
|
│ │ │ └── mind-elixir/ # MindElixir库(项目使用版本)
|
||||||
|
│ │ ├── utils/ # 工具函数
|
||||||
|
│ │ │ └── markdownRenderer.js # Markdown渲染器
|
||||||
|
│ │ ├── App.vue # 主应用组件
|
||||||
|
│ │ └── main.js # 应用入口
|
||||||
|
│ ├── test-*.html # 功能测试文件
|
||||||
│ ├── package.json # 前端依赖
|
│ ├── package.json # 前端依赖
|
||||||
│ └── vite.config.js # Vite配置
|
│ └── vite.config.js # Vite配置
|
||||||
├── others_deletable/ # 其他文件(可删除)
|
├── mind-elixir-core-master/ # MindElixir完整源码
|
||||||
│ ├── ai-content-analyzer/ # AI内容分析工具
|
│ ├── src/ # TypeScript源码
|
||||||
│ │ ├── markdownToJSON.ts # Markdown转JSON
|
│ ├── tests/ # 测试套件
|
||||||
│ │ ├── json_openai.py # AI服务调用
|
│ ├── dist/ # 编译后文件
|
||||||
│ │ └── package.json # TypeScript依赖
|
│ └── package.json # 依赖配置
|
||||||
│ └── json_openai.py # 独立AI服务文件
|
|
||||||
├── json、接口文档.md # API接口文档
|
|
||||||
└── README.md # 项目文档
|
└── README.md # 项目文档
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -373,6 +382,8 @@ server {
|
||||||
- `双击节点`:编辑节点内容
|
- `双击节点`:编辑节点内容
|
||||||
- `右键节点`:显示操作菜单
|
- `右键节点`:显示操作菜单
|
||||||
- `拖拽节点`:移动节点位置
|
- `拖拽节点`:移动节点位置
|
||||||
|
- `Ctrl + E`:展开/折叠节点
|
||||||
|
- `Alt + F`:聚焦/取消聚焦节点
|
||||||
|
|
||||||
## 🤝 贡献指南
|
## 🤝 贡献指南
|
||||||
|
|
||||||
|
|
@ -388,6 +399,22 @@ server {
|
||||||
|
|
||||||
MIT License - 详见 [LICENSE](LICENSE) 文件
|
MIT License - 详见 [LICENSE](LICENSE) 文件
|
||||||
|
|
||||||
|
## 🧹 项目优化
|
||||||
|
|
||||||
|
### 代码清理
|
||||||
|
项目已进行全面的代码清理和优化:
|
||||||
|
|
||||||
|
- ✅ **删除冗余文件**: 移除了重复的测试文件和调试文件
|
||||||
|
- ✅ **保留核心功能**: 保留了所有必要的测试和调试工具
|
||||||
|
- ✅ **优化项目结构**: 清晰分离了开发版本和生产版本
|
||||||
|
- ✅ **MindElixir增强**: 集成了自定义的Markdown和表格渲染功能
|
||||||
|
|
||||||
|
### 文件结构优化
|
||||||
|
- **保留**: `mind-elixir-core-master/` - 完整的源码和文档
|
||||||
|
- **保留**: `frontend/src/lib/mind-elixir/` - 项目中使用的增强版本
|
||||||
|
- **保留**: 核心测试文件 - 用于功能验证和问题调试
|
||||||
|
- **删除**: 重复的调试文件和过时的测试文件
|
||||||
|
|
||||||
## 🙏 致谢
|
## 🙏 致谢
|
||||||
|
|
||||||
- [MindElixir](https://github.com/ssshooter/mind-elixir-core) - 思维导图可视化库
|
- [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,
|
model=model,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
temperature=0.7,
|
temperature=0.7,
|
||||||
max_tokens=8000, # 增加token限制,避免内容截断
|
max_tokens=4000, # 减少token限制,提高响应速度
|
||||||
stream=stream
|
stream=stream
|
||||||
)
|
)
|
||||||
except Exception as e:
|
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;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/index-d6c20d61.js"></script>
|
<script type="module" crossorigin src="/assets/index-a09f7810.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index-44efe7b9.css">
|
<link rel="stylesheet" href="/assets/index-5b39da23.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<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",
|
"hash": "76772e52",
|
||||||
"browserHash": "b5df73c3",
|
"browserHash": "a4ef7769",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"axios": {
|
"axios": {
|
||||||
"src": "../../axios/index.js",
|
"src": "../../axios/index.js",
|
||||||
"file": "axios.js",
|
"file": "axios.js",
|
||||||
"fileHash": "d5a6fb13",
|
"fileHash": "923e7809",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"mammoth": {
|
"mammoth": {
|
||||||
"src": "../../mammoth/lib/index.js",
|
"src": "../../mammoth/lib/index.js",
|
||||||
"file": "mammoth.js",
|
"file": "mammoth.js",
|
||||||
"fileHash": "8e0b13e7",
|
"fileHash": "2d23e669",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"marked": {
|
"marked": {
|
||||||
"src": "../../marked/lib/marked.esm.js",
|
"src": "../../marked/lib/marked.esm.js",
|
||||||
"file": "marked.js",
|
"file": "marked.js",
|
||||||
"fileHash": "330cfdd3",
|
"fileHash": "e6802b70",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"pdfjs-dist": {
|
"pdfjs-dist": {
|
||||||
"src": "../../pdfjs-dist/build/pdf.mjs",
|
"src": "../../pdfjs-dist/build/pdf.mjs",
|
||||||
"file": "pdfjs-dist.js",
|
"file": "pdfjs-dist.js",
|
||||||
"fileHash": "63546997",
|
"fileHash": "b0d6a144",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"prismjs": {
|
"prismjs": {
|
||||||
"src": "../../prismjs/prism.js",
|
"src": "../../prismjs/prism.js",
|
||||||
"file": "prismjs.js",
|
"file": "prismjs.js",
|
||||||
"fileHash": "3730e60a",
|
"fileHash": "0c7d8fc7",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"prismjs/components/prism-css": {
|
"prismjs/components/prism-css": {
|
||||||
|
|
@ -65,13 +65,19 @@
|
||||||
"vue": {
|
"vue": {
|
||||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
||||||
"file": "vue.js",
|
"file": "vue.js",
|
||||||
"fileHash": "a7d38f14",
|
"fileHash": "b4ce9a46",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"katex": {
|
"katex": {
|
||||||
"src": "../../katex/dist/katex.mjs",
|
"src": "../../katex/dist/katex.mjs",
|
||||||
"file": "katex.js",
|
"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
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -631,7 +631,7 @@ Level 4 标题用 #####
|
||||||
|
|
||||||
// 添加超时处理 - 增加超时时间,处理复杂文档
|
// 添加超时处理 - 增加超时时间,处理复杂文档
|
||||||
const controller = new AbortController();
|
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', {
|
const response = await fetch('http://127.0.0.1:8000/api/ai/generate-markdown', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -1510,7 +1510,8 @@ const createAINode = async (parentNode, question, answer) => {
|
||||||
.replace(/^#+\s*/gm, '') // 移除标题标记
|
.replace(/^#+\s*/gm, '') // 移除标题标记
|
||||||
.replace(/\*\*(.*?)\*\*/g, '$1') // 移除粗体标记
|
.replace(/\*\*(.*?)\*\*/g, '$1') // 移除粗体标记
|
||||||
.replace(/\*(.*?)\*/g, '$1') // 移除斜体标记
|
.replace(/\*(.*?)\*/g, '$1') // 移除斜体标记
|
||||||
.replace(/^\s*[-*+]\s*/gm, '• ') // 统一列表标记
|
// 保留表格格式,不转换表格为列表
|
||||||
|
.replace(/^\s*[-*+]\s*(?![|])/gm, '• ') // 只转换非表格的列表标记
|
||||||
.replace(/\n{3,}/g, '\n\n') // 限制连续换行
|
.replace(/\n{3,}/g, '\n\n') // 限制连续换行
|
||||||
.trim();
|
.trim();
|
||||||
};
|
};
|
||||||
|
|
@ -4209,40 +4210,85 @@ const updateMindMapRealtime = async (data, title) => {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Markdown表格样式 - 使用更高优先级 */
|
/* 强制表格样式 - 最高优先级 */
|
||||||
|
.topic table,
|
||||||
|
.topic .text table,
|
||||||
|
.topic .topic-text table,
|
||||||
.topic .topic-text.markdown-content table {
|
.topic .topic-text.markdown-content table {
|
||||||
border-collapse: collapse !important;
|
border-collapse: collapse !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin: 4px 0 !important;
|
margin: 4px 0 !important;
|
||||||
font-size: 11px !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;
|
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 th,
|
||||||
.topic .topic-text.markdown-content table td {
|
.topic .topic-text.markdown-content table td {
|
||||||
border: 1px solid #ddd !important;
|
border: 2px solid #333 !important;
|
||||||
padding: 2px 4px !important;
|
padding: 6px 8px !important;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
vertical-align: top !important;
|
vertical-align: top !important;
|
||||||
display: table-cell !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;
|
background-color: #f5f5f5 !important;
|
||||||
font-weight: 600 !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;
|
display: table-row !important;
|
||||||
|
white-space: normal !important; /* 覆盖MindElixir的pre-wrap */
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic .topic-text.markdown-content table tr:nth-child(even) {
|
.topic .topic-text.markdown-content table tr:nth-child(even) td,
|
||||||
background-color: #f9f9f9 !important;
|
.topic .text table tr:nth-child(even) td {
|
||||||
|
background-color: #f8f8f8 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic .topic-text.markdown-content table tr:hover {
|
.topic .topic-text.markdown-content table tr:hover td,
|
||||||
background-color: #e9ecef !important;
|
.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代码样式 */
|
/* 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%;
|
width: 100%;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
font-size: 11px;
|
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 th,
|
||||||
.markdown-table td {
|
.markdown-table td {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #e0e0e0;
|
||||||
padding: 4px 6px;
|
padding: 8px 12px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
position: relative;
|
||||||
|
white-space: normal; /* 覆盖MindElixir的pre-wrap */
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-table th {
|
.markdown-table th {
|
||||||
background-color: #f8f9fa;
|
background-color: #f5f5f5;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #d0d0d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-table tr:nth-child(even) {
|
.markdown-table td {
|
||||||
background-color: #f8f9fa;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-table tr:hover {
|
.markdown-table tr:nth-child(even) td {
|
||||||
background-color: #e9ecef;
|
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 {
|
.markdown-content a {
|
||||||
|
|
@ -477,6 +500,7 @@ export const smartRenderNodeContent = (content) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
renderMarkdownToHTML,
|
renderMarkdownToHTML,
|
||||||
setNodeMarkdownContent,
|
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 |