vault backup: 2026-04-13 17:46:47
This commit is contained in:
41
.claude/skills/defuddle/SKILL.md
Normal file
41
.claude/skills/defuddle/SKILL.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: defuddle
|
||||
description: Extract clean markdown content from web pages using Defuddle CLI, removing clutter and navigation to save tokens. Use instead of WebFetch when the user provides a URL to read or analyze, for online documentation, articles, blog posts, or any standard web page. Do NOT use for URLs ending in .md — those are already markdown, use WebFetch directly.
|
||||
---
|
||||
|
||||
# Defuddle
|
||||
|
||||
Use Defuddle CLI to extract clean readable content from web pages. Prefer over WebFetch for standard web pages — it removes navigation, ads, and clutter, reducing token usage.
|
||||
|
||||
If not installed: `npm install -g defuddle`
|
||||
|
||||
## Usage
|
||||
|
||||
Always use `--md` for markdown output:
|
||||
|
||||
```bash
|
||||
defuddle parse <url> --md
|
||||
```
|
||||
|
||||
Save to file:
|
||||
|
||||
```bash
|
||||
defuddle parse <url> --md -o content.md
|
||||
```
|
||||
|
||||
Extract specific metadata:
|
||||
|
||||
```bash
|
||||
defuddle parse <url> -p title
|
||||
defuddle parse <url> -p description
|
||||
defuddle parse <url> -p domain
|
||||
```
|
||||
|
||||
## Output formats
|
||||
|
||||
| Flag | Format |
|
||||
|------|--------|
|
||||
| `--md` | Markdown (default choice) |
|
||||
| `--json` | JSON with both HTML and markdown |
|
||||
| (none) | HTML |
|
||||
| `-p <name>` | Specific metadata property |
|
||||
476
.claude/skills/excalidraw-diagram/SKILL.md
Normal file
476
.claude/skills/excalidraw-diagram/SKILL.md
Normal file
@@ -0,0 +1,476 @@
|
||||
---
|
||||
name: excalidraw-diagram
|
||||
description: Generate Excalidraw diagrams from text content. Supports three output modes - Obsidian (.md), Standard (.excalidraw), and Animated (.excalidraw with animation order). Triggers on "Excalidraw", "画图", "流程图", "思维导图", "可视化", "diagram", "标准Excalidraw", "standard excalidraw", "Excalidraw动画", "动画图", "animate".
|
||||
metadata:
|
||||
version: 1.2.1
|
||||
---
|
||||
|
||||
# Excalidraw Diagram Generator
|
||||
|
||||
Create Excalidraw diagrams from text content with multiple output formats.
|
||||
|
||||
## Output Modes
|
||||
|
||||
根据用户的触发词选择输出模式:
|
||||
|
||||
| 触发词 | 输出模式 | 文件格式 | 用途 |
|
||||
|--------|----------|----------|------|
|
||||
| `Excalidraw`、`画图`、`流程图`、`思维导图` | **Obsidian**(默认) | `.md` | 在 Obsidian 中直接打开 |
|
||||
| `标准Excalidraw`、`standard excalidraw` | **Standard** | `.excalidraw` | 在 excalidraw.com 打开/编辑/分享 |
|
||||
| `Excalidraw动画`、`动画图`、`animate` | **Animated** | `.excalidraw` | 拖到 excalidraw-animate 生成动画 |
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Detect output mode** from trigger words (see Output Modes table above)
|
||||
2. Analyze content - identify concepts, relationships, hierarchy
|
||||
3. Choose diagram type (see Diagram Types below)
|
||||
4. Generate Excalidraw JSON (add animation order if Animated mode)
|
||||
5. Output in correct format based on mode
|
||||
6. **Automatically save to current working directory**
|
||||
7. Notify user with file path and usage instructions
|
||||
|
||||
## Output Formats
|
||||
|
||||
### Mode 1: Obsidian Format (Default)
|
||||
|
||||
**严格按照以下结构输出,不得有任何修改:**
|
||||
|
||||
```markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
tags: [excalidraw]
|
||||
---
|
||||
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
|
||||
|
||||
# Excalidraw Data
|
||||
|
||||
## Text Elements
|
||||
%%
|
||||
## Drawing
|
||||
\`\`\`json
|
||||
{JSON 完整数据}
|
||||
\`\`\`
|
||||
%%
|
||||
```
|
||||
|
||||
**关键要点:**
|
||||
- Frontmatter 必须包含 `tags: [excalidraw]`
|
||||
- 警告信息必须完整
|
||||
- JSON 必须被 `%%` 标记包围
|
||||
- 不能使用 `excalidraw-plugin: parsed` 以外的其他 frontmatter 设置
|
||||
- **文件扩展名**:`.md`
|
||||
|
||||
### Mode 2: Standard Excalidraw Format
|
||||
|
||||
直接输出纯 JSON 文件,可在 excalidraw.com 打开:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [...],
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要点:**
|
||||
- `source` 使用 `https://excalidraw.com`(不是 Obsidian 插件)
|
||||
- 纯 JSON,无 Markdown 包装
|
||||
- **文件扩展名**:`.excalidraw`
|
||||
|
||||
### Mode 3: Animated Excalidraw Format
|
||||
|
||||
与 Standard 格式相同,但每个元素添加 `customData.animate` 字段控制动画顺序:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "element-1",
|
||||
"type": "rectangle",
|
||||
"customData": {
|
||||
"animate": {
|
||||
"order": 1,
|
||||
"duration": 500
|
||||
}
|
||||
},
|
||||
...其他标准字段
|
||||
}
|
||||
```
|
||||
|
||||
**动画顺序规则:**
|
||||
- `order`: 动画播放顺序(1, 2, 3...),数字越小越先出现
|
||||
- `duration`: 该元素的绘制时长(毫秒),默认 500
|
||||
- 相同 `order` 的元素同时出现
|
||||
- 建议顺序:标题 → 主要框架 → 连接线 → 细节文字
|
||||
|
||||
**使用方法:**
|
||||
1. 生成 `.excalidraw` 文件
|
||||
2. 拖到 https://dai-shi.github.io/excalidraw-animate/
|
||||
3. 点击 Animate 预览,然后导出 SVG 或 WebM
|
||||
|
||||
**文件扩展名**:`.excalidraw`
|
||||
|
||||
---
|
||||
|
||||
## Diagram Types & Selection Guide
|
||||
|
||||
选择合适的图表形式,以提升理解力与视觉吸引力。
|
||||
|
||||
| 类型 | 英文 | 使用场景 | 做法 |
|
||||
|------|------|---------|------|
|
||||
| **流程图** | Flowchart | 步骤说明、工作流程、任务执行顺序 | 用箭头连接各步骤,清晰表达流程走向 |
|
||||
| **思维导图** | Mind Map | 概念发散、主题分类、灵感捕捉 | 以中心为核心向外发散,放射状结构 |
|
||||
| **层级图** | Hierarchy | 组织结构、内容分级、系统拆解 | 自上而下或自左至右构建层级节点 |
|
||||
| **关系图** | Relationship | 要素之间的影响、依赖、互动 | 图形间用连线表示关联,箭头与说明 |
|
||||
| **对比图** | Comparison | 两种以上方案或观点的对照分析 | 左右两栏或表格形式,标明比较维度 |
|
||||
| **时间线图** | Timeline | 事件发展、项目进度、模型演化 | 以时间为轴,标出关键时间点与事件 |
|
||||
| **矩阵图** | Matrix | 双维度分类、任务优先级、定位 | 建立 X 与 Y 两个维度,坐标平面安置 |
|
||||
| **自由布局** | Freeform | 内容零散、灵感记录、初步信息收集 | 无需结构限制,自由放置图块与箭头 |
|
||||
|
||||
## Design Rules
|
||||
|
||||
### Text & Format
|
||||
- **所有文本元素必须使用** `fontFamily: 5`(Excalifont 手写字体)
|
||||
- **文本中的双引号替换规则**:`"` 替换为 `『』`
|
||||
- **文本中的圆括号替换规则**:`()` 替换为 `「」`
|
||||
- **字体大小规则**(硬性下限,低于此值在正常缩放下不可读):
|
||||
- 标题:20-28px(最小 20px)
|
||||
- 副标题:18-20px
|
||||
- 正文/标签:16-18px(最小 16px)
|
||||
- 次要注释:14px(仅限不重要的辅助说明,慎用)
|
||||
- **绝对禁止低于 14px**
|
||||
- **行高**:所有文本使用 `lineHeight: 1.25`
|
||||
- **文字居中估算**:独立文本元素没有自动居中,需手动计算 x 坐标:
|
||||
- 估算文字宽度:`estimatedWidth = text.length * fontSize * 0.5`(CJK 字符用 `* 1.0`)
|
||||
- 居中公式:`x = centerX - estimatedWidth / 2`
|
||||
- 示例:文字 "Hello"(5字符, fontSize 20)居中于 x=300 → `estimatedWidth = 5 * 20 * 0.5 = 50` → `x = 300 - 25 = 275`
|
||||
|
||||
### Layout & Design
|
||||
- **画布范围**:建议所有元素在 0-1200 x 0-800 区域内
|
||||
- **最小形状尺寸**:带文字的矩形/椭圆不小于 120x60px
|
||||
- **元素间距**:最小 20-30px 间距,防止重叠
|
||||
- **层次清晰**:使用不同颜色和形状区分不同层级的信息
|
||||
- **图形元素**:适当使用矩形框、圆形、箭头等元素来组织信息
|
||||
- **禁止 Emoji**:不要在图表文本中使用任何 Emoji 符号,如需视觉标记请使用简单图形(圆形、方形、箭头)或颜色区分
|
||||
|
||||
### Color Palette
|
||||
|
||||
**文字颜色(strokeColor for text):**
|
||||
|
||||
| 用途 | 色值 | 说明 |
|
||||
|------|------|------|
|
||||
| 标题 | `#1e40af` | 深蓝 |
|
||||
| 副标题/连接线 | `#3b82f6` | 亮蓝 |
|
||||
| 正文文字 | `#374151` | 深灰(白底最浅不低于 `#757575`) |
|
||||
| 强调/重点 | `#f59e0b` | 金色 |
|
||||
|
||||
**形状填充色(backgroundColor, fillStyle: "solid"):**
|
||||
|
||||
| 色值 | 语义 | 适用场景 |
|
||||
|------|------|---------|
|
||||
| `#a5d8ff` | 浅蓝 | 输入、数据源、主要节点 |
|
||||
| `#b2f2bb` | 浅绿 | 成功、输出、已完成 |
|
||||
| `#ffd8a8` | 浅橙 | 警告、待处理、外部依赖 |
|
||||
| `#d0bfff` | 浅紫 | 处理中、中间件、特殊项 |
|
||||
| `#ffc9c9` | 浅红 | 错误、关键、告警 |
|
||||
| `#fff3bf` | 浅黄 | 备注、决策、规划 |
|
||||
| `#c3fae8` | 浅青 | 存储、数据、缓存 |
|
||||
| `#eebefa` | 浅粉 | 分析、指标、统计 |
|
||||
|
||||
**区域背景色(大矩形 + opacity: 30,用于分层图表):**
|
||||
|
||||
| 色值 | 语义 |
|
||||
|------|------|
|
||||
| `#dbe4ff` | 前端/UI 层 |
|
||||
| `#e5dbff` | 逻辑/处理层 |
|
||||
| `#d3f9d8` | 数据/工具层 |
|
||||
|
||||
**对比度规则:**
|
||||
- 白底上文字最浅不低于 `#757575`,否则不可读
|
||||
- 浅色填充上用深色变体文字(如浅绿底用 `#15803d`,不用 `#22c55e`)
|
||||
- 避免浅灰色文字(`#b0b0b0`、`#999`)出现在白底上
|
||||
|
||||
参考:[references/excalidraw-schema.md](references/excalidraw-schema.md)
|
||||
|
||||
## JSON Structure
|
||||
|
||||
**Obsidian 模式:**
|
||||
```json
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://github.com/zsviczian/obsidian-excalidraw-plugin",
|
||||
"elements": [...],
|
||||
"appState": { "gridSize": null, "viewBackgroundColor": "#ffffff" },
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
|
||||
**Standard / Animated 模式:**
|
||||
```json
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [...],
|
||||
"appState": { "gridSize": null, "viewBackgroundColor": "#ffffff" },
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Element Template
|
||||
|
||||
Each element requires these fields (do NOT add extra fields like `frameId`, `index`, `versionNonce`, `rawText` -- they may cause issues on excalidraw.com. `boundElements` must be `null` not `[]`, `updated` must be `1` not timestamps):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "unique-id",
|
||||
"type": "rectangle",
|
||||
"x": 100, "y": 100,
|
||||
"width": 200, "height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"roundness": {"type": 3},
|
||||
"seed": 123456789,
|
||||
"version": 1,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1,
|
||||
"link": null,
|
||||
"locked": false
|
||||
}
|
||||
```
|
||||
|
||||
`strokeStyle` values: `"solid"`(实线,默认)| `"dashed"`(虚线)| `"dotted"`(点线)。虚线适合表示可选路径、异步流、弱关联等。
|
||||
|
||||
Text elements add:
|
||||
```json
|
||||
{
|
||||
"text": "显示文本",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "显示文本",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
```
|
||||
|
||||
**Animated 模式额外添加** `customData` 字段:
|
||||
```json
|
||||
{
|
||||
"id": "title-1",
|
||||
"type": "text",
|
||||
"customData": {
|
||||
"animate": {
|
||||
"order": 1,
|
||||
"duration": 500
|
||||
}
|
||||
},
|
||||
...其他字段
|
||||
}
|
||||
```
|
||||
|
||||
See [references/excalidraw-schema.md](references/excalidraw-schema.md) for all element types.
|
||||
|
||||
---
|
||||
|
||||
## Additional Technical Requirements
|
||||
|
||||
### Text Elements 处理
|
||||
- `## Text Elements` 部分在 Markdown 中**必须留空**,仅用 `%%` 作为分隔符
|
||||
- Obsidian ExcaliDraw 插件会根据 JSON 数据**自动填充文本元素**
|
||||
- 不需要手动列出所有文本内容
|
||||
|
||||
### 坐标与布局
|
||||
- **坐标系统**:左上角为原点 (0,0)
|
||||
- **推荐范围**:所有元素在 0-1200 x 0-800 像素范围内
|
||||
- **元素 ID**:每个元素需要唯一的 `id`(可以是字符串,如「title」「box1」等)
|
||||
|
||||
### Required Fields for All Elements
|
||||
|
||||
**IMPORTANT**: Do NOT include `frameId`, `index`, `versionNonce`, or `rawText` fields. Use `boundElements: null` (not `[]`), and `updated: 1` (not timestamps).
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "unique-identifier",
|
||||
"type": "rectangle|text|arrow|ellipse|diamond",
|
||||
"x": 100, "y": 100,
|
||||
"width": 200, "height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#color-hex",
|
||||
"backgroundColor": "transparent|#color-hex",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid|dashed|dotted",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"roundness": {"type": 3},
|
||||
"seed": 123456789,
|
||||
"version": 1,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1,
|
||||
"link": null,
|
||||
"locked": false
|
||||
}
|
||||
```
|
||||
|
||||
### Text-Specific Properties
|
||||
文本元素 (type: "text") 需要额外属性(do NOT include `rawText`):
|
||||
```json
|
||||
{
|
||||
"text": "显示文本",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "显示文本",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
```
|
||||
|
||||
### appState 配置
|
||||
```json
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
}
|
||||
```
|
||||
|
||||
### files 字段
|
||||
```json
|
||||
"files": {}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
- **文字偏移** — 独立 text 元素的 `x` 是左边缘,不是中心。必须用居中公式手动计算,否则文字会偏到一边
|
||||
- **元素重叠** — y 坐标相近的元素容易堆叠。放置新元素前检查与周围元素是否有至少 20px 间距
|
||||
- **画布留白不足** — 内容不要贴着画布边缘。在四周留 50-80px 的 padding
|
||||
- **标题没有居中于图表** — 标题应居中于下方图表的整体宽度,不是固定在 x=0
|
||||
- **箭头标签溢出** — 长文字标签(如 "ATP + NADPH")会超出短箭头。保持标签简短或加大箭头长度
|
||||
- **对比度不够** — 浅色文字在白底上几乎不可见。文字颜色不低于 `#757575`,有色文字用深色变体
|
||||
- **字号太小** — 低于 14px 在正常缩放下不可读,正文最小 16px
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Auto-save & File Generation Workflow
|
||||
|
||||
当生成 Excalidraw 图表时,**必须自动执行以下步骤**:
|
||||
|
||||
#### 1. 选择合适的图表类型
|
||||
- 根据用户提供的内容特性,参考上方 「Diagram Types & Selection Guide」 表
|
||||
- 分析内容的核心诉求,选择最合适的可视化形式
|
||||
|
||||
#### 2. 生成有意义的文件名
|
||||
|
||||
根据输出模式选择文件扩展名:
|
||||
|
||||
| 模式 | 文件名格式 | 示例 |
|
||||
|------|-----------|------|
|
||||
| Obsidian | `[主题].[类型].md` | `商业模式.relationship.md` |
|
||||
| Standard | `[主题].[类型].excalidraw` | `商业模式.relationship.excalidraw` |
|
||||
| Animated | `[主题].[类型].animate.excalidraw` | `商业模式.relationship.animate.excalidraw` |
|
||||
|
||||
- 优先使用中文以提高清晰度
|
||||
|
||||
#### 3. 使用 Write 工具自动保存文件
|
||||
- **保存位置**:当前工作目录(自动检测环境变量)
|
||||
- **完整路径**:`{current_directory}/[filename].md`
|
||||
- 这样可以实现灵活迁移,无需硬编码路径
|
||||
|
||||
#### 4. 确保 Markdown 结构完全正确
|
||||
**必须按以下格式生成**(不能有任何修改):
|
||||
|
||||
```markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
tags: [excalidraw]
|
||||
---
|
||||
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
|
||||
|
||||
# Excalidraw Data
|
||||
|
||||
## Text Elements
|
||||
%%
|
||||
## Drawing
|
||||
\`\`\`json
|
||||
{完整的 JSON 数据}
|
||||
\`\`\`
|
||||
%%
|
||||
```
|
||||
|
||||
#### 5. JSON 数据要求
|
||||
- 包含完整的 Excalidraw JSON 结构
|
||||
- 所有文本元素使用 `fontFamily: 5`
|
||||
- 文本中的 `"` 替换为 `『』`
|
||||
- 文本中的 `()` 替换为 `「」`
|
||||
- JSON 格式必须有效,通过语法检查
|
||||
- 所有元素有唯一的 `id`
|
||||
- 包含 `appState` 和 `files: {}` 字段
|
||||
|
||||
#### 6. 用户反馈与确认
|
||||
向用户报告:
|
||||
- 图表已生成
|
||||
- 精确的保存位置
|
||||
- 如何在 Obsidian 中查看
|
||||
- 图表的设计选择说明(选择了什么类型的图表、为什么)
|
||||
- 是否需要调整或修改
|
||||
|
||||
### Example Output Messages
|
||||
|
||||
**Obsidian 模式:**
|
||||
```
|
||||
Excalidraw 图已生成!
|
||||
|
||||
保存位置:商业模式.relationship.md
|
||||
|
||||
使用方法:
|
||||
1. 在 Obsidian 中打开此文件
|
||||
2. 点击右上角 MORE OPTIONS 菜单
|
||||
3. 选择 Switch to EXCALIDRAW VIEW
|
||||
```
|
||||
|
||||
**Standard 模式:**
|
||||
```
|
||||
Excalidraw 图已生成!
|
||||
|
||||
保存位置:商业模式.relationship.excalidraw
|
||||
|
||||
使用方法:
|
||||
1. 打开 https://excalidraw.com
|
||||
2. 点击左上角菜单 → Open → 选择此文件
|
||||
3. 或直接拖拽文件到 excalidraw.com 页面
|
||||
```
|
||||
|
||||
**Animated 模式:**
|
||||
```
|
||||
Excalidraw 动画图已生成!
|
||||
|
||||
保存位置:商业模式.relationship.animate.excalidraw
|
||||
|
||||
动画顺序:标题(1) → 主框架(2-4) → 连接线(5-7) → 说明文字(8-10)
|
||||
|
||||
生成动画:
|
||||
1. 打开 https://dai-shi.github.io/excalidraw-animate/
|
||||
2. 点击 Load File 选择此文件
|
||||
3. 预览动画效果
|
||||
4. 点击 Export 导出 SVG 或 WebM
|
||||
```
|
||||
@@ -0,0 +1,201 @@
|
||||
# Excalidraw JSON Schema Reference
|
||||
|
||||
## Color Palette
|
||||
|
||||
### Primary Colors
|
||||
| Purpose | Color | Hex |
|
||||
|---------|-------|-----|
|
||||
| Main Title | Deep Blue | `#1e40af` |
|
||||
| Subtitle | Medium Blue | `#3b82f6` |
|
||||
| Body Text | Dark Gray | `#374151` |
|
||||
| Emphasis | Orange | `#f59e0b` |
|
||||
| Success | Green | `#10b981` |
|
||||
| Warning | Red | `#ef4444` |
|
||||
|
||||
### Background Colors
|
||||
| Purpose | Color | Hex |
|
||||
|---------|-------|-----|
|
||||
| Light Blue | Background | `#dbeafe` |
|
||||
| Light Gray | Neutral | `#f3f4f6` |
|
||||
| Light Orange | Highlight | `#fef3c7` |
|
||||
| Light Green | Success | `#d1fae5` |
|
||||
| Light Purple | Accent | `#ede9fe` |
|
||||
|
||||
## Element Types
|
||||
|
||||
### Rectangle
|
||||
```json
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "unique-id",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"strokeColor": "#1e40af",
|
||||
"backgroundColor": "#dbeafe",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"roundness": { "type": 3 }
|
||||
}
|
||||
```
|
||||
|
||||
### Text
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"id": "unique-id",
|
||||
"x": 150,
|
||||
"y": 130,
|
||||
"text": "Content here",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"strokeColor": "#1e40af",
|
||||
"backgroundColor": "transparent"
|
||||
}
|
||||
```
|
||||
|
||||
### Arrow
|
||||
```json
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "unique-id",
|
||||
"x": 300,
|
||||
"y": 140,
|
||||
"width": 100,
|
||||
"height": 0,
|
||||
"points": [[0, 0], [100, 0]],
|
||||
"strokeColor": "#374151",
|
||||
"strokeWidth": 2,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow"
|
||||
}
|
||||
```
|
||||
|
||||
### Ellipse
|
||||
```json
|
||||
{
|
||||
"type": "ellipse",
|
||||
"id": "unique-id",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 120,
|
||||
"height": 120,
|
||||
"strokeColor": "#10b981",
|
||||
"backgroundColor": "#d1fae5",
|
||||
"fillStyle": "solid"
|
||||
}
|
||||
```
|
||||
|
||||
### Diamond
|
||||
```json
|
||||
{
|
||||
"type": "diamond",
|
||||
"id": "unique-id",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"strokeColor": "#f59e0b",
|
||||
"backgroundColor": "#fef3c7",
|
||||
"fillStyle": "solid"
|
||||
}
|
||||
```
|
||||
|
||||
### Line
|
||||
```json
|
||||
{
|
||||
"type": "line",
|
||||
"id": "unique-id",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"points": [[0, 0], [200, 100]],
|
||||
"strokeColor": "#374151",
|
||||
"strokeWidth": 2
|
||||
}
|
||||
```
|
||||
|
||||
## Full JSON Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
// Array of elements
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Font Family Values
|
||||
|
||||
| Value | Font Name |
|
||||
|-------|-----------|
|
||||
| 1 | Virgil (hand-drawn) |
|
||||
| 2 | Helvetica |
|
||||
| 3 | Cascadia |
|
||||
| 4 | Assistant |
|
||||
| 5 | Excalifont (recommended) |
|
||||
|
||||
## Fill Styles
|
||||
|
||||
- `solid` - Solid fill
|
||||
- `hachure` - Hatched lines
|
||||
- `cross-hatch` - Cross-hatched
|
||||
- `dots` - Dotted pattern
|
||||
|
||||
## Roundness Types
|
||||
|
||||
- `{ "type": 1 }` - Sharp corners
|
||||
- `{ "type": 2 }` - Slight rounding
|
||||
- `{ "type": 3 }` - Full rounding (recommended)
|
||||
|
||||
## Element Binding
|
||||
|
||||
To connect text to a container:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "container-id",
|
||||
"boundElements": [{ "id": "text-id", "type": "text" }]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"id": "text-id",
|
||||
"containerId": "container-id"
|
||||
}
|
||||
```
|
||||
|
||||
## Arrow Binding
|
||||
|
||||
To connect arrows to shapes:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "arrow",
|
||||
"startBinding": {
|
||||
"elementId": "source-shape-id",
|
||||
"focus": 0,
|
||||
"gap": 5
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "target-shape-id",
|
||||
"focus": 0,
|
||||
"gap": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
276
.claude/skills/mermaid-visualizer/SKILL.md
Normal file
276
.claude/skills/mermaid-visualizer/SKILL.md
Normal file
@@ -0,0 +1,276 @@
|
||||
---
|
||||
name: mermaid-visualizer
|
||||
description: Transform text content into professional Mermaid diagrams for presentations and documentation. Use when users ask to visualize concepts, create flowcharts, or make diagrams from text. Supports process flows, system architectures, comparisons, mindmaps, and more with built-in syntax error prevention.
|
||||
---
|
||||
|
||||
# Mermaid Visualizer
|
||||
|
||||
## Overview
|
||||
|
||||
Convert text content into clean, professional Mermaid diagrams optimized for presentations and documentation. Automatically handles common syntax pitfalls (list syntax conflicts, subgraph naming, spacing issues) to ensure diagrams render correctly in Obsidian, GitHub, and other Mermaid-compatible platforms.
|
||||
|
||||
## Quick Start
|
||||
|
||||
When creating a Mermaid diagram:
|
||||
|
||||
1. **Analyze the content** - Identify key concepts, relationships, and flow
|
||||
2. **Choose diagram type** - Select the most appropriate visualization (see Diagram Types below)
|
||||
3. **Select configuration** - Determine layout, detail level, and styling
|
||||
4. **Generate diagram** - Create syntactically correct Mermaid code
|
||||
5. **Output in markdown** - Wrap in proper code fence with optional explanation
|
||||
|
||||
**Default assumptions:**
|
||||
- Vertical layout (TB) unless horizontal requested
|
||||
- Medium detail level (balanced between simplicity and information)
|
||||
- Professional color scheme with semantic colors
|
||||
- Obsidian/GitHub compatible syntax
|
||||
|
||||
## Diagram Types
|
||||
|
||||
### 1. Process Flow (graph TB/LR)
|
||||
**Best for:** Workflows, decision trees, sequential processes, AI agent architectures
|
||||
|
||||
**Use when:** Content describes steps, stages, or a sequence of actions
|
||||
|
||||
**Key features:**
|
||||
- Swimlanes via subgraph for grouping related steps
|
||||
- Arrow labels for transitions
|
||||
- Feedback loops and branches
|
||||
- Color-coded stages
|
||||
|
||||
**Configuration options:**
|
||||
- `layout`: "vertical" (TB), "horizontal" (LR)
|
||||
- `detail`: "simple" (core steps only), "standard" (with descriptions), "detailed" (with annotations)
|
||||
- `style`: "minimal", "professional", "colorful"
|
||||
|
||||
### 2. Circular Flow (graph TD with circular layout)
|
||||
**Best for:** Cyclic processes, continuous improvement loops, agent feedback systems
|
||||
|
||||
**Use when:** Content emphasizes iteration, feedback, or circular relationships
|
||||
|
||||
**Key features:**
|
||||
- Central hub with radiating elements
|
||||
- Curved feedback arrows
|
||||
- Clear cycle indicators
|
||||
|
||||
### 3. Comparison Diagram (graph TB with parallel paths)
|
||||
**Best for:** Before/after comparisons, A vs B analysis, traditional vs modern systems
|
||||
|
||||
**Use when:** Content contrasts two or more approaches or systems
|
||||
|
||||
**Key features:**
|
||||
- Side-by-side layout
|
||||
- Central comparison node
|
||||
- Clear differentiation via color/style
|
||||
|
||||
### 4. Mindmap
|
||||
**Best for:** Hierarchical concepts, knowledge organization, topic breakdowns
|
||||
|
||||
**Use when:** Content is hierarchical with clear parent-child relationships
|
||||
|
||||
**Key features:**
|
||||
- Radial tree structure
|
||||
- Multiple levels of nesting
|
||||
- Clean visual hierarchy
|
||||
|
||||
### 5. Sequence Diagram
|
||||
**Best for:** Interactions between components, API calls, message flows
|
||||
|
||||
**Use when:** Content involves communication between actors/systems over time
|
||||
|
||||
**Key features:**
|
||||
- Timeline-based layout
|
||||
- Clear actor separation
|
||||
- Activation boxes for processes
|
||||
|
||||
### 6. State Diagram
|
||||
**Best for:** System states, status transitions, lifecycle stages
|
||||
|
||||
**Use when:** Content describes states and transitions between them
|
||||
|
||||
**Key features:**
|
||||
- Clear state nodes
|
||||
- Labeled transitions
|
||||
- Start and end states
|
||||
|
||||
## Critical Syntax Rules
|
||||
|
||||
**Always follow these rules to prevent parsing errors:**
|
||||
|
||||
### Rule 1: Avoid List Syntax Conflicts
|
||||
```
|
||||
❌ WRONG: [1. Perception] → Triggers "Unsupported markdown: list"
|
||||
✅ RIGHT: [1.Perception] → Remove space after period
|
||||
✅ RIGHT: [① Perception] → Use circled numbers (①②③④⑤⑥⑦⑧⑨⑩)
|
||||
✅ RIGHT: [(1) Perception] → Use parentheses
|
||||
✅ RIGHT: [Step 1: Perception] → Use "Step" prefix
|
||||
```
|
||||
|
||||
### Rule 2: Subgraph Naming
|
||||
```
|
||||
❌ WRONG: subgraph AI Agent Core → Space in name without quotes
|
||||
✅ RIGHT: subgraph agent["AI Agent Core"] → Use ID with display name
|
||||
✅ RIGHT: subgraph agent → Use simple ID only
|
||||
```
|
||||
|
||||
### Rule 3: Node References
|
||||
```
|
||||
❌ WRONG: Title --> AI Agent Core → Reference display name directly
|
||||
✅ RIGHT: Title --> agent → Reference subgraph ID
|
||||
```
|
||||
|
||||
### Rule 4: Special Characters in Node Text
|
||||
```
|
||||
✅ Use quotes for text with spaces: ["Text with spaces"]
|
||||
✅ Escape or avoid: quotation marks → use 『』instead
|
||||
✅ Escape or avoid: parentheses → use 「」instead
|
||||
✅ Line breaks in circle nodes only: ((Text<br/>Break))
|
||||
```
|
||||
|
||||
### Rule 5: Arrow Types
|
||||
- `-->` solid arrow
|
||||
- `-.->` dashed arrow (for supporting systems, optional paths)
|
||||
- `==>` thick arrow (for emphasis)
|
||||
- `~~~` invisible link (for layout only)
|
||||
|
||||
For complete syntax reference and edge cases, see [references/syntax-rules.md](references/syntax-rules.md)
|
||||
|
||||
## Configuration Options
|
||||
|
||||
All diagrams accept these parameters:
|
||||
|
||||
**Layout:**
|
||||
- `direction`: "vertical" (TB), "horizontal" (LR), "right-to-left" (RL), "bottom-to-top" (BT)
|
||||
- `aspect`: "portrait" (default), "landscape" (wide), "square"
|
||||
|
||||
**Detail Level:**
|
||||
- `simple`: Core elements only, minimal labels
|
||||
- `standard`: Balanced detail with key descriptions (default)
|
||||
- `detailed`: Full annotations, explanations, and metadata
|
||||
- `presentation`: Optimized for slides (larger text, fewer details)
|
||||
|
||||
**Style:**
|
||||
- `minimal`: Monochrome, clean lines
|
||||
- `professional`: Semantic colors, clear hierarchy (default)
|
||||
- `colorful`: Vibrant colors, high contrast
|
||||
- `academic`: Formal styling for papers/documentation
|
||||
|
||||
**Additional Options:**
|
||||
- `show_legend`: true/false - Include color/symbol legend
|
||||
- `numbered`: true/false - Add sequence numbers to steps
|
||||
- `title`: string - Add diagram title
|
||||
|
||||
## Example Usage Patterns
|
||||
|
||||
**Pattern 1: Basic request**
|
||||
```
|
||||
User: "Visualize the software development lifecycle"
|
||||
Response: [Analyze → Choose graph TB → Generate with standard detail]
|
||||
```
|
||||
|
||||
**Pattern 2: With configuration**
|
||||
```
|
||||
User: "Create a horizontal flowchart of our sales process with lots of detail"
|
||||
Response: [Analyze → Choose graph LR → Generate with detailed level]
|
||||
```
|
||||
|
||||
**Pattern 3: Comparison**
|
||||
```
|
||||
User: "Compare traditional AI vs AI agents"
|
||||
Response: [Analyze → Choose comparison layout → Generate with contrasting styles]
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Understand the content**
|
||||
- Identify main concepts, entities, and relationships
|
||||
- Determine hierarchy or sequence
|
||||
- Note any comparisons or contrasts
|
||||
|
||||
2. **Select diagram type**
|
||||
- Match content structure to diagram type
|
||||
- Consider user's presentation context
|
||||
- Default to process flow if ambiguous
|
||||
|
||||
3. **Choose configuration**
|
||||
- Apply user-specified options
|
||||
- Use sensible defaults for unspecified options
|
||||
- Optimize for readability
|
||||
|
||||
4. **Generate Mermaid code**
|
||||
- Follow all syntax rules strictly
|
||||
- Use semantic naming (descriptive IDs)
|
||||
- Apply consistent styling
|
||||
- Test for common errors:
|
||||
* No "number. space" patterns in node text
|
||||
* All subgraphs use ID["display name"] format
|
||||
* All node references use IDs not display names
|
||||
|
||||
5. **Output with context**
|
||||
- Wrap in ```mermaid code fence
|
||||
- Add brief explanation of diagram structure
|
||||
- Mention rendering compatibility (Obsidian, GitHub, etc.)
|
||||
- Offer to adjust or create variations
|
||||
|
||||
## Color Scheme Defaults
|
||||
|
||||
Standard professional palette:
|
||||
- Green (#d3f9d8/#2f9e44): Input, perception, start states
|
||||
- Red (#ffe3e3/#c92a2a): Planning, decision points
|
||||
- Purple (#e5dbff/#5f3dc4): Processing, reasoning
|
||||
- Orange (#ffe8cc/#d9480f): Actions, tool usage
|
||||
- Cyan (#c5f6fa/#0c8599): Output, execution, results
|
||||
- Yellow (#fff4e6/#e67700): Storage, memory, data
|
||||
- Pink (#f3d9fa/#862e9c): Learning, optimization
|
||||
- Blue (#e7f5ff/#1971c2): Metadata, definitions, titles
|
||||
- Gray (#f8f9fa/#868e96): Neutral elements, traditional systems
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Swimlane Pattern (Grouping)
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph core["Core Process"]
|
||||
A --> B --> C
|
||||
end
|
||||
subgraph support["Supporting Systems"]
|
||||
D
|
||||
E
|
||||
end
|
||||
core -.-> support
|
||||
```
|
||||
|
||||
### Feedback Loop Pattern
|
||||
```mermaid
|
||||
graph TB
|
||||
A[Start] --> B[Process]
|
||||
B --> C[End]
|
||||
C -.->|Feedback| A
|
||||
```
|
||||
|
||||
### Hub and Spoke Pattern
|
||||
```mermaid
|
||||
graph TB
|
||||
Central[Hub]
|
||||
A[Spoke 1] --> Central
|
||||
B[Spoke 2] --> Central
|
||||
C[Spoke 3] --> Central
|
||||
```
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before outputting, verify:
|
||||
- [ ] No "number. space" patterns in any node text
|
||||
- [ ] All subgraphs use proper ID syntax
|
||||
- [ ] All arrows use correct syntax (-->, -.->)
|
||||
- [ ] Colors applied consistently
|
||||
- [ ] Layout direction specified
|
||||
- [ ] Style declarations present
|
||||
- [ ] No ambiguous node references
|
||||
- [ ] Compatible with Obsidian/GitHub renderers
|
||||
- [ ] **No Emoji** in any node text - use text labels or color coding instead
|
||||
|
||||
## References
|
||||
|
||||
For detailed syntax rules and troubleshooting, see:
|
||||
- [references/syntax-rules.md](references/syntax-rules.md) - Complete syntax reference and error prevention
|
||||
484
.claude/skills/mermaid-visualizer/references/syntax-rules.md
Normal file
484
.claude/skills/mermaid-visualizer/references/syntax-rules.md
Normal file
@@ -0,0 +1,484 @@
|
||||
# Mermaid Syntax Rules Reference
|
||||
|
||||
This reference provides comprehensive syntax rules and error prevention strategies for Mermaid diagrams. Load this when encountering syntax errors or needing detailed syntax information.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Critical Error Prevention](#critical-error-prevention)
|
||||
2. [Node Syntax](#node-syntax)
|
||||
3. [Subgraph Syntax](#subgraph-syntax)
|
||||
4. [Arrow and Connection Types](#arrow-and-connection-types)
|
||||
5. [Styling and Colors](#styling-and-colors)
|
||||
6. [Layout and Direction](#layout-and-direction)
|
||||
7. [Advanced Patterns](#advanced-patterns)
|
||||
8. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Critical Error Prevention
|
||||
|
||||
### List Syntax Conflict (Most Common Error)
|
||||
|
||||
**Problem:** Mermaid parser interprets `number. space` as Markdown ordered list syntax.
|
||||
|
||||
**Error Message:** `Parse error: Unsupported markdown: list`
|
||||
|
||||
**Solutions:**
|
||||
|
||||
```mermaid
|
||||
❌ [1. Perception]
|
||||
❌ [2. Planning]
|
||||
❌ [3. Reasoning]
|
||||
|
||||
✅ [1.Perception] # Remove space
|
||||
✅ [① Perception] # Use circled numbers
|
||||
✅ [(1) Perception] # Use parentheses
|
||||
✅ [Step 1: Perception] # Use prefix
|
||||
✅ [Step 1 - Perception] # Use dash
|
||||
✅ [Perception] # Remove numbering
|
||||
```
|
||||
|
||||
**Circled number reference:**
|
||||
```
|
||||
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳
|
||||
```
|
||||
|
||||
### Subgraph Naming Rules
|
||||
|
||||
**Rule:** Subgraphs with spaces must use ID + display name format.
|
||||
|
||||
```mermaid
|
||||
❌ subgraph Core Process
|
||||
A --> B
|
||||
end
|
||||
|
||||
✅ subgraph core["Core Process"]
|
||||
A --> B
|
||||
end
|
||||
|
||||
✅ subgraph core_process
|
||||
A --> B
|
||||
end
|
||||
```
|
||||
|
||||
**Referencing subgraphs:**
|
||||
```mermaid
|
||||
❌ Title --> Core Process # Cannot reference display name
|
||||
✅ Title --> core # Must reference ID
|
||||
```
|
||||
|
||||
### Node Reference Rules
|
||||
|
||||
**Rule:** Always reference nodes by ID, never by display text.
|
||||
|
||||
```mermaid
|
||||
# Define nodes
|
||||
A[Display Text A]
|
||||
B["Display Text B"]
|
||||
|
||||
# Reference nodes
|
||||
A --> B ✅ Use node IDs
|
||||
Display Text A --> Display Text B ❌ Cannot use display text
|
||||
```
|
||||
|
||||
## Node Syntax
|
||||
|
||||
### Basic Node Types
|
||||
|
||||
```mermaid
|
||||
# Rectangle (default)
|
||||
A[Rectangle Text]
|
||||
|
||||
# Rectangle with rounded corners
|
||||
B(Rounded Text)
|
||||
|
||||
# Stadium shape
|
||||
C([Stadium Text])
|
||||
|
||||
# Circle
|
||||
D((Circle<br/>Text))
|
||||
|
||||
# Asymmetric shape
|
||||
E>Right Arrow]
|
||||
|
||||
# Rhombus (decision)
|
||||
F{Decision?}
|
||||
|
||||
# Hexagon
|
||||
G{{Hexagon}}
|
||||
|
||||
# Parallelogram
|
||||
H[/Parallelogram/]
|
||||
|
||||
# Database
|
||||
I[(Database)]
|
||||
|
||||
# Trapezoid
|
||||
J[/Trapezoid\]
|
||||
```
|
||||
|
||||
### Node Text Rules
|
||||
|
||||
**Line breaks:**
|
||||
- `<br/>` only works in circle nodes: `((Text<br/>Break))`
|
||||
- For other nodes, use separate annotation nodes or keep text concise
|
||||
|
||||
**Special characters:**
|
||||
- Spaces: Use quotes if needed: `["Text with spaces"]`
|
||||
- Quotes: Replace with 『』or avoid
|
||||
- Parentheses: Replace with 「」or avoid
|
||||
- Colons: Generally safe but avoid if causing issues
|
||||
- Hyphens/dashes: Safe to use
|
||||
|
||||
**Length guidelines:**
|
||||
- Keep node text under 50 characters
|
||||
- Use multiple lines (circle nodes) or separate annotation nodes for longer content
|
||||
- Consider splitting into multiple nodes if text is too long
|
||||
|
||||
## Subgraph Syntax
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
# Correct format with ID and display name
|
||||
subgraph id["Display Name"]
|
||||
direction TB
|
||||
A --> B
|
||||
end
|
||||
|
||||
# Simple ID only (no spaces)
|
||||
subgraph simple
|
||||
C --> D
|
||||
end
|
||||
|
||||
# Can set direction inside subgraph
|
||||
subgraph horiz["Horizontal"]
|
||||
direction LR
|
||||
E --> F
|
||||
end
|
||||
```
|
||||
|
||||
### Nested Subgraphs
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph outer["Outer Group"]
|
||||
direction TB
|
||||
|
||||
subgraph inner1["Inner 1"]
|
||||
A --> B
|
||||
end
|
||||
|
||||
subgraph inner2["Inner 2"]
|
||||
C --> D
|
||||
end
|
||||
|
||||
inner1 -.-> inner2
|
||||
end
|
||||
```
|
||||
|
||||
**Limitation:** Keep nesting to 2 levels maximum for readability.
|
||||
|
||||
### Connecting Subgraphs
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph g1["Group 1"]
|
||||
A[Node A]
|
||||
end
|
||||
|
||||
subgraph g2["Group 2"]
|
||||
B[Node B]
|
||||
end
|
||||
|
||||
# Connect individual nodes (recommended)
|
||||
A --> B
|
||||
|
||||
# Connect subgraphs (creates invisible link for layout)
|
||||
g1 -.-> g2
|
||||
```
|
||||
|
||||
## Arrow and Connection Types
|
||||
|
||||
### Basic Arrows
|
||||
|
||||
```mermaid
|
||||
A --> B # Solid arrow
|
||||
A -.-> B # Dashed arrow
|
||||
A ==> B # Thick arrow
|
||||
A ~~~> B # Invisible link (layout only, not rendered)
|
||||
```
|
||||
|
||||
### Arrow Labels
|
||||
|
||||
```mermaid
|
||||
A -->|Label Text| B
|
||||
A -.->|Optional| B
|
||||
A ==>|Important| B
|
||||
```
|
||||
|
||||
### Multi-target Connections
|
||||
|
||||
```mermaid
|
||||
# One to many
|
||||
A --> B & C & D
|
||||
|
||||
# Many to one
|
||||
A & B & C --> D
|
||||
|
||||
# Chaining
|
||||
A --> B --> C --> D
|
||||
```
|
||||
|
||||
### Bidirectional
|
||||
|
||||
```mermaid
|
||||
A <--> B # Bidirectional solid
|
||||
A <-.-> B # Bidirectional dashed
|
||||
```
|
||||
|
||||
## Styling and Colors
|
||||
|
||||
### Inline Styling
|
||||
|
||||
```mermaid
|
||||
style NodeID fill:#color,stroke:#color,stroke-width:2px
|
||||
```
|
||||
|
||||
### Color Format
|
||||
|
||||
- Hex colors: `#ff0000` or `#f00`
|
||||
- RGB: `rgb(255,0,0)`
|
||||
- Color names: `red`, `blue`, etc. (limited support)
|
||||
|
||||
### Common Style Patterns
|
||||
|
||||
```mermaid
|
||||
# Professional look
|
||||
style A fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px
|
||||
|
||||
# Emphasis
|
||||
style B fill:#ffe3e3,stroke:#c92a2a,stroke-width:3px
|
||||
|
||||
# Muted/secondary
|
||||
style C fill:#f8f9fa,stroke:#dee2e6,stroke-width:1px
|
||||
|
||||
# Title/header
|
||||
style D fill:#1971c2,stroke:#1971c2,stroke-width:3px,color:#ffffff
|
||||
```
|
||||
|
||||
### Styling Multiple Nodes
|
||||
|
||||
```mermaid
|
||||
# Apply same style to multiple nodes
|
||||
style A,B,C fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px
|
||||
```
|
||||
|
||||
## Layout and Direction
|
||||
|
||||
### Direction Codes
|
||||
|
||||
```mermaid
|
||||
graph TB # Top to Bottom (vertical)
|
||||
graph BT # Bottom to Top
|
||||
graph LR # Left to Right (horizontal)
|
||||
graph RL # Right to Left
|
||||
graph TD # Top Down (same as TB)
|
||||
```
|
||||
|
||||
### Layout Control Tips
|
||||
|
||||
1. **Vertical layouts (TB/BT):** Best for sequential processes, hierarchies
|
||||
2. **Horizontal layouts (LR/RL):** Best for timelines, wide displays
|
||||
3. **Mixed directions:** Set different directions in subgraphs
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph vertical["Vertical Flow"]
|
||||
direction TB
|
||||
A --> B --> C
|
||||
end
|
||||
|
||||
subgraph horizontal["Horizontal Flow"]
|
||||
direction LR
|
||||
D --> E --> F
|
||||
end
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Feedback Loop Pattern
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
A[Start] --> B[Process]
|
||||
B --> C[Output]
|
||||
C -.->|Feedback| A
|
||||
|
||||
style A fill:#d3f9d8,stroke:#2f9e44,stroke-width:2px
|
||||
style B fill:#e5dbff,stroke:#5f3dc4,stroke-width:2px
|
||||
style C fill:#c5f6fa,stroke:#0c8599,stroke-width:2px
|
||||
```
|
||||
|
||||
### Swimlane Pattern
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph lane1["Lane 1"]
|
||||
A[Step 1] --> B[Step 2]
|
||||
end
|
||||
|
||||
subgraph lane2["Lane 2"]
|
||||
C[Step 3] --> D[Step 4]
|
||||
end
|
||||
|
||||
B --> C
|
||||
```
|
||||
|
||||
### Hub and Spoke
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Hub[Central Hub]
|
||||
|
||||
A[Spoke 1] --> Hub
|
||||
B[Spoke 2] --> Hub
|
||||
C[Spoke 3] --> Hub
|
||||
Hub --> D[Output]
|
||||
```
|
||||
|
||||
### Decision Tree
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Start[Start] --> Decision{Decision Point?}
|
||||
Decision -->|Option A| PathA[Path A]
|
||||
Decision -->|Option B| PathB[Path B]
|
||||
Decision -->|Option C| PathC[Path C]
|
||||
|
||||
PathA --> End[End]
|
||||
PathB --> End
|
||||
PathC --> End
|
||||
```
|
||||
|
||||
### Comparison Layout
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Title[Comparison]
|
||||
|
||||
subgraph left["System A"]
|
||||
A1[Feature 1]
|
||||
A2[Feature 2]
|
||||
A3[Feature 3]
|
||||
end
|
||||
|
||||
subgraph right["System B"]
|
||||
B1[Feature 1]
|
||||
B2[Feature 2]
|
||||
B3[Feature 3]
|
||||
end
|
||||
|
||||
Title --> left
|
||||
Title --> right
|
||||
|
||||
subgraph compare["Key Differences"]
|
||||
Diff[Difference Summary]
|
||||
end
|
||||
|
||||
left --> compare
|
||||
right --> compare
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Errors and Solutions
|
||||
|
||||
#### Error: "Parse error on line X: Expecting 'SEMI', 'NEWLINE', 'EOF'"
|
||||
|
||||
**Causes:**
|
||||
1. Subgraph name with spaces not using ID format
|
||||
2. Node reference using display text instead of ID
|
||||
3. Invalid special characters in node text
|
||||
|
||||
**Solutions:**
|
||||
- Use `subgraph id["Display Name"]` format
|
||||
- Reference nodes by ID only
|
||||
- Quote node text with special characters
|
||||
|
||||
#### Error: "Unsupported markdown: list"
|
||||
|
||||
**Cause:** Using `number. space` pattern in node text
|
||||
|
||||
**Solution:** Remove space or use alternatives (①, (1), Step 1:)
|
||||
|
||||
#### Error: "Parse error: unexpected character"
|
||||
|
||||
**Causes:**
|
||||
1. Unescaped special characters
|
||||
2. Improper quotes
|
||||
3. Invalid Mermaid syntax
|
||||
|
||||
**Solutions:**
|
||||
- Replace problematic characters (quotes → 『』, parens → 「」)
|
||||
- Use proper node definition syntax
|
||||
- Check arrow syntax
|
||||
|
||||
#### Diagram doesn't render correctly
|
||||
|
||||
**Causes:**
|
||||
1. Missing style declarations
|
||||
2. Incorrect direction specification
|
||||
3. Invalid connections
|
||||
|
||||
**Solutions:**
|
||||
- Verify all style declarations use valid syntax
|
||||
- Check direction is set in graph declaration or subgraph
|
||||
- Ensure all node IDs are defined before referencing
|
||||
|
||||
### Validation Checklist
|
||||
|
||||
Before finalizing any diagram:
|
||||
|
||||
- [ ] No `number. space` patterns in node text
|
||||
- [ ] All subgraphs use proper ID syntax if they contain spaces
|
||||
- [ ] All node references use IDs not display text
|
||||
- [ ] All arrows use valid syntax (-->, -.->)
|
||||
- [ ] All style declarations are syntactically correct
|
||||
- [ ] Direction is explicitly set
|
||||
- [ ] No unescaped special characters in node text
|
||||
- [ ] All connections reference defined nodes
|
||||
|
||||
### Platform-Specific Notes
|
||||
|
||||
**Obsidian:**
|
||||
- Older Mermaid version, more strict parsing
|
||||
- Limited support for `<br/>` (only in circle nodes)
|
||||
- Test diagrams before finalizing
|
||||
|
||||
**GitHub:**
|
||||
- Good Mermaid support
|
||||
- Renders most modern syntax
|
||||
- May differ slightly from Obsidian rendering
|
||||
|
||||
**Mermaid Live Editor:**
|
||||
- Most up-to-date parser
|
||||
- Best for testing new syntax
|
||||
- May support features not available in Obsidian/GitHub
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Safe Numbering Methods
|
||||
✅ `1.Text` `①Text` `(1)Text` `Step 1:Text`
|
||||
❌ `1. Text`
|
||||
|
||||
### Safe Subgraph Syntax
|
||||
✅ `subgraph id["Name"]` `subgraph simple_name`
|
||||
❌ `subgraph Name With Spaces`
|
||||
|
||||
### Safe Node References
|
||||
✅ `NodeID --> AnotherID`
|
||||
❌ `"Display Text" --> "Other Text"`
|
||||
|
||||
### Safe Special Characters
|
||||
✅ `『』` for quotes, `「」` for parentheses
|
||||
❌ `"` unescaped quotes, `()` in problematic contexts
|
||||
497
.claude/skills/obsidian-bases/SKILL.md
Normal file
497
.claude/skills/obsidian-bases/SKILL.md
Normal file
@@ -0,0 +1,497 @@
|
||||
---
|
||||
name: obsidian-bases
|
||||
description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
|
||||
---
|
||||
|
||||
# Obsidian Bases Skill
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Create the file**: Create a `.base` file in the vault with valid YAML content
|
||||
2. **Define scope**: Add `filters` to select which notes appear (by tag, folder, property, or date)
|
||||
3. **Add formulas** (optional): Define computed properties in the `formulas` section
|
||||
4. **Configure views**: Add one or more views (`table`, `cards`, `list`, or `map`) with `order` specifying which properties to display
|
||||
5. **Validate**: Verify the file is valid YAML with no syntax errors. Check that all referenced properties and formulas exist. Common issues: unquoted strings containing special YAML characters, mismatched quotes in formula expressions, referencing `formula.X` without defining `X` in `formulas`
|
||||
6. **Test in Obsidian**: Open the `.base` file in Obsidian to confirm the view renders correctly. If it shows a YAML error, check quoting rules below
|
||||
|
||||
## Schema
|
||||
|
||||
Base files use the `.base` extension and contain valid YAML.
|
||||
|
||||
```yaml
|
||||
# Global filters apply to ALL views in the base
|
||||
filters:
|
||||
# Can be a single filter string
|
||||
# OR a recursive filter object with and/or/not
|
||||
and: []
|
||||
or: []
|
||||
not: []
|
||||
|
||||
# Define formula properties that can be used across all views
|
||||
formulas:
|
||||
formula_name: 'expression'
|
||||
|
||||
# Configure display names and settings for properties
|
||||
properties:
|
||||
property_name:
|
||||
displayName: "Display Name"
|
||||
formula.formula_name:
|
||||
displayName: "Formula Display Name"
|
||||
file.ext:
|
||||
displayName: "Extension"
|
||||
|
||||
# Define custom summary formulas
|
||||
summaries:
|
||||
custom_summary_name: 'values.mean().round(3)'
|
||||
|
||||
# Define one or more views
|
||||
views:
|
||||
- type: table | cards | list | map
|
||||
name: "View Name"
|
||||
limit: 10 # Optional: limit results
|
||||
groupBy: # Optional: group results
|
||||
property: property_name
|
||||
direction: ASC | DESC
|
||||
filters: # View-specific filters
|
||||
and: []
|
||||
order: # Properties to display in order
|
||||
- file.name
|
||||
- property_name
|
||||
- formula.formula_name
|
||||
summaries: # Map properties to summary formulas
|
||||
property_name: Average
|
||||
```
|
||||
|
||||
## Filter Syntax
|
||||
|
||||
Filters narrow down results. They can be applied globally or per-view.
|
||||
|
||||
### Filter Structure
|
||||
|
||||
```yaml
|
||||
# Single filter
|
||||
filters: 'status == "done"'
|
||||
|
||||
# AND - all conditions must be true
|
||||
filters:
|
||||
and:
|
||||
- 'status == "done"'
|
||||
- 'priority > 3'
|
||||
|
||||
# OR - any condition can be true
|
||||
filters:
|
||||
or:
|
||||
- 'file.hasTag("book")'
|
||||
- 'file.hasTag("article")'
|
||||
|
||||
# NOT - exclude matching items
|
||||
filters:
|
||||
not:
|
||||
- 'file.hasTag("archived")'
|
||||
|
||||
# Nested filters
|
||||
filters:
|
||||
or:
|
||||
- file.hasTag("tag")
|
||||
- and:
|
||||
- file.hasTag("book")
|
||||
- file.hasLink("Textbook")
|
||||
- not:
|
||||
- file.hasTag("book")
|
||||
- file.inFolder("Required Reading")
|
||||
```
|
||||
|
||||
### Filter Operators
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `==` | equals |
|
||||
| `!=` | not equal |
|
||||
| `>` | greater than |
|
||||
| `<` | less than |
|
||||
| `>=` | greater than or equal |
|
||||
| `<=` | less than or equal |
|
||||
| `&&` | logical and |
|
||||
| `\|\|` | logical or |
|
||||
| <code>!</code> | logical not |
|
||||
|
||||
## Properties
|
||||
|
||||
### Three Types of Properties
|
||||
|
||||
1. **Note properties** - From frontmatter: `note.author` or just `author`
|
||||
2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
|
||||
3. **Formula properties** - Computed values: `formula.my_formula`
|
||||
|
||||
### File Properties Reference
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `file.name` | String | File name |
|
||||
| `file.basename` | String | File name without extension |
|
||||
| `file.path` | String | Full path to file |
|
||||
| `file.folder` | String | Parent folder path |
|
||||
| `file.ext` | String | File extension |
|
||||
| `file.size` | Number | File size in bytes |
|
||||
| `file.ctime` | Date | Created time |
|
||||
| `file.mtime` | Date | Modified time |
|
||||
| `file.tags` | List | All tags in file |
|
||||
| `file.links` | List | Internal links in file |
|
||||
| `file.backlinks` | List | Files linking to this file |
|
||||
| `file.embeds` | List | Embeds in the note |
|
||||
| `file.properties` | Object | All frontmatter properties |
|
||||
|
||||
### The `this` Keyword
|
||||
|
||||
- In main content area: refers to the base file itself
|
||||
- When embedded: refers to the embedding file
|
||||
- In sidebar: refers to the active file in main content
|
||||
|
||||
## Formula Syntax
|
||||
|
||||
Formulas compute values from properties. Defined in the `formulas` section.
|
||||
|
||||
```yaml
|
||||
formulas:
|
||||
# Simple arithmetic
|
||||
total: "price * quantity"
|
||||
|
||||
# Conditional logic
|
||||
status_icon: 'if(done, "✅", "⏳")'
|
||||
|
||||
# String formatting
|
||||
formatted_price: 'if(price, price.toFixed(2) + " dollars")'
|
||||
|
||||
# Date formatting
|
||||
created: 'file.ctime.format("YYYY-MM-DD")'
|
||||
|
||||
# Calculate days since created (use .days for Duration)
|
||||
days_old: '(now() - file.ctime).days'
|
||||
|
||||
# Calculate days until due date
|
||||
days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
|
||||
```
|
||||
|
||||
## Key Functions
|
||||
|
||||
Most commonly used functions. For the complete reference of all types (Date, String, Number, List, File, Link, Object, RegExp), see [FUNCTIONS_REFERENCE.md](references/FUNCTIONS_REFERENCE.md).
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date(string): date` | Parse string to date (`YYYY-MM-DD HH:mm:ss`) |
|
||||
| `now()` | `now(): date` | Current date and time |
|
||||
| `today()` | `today(): date` | Current date (time = 00:00:00) |
|
||||
| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
|
||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
||||
| `file()` | `file(path): file` | Get file object |
|
||||
| `link()` | `link(path, display?): Link` | Create a link |
|
||||
|
||||
### Duration Type
|
||||
|
||||
When subtracting two dates, the result is a **Duration** type (not a number).
|
||||
|
||||
**Duration Fields:** `duration.days`, `duration.hours`, `duration.minutes`, `duration.seconds`, `duration.milliseconds`
|
||||
|
||||
**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. Access a numeric field first (like `.days`), then apply number functions.
|
||||
|
||||
```yaml
|
||||
# CORRECT: Calculate days between dates
|
||||
"(date(due_date) - today()).days" # Returns number of days
|
||||
"(now() - file.ctime).days" # Days since created
|
||||
"(date(due_date) - today()).days.round(0)" # Rounded days
|
||||
|
||||
# WRONG - will cause error:
|
||||
# "((date(due) - today()) / 86400000).round(0)" # Duration doesn't support division then round
|
||||
```
|
||||
|
||||
### Date Arithmetic
|
||||
|
||||
```yaml
|
||||
# Duration units: y/year/years, M/month/months, d/day/days,
|
||||
# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
|
||||
"now() + \"1 day\"" # Tomorrow
|
||||
"today() + \"7d\"" # A week from today
|
||||
"now() - file.ctime" # Returns Duration
|
||||
"(now() - file.ctime).days" # Get days as number
|
||||
```
|
||||
|
||||
## View Types
|
||||
|
||||
### Table View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: table
|
||||
name: "My Table"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- due_date
|
||||
summaries:
|
||||
price: Sum
|
||||
count: Average
|
||||
```
|
||||
|
||||
### Cards View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: cards
|
||||
name: "Gallery"
|
||||
order:
|
||||
- file.name
|
||||
- cover_image
|
||||
- description
|
||||
```
|
||||
|
||||
### List View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: list
|
||||
name: "Simple List"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
```
|
||||
|
||||
### Map View
|
||||
|
||||
Requires latitude/longitude properties and the Maps community plugin.
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: map
|
||||
name: "Locations"
|
||||
# Map-specific settings for lat/lng properties
|
||||
```
|
||||
|
||||
## Default Summary Formulas
|
||||
|
||||
| Name | Input Type | Description |
|
||||
|------|------------|-------------|
|
||||
| `Average` | Number | Mathematical mean |
|
||||
| `Min` | Number | Smallest number |
|
||||
| `Max` | Number | Largest number |
|
||||
| `Sum` | Number | Sum of all numbers |
|
||||
| `Range` | Number | Max - Min |
|
||||
| `Median` | Number | Mathematical median |
|
||||
| `Stddev` | Number | Standard deviation |
|
||||
| `Earliest` | Date | Earliest date |
|
||||
| `Latest` | Date | Latest date |
|
||||
| `Range` | Date | Latest - Earliest |
|
||||
| `Checked` | Boolean | Count of true values |
|
||||
| `Unchecked` | Boolean | Count of false values |
|
||||
| `Empty` | Any | Count of empty values |
|
||||
| `Filled` | Any | Count of non-empty values |
|
||||
| `Unique` | Any | Count of unique values |
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Task Tracker Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.hasTag("task")
|
||||
- 'file.ext == "md"'
|
||||
|
||||
formulas:
|
||||
days_until_due: 'if(due, (date(due) - today()).days, "")'
|
||||
is_overdue: 'if(due, date(due) < today() && status != "done", false)'
|
||||
priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))'
|
||||
|
||||
properties:
|
||||
status:
|
||||
displayName: Status
|
||||
formula.days_until_due:
|
||||
displayName: "Days Until Due"
|
||||
formula.priority_label:
|
||||
displayName: Priority
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "Active Tasks"
|
||||
filters:
|
||||
and:
|
||||
- 'status != "done"'
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- formula.priority_label
|
||||
- due
|
||||
- formula.days_until_due
|
||||
groupBy:
|
||||
property: status
|
||||
direction: ASC
|
||||
summaries:
|
||||
formula.days_until_due: Average
|
||||
|
||||
- type: table
|
||||
name: "Completed"
|
||||
filters:
|
||||
and:
|
||||
- 'status == "done"'
|
||||
order:
|
||||
- file.name
|
||||
- completed_date
|
||||
```
|
||||
|
||||
### Reading List Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
or:
|
||||
- file.hasTag("book")
|
||||
- file.hasTag("article")
|
||||
|
||||
formulas:
|
||||
reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
|
||||
status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))'
|
||||
year_read: 'if(finished_date, date(finished_date).year, "")'
|
||||
|
||||
properties:
|
||||
author:
|
||||
displayName: Author
|
||||
formula.status_icon:
|
||||
displayName: ""
|
||||
formula.reading_time:
|
||||
displayName: "Est. Time"
|
||||
|
||||
views:
|
||||
- type: cards
|
||||
name: "Library"
|
||||
order:
|
||||
- cover
|
||||
- file.name
|
||||
- author
|
||||
- formula.status_icon
|
||||
filters:
|
||||
not:
|
||||
- 'status == "dropped"'
|
||||
|
||||
- type: table
|
||||
name: "Reading List"
|
||||
filters:
|
||||
and:
|
||||
- 'status == "to-read"'
|
||||
order:
|
||||
- file.name
|
||||
- author
|
||||
- pages
|
||||
- formula.reading_time
|
||||
```
|
||||
|
||||
### Daily Notes Index
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Daily Notes")
|
||||
- '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
|
||||
|
||||
formulas:
|
||||
word_estimate: '(file.size / 5).round(0)'
|
||||
day_of_week: 'date(file.basename).format("dddd")'
|
||||
|
||||
properties:
|
||||
formula.day_of_week:
|
||||
displayName: "Day"
|
||||
formula.word_estimate:
|
||||
displayName: "~Words"
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "Recent Notes"
|
||||
limit: 30
|
||||
order:
|
||||
- file.name
|
||||
- formula.day_of_week
|
||||
- formula.word_estimate
|
||||
- file.mtime
|
||||
```
|
||||
|
||||
## Embedding Bases
|
||||
|
||||
Embed in Markdown files:
|
||||
|
||||
```markdown
|
||||
![[MyBase.base]]
|
||||
|
||||
<!-- Specific view -->
|
||||
![[MyBase.base#View Name]]
|
||||
```
|
||||
|
||||
## YAML Quoting Rules
|
||||
|
||||
- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
|
||||
- Use double quotes for simple strings: `"My View Name"`
|
||||
- Escape nested quotes properly in complex expressions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### YAML Syntax Errors
|
||||
|
||||
**Unquoted special characters**: Strings containing `:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` `` must be quoted.
|
||||
|
||||
```yaml
|
||||
# WRONG - colon in unquoted string
|
||||
displayName: Status: Active
|
||||
|
||||
# CORRECT
|
||||
displayName: "Status: Active"
|
||||
```
|
||||
|
||||
**Mismatched quotes in formulas**: When a formula contains double quotes, wrap the entire formula in single quotes.
|
||||
|
||||
```yaml
|
||||
# WRONG - double quotes inside double quotes
|
||||
formulas:
|
||||
label: "if(done, "Yes", "No")"
|
||||
|
||||
# CORRECT - single quotes wrapping double quotes
|
||||
formulas:
|
||||
label: 'if(done, "Yes", "No")'
|
||||
```
|
||||
|
||||
### Common Formula Errors
|
||||
|
||||
**Duration math without field access**: Subtracting dates returns a Duration, not a number. Always access `.days`, `.hours`, etc.
|
||||
|
||||
```yaml
|
||||
# WRONG - Duration is not a number
|
||||
"(now() - file.ctime).round(0)"
|
||||
|
||||
# CORRECT - access .days first, then round
|
||||
"(now() - file.ctime).days.round(0)"
|
||||
```
|
||||
|
||||
**Missing null checks**: Properties may not exist on all notes. Use `if()` to guard.
|
||||
|
||||
```yaml
|
||||
# WRONG - crashes if due_date is empty
|
||||
"(date(due_date) - today()).days"
|
||||
|
||||
# CORRECT - guard with if()
|
||||
'if(due_date, (date(due_date) - today()).days, "")'
|
||||
```
|
||||
|
||||
**Referencing undefined formulas**: Ensure every `formula.X` in `order` or `properties` has a matching entry in `formulas`.
|
||||
|
||||
```yaml
|
||||
# This will fail silently if 'total' is not defined in formulas
|
||||
order:
|
||||
- formula.total
|
||||
|
||||
# Fix: define it
|
||||
formulas:
|
||||
total: "price * quantity"
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Bases Syntax](https://help.obsidian.md/bases/syntax)
|
||||
- [Functions](https://help.obsidian.md/bases/functions)
|
||||
- [Views](https://help.obsidian.md/bases/views)
|
||||
- [Formulas](https://help.obsidian.md/formulas)
|
||||
- [Complete Functions Reference](references/FUNCTIONS_REFERENCE.md)
|
||||
173
.claude/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md
Normal file
173
.claude/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Functions Reference
|
||||
|
||||
## Global Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
|
||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
||||
| `now()` | `now(): date` | Current date and time |
|
||||
| `today()` | `today(): date` | Current date (time = 00:00:00) |
|
||||
| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
|
||||
| `min()` | `min(n1, n2, ...): number` | Smallest number |
|
||||
| `max()` | `max(n1, n2, ...): number` | Largest number |
|
||||
| `number()` | `number(any): number` | Convert to number |
|
||||
| `link()` | `link(path, display?): Link` | Create a link |
|
||||
| `list()` | `list(element): List` | Wrap in list if not already |
|
||||
| `file()` | `file(path): file` | Get file object |
|
||||
| `image()` | `image(path): image` | Create image for rendering |
|
||||
| `icon()` | `icon(name): icon` | Lucide icon by name |
|
||||
| `html()` | `html(string): html` | Render as HTML |
|
||||
| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
|
||||
|
||||
## Any Type Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
|
||||
| `isType()` | `any.isType(type): boolean` | Check type |
|
||||
| `toString()` | `any.toString(): string` | Convert to string |
|
||||
|
||||
## Date Functions & Fields
|
||||
|
||||
**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date.date(): date` | Remove time portion |
|
||||
| `format()` | `date.format(string): string` | Format with Moment.js pattern |
|
||||
| `time()` | `date.time(): string` | Get time as string |
|
||||
| `relative()` | `date.relative(): string` | Human-readable relative time |
|
||||
| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
|
||||
|
||||
## Duration Type
|
||||
|
||||
When subtracting two dates, the result is a **Duration** type (not a number). Duration has its own properties and methods.
|
||||
|
||||
**Duration Fields:**
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `duration.days` | Number | Total days in duration |
|
||||
| `duration.hours` | Number | Total hours in duration |
|
||||
| `duration.minutes` | Number | Total minutes in duration |
|
||||
| `duration.seconds` | Number | Total seconds in duration |
|
||||
| `duration.milliseconds` | Number | Total milliseconds in duration |
|
||||
|
||||
**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. You must access a numeric field first (like `.days`), then apply number functions.
|
||||
|
||||
```yaml
|
||||
# CORRECT: Calculate days between dates
|
||||
"(date(due_date) - today()).days" # Returns number of days
|
||||
"(now() - file.ctime).days" # Days since created
|
||||
|
||||
# CORRECT: Round the numeric result if needed
|
||||
"(date(due_date) - today()).days.round(0)" # Rounded days
|
||||
"(now() - file.ctime).hours.round(0)" # Rounded hours
|
||||
|
||||
# WRONG - will cause error:
|
||||
# "((date(due) - today()) / 86400000).round(0)" # Duration doesn't support division then round
|
||||
```
|
||||
|
||||
## Date Arithmetic
|
||||
|
||||
```yaml
|
||||
# Duration units: y/year/years, M/month/months, d/day/days,
|
||||
# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
|
||||
|
||||
# Add/subtract durations
|
||||
"date + \"1M\"" # Add 1 month
|
||||
"date - \"2h\"" # Subtract 2 hours
|
||||
"now() + \"1 day\"" # Tomorrow
|
||||
"today() + \"7d\"" # A week from today
|
||||
|
||||
# Subtract dates returns Duration type
|
||||
"now() - file.ctime" # Returns Duration
|
||||
"(now() - file.ctime).days" # Get days as number
|
||||
"(now() - file.ctime).hours" # Get hours as number
|
||||
|
||||
# Complex duration arithmetic
|
||||
"now() + (duration('1d') * 2)"
|
||||
```
|
||||
|
||||
## String Functions
|
||||
|
||||
**Field:** `string.length`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `contains()` | `string.contains(value): boolean` | Check substring |
|
||||
| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
|
||||
| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
|
||||
| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
|
||||
| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
|
||||
| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
|
||||
| `lower()` | `string.lower(): string` | To lowercase |
|
||||
| `title()` | `string.title(): string` | To Title Case |
|
||||
| `trim()` | `string.trim(): string` | Remove whitespace |
|
||||
| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
|
||||
| `repeat()` | `string.repeat(count): string` | Repeat string |
|
||||
| `reverse()` | `string.reverse(): string` | Reverse string |
|
||||
| `slice()` | `string.slice(start, end?): string` | Substring |
|
||||
| `split()` | `string.split(separator, n?): list` | Split to list |
|
||||
|
||||
## Number Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `abs()` | `number.abs(): number` | Absolute value |
|
||||
| `ceil()` | `number.ceil(): number` | Round up |
|
||||
| `floor()` | `number.floor(): number` | Round down |
|
||||
| `round()` | `number.round(digits?): number` | Round to digits |
|
||||
| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
|
||||
| `isEmpty()` | `number.isEmpty(): boolean` | Not present |
|
||||
|
||||
## List Functions
|
||||
|
||||
**Field:** `list.length`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `contains()` | `list.contains(value): boolean` | Element exists |
|
||||
| `containsAll()` | `list.containsAll(...values): boolean` | All elements exist |
|
||||
| `containsAny()` | `list.containsAny(...values): boolean` | Any element exists |
|
||||
| `filter()` | `list.filter(expression): list` | Filter by condition (uses `value`, `index`) |
|
||||
| `map()` | `list.map(expression): list` | Transform elements (uses `value`, `index`) |
|
||||
| `reduce()` | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
|
||||
| `flat()` | `list.flat(): list` | Flatten nested lists |
|
||||
| `join()` | `list.join(separator): string` | Join to string |
|
||||
| `reverse()` | `list.reverse(): list` | Reverse order |
|
||||
| `slice()` | `list.slice(start, end?): list` | Sublist |
|
||||
| `sort()` | `list.sort(): list` | Sort ascending |
|
||||
| `unique()` | `list.unique(): list` | Remove duplicates |
|
||||
| `isEmpty()` | `list.isEmpty(): boolean` | No elements |
|
||||
|
||||
## File Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `asLink()` | `file.asLink(display?): Link` | Convert to link |
|
||||
| `hasLink()` | `file.hasLink(otherFile): boolean` | Has link to file |
|
||||
| `hasTag()` | `file.hasTag(...tags): boolean` | Has any of the tags |
|
||||
| `hasProperty()` | `file.hasProperty(name): boolean` | Has property |
|
||||
| `inFolder()` | `file.inFolder(folder): boolean` | In folder or subfolder |
|
||||
|
||||
## Link Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `asFile()` | `link.asFile(): file` | Get file object |
|
||||
| `linksTo()` | `link.linksTo(file): boolean` | Links to file |
|
||||
|
||||
## Object Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `isEmpty()` | `object.isEmpty(): boolean` | No properties |
|
||||
| `keys()` | `object.keys(): list` | List of keys |
|
||||
| `values()` | `object.values(): list` | List of values |
|
||||
|
||||
## Regular Expression Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `matches()` | `regexp.matches(string): boolean` | Test if matches |
|
||||
211
.claude/skills/obsidian-canvas-creator/SKILL.md
Normal file
211
.claude/skills/obsidian-canvas-creator/SKILL.md
Normal file
@@ -0,0 +1,211 @@
|
||||
---
|
||||
name: obsidian-canvas-creator
|
||||
description: Create Obsidian Canvas files from text content, supporting both MindMap and freeform layouts. Use this skill when users want to visualize content as an interactive canvas, create mind maps, or organize information spatially in Obsidian format.
|
||||
---
|
||||
|
||||
# Obsidian Canvas Creator
|
||||
|
||||
Transform text content into structured Obsidian Canvas files with support for MindMap and freeform layouts.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- User requests to create a canvas, mind map, or visual diagram from text
|
||||
- User wants to organize information spatially
|
||||
- User mentions "Obsidian Canvas" or similar visualization tools
|
||||
- Converting structured content (articles, notes, outlines) into visual format
|
||||
|
||||
## Core Workflow
|
||||
|
||||
### 1. Analyze Content
|
||||
|
||||
Read and understand the input content:
|
||||
- Identify main topics and hierarchical relationships
|
||||
- Extract key points, facts, and supporting details
|
||||
- Note any existing structure (headings, lists, sections)
|
||||
|
||||
### 2. Determine Layout Type
|
||||
|
||||
Ask user to choose or infer from context:
|
||||
|
||||
**MindMap Layout:**
|
||||
- Radial structure from center
|
||||
- Parent-child relationships
|
||||
- Clear hierarchy
|
||||
- Good for: brainstorming, topic exploration, hierarchical content
|
||||
|
||||
**Freeform Layout:**
|
||||
- Custom positioning
|
||||
- Flexible relationships
|
||||
- Multiple connection types
|
||||
- Good for: complex networks, non-hierarchical content, custom arrangements
|
||||
|
||||
### 3. Plan Structure
|
||||
|
||||
**For MindMap:**
|
||||
- Identify central concept (root node)
|
||||
- Map primary branches (main topics)
|
||||
- Organize secondary branches (subtopics)
|
||||
- Position leaf nodes (details)
|
||||
|
||||
**For Freeform:**
|
||||
- Group related concepts
|
||||
- Identify connection patterns
|
||||
- Plan spatial zones
|
||||
- Consider visual flow
|
||||
|
||||
### 4. Generate Canvas
|
||||
|
||||
Create JSON following the Canvas specification:
|
||||
|
||||
**Node Creation:**
|
||||
- Assign unique 8-12 character hex IDs
|
||||
- Set appropriate dimensions based on content length
|
||||
- Apply consistent color schemes
|
||||
- Ensure no coordinate overlaps
|
||||
|
||||
**Edge Creation:**
|
||||
- Connect parent-child relationships
|
||||
- Use appropriate arrow styles
|
||||
- Add labels for complex relationships
|
||||
- Choose line styles (straight for hierarchy, curved for cross-references)
|
||||
|
||||
**Grouping (Optional):**
|
||||
- Create visual containers for related nodes
|
||||
- Use subtle background colors
|
||||
- Add descriptive labels
|
||||
|
||||
### 5. Apply Layout Algorithm
|
||||
|
||||
**MindMap Layout Calculations:**
|
||||
|
||||
Refer to `references/layout-algorithms.md` for detailed algorithms. Key principles:
|
||||
|
||||
- Center root at (0, 0)
|
||||
- Distribute primary nodes radially
|
||||
- Space secondary nodes based on sibling count
|
||||
- Maintain minimum spacing: 320px horizontal, 200px vertical
|
||||
|
||||
**Freeform Layout Principles:**
|
||||
|
||||
- Start with logical groupings
|
||||
- Position groups with clear separation
|
||||
- Connect across groups with curved edges
|
||||
- Balance visual weight across canvas
|
||||
|
||||
### 6. Validate and Output
|
||||
|
||||
Before outputting:
|
||||
|
||||
**Validation Checklist:**
|
||||
- All nodes have unique IDs
|
||||
- No coordinate overlaps (check distance > node dimensions + spacing)
|
||||
- All edges reference valid node IDs
|
||||
- Groups (if any) have labels
|
||||
- Colors use consistent format (hex or preset numbers)
|
||||
- JSON is properly escaped (Chinese quotes: 『』 for double, 「」 for single)
|
||||
|
||||
**Output Format:**
|
||||
- Complete, valid JSON Canvas file
|
||||
- No additional explanation text
|
||||
- Directly importable into Obsidian
|
||||
|
||||
## Node Sizing Guidelines
|
||||
|
||||
**Text Length-Based Sizing:**
|
||||
- Short text (<30 chars): 220 × 100 px
|
||||
- Medium text (30-60 chars): 260 × 120 px
|
||||
- Long text (60-100 chars): 320 × 140 px
|
||||
- Very long text (>100 chars): 320 × 180 px
|
||||
|
||||
## Color Schemes
|
||||
|
||||
**Preset Colors (Recommended):**
|
||||
- `"1"` - Red (warnings, important)
|
||||
- `"2"` - Orange (action items)
|
||||
- `"3"` - Yellow (questions, notes)
|
||||
- `"4"` - Green (positive, completed)
|
||||
- `"5"` - Cyan (information, details)
|
||||
- `"6"` - Purple (concepts, abstract)
|
||||
|
||||
**Custom Hex Colors:**
|
||||
Use for brand consistency or specific themes. Always use uppercase format: `"#4A90E2"`
|
||||
|
||||
## Critical Rules
|
||||
|
||||
1. **Quote Handling:**
|
||||
- Chinese double quotes → 『』
|
||||
- Chinese single quotes → 「」
|
||||
- English double quotes → `\"`
|
||||
|
||||
2. **ID Generation:**
|
||||
- 8-12 character random hex strings
|
||||
- Must be unique across all nodes and edges
|
||||
|
||||
3. **Z-Index Order:**
|
||||
- Output groups first (bottom layer)
|
||||
- Then subgroups
|
||||
- Finally text/link nodes (top layer)
|
||||
|
||||
4. **Spacing Requirements:**
|
||||
- Minimum horizontal: 320px between node centers
|
||||
- Minimum vertical: 200px between node centers
|
||||
- Account for node dimensions when calculating
|
||||
|
||||
5. **JSON Structure:**
|
||||
- Top level contains only `nodes` and `edges` arrays
|
||||
- No extra wrapping objects
|
||||
- No comments in output
|
||||
|
||||
6. **No Emoji:**
|
||||
- Do not use any Emoji symbols in node text
|
||||
- Use color coding or text labels for visual distinction instead
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple MindMap Request
|
||||
User: "Create a mind map about solar system planets"
|
||||
|
||||
Process:
|
||||
1. Identify center: "Solar System"
|
||||
2. Primary branches: Inner Planets, Outer Planets, Dwarf Planets
|
||||
3. Secondary nodes: Individual planets with key facts
|
||||
4. Apply radial layout
|
||||
5. Generate JSON with proper spacing
|
||||
|
||||
### Freeform Content Request
|
||||
User: "Turn this article into a canvas" + [article text]
|
||||
|
||||
Process:
|
||||
1. Extract article structure (intro, body sections, conclusion)
|
||||
2. Identify key concepts and relationships
|
||||
3. Group related sections spatially
|
||||
4. Connect with labeled edges
|
||||
5. Apply freeform layout with clear zones
|
||||
|
||||
## Reference Documents
|
||||
|
||||
- **Canvas Specification**: `references/canvas-spec.md` - Complete JSON Canvas format specification
|
||||
- **Layout Algorithms**: `references/layout-algorithms.md` - Detailed positioning algorithms for both layout types
|
||||
|
||||
Load these references when:
|
||||
- Need specification details for edge cases
|
||||
- Implementing complex layout calculations
|
||||
- Troubleshooting validation errors
|
||||
|
||||
## Tips for Quality Canvases
|
||||
|
||||
1. **Keep text concise**: Each node should be scannable (<2 lines preferred)
|
||||
2. **Use hierarchy**: Group by importance and relationship
|
||||
3. **Balance the canvas**: Distribute nodes to avoid clustering
|
||||
4. **Strategic colors**: Use colors to encode meaning, not just decoration
|
||||
5. **Meaningful connections**: Only add edges that clarify relationships
|
||||
6. **Test in Obsidian**: Verify the output opens correctly
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
- Overlapping nodes (always check distances)
|
||||
- Inconsistent quote escaping (breaks JSON parsing)
|
||||
- Missing group labels (causes sidebar navigation issues)
|
||||
- Too much text in nodes (use file nodes for long content)
|
||||
- Duplicate IDs (each must be unique)
|
||||
- Unconnected nodes (unless intentional islands)
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "group01",
|
||||
"type": "group",
|
||||
"x": -50,
|
||||
"y": -50,
|
||||
"width": 600,
|
||||
"height": 400,
|
||||
"label": "Group 1 - Core Concepts",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "group02",
|
||||
"type": "group",
|
||||
"x": 650,
|
||||
"y": -50,
|
||||
"width": 600,
|
||||
"height": 400,
|
||||
"label": "Group 2 - Applications",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "node01",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Concept A",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "node02",
|
||||
"type": "text",
|
||||
"x": 290,
|
||||
"y": 0,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Concept B",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "node03",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 150,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Concept C",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "node04",
|
||||
"type": "text",
|
||||
"x": 290,
|
||||
"y": 150,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Concept D",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "node05",
|
||||
"type": "text",
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Application 1",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "node06",
|
||||
"type": "text",
|
||||
"x": 990,
|
||||
"y": 0,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Application 2",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "node07",
|
||||
"type": "text",
|
||||
"x": 700,
|
||||
"y": 150,
|
||||
"width": 240,
|
||||
"height": 100,
|
||||
"text": "Application 3",
|
||||
"color": "5"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e1",
|
||||
"fromNode": "node01",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "node03",
|
||||
"toSide": "top",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e2",
|
||||
"fromNode": "node02",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "node04",
|
||||
"toSide": "top",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e3",
|
||||
"fromNode": "node01",
|
||||
"fromSide": "right",
|
||||
"toNode": "node05",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"label": "leads to",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "e4",
|
||||
"fromNode": "node02",
|
||||
"fromSide": "right",
|
||||
"toNode": "node06",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"label": "enables",
|
||||
"color": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "root001",
|
||||
"type": "text",
|
||||
"x": -150,
|
||||
"y": -60,
|
||||
"width": 300,
|
||||
"height": 120,
|
||||
"text": "# Central Topic\n\nMain concept goes here",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "branch01",
|
||||
"type": "text",
|
||||
"x": 250,
|
||||
"y": -200,
|
||||
"width": 220,
|
||||
"height": 100,
|
||||
"text": "Branch 1\n\nFirst main idea",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "branch02",
|
||||
"type": "text",
|
||||
"x": 250,
|
||||
"y": -50,
|
||||
"width": 220,
|
||||
"height": 100,
|
||||
"text": "Branch 2\n\nSecond main idea",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "branch03",
|
||||
"type": "text",
|
||||
"x": 250,
|
||||
"y": 100,
|
||||
"width": 220,
|
||||
"height": 100,
|
||||
"text": "Branch 3\n\nThird main idea",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "detail01",
|
||||
"type": "text",
|
||||
"x": 550,
|
||||
"y": -200,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"text": "Detail A",
|
||||
"color": "6"
|
||||
},
|
||||
{
|
||||
"id": "detail02",
|
||||
"type": "text",
|
||||
"x": 550,
|
||||
"y": -100,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"text": "Detail B",
|
||||
"color": "6"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e1",
|
||||
"fromNode": "root001",
|
||||
"fromSide": "right",
|
||||
"toNode": "branch01",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e2",
|
||||
"fromNode": "root001",
|
||||
"fromSide": "right",
|
||||
"toNode": "branch02",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e3",
|
||||
"fromNode": "root001",
|
||||
"fromSide": "right",
|
||||
"toNode": "branch03",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e4",
|
||||
"fromNode": "branch01",
|
||||
"fromSide": "right",
|
||||
"toNode": "detail01",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e5",
|
||||
"fromNode": "branch01",
|
||||
"fromSide": "right",
|
||||
"toNode": "detail02",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
}
|
||||
]
|
||||
}
|
||||
403
.claude/skills/obsidian-canvas-creator/references/canvas-spec.md
Normal file
403
.claude/skills/obsidian-canvas-creator/references/canvas-spec.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# JSON Canvas Specification for Obsidian
|
||||
|
||||
Version 1.0 — 2024-03-11
|
||||
|
||||
## Overview
|
||||
|
||||
JSON Canvas is a format for representing infinite canvas documents. This specification defines the structure for creating canvas files compatible with Obsidian.
|
||||
|
||||
## Top Level Structure
|
||||
|
||||
The root JSON object contains two optional arrays:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [...],
|
||||
"edges": [...]
|
||||
}
|
||||
```
|
||||
|
||||
- `nodes` (optional, array): All canvas objects (text, files, links, groups)
|
||||
- `edges` (optional, array): All connections between nodes
|
||||
|
||||
## Node Types
|
||||
|
||||
### Common Attributes
|
||||
|
||||
All nodes share these required attributes:
|
||||
|
||||
- `id` (required, string): Unique identifier for the node
|
||||
- `type` (required, string): Node type (`text`, `file`, `link`, `group`)
|
||||
- `x` (required, integer): X position in pixels
|
||||
- `y` (required, integer): Y position in pixels
|
||||
- `width` (required, integer): Width in pixels
|
||||
- `height` (required, integer): Height in pixels
|
||||
- `color` (optional, string/number): Color (hex `"#FF0000"` or preset `"1"`)
|
||||
|
||||
### Text Nodes
|
||||
|
||||
Store plain text with Markdown formatting.
|
||||
|
||||
**Required Attributes:**
|
||||
- `text` (string): Content in Markdown syntax
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "abc123",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "# Main Topic\n\nKey point here",
|
||||
"color": "4"
|
||||
}
|
||||
```
|
||||
|
||||
### File Nodes
|
||||
|
||||
Reference other files or attachments (images, PDFs, etc.).
|
||||
|
||||
**Required Attributes:**
|
||||
- `file` (string): Path to file in the vault
|
||||
|
||||
**Optional Attributes:**
|
||||
- `subpath` (string): Link to specific heading/block (starts with `#`)
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "def456",
|
||||
"type": "file",
|
||||
"x": 300,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Images/diagram.png"
|
||||
}
|
||||
```
|
||||
|
||||
**With Subpath:**
|
||||
```json
|
||||
{
|
||||
"id": "ghi789",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"file": "Notes/Meeting Notes.md",
|
||||
"subpath": "#Action Items"
|
||||
}
|
||||
```
|
||||
|
||||
### Link Nodes
|
||||
|
||||
Reference external URLs.
|
||||
|
||||
**Required Attributes:**
|
||||
- `url` (string): Full URL including protocol
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "jkl012",
|
||||
"type": "link",
|
||||
"x": 0,
|
||||
"y": -200,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"url": "https://obsidian.md",
|
||||
"color": "5"
|
||||
}
|
||||
```
|
||||
|
||||
### Group Nodes
|
||||
|
||||
Visual containers for organizing related nodes.
|
||||
|
||||
**Optional Attributes:**
|
||||
- `label` (string): Text label for the group (recommended)
|
||||
- `background` (string): Path to background image
|
||||
- `backgroundStyle` (string): Image rendering style
|
||||
- `cover`: Fill entire node
|
||||
- `ratio`: Maintain aspect ratio
|
||||
- `repeat`: Tile as pattern
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"id": "group1",
|
||||
"type": "group",
|
||||
"x": -50,
|
||||
"y": -50,
|
||||
"width": 600,
|
||||
"height": 400,
|
||||
"label": "Main Concepts",
|
||||
"color": "4"
|
||||
}
|
||||
```
|
||||
|
||||
**With Background:**
|
||||
```json
|
||||
{
|
||||
"id": "group2",
|
||||
"type": "group",
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 500,
|
||||
"height": 600,
|
||||
"label": "Reference Materials",
|
||||
"background": "Images/texture.png",
|
||||
"backgroundStyle": "repeat"
|
||||
}
|
||||
```
|
||||
|
||||
## Z-Index and Layering
|
||||
|
||||
Nodes are displayed in array order:
|
||||
- **First node**: Bottom layer (rendered below others)
|
||||
- **Last node**: Top layer (rendered above others)
|
||||
|
||||
**Best Practice Order:**
|
||||
1. Group nodes (backgrounds)
|
||||
2. Sub-groups
|
||||
3. Regular nodes (text, file, link)
|
||||
|
||||
This ensures groups appear behind content.
|
||||
|
||||
## Edges (Connections)
|
||||
|
||||
Edges connect nodes with lines.
|
||||
|
||||
**Required Attributes:**
|
||||
- `id` (required, string): Unique identifier
|
||||
- `fromNode` (required, string): Starting node ID
|
||||
- `toNode` (required, string): Ending node ID
|
||||
|
||||
**Optional Attributes:**
|
||||
- `fromSide` (string): Starting edge side
|
||||
- Values: `top`, `right`, `bottom`, `left`
|
||||
- `fromEnd` (string): Start endpoint shape
|
||||
- Values: `none` (default), `arrow`
|
||||
- `toSide` (string): Ending edge side
|
||||
- Values: `top`, `right`, `bottom`, `left`
|
||||
- `toEnd` (string): End endpoint shape
|
||||
- Values: `arrow` (default), `none`
|
||||
- `color` (string/number): Edge color
|
||||
- `label` (string): Text label on edge
|
||||
|
||||
**Example - Simple Connection:**
|
||||
```json
|
||||
{
|
||||
"id": "edge1",
|
||||
"fromNode": "abc123",
|
||||
"toNode": "def456"
|
||||
}
|
||||
```
|
||||
|
||||
**Example - Fully Specified:**
|
||||
```json
|
||||
{
|
||||
"id": "edge2",
|
||||
"fromNode": "def456",
|
||||
"fromSide": "bottom",
|
||||
"fromEnd": "none",
|
||||
"toNode": "ghi789",
|
||||
"toSide": "top",
|
||||
"toEnd": "arrow",
|
||||
"color": "3",
|
||||
"label": "leads to"
|
||||
}
|
||||
```
|
||||
|
||||
## Color System
|
||||
|
||||
### Preset Colors
|
||||
|
||||
Use string numbers `"1"` through `"6"`:
|
||||
|
||||
- `"1"` - Red
|
||||
- `"2"` - Orange
|
||||
- `"3"` - Yellow
|
||||
- `"4"` - Green
|
||||
- `"5"` - Cyan
|
||||
- `"6"` - Purple
|
||||
|
||||
**Note:** Exact colors adapt to Obsidian's theme. These provide semantic meaning across light/dark modes.
|
||||
|
||||
### Custom Hex Colors
|
||||
|
||||
Use hex format: `"#RRGGBB"`
|
||||
|
||||
**Examples:**
|
||||
- `"#4A90E2"` (blue)
|
||||
- `"#50E3C2"` (teal)
|
||||
- `"#F5A623"` (orange)
|
||||
|
||||
**Best Practice:** Use consistent format within a canvas (all hex OR all presets).
|
||||
|
||||
## Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "group001",
|
||||
"type": "group",
|
||||
"x": -50,
|
||||
"y": -50,
|
||||
"width": 700,
|
||||
"height": 500,
|
||||
"label": "Core Concepts",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "center01",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 120,
|
||||
"text": "# Central Topic\n\nMain idea here",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "branch01",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": -100,
|
||||
"width": 220,
|
||||
"height": 100,
|
||||
"text": "Subtopic A",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "branch02",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 100,
|
||||
"width": 220,
|
||||
"height": 100,
|
||||
"text": "Subtopic B",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "detail01",
|
||||
"type": "text",
|
||||
"x": 700,
|
||||
"y": -100,
|
||||
"width": 200,
|
||||
"height": 80,
|
||||
"text": "Detail 1",
|
||||
"color": "6"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e1",
|
||||
"fromNode": "center01",
|
||||
"fromSide": "right",
|
||||
"toNode": "branch01",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e2",
|
||||
"fromNode": "center01",
|
||||
"fromSide": "right",
|
||||
"toNode": "branch02",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "e3",
|
||||
"fromNode": "branch01",
|
||||
"fromSide": "right",
|
||||
"toNode": "detail01",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Requirements
|
||||
|
||||
When creating canvas files, ensure:
|
||||
|
||||
1. **Unique IDs**: All `id` values must be unique across nodes and edges
|
||||
2. **Valid References**: All edge `fromNode` and `toNode` must reference existing node IDs
|
||||
3. **Required Fields**: All required attributes are present for each type
|
||||
4. **Valid Coordinates**: All position/dimension values are integers
|
||||
5. **Color Format**: Colors use either hex (`"#RRGGBB"`) or preset strings (`"1"` to `"6"`)
|
||||
6. **Quote Escaping**: Special characters properly escaped in JSON strings
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue: Canvas won't open in Obsidian
|
||||
**Solutions:**
|
||||
- Validate JSON syntax (use JSON validator)
|
||||
- Check all IDs are unique
|
||||
- Verify all edge references exist
|
||||
- Ensure required fields present
|
||||
|
||||
### Issue: Nodes appear overlapped
|
||||
**Solutions:**
|
||||
- Increase spacing between coordinates
|
||||
- Account for node dimensions in positioning
|
||||
- Use minimum spacing: 320px horizontal, 200px vertical
|
||||
|
||||
### Issue: Groups don't show properly
|
||||
**Solutions:**
|
||||
- Ensure groups appear before content nodes in array
|
||||
- Add explicit `label` to all groups
|
||||
- Check group dimensions encompass child nodes
|
||||
|
||||
### Issue: Colors don't match expectations
|
||||
**Solutions:**
|
||||
- Use consistent color format (all hex OR all presets)
|
||||
- Remember presets adapt to theme
|
||||
- Test in both light and dark mode if using custom colors
|
||||
|
||||
### Issue: Text appears truncated
|
||||
**Solutions:**
|
||||
- Increase node dimensions
|
||||
- Break long text into multiple nodes
|
||||
- Use file nodes for lengthy content
|
||||
|
||||
## Character Encoding for Chinese Content
|
||||
|
||||
When canvas contains Chinese text, apply these transformations:
|
||||
|
||||
- Chinese double quotes `"` → `『』`
|
||||
- Chinese single quotes `'` → `「」`
|
||||
- English double quotes must be escaped: `\"`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"text": "『核心概念』包含:「子概念A」和「子概念B」"
|
||||
}
|
||||
```
|
||||
|
||||
This prevents JSON parsing errors with mixed-language content.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Large Canvases**: Keep node count reasonable (<500 for smooth performance)
|
||||
- **Image Files**: Use compressed images for backgrounds
|
||||
- **Text Length**: Keep node text concise; use file nodes for long content
|
||||
- **Edge Complexity**: Minimize crossing edges for clarity
|
||||
|
||||
## Future Extensions
|
||||
|
||||
This specification may be extended with:
|
||||
- Additional node types
|
||||
- More edge styling options
|
||||
- Animation properties
|
||||
- Interactive behaviors
|
||||
|
||||
Always check Obsidian documentation for latest Canvas features.
|
||||
@@ -0,0 +1,614 @@
|
||||
# Layout Algorithms for Obsidian Canvas
|
||||
|
||||
Detailed algorithms for positioning nodes in MindMap and Freeform layouts.
|
||||
|
||||
## Layout Principles
|
||||
|
||||
### Universal Spacing Constants
|
||||
|
||||
```
|
||||
HORIZONTAL_SPACING = 320 // Minimum horizontal space between node centers
|
||||
VERTICAL_SPACING = 200 // Minimum vertical space between node centers
|
||||
NODE_PADDING = 20 // Internal padding within nodes
|
||||
```
|
||||
|
||||
### Collision Detection
|
||||
|
||||
Before finalizing any node position, verify:
|
||||
|
||||
```python
|
||||
def check_collision(node1, node2):
|
||||
"""Returns True if nodes overlap or are too close"""
|
||||
center1_x = node1.x + node1.width / 2
|
||||
center1_y = node1.y + node1.height / 2
|
||||
center2_x = node2.x + node2.width / 2
|
||||
center2_y = node2.y + node2.height / 2
|
||||
|
||||
dx = abs(center1_x - center2_x)
|
||||
dy = abs(center1_y - center2_y)
|
||||
|
||||
min_dx = (node1.width + node2.width) / 2 + HORIZONTAL_SPACING
|
||||
min_dy = (node1.height + node2.height) / 2 + VERTICAL_SPACING
|
||||
|
||||
return dx < min_dx or dy < min_dy
|
||||
```
|
||||
|
||||
## MindMap Layout Algorithm
|
||||
|
||||
### 1. Radial Tree Layout
|
||||
|
||||
Place root at center, arrange children radially.
|
||||
|
||||
#### Step 1: Position Root Node
|
||||
|
||||
```python
|
||||
root = {
|
||||
"x": 0 - (root_width / 2), # Center horizontally
|
||||
"y": 0 - (root_height / 2), # Center vertically
|
||||
"width": root_width,
|
||||
"height": root_height
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2: Calculate Primary Branch Positions
|
||||
|
||||
Distribute first-level children around root:
|
||||
|
||||
```python
|
||||
def position_primary_branches(root, children, radius=400):
|
||||
"""Position first-level children in a circle around root"""
|
||||
n = len(children)
|
||||
angle_step = 2 * pi / n
|
||||
|
||||
positions = []
|
||||
for i, child in enumerate(children):
|
||||
angle = i * angle_step
|
||||
|
||||
# Calculate position on circle
|
||||
x = root.center_x + radius * cos(angle) - child.width / 2
|
||||
y = root.center_y + radius * sin(angle) - child.height / 2
|
||||
|
||||
positions.append({"x": x, "y": y})
|
||||
|
||||
return positions
|
||||
```
|
||||
|
||||
**Radius Selection:**
|
||||
- Small canvases (≤10 children): 400px
|
||||
- Medium canvases (11-20 children): 500px
|
||||
- Large canvases (>20 children): 600px
|
||||
|
||||
#### Step 3: Position Secondary Branches
|
||||
|
||||
For each primary branch, arrange its children:
|
||||
|
||||
**Horizontal Layout** (preferred for most cases):
|
||||
|
||||
```python
|
||||
def position_secondary_horizontal(parent, children, distance=350):
|
||||
"""Arrange children horizontally to the right of parent"""
|
||||
n = len(children)
|
||||
total_height = sum(child.height for child in children)
|
||||
total_spacing = (n - 1) * VERTICAL_SPACING
|
||||
|
||||
# Start position (top of vertical arrangement)
|
||||
start_y = parent.center_y - (total_height + total_spacing) / 2
|
||||
|
||||
positions = []
|
||||
current_y = start_y
|
||||
|
||||
for child in children:
|
||||
x = parent.x + parent.width + distance
|
||||
y = current_y
|
||||
|
||||
positions.append({"x": x, "y": y})
|
||||
current_y += child.height + VERTICAL_SPACING
|
||||
|
||||
return positions
|
||||
```
|
||||
|
||||
**Vertical Layout** (for left/right primary branches):
|
||||
|
||||
```python
|
||||
def position_secondary_vertical(parent, children, distance=250):
|
||||
"""Arrange children vertically below parent"""
|
||||
n = len(children)
|
||||
total_width = sum(child.width for child in children)
|
||||
total_spacing = (n - 1) * HORIZONTAL_SPACING
|
||||
|
||||
# Start position (left of horizontal arrangement)
|
||||
start_x = parent.center_x - (total_width + total_spacing) / 2
|
||||
|
||||
positions = []
|
||||
current_x = start_x
|
||||
|
||||
for child in children:
|
||||
x = current_x
|
||||
y = parent.y + parent.height + distance
|
||||
|
||||
positions.append({"x": x, "y": y})
|
||||
current_x += child.width + HORIZONTAL_SPACING
|
||||
|
||||
return positions
|
||||
```
|
||||
|
||||
#### Step 4: Balance and Adjust
|
||||
|
||||
After initial placement, check for collisions and adjust:
|
||||
|
||||
```python
|
||||
def balance_layout(nodes):
|
||||
"""Adjust nodes to prevent overlaps"""
|
||||
max_iterations = 10
|
||||
|
||||
for iteration in range(max_iterations):
|
||||
collisions = find_all_collisions(nodes)
|
||||
if not collisions:
|
||||
break
|
||||
|
||||
for node1, node2 in collisions:
|
||||
# Move node2 away from node1
|
||||
dx = node2.center_x - node1.center_x
|
||||
dy = node2.center_y - node1.center_y
|
||||
distance = sqrt(dx*dx + dy*dy)
|
||||
|
||||
# Calculate required distance
|
||||
min_dist = calculate_min_distance(node1, node2)
|
||||
|
||||
if distance > 0:
|
||||
# Move proportionally
|
||||
move_x = (dx / distance) * (min_dist - distance) / 2
|
||||
move_y = (dy / distance) * (min_dist - distance) / 2
|
||||
|
||||
node2.x += move_x
|
||||
node2.y += move_y
|
||||
```
|
||||
|
||||
### 2. Tree Layout (Hierarchical Top-Down)
|
||||
|
||||
Alternative for deep hierarchies.
|
||||
|
||||
#### Positioning Formula
|
||||
|
||||
```python
|
||||
def position_tree_layout(root, tree):
|
||||
"""Top-down tree layout"""
|
||||
# Level 0 (root)
|
||||
root.x = 0 - root.width / 2
|
||||
root.y = 0 - root.height / 2
|
||||
|
||||
# Process each level
|
||||
for level in range(1, max_depth):
|
||||
nodes_at_level = get_nodes_at_level(tree, level)
|
||||
|
||||
# Calculate horizontal spacing
|
||||
total_width = sum(node.width for node in nodes_at_level)
|
||||
total_spacing = (len(nodes_at_level) - 1) * HORIZONTAL_SPACING
|
||||
|
||||
start_x = -(total_width + total_spacing) / 2
|
||||
y = level * (150 + VERTICAL_SPACING) # 150px level height
|
||||
|
||||
current_x = start_x
|
||||
for node in nodes_at_level:
|
||||
node.x = current_x
|
||||
node.y = y
|
||||
current_x += node.width + HORIZONTAL_SPACING
|
||||
```
|
||||
|
||||
## Freeform Layout Algorithm
|
||||
|
||||
### 1. Content-Based Grouping
|
||||
|
||||
First, identify natural groupings in content:
|
||||
|
||||
```python
|
||||
def identify_groups(nodes, content_structure):
|
||||
"""Group nodes by semantic relationships"""
|
||||
groups = []
|
||||
|
||||
# Analyze content structure
|
||||
for section in content_structure:
|
||||
group_nodes = [node for node in nodes if node.section == section]
|
||||
|
||||
if len(group_nodes) > 1:
|
||||
groups.append({
|
||||
"label": section.title,
|
||||
"nodes": group_nodes
|
||||
})
|
||||
|
||||
return groups
|
||||
```
|
||||
|
||||
### 2. Grid-Based Zone Layout
|
||||
|
||||
Divide canvas into zones for different groups:
|
||||
|
||||
```python
|
||||
def layout_zones(groups, canvas_width=2000, canvas_height=1500):
|
||||
"""Arrange groups in grid zones"""
|
||||
n_groups = len(groups)
|
||||
|
||||
# Calculate grid dimensions
|
||||
cols = ceil(sqrt(n_groups))
|
||||
rows = ceil(n_groups / cols)
|
||||
|
||||
zone_width = canvas_width / cols
|
||||
zone_height = canvas_height / rows
|
||||
|
||||
# Assign zones
|
||||
zones = []
|
||||
for i, group in enumerate(groups):
|
||||
col = i % cols
|
||||
row = i // cols
|
||||
|
||||
zone = {
|
||||
"x": col * zone_width - canvas_width / 2,
|
||||
"y": row * zone_height - canvas_height / 2,
|
||||
"width": zone_width * 0.9, # Leave 10% margin
|
||||
"height": zone_height * 0.9,
|
||||
"group": group
|
||||
}
|
||||
zones.append(zone)
|
||||
|
||||
return zones
|
||||
```
|
||||
|
||||
### 3. Within-Zone Node Positioning
|
||||
|
||||
Position nodes within each zone:
|
||||
|
||||
**Option A: Organic Flow**
|
||||
|
||||
```python
|
||||
def position_organic(zone, nodes):
|
||||
"""Organic, flowing arrangement within zone"""
|
||||
positions = []
|
||||
|
||||
# Start at zone top-left with margin
|
||||
current_x = zone.x + 50
|
||||
current_y = zone.y + 50
|
||||
row_height = 0
|
||||
|
||||
for node in nodes:
|
||||
# Check if node fits in current row
|
||||
if current_x + node.width > zone.x + zone.width - 50:
|
||||
# Move to next row
|
||||
current_x = zone.x + 50
|
||||
current_y += row_height + VERTICAL_SPACING
|
||||
row_height = 0
|
||||
|
||||
positions.append({
|
||||
"x": current_x,
|
||||
"y": current_y
|
||||
})
|
||||
|
||||
current_x += node.width + HORIZONTAL_SPACING
|
||||
row_height = max(row_height, node.height)
|
||||
|
||||
return positions
|
||||
```
|
||||
|
||||
**Option B: Structured Grid**
|
||||
|
||||
```python
|
||||
def position_grid(zone, nodes):
|
||||
"""Grid arrangement within zone"""
|
||||
n = len(nodes)
|
||||
cols = ceil(sqrt(n))
|
||||
rows = ceil(n / cols)
|
||||
|
||||
cell_width = (zone.width - 100) / cols # 50px margin each side
|
||||
cell_height = (zone.height - 100) / rows
|
||||
|
||||
positions = []
|
||||
for i, node in enumerate(nodes):
|
||||
col = i % cols
|
||||
row = i // cols
|
||||
|
||||
# Center node in cell
|
||||
x = zone.x + 50 + col * cell_width + (cell_width - node.width) / 2
|
||||
y = zone.y + 50 + row * cell_height + (cell_height - node.height) / 2
|
||||
|
||||
positions.append({"x": x, "y": y})
|
||||
|
||||
return positions
|
||||
```
|
||||
|
||||
### 4. Cross-Zone Connections
|
||||
|
||||
Calculate optimal edge paths between zones:
|
||||
|
||||
```python
|
||||
def calculate_edge_path(from_node, to_node):
|
||||
"""Determine edge connection points"""
|
||||
# Calculate centers
|
||||
from_center = (from_node.x + from_node.width/2,
|
||||
from_node.y + from_node.height/2)
|
||||
to_center = (to_node.x + to_node.width/2,
|
||||
to_node.y + to_node.height/2)
|
||||
|
||||
# Determine best sides to connect
|
||||
dx = to_center[0] - from_center[0]
|
||||
dy = to_center[1] - from_center[1]
|
||||
|
||||
# Choose sides based on direction
|
||||
if abs(dx) > abs(dy):
|
||||
# Horizontal connection
|
||||
from_side = "right" if dx > 0 else "left"
|
||||
to_side = "left" if dx > 0 else "right"
|
||||
else:
|
||||
# Vertical connection
|
||||
from_side = "bottom" if dy > 0 else "top"
|
||||
to_side = "top" if dy > 0 else "bottom"
|
||||
|
||||
return {
|
||||
"fromSide": from_side,
|
||||
"toSide": to_side
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Force-Directed Layout
|
||||
|
||||
For complex networks with many cross-connections:
|
||||
|
||||
```python
|
||||
def force_directed_layout(nodes, edges, iterations=100):
|
||||
"""Spring-based layout algorithm"""
|
||||
# Constants
|
||||
SPRING_LENGTH = 200
|
||||
SPRING_CONSTANT = 0.1
|
||||
REPULSION_CONSTANT = 5000
|
||||
|
||||
for iteration in range(iterations):
|
||||
# Calculate repulsive forces (all pairs)
|
||||
for node1 in nodes:
|
||||
force_x, force_y = 0, 0
|
||||
|
||||
for node2 in nodes:
|
||||
if node1 == node2:
|
||||
continue
|
||||
|
||||
dx = node1.x - node2.x
|
||||
dy = node1.y - node2.y
|
||||
distance = sqrt(dx*dx + dy*dy)
|
||||
|
||||
if distance > 0:
|
||||
# Repulsive force
|
||||
force = REPULSION_CONSTANT / (distance * distance)
|
||||
force_x += (dx / distance) * force
|
||||
force_y += (dy / distance) * force
|
||||
|
||||
node1.force_x = force_x
|
||||
node1.force_y = force_y
|
||||
|
||||
# Calculate attractive forces (connected nodes)
|
||||
for edge in edges:
|
||||
node1 = get_node(edge.fromNode)
|
||||
node2 = get_node(edge.toNode)
|
||||
|
||||
dx = node2.x - node1.x
|
||||
dy = node2.y - node1.y
|
||||
distance = sqrt(dx*dx + dy*dy)
|
||||
|
||||
# Spring force
|
||||
force = SPRING_CONSTANT * (distance - SPRING_LENGTH)
|
||||
|
||||
node1.force_x += (dx / distance) * force
|
||||
node1.force_y += (dy / distance) * force
|
||||
node2.force_x -= (dx / distance) * force
|
||||
node2.force_y -= (dy / distance) * force
|
||||
|
||||
# Apply forces
|
||||
for node in nodes:
|
||||
node.x += node.force_x
|
||||
node.y += node.force_y
|
||||
```
|
||||
|
||||
### Hierarchical Clustering
|
||||
|
||||
Group related nodes automatically:
|
||||
|
||||
```python
|
||||
def hierarchical_cluster(nodes, similarity_threshold=0.7):
|
||||
"""Cluster nodes by content similarity"""
|
||||
clusters = []
|
||||
|
||||
# Calculate similarity matrix
|
||||
similarity = calculate_similarity_matrix(nodes)
|
||||
|
||||
# Agglomerative clustering
|
||||
current_clusters = [[node] for node in nodes]
|
||||
|
||||
while len(current_clusters) > 1:
|
||||
# Find most similar clusters
|
||||
max_sim = 0
|
||||
merge_i, merge_j = 0, 1
|
||||
|
||||
for i in range(len(current_clusters)):
|
||||
for j in range(i + 1, len(current_clusters)):
|
||||
sim = cluster_similarity(current_clusters[i],
|
||||
current_clusters[j],
|
||||
similarity)
|
||||
if sim > max_sim:
|
||||
max_sim = sim
|
||||
merge_i, merge_j = i, j
|
||||
|
||||
if max_sim < similarity_threshold:
|
||||
break
|
||||
|
||||
# Merge clusters
|
||||
current_clusters[merge_i].extend(current_clusters[merge_j])
|
||||
current_clusters.pop(merge_j)
|
||||
|
||||
return current_clusters
|
||||
```
|
||||
|
||||
## Layout Optimization
|
||||
|
||||
### Minimize Edge Crossings
|
||||
|
||||
```python
|
||||
def minimize_crossings(nodes, edges):
|
||||
"""Reduce edge crossing through node repositioning"""
|
||||
crossings = count_crossings(edges)
|
||||
|
||||
# Try swapping adjacent nodes
|
||||
improved = True
|
||||
while improved:
|
||||
improved = False
|
||||
|
||||
for i in range(len(nodes) - 1):
|
||||
# Swap nodes i and i+1
|
||||
swap_positions(nodes[i], nodes[i+1])
|
||||
new_crossings = count_crossings(edges)
|
||||
|
||||
if new_crossings < crossings:
|
||||
crossings = new_crossings
|
||||
improved = True
|
||||
else:
|
||||
# Swap back
|
||||
swap_positions(nodes[i], nodes[i+1])
|
||||
```
|
||||
|
||||
### Visual Balance
|
||||
|
||||
```python
|
||||
def calculate_visual_weight(canvas):
|
||||
"""Calculate center of mass for visual balance"""
|
||||
total_weight = 0
|
||||
weighted_x = 0
|
||||
weighted_y = 0
|
||||
|
||||
for node in canvas.nodes:
|
||||
# Weight is proportional to area
|
||||
weight = node.width * node.height
|
||||
total_weight += weight
|
||||
|
||||
weighted_x += node.center_x * weight
|
||||
weighted_y += node.center_y * weight
|
||||
|
||||
center_x = weighted_x / total_weight
|
||||
center_y = weighted_y / total_weight
|
||||
|
||||
# Shift entire canvas to center at (0, 0)
|
||||
offset_x = -center_x
|
||||
offset_y = -center_y
|
||||
|
||||
for node in canvas.nodes:
|
||||
node.x += offset_x
|
||||
node.y += offset_y
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Spatial Indexing
|
||||
|
||||
For large canvases, use spatial indexing to speed up collision detection:
|
||||
|
||||
```python
|
||||
class SpatialGrid:
|
||||
"""Grid-based spatial index for fast collision detection"""
|
||||
|
||||
def __init__(self, cell_size=500):
|
||||
self.cell_size = cell_size
|
||||
self.grid = {}
|
||||
|
||||
def add_node(self, node):
|
||||
"""Add node to grid"""
|
||||
cells = self.get_cells(node)
|
||||
for cell in cells:
|
||||
if cell not in self.grid:
|
||||
self.grid[cell] = []
|
||||
self.grid[cell].append(node)
|
||||
|
||||
def get_cells(self, node):
|
||||
"""Get grid cells node occupies"""
|
||||
min_x = int(node.x / self.cell_size)
|
||||
max_x = int((node.x + node.width) / self.cell_size)
|
||||
min_y = int(node.y / self.cell_size)
|
||||
max_y = int((node.y + node.height) / self.cell_size)
|
||||
|
||||
cells = []
|
||||
for x in range(min_x, max_x + 1):
|
||||
for y in range(min_y, max_y + 1):
|
||||
cells.append((x, y))
|
||||
return cells
|
||||
|
||||
def get_nearby_nodes(self, node):
|
||||
"""Get nodes in nearby cells"""
|
||||
cells = self.get_cells(node)
|
||||
nearby = set()
|
||||
|
||||
for cell in cells:
|
||||
if cell in self.grid:
|
||||
nearby.update(self.grid[cell])
|
||||
|
||||
return nearby
|
||||
```
|
||||
|
||||
## Common Layout Patterns
|
||||
|
||||
### Timeline Layout
|
||||
|
||||
For chronological content:
|
||||
|
||||
```python
|
||||
def layout_timeline(events, direction="horizontal"):
|
||||
"""Create timeline layout"""
|
||||
if direction == "horizontal":
|
||||
for i, event in enumerate(events):
|
||||
event.x = i * (event.width + HORIZONTAL_SPACING)
|
||||
event.y = 0
|
||||
else: # vertical
|
||||
for i, event in enumerate(events):
|
||||
event.x = 0
|
||||
event.y = i * (event.height + VERTICAL_SPACING)
|
||||
```
|
||||
|
||||
### Circular Layout
|
||||
|
||||
For cyclical processes:
|
||||
|
||||
```python
|
||||
def layout_circular(nodes, radius=500):
|
||||
"""Arrange nodes in a circle"""
|
||||
n = len(nodes)
|
||||
angle_step = 2 * pi / n
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
angle = i * angle_step
|
||||
node.x = radius * cos(angle) - node.width / 2
|
||||
node.y = radius * sin(angle) - node.height / 2
|
||||
```
|
||||
|
||||
### Matrix Layout
|
||||
|
||||
For comparing multiple dimensions:
|
||||
|
||||
```python
|
||||
def layout_matrix(nodes, rows, cols):
|
||||
"""Arrange nodes in a matrix"""
|
||||
cell_width = 400
|
||||
cell_height = 250
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
row = i // cols
|
||||
col = i % cols
|
||||
|
||||
node.x = col * cell_width
|
||||
node.y = row * cell_height
|
||||
```
|
||||
|
||||
## Quality Checks
|
||||
|
||||
Before finalizing layout, verify:
|
||||
|
||||
1. **No Overlaps**: All nodes have minimum spacing
|
||||
2. **Balanced**: Visual center near (0, 0)
|
||||
3. **Accessible**: All nodes reachable via edges
|
||||
4. **Readable**: Text sizes appropriate for zoom level
|
||||
5. **Efficient**: Edge paths reasonably direct
|
||||
|
||||
Use these algorithms as foundations, adapting to specific content and user preferences.
|
||||
106
.claude/skills/obsidian-cli/SKILL.md
Normal file
106
.claude/skills/obsidian-cli/SKILL.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: obsidian-cli
|
||||
description: Interact with Obsidian vaults using the Obsidian CLI to read, create, search, and manage notes, tasks, properties, and more. Also supports plugin and theme development with commands to reload plugins, run JavaScript, capture errors, take screenshots, and inspect the DOM. Use when the user asks to interact with their Obsidian vault, manage notes, search vault content, perform vault operations from the command line, or develop and debug Obsidian plugins and themes.
|
||||
---
|
||||
|
||||
# Obsidian CLI
|
||||
|
||||
Use the `obsidian` CLI to interact with a running Obsidian instance. Requires Obsidian to be open.
|
||||
|
||||
## Command reference
|
||||
|
||||
Run `obsidian help` to see all available commands. This is always up to date. Full docs: https://help.obsidian.md/cli
|
||||
|
||||
## Syntax
|
||||
|
||||
**Parameters** take a value with `=`. Quote values with spaces:
|
||||
|
||||
```bash
|
||||
obsidian create name="My Note" content="Hello world"
|
||||
```
|
||||
|
||||
**Flags** are boolean switches with no value:
|
||||
|
||||
```bash
|
||||
obsidian create name="My Note" silent overwrite
|
||||
```
|
||||
|
||||
For multiline content use `\n` for newline and `\t` for tab.
|
||||
|
||||
## File targeting
|
||||
|
||||
Many commands accept `file` or `path` to target a file. Without either, the active file is used.
|
||||
|
||||
- `file=<name>` — resolves like a wikilink (name only, no path or extension needed)
|
||||
- `path=<path>` — exact path from vault root, e.g. `folder/note.md`
|
||||
|
||||
## Vault targeting
|
||||
|
||||
Commands target the most recently focused vault by default. Use `vault=<name>` as the first parameter to target a specific vault:
|
||||
|
||||
```bash
|
||||
obsidian vault="My Vault" search query="test"
|
||||
```
|
||||
|
||||
## Common patterns
|
||||
|
||||
```bash
|
||||
obsidian read file="My Note"
|
||||
obsidian create name="New Note" content="# Hello" template="Template" silent
|
||||
obsidian append file="My Note" content="New line"
|
||||
obsidian search query="search term" limit=10
|
||||
obsidian daily:read
|
||||
obsidian daily:append content="- [ ] New task"
|
||||
obsidian property:set name="status" value="done" file="My Note"
|
||||
obsidian tasks daily todo
|
||||
obsidian tags sort=count counts
|
||||
obsidian backlinks file="My Note"
|
||||
```
|
||||
|
||||
Use `--copy` on any command to copy output to clipboard. Use `silent` to prevent files from opening. Use `total` on list commands to get a count.
|
||||
|
||||
## Plugin development
|
||||
|
||||
### Develop/test cycle
|
||||
|
||||
After making code changes to a plugin or theme, follow this workflow:
|
||||
|
||||
1. **Reload** the plugin to pick up changes:
|
||||
```bash
|
||||
obsidian plugin:reload id=my-plugin
|
||||
```
|
||||
2. **Check for errors** — if errors appear, fix and repeat from step 1:
|
||||
```bash
|
||||
obsidian dev:errors
|
||||
```
|
||||
3. **Verify visually** with a screenshot or DOM inspection:
|
||||
```bash
|
||||
obsidian dev:screenshot path=screenshot.png
|
||||
obsidian dev:dom selector=".workspace-leaf" text
|
||||
```
|
||||
4. **Check console output** for warnings or unexpected logs:
|
||||
```bash
|
||||
obsidian dev:console level=error
|
||||
```
|
||||
|
||||
### Additional developer commands
|
||||
|
||||
Run JavaScript in the app context:
|
||||
|
||||
```bash
|
||||
obsidian eval code="app.vault.getFiles().length"
|
||||
```
|
||||
|
||||
Inspect CSS values:
|
||||
|
||||
```bash
|
||||
obsidian dev:css selector=".workspace-leaf" prop=background-color
|
||||
```
|
||||
|
||||
Toggle mobile emulation:
|
||||
|
||||
```bash
|
||||
obsidian dev:mobile on
|
||||
```
|
||||
|
||||
Run `obsidian help` to see additional developer commands including CDP and debugger controls.
|
||||
196
.claude/skills/obsidian-markdown/SKILL.md
Normal file
196
.claude/skills/obsidian-markdown/SKILL.md
Normal file
@@ -0,0 +1,196 @@
|
||||
---
|
||||
name: obsidian-markdown
|
||||
description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
|
||||
---
|
||||
|
||||
# Obsidian Flavored Markdown Skill
|
||||
|
||||
Create and edit valid Obsidian Flavored Markdown. Obsidian extends CommonMark and GFM with wikilinks, embeds, callouts, properties, comments, and other syntax. This skill covers only Obsidian-specific extensions -- standard Markdown (headings, bold, italic, lists, quotes, code blocks, tables) is assumed knowledge.
|
||||
|
||||
## Workflow: Creating an Obsidian Note
|
||||
|
||||
1. **Add frontmatter** with properties (title, tags, aliases) at the top of the file. See [PROPERTIES.md](references/PROPERTIES.md) for all property types.
|
||||
2. **Write content** using standard Markdown for structure, plus Obsidian-specific syntax below.
|
||||
3. **Link related notes** using wikilinks (`[[Note]]`) for internal vault connections, or standard Markdown links for external URLs.
|
||||
4. **Embed content** from other notes, images, or PDFs using the `![[embed]]` syntax. See [EMBEDS.md](references/EMBEDS.md) for all embed types.
|
||||
5. **Add callouts** for highlighted information using `> [!type]` syntax. See [CALLOUTS.md](references/CALLOUTS.md) for all callout types.
|
||||
6. **Verify** the note renders correctly in Obsidian's reading view.
|
||||
|
||||
> When choosing between wikilinks and Markdown links: use `[[wikilinks]]` for notes within the vault (Obsidian tracks renames automatically) and `[text](url)` for external URLs only.
|
||||
|
||||
## Internal Links (Wikilinks)
|
||||
|
||||
```markdown
|
||||
[[Note Name]] Link to note
|
||||
[[Note Name|Display Text]] Custom display text
|
||||
[[Note Name#Heading]] Link to heading
|
||||
[[Note Name#^block-id]] Link to block
|
||||
[[#Heading in same note]] Same-note heading link
|
||||
```
|
||||
|
||||
Define a block ID by appending `^block-id` to any paragraph:
|
||||
|
||||
```markdown
|
||||
This paragraph can be linked to. ^my-block-id
|
||||
```
|
||||
|
||||
For lists and quotes, place the block ID on a separate line after the block:
|
||||
|
||||
```markdown
|
||||
> A quote block
|
||||
|
||||
^quote-id
|
||||
```
|
||||
|
||||
## Embeds
|
||||
|
||||
Prefix any wikilink with `!` to embed its content inline:
|
||||
|
||||
```markdown
|
||||
![[Note Name]] Embed full note
|
||||
![[Note Name#Heading]] Embed section
|
||||
![[image.png]] Embed image
|
||||
![[image.png|300]] Embed image with width
|
||||
![[document.pdf#page=3]] Embed PDF page
|
||||
```
|
||||
|
||||
See [EMBEDS.md](references/EMBEDS.md) for audio, video, search embeds, and external images.
|
||||
|
||||
## Callouts
|
||||
|
||||
```markdown
|
||||
> [!note]
|
||||
> Basic callout.
|
||||
|
||||
> [!warning] Custom Title
|
||||
> Callout with a custom title.
|
||||
|
||||
> [!faq]- Collapsed by default
|
||||
> Foldable callout (- collapsed, + expanded).
|
||||
```
|
||||
|
||||
Common types: `note`, `tip`, `warning`, `info`, `example`, `quote`, `bug`, `danger`, `success`, `failure`, `question`, `abstract`, `todo`.
|
||||
|
||||
See [CALLOUTS.md](references/CALLOUTS.md) for the full list with aliases, nesting, and custom CSS callouts.
|
||||
|
||||
## Properties (Frontmatter)
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: My Note
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- active
|
||||
aliases:
|
||||
- Alternative Name
|
||||
cssclasses:
|
||||
- custom-class
|
||||
---
|
||||
```
|
||||
|
||||
Default properties: `tags` (searchable labels), `aliases` (alternative note names for link suggestions), `cssclasses` (CSS classes for styling).
|
||||
|
||||
See [PROPERTIES.md](references/PROPERTIES.md) for all property types, tag syntax rules, and advanced usage.
|
||||
|
||||
## Tags
|
||||
|
||||
```markdown
|
||||
#tag Inline tag
|
||||
#nested/tag Nested tag with hierarchy
|
||||
```
|
||||
|
||||
Tags can contain letters, numbers (not first character), underscores, hyphens, and forward slashes. Tags can also be defined in frontmatter under the `tags` property.
|
||||
|
||||
## Comments
|
||||
|
||||
```markdown
|
||||
This is visible %%but this is hidden%% text.
|
||||
|
||||
%%
|
||||
This entire block is hidden in reading view.
|
||||
%%
|
||||
```
|
||||
|
||||
## Obsidian-Specific Formatting
|
||||
|
||||
```markdown
|
||||
==Highlighted text== Highlight syntax
|
||||
```
|
||||
|
||||
## Math (LaTeX)
|
||||
|
||||
```markdown
|
||||
Inline: $e^{i\pi} + 1 = 0$
|
||||
|
||||
Block:
|
||||
$$
|
||||
\frac{a}{b} = c
|
||||
$$
|
||||
```
|
||||
|
||||
## Diagrams (Mermaid)
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Do this]
|
||||
B -->|No| D[Do that]
|
||||
```
|
||||
````
|
||||
|
||||
To link Mermaid nodes to Obsidian notes, add `class NodeName internal-link;`.
|
||||
|
||||
## Footnotes
|
||||
|
||||
```markdown
|
||||
Text with a footnote[^1].
|
||||
|
||||
[^1]: Footnote content.
|
||||
|
||||
Inline footnote.^[This is inline.]
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
````markdown
|
||||
---
|
||||
title: Project Alpha
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- active
|
||||
status: in-progress
|
||||
---
|
||||
|
||||
# Project Alpha
|
||||
|
||||
This project aims to [[improve workflow]] using modern techniques.
|
||||
|
||||
> [!important] Key Deadline
|
||||
> The first milestone is due on ==January 30th==.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Initial planning
|
||||
- [ ] Development phase
|
||||
- [ ] Backend implementation
|
||||
- [ ] Frontend design
|
||||
|
||||
## Notes
|
||||
|
||||
The algorithm uses $O(n \log n)$ sorting. See [[Algorithm Notes#Sorting]] for details.
|
||||
|
||||
![[Architecture Diagram.png|600]]
|
||||
|
||||
Reviewed in [[Meeting Notes 2024-01-10#Decisions]].
|
||||
````
|
||||
|
||||
## References
|
||||
|
||||
- [Obsidian Flavored Markdown](https://help.obsidian.md/obsidian-flavored-markdown)
|
||||
- [Internal links](https://help.obsidian.md/links)
|
||||
- [Embed files](https://help.obsidian.md/embeds)
|
||||
- [Callouts](https://help.obsidian.md/callouts)
|
||||
- [Properties](https://help.obsidian.md/properties)
|
||||
58
.claude/skills/obsidian-markdown/references/CALLOUTS.md
Normal file
58
.claude/skills/obsidian-markdown/references/CALLOUTS.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Callouts Reference
|
||||
|
||||
## Basic Callout
|
||||
|
||||
```markdown
|
||||
> [!note]
|
||||
> This is a note callout.
|
||||
|
||||
> [!info] Custom Title
|
||||
> This callout has a custom title.
|
||||
|
||||
> [!tip] Title Only
|
||||
```
|
||||
|
||||
## Foldable Callouts
|
||||
|
||||
```markdown
|
||||
> [!faq]- Collapsed by default
|
||||
> This content is hidden until expanded.
|
||||
|
||||
> [!faq]+ Expanded by default
|
||||
> This content is visible but can be collapsed.
|
||||
```
|
||||
|
||||
## Nested Callouts
|
||||
|
||||
```markdown
|
||||
> [!question] Outer callout
|
||||
> > [!note] Inner callout
|
||||
> > Nested content
|
||||
```
|
||||
|
||||
## Supported Callout Types
|
||||
|
||||
| Type | Aliases | Color / Icon |
|
||||
|------|---------|-------------|
|
||||
| `note` | - | Blue, pencil |
|
||||
| `abstract` | `summary`, `tldr` | Teal, clipboard |
|
||||
| `info` | - | Blue, info |
|
||||
| `todo` | - | Blue, checkbox |
|
||||
| `tip` | `hint`, `important` | Cyan, flame |
|
||||
| `success` | `check`, `done` | Green, checkmark |
|
||||
| `question` | `help`, `faq` | Yellow, question mark |
|
||||
| `warning` | `caution`, `attention` | Orange, warning |
|
||||
| `failure` | `fail`, `missing` | Red, X |
|
||||
| `danger` | `error` | Red, zap |
|
||||
| `bug` | - | Red, bug |
|
||||
| `example` | - | Purple, list |
|
||||
| `quote` | `cite` | Gray, quote |
|
||||
|
||||
## Custom Callouts (CSS)
|
||||
|
||||
```css
|
||||
.callout[data-callout="custom-type"] {
|
||||
--callout-color: 255, 0, 0;
|
||||
--callout-icon: lucide-alert-circle;
|
||||
}
|
||||
```
|
||||
63
.claude/skills/obsidian-markdown/references/EMBEDS.md
Normal file
63
.claude/skills/obsidian-markdown/references/EMBEDS.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Embeds Reference
|
||||
|
||||
## Embed Notes
|
||||
|
||||
```markdown
|
||||
![[Note Name]]
|
||||
![[Note Name#Heading]]
|
||||
![[Note Name#^block-id]]
|
||||
```
|
||||
|
||||
## Embed Images
|
||||
|
||||
```markdown
|
||||
![[image.png]]
|
||||
![[image.png|640x480]] Width x Height
|
||||
![[image.png|300]] Width only (maintains aspect ratio)
|
||||
```
|
||||
|
||||
## External Images
|
||||
|
||||
```markdown
|
||||

|
||||

|
||||
```
|
||||
|
||||
## Embed Audio
|
||||
|
||||
```markdown
|
||||
![[audio.mp3]]
|
||||
![[audio.ogg]]
|
||||
```
|
||||
|
||||
## Embed PDF
|
||||
|
||||
```markdown
|
||||
![[document.pdf]]
|
||||
![[document.pdf#page=3]]
|
||||
![[document.pdf#height=400]]
|
||||
```
|
||||
|
||||
## Embed Lists
|
||||
|
||||
```markdown
|
||||
![[Note#^list-id]]
|
||||
```
|
||||
|
||||
Where the list has a block ID:
|
||||
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
^list-id
|
||||
```
|
||||
|
||||
## Embed Search Results
|
||||
|
||||
````markdown
|
||||
```query
|
||||
tag:#project status:done
|
||||
```
|
||||
````
|
||||
61
.claude/skills/obsidian-markdown/references/PROPERTIES.md
Normal file
61
.claude/skills/obsidian-markdown/references/PROPERTIES.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Properties (Frontmatter) Reference
|
||||
|
||||
Properties use YAML frontmatter at the start of a note:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: My Note Title
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- important
|
||||
aliases:
|
||||
- My Note
|
||||
- Alternative Name
|
||||
cssclasses:
|
||||
- custom-class
|
||||
status: in-progress
|
||||
rating: 4.5
|
||||
completed: false
|
||||
due: 2024-02-01T14:30:00
|
||||
---
|
||||
```
|
||||
|
||||
## Property Types
|
||||
|
||||
| Type | Example |
|
||||
|------|---------|
|
||||
| Text | `title: My Title` |
|
||||
| Number | `rating: 4.5` |
|
||||
| Checkbox | `completed: true` |
|
||||
| Date | `date: 2024-01-15` |
|
||||
| Date & Time | `due: 2024-01-15T14:30:00` |
|
||||
| List | `tags: [one, two]` or YAML list |
|
||||
| Links | `related: "[[Other Note]]"` |
|
||||
|
||||
## Default Properties
|
||||
|
||||
- `tags` - Note tags (searchable, shown in graph view)
|
||||
- `aliases` - Alternative names for the note (used in link suggestions)
|
||||
- `cssclasses` - CSS classes applied to the note in reading/editing view
|
||||
|
||||
## Tags
|
||||
|
||||
```markdown
|
||||
#tag
|
||||
#nested/tag
|
||||
#tag-with-dashes
|
||||
#tag_with_underscores
|
||||
```
|
||||
|
||||
Tags can contain: letters (any language), numbers (not first character), underscores `_`, hyphens `-`, forward slashes `/` (for nesting).
|
||||
|
||||
In frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
tags:
|
||||
- tag1
|
||||
- nested/tag2
|
||||
---
|
||||
```
|
||||
@@ -4,8 +4,8 @@
|
||||
"title": "Greet user in Chinese",
|
||||
"titleGenerationStatus": "success",
|
||||
"createdAt": 1776071385957,
|
||||
"updatedAt": 1776071392966,
|
||||
"lastResponseAt": 1776071392966,
|
||||
"updatedAt": 1776073391615,
|
||||
"lastResponseAt": 1776073391615,
|
||||
"sessionId": "c432006f-72f1-4f80-9625-7cb62e468878",
|
||||
"providerState": {
|
||||
"providerSessionId": "c432006f-72f1-4f80-9625-7cb62e468878"
|
||||
@@ -13,12 +13,12 @@
|
||||
"currentNote": "07-Other/AI/Obsidian/Obsidian CLI.md",
|
||||
"usage": {
|
||||
"model": "opus[1m]",
|
||||
"inputTokens": 3,
|
||||
"cacheCreationInputTokens": 22007,
|
||||
"cacheReadInputTokens": 0,
|
||||
"inputTokens": 1,
|
||||
"cacheCreationInputTokens": 114,
|
||||
"cacheReadInputTokens": 35166,
|
||||
"contextWindow": 1000000,
|
||||
"contextTokens": 22010,
|
||||
"percentage": 2,
|
||||
"contextTokens": 35281,
|
||||
"percentage": 4,
|
||||
"contextWindowIsAuthoritative": true
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,7 @@
|
||||
2. 安装Obsidian适配ClaudeCode的插件(可选)
|
||||
1. Claudian:https://github.com/YishenTu/claudian
|
||||
2. 配置自定义变量。
|
||||
3. https://github.com/kepano/obsidian-skills
|
||||
3. 安装 https://github.com/kepano/obsidian-skills
|
||||
1. npm install -g defuddle
|
||||
4. 安装Obsidian绘图增强Skill https://github.com/axtonliu/axton-obsidian-visual-skills
|
||||
|
||||
|
||||
Reference in New Issue
Block a user