mirror of
https://github.com/kepano/obsidian-skills.git
synced 2026-06-04 10:14:01 +08:00
Merge pull request #44 from popey/improve/skill-review-optimization
feat: improve skill quality scores across 4 skills
This commit is contained in:
+96
-508
@@ -5,15 +5,9 @@ description: Create and edit JSON Canvas files (.canvas) with nodes, edges, grou
|
||||
|
||||
# JSON Canvas Skill
|
||||
|
||||
This skill enables skills-compatible agents to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications.
|
||||
|
||||
## Overview
|
||||
|
||||
JSON Canvas is an open file format for infinite canvas data. Canvas files use the `.canvas` extension and contain valid JSON following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/).
|
||||
|
||||
## File Structure
|
||||
|
||||
A canvas file contains two top-level arrays:
|
||||
A canvas file (`.canvas`) contains two top-level arrays following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -25,37 +19,64 @@ A canvas file contains two top-level arrays:
|
||||
- `nodes` (optional): Array of node objects
|
||||
- `edges` (optional): Array of edge objects connecting nodes
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### 1. Create a New Canvas
|
||||
|
||||
1. Create a `.canvas` file with the base structure `{"nodes": [], "edges": []}`
|
||||
2. Generate unique 16-character hex IDs for each node (e.g., `"6f0ad84f44ce9c17"`)
|
||||
3. Add nodes with required fields: `id`, `type`, `x`, `y`, `width`, `height`
|
||||
4. Add edges referencing valid node IDs via `fromNode` and `toNode`
|
||||
5. **Validate**: Parse the JSON to confirm it is valid. Verify all `fromNode`/`toNode` values exist in the nodes array
|
||||
|
||||
### 2. Add a Node to an Existing Canvas
|
||||
|
||||
1. Read and parse the existing `.canvas` file
|
||||
2. Generate a unique ID that does not collide with existing node or edge IDs
|
||||
3. Choose position (`x`, `y`) that avoids overlapping existing nodes (leave 50-100px spacing)
|
||||
4. Append the new node object to the `nodes` array
|
||||
5. Optionally add edges connecting the new node to existing nodes
|
||||
6. **Validate**: Confirm all IDs are unique and all edge references resolve to existing nodes
|
||||
|
||||
### 3. Connect Two Nodes
|
||||
|
||||
1. Identify the source and target node IDs
|
||||
2. Generate a unique edge ID
|
||||
3. Set `fromNode` and `toNode` to the source and target IDs
|
||||
4. Optionally set `fromSide`/`toSide` (top, right, bottom, left) for anchor points
|
||||
5. Optionally set `label` for descriptive text on the edge
|
||||
6. Append the edge to the `edges` array
|
||||
7. **Validate**: Confirm both `fromNode` and `toNode` reference existing node IDs
|
||||
|
||||
### 4. Edit an Existing Canvas
|
||||
|
||||
1. Read and parse the `.canvas` file as JSON
|
||||
2. Locate the target node or edge by `id`
|
||||
3. Modify the desired attributes (text, position, color, etc.)
|
||||
4. Write the updated JSON back to the file
|
||||
5. **Validate**: Re-check all ID uniqueness and edge reference integrity after editing
|
||||
|
||||
## Nodes
|
||||
|
||||
Nodes are objects placed on the canvas. There are four node types:
|
||||
- `text` - Text content with Markdown
|
||||
- `file` - Reference to files/attachments
|
||||
- `link` - External URL
|
||||
- `group` - Visual container for other nodes
|
||||
|
||||
### Z-Index Ordering
|
||||
|
||||
Nodes are ordered by z-index in the array:
|
||||
- First node = bottom layer (displayed below others)
|
||||
- Last node = top layer (displayed above others)
|
||||
Nodes are objects placed on the canvas. Array order determines z-index: first node = bottom layer, last node = top layer.
|
||||
|
||||
### Generic Node Attributes
|
||||
|
||||
All nodes share these attributes:
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `id` | Yes | string | Unique identifier for the node |
|
||||
| `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` |
|
||||
| `id` | Yes | string | Unique 16-char hex identifier |
|
||||
| `type` | Yes | string | `text`, `file`, `link`, or `group` |
|
||||
| `x` | Yes | integer | X position in pixels |
|
||||
| `y` | Yes | integer | Y position in pixels |
|
||||
| `width` | Yes | integer | Width in pixels |
|
||||
| `height` | Yes | integer | Height in pixels |
|
||||
| `color` | No | canvasColor | Node color (see Color section) |
|
||||
| `color` | No | canvasColor | Preset `"1"`-`"6"` or hex (e.g., `"#FF0000"`) |
|
||||
|
||||
### Text Nodes
|
||||
|
||||
Text nodes contain Markdown content.
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -69,27 +90,14 @@ Text nodes contain Markdown content.
|
||||
}
|
||||
```
|
||||
|
||||
#### Newline Escaping (Common Pitfall)
|
||||
|
||||
In JSON, newline characters inside strings **must** be represented as `\n`. Do **not** use the literal sequence `\\n` in a `.canvas` file—Obsidian will render it as the characters `\` and `n` instead of a line break.
|
||||
|
||||
Examples:
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "Line 1\nLine 2" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "Line 1\\nLine 2" }
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
||||
**Newline pitfall**: Use `\n` for line breaks in JSON strings. Do **not** use the literal `\\n` -- Obsidian renders that as the characters `\` and `n`.
|
||||
|
||||
### File Nodes
|
||||
|
||||
File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `file` | Yes | string | Path to file within the system |
|
||||
| `subpath` | No | string | Link to heading or block (starts with `#`) |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -103,27 +111,11 @@ File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "b2c3d4e5f6789012",
|
||||
"type": "file",
|
||||
"x": 500,
|
||||
"y": 400,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Notes/Project Overview.md",
|
||||
"subpath": "#Implementation"
|
||||
}
|
||||
```
|
||||
### Link Nodes
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `file` | Yes | string | Path to file within the system |
|
||||
| `subpath` | No | string | Link to heading or block (starts with `#`) |
|
||||
|
||||
### Link Nodes
|
||||
|
||||
Link nodes display external URLs.
|
||||
| `url` | Yes | string | External URL |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -137,13 +129,15 @@ Link nodes display external URLs.
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `url` | Yes | string | External URL |
|
||||
|
||||
### Group Nodes
|
||||
|
||||
Group nodes are visual containers for organizing other nodes.
|
||||
Groups are visual containers for organizing other nodes. Position child nodes inside the group's bounds.
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `label` | No | string | Text label for the group |
|
||||
| `background` | No | string | Path to background image |
|
||||
| `backgroundStyle` | No | string | `cover`, `ratio`, or `repeat` |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -158,107 +152,37 @@ Group nodes are visual containers for organizing other nodes.
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "e5f67890123456ab",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 700,
|
||||
"width": 800,
|
||||
"height": 500,
|
||||
"label": "Resources",
|
||||
"background": "Attachments/background.png",
|
||||
"backgroundStyle": "cover"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `label` | No | string | Text label for the group |
|
||||
| `background` | No | string | Path to background image |
|
||||
| `backgroundStyle` | No | string | Background rendering style |
|
||||
|
||||
#### Background Styles
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `cover` | Fills entire width and height of node |
|
||||
| `ratio` | Maintains aspect ratio of background image |
|
||||
| `repeat` | Repeats image as pattern in both directions |
|
||||
|
||||
## Edges
|
||||
|
||||
Edges are lines connecting nodes.
|
||||
Edges connect nodes via `fromNode` and `toNode` IDs.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "f67890123456789a",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"toNode": "a1b2c3d4e5f67890"
|
||||
}
|
||||
```
|
||||
| Attribute | Required | Type | Default | Description |
|
||||
|-----------|----------|------|---------|-------------|
|
||||
| `id` | Yes | string | - | Unique identifier |
|
||||
| `fromNode` | Yes | string | - | Source node ID |
|
||||
| `fromSide` | No | string | - | `top`, `right`, `bottom`, or `left` |
|
||||
| `fromEnd` | No | string | `none` | `none` or `arrow` |
|
||||
| `toNode` | Yes | string | - | Target node ID |
|
||||
| `toSide` | No | string | - | `top`, `right`, `bottom`, or `left` |
|
||||
| `toEnd` | No | string | `arrow` | `none` or `arrow` |
|
||||
| `color` | No | canvasColor | - | Line color |
|
||||
| `label` | No | string | - | Text label |
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "0123456789abcdef",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"fromSide": "right",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b2c3d4e5f6789012",
|
||||
"toNode": "a1b2c3d4e5f67890",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "1",
|
||||
"label": "leads to"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Default | Description |
|
||||
|-----------|----------|------|---------|-------------|
|
||||
| `id` | Yes | string | - | Unique identifier for the edge |
|
||||
| `fromNode` | Yes | string | - | Node ID where connection starts |
|
||||
| `fromSide` | No | string | - | Side where edge starts |
|
||||
| `fromEnd` | No | string | `none` | Shape at edge start |
|
||||
| `toNode` | Yes | string | - | Node ID where connection ends |
|
||||
| `toSide` | No | string | - | Side where edge ends |
|
||||
| `toEnd` | No | string | `arrow` | Shape at edge end |
|
||||
| `color` | No | canvasColor | - | Line color |
|
||||
| `label` | No | string | - | Text label for the edge |
|
||||
|
||||
### Side Values
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `top` | Top edge of node |
|
||||
| `right` | Right edge of node |
|
||||
| `bottom` | Bottom edge of node |
|
||||
| `left` | Left edge of node |
|
||||
|
||||
### End Shapes
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `none` | No endpoint shape |
|
||||
| `arrow` | Arrow endpoint |
|
||||
|
||||
## Colors
|
||||
|
||||
The `canvasColor` type can be specified in two ways:
|
||||
|
||||
### Hex Colors
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "#FF0000"
|
||||
}
|
||||
```
|
||||
|
||||
### Preset Colors
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "1"
|
||||
}
|
||||
```
|
||||
The `canvasColor` type accepts either a hex string or a preset number:
|
||||
|
||||
| Preset | Color |
|
||||
|--------|-------|
|
||||
@@ -269,360 +193,23 @@ The `canvasColor` type can be specified in two ways:
|
||||
| `"5"` | Cyan |
|
||||
| `"6"` | Purple |
|
||||
|
||||
Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Simple Canvas with Text and Connections
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "8a9b0c1d2e3f4a5b",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 150,
|
||||
"text": "# Main Idea\n\nThis is the central concept."
|
||||
},
|
||||
{
|
||||
"id": "1a2b3c4d5e6f7a8b",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": -100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point A\n\nDetails here."
|
||||
},
|
||||
{
|
||||
"id": "2b3c4d5e6f7a8b9c",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point B\n\nMore details."
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "3c4d5e6f7a8b9c0d",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1a2b3c4d5e6f7a8b",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "4d5e6f7a8b9c0d1e",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "2b3c4d5e6f7a8b9c",
|
||||
"toSide": "left"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Project Board with Groups
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "5e6f7a8b9c0d1e2f",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "To Do",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "6f7a8b9c0d1e2f3a",
|
||||
"type": "group",
|
||||
"x": 350,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "In Progress",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "7a8b9c0d1e2f3a4b",
|
||||
"type": "group",
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "Done",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "8b9c0d1e2f3a4b5c",
|
||||
"type": "text",
|
||||
"x": 20,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 1\n\nImplement feature X"
|
||||
},
|
||||
{
|
||||
"id": "9c0d1e2f3a4b5c6d",
|
||||
"type": "text",
|
||||
"x": 370,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 2\n\nReview PR #123",
|
||||
"color": "2"
|
||||
},
|
||||
{
|
||||
"id": "0d1e2f3a4b5c6d7e",
|
||||
"type": "text",
|
||||
"x": 720,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 3\n\n~~Setup CI/CD~~"
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
```
|
||||
|
||||
### Research Canvas with Files and Links
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "1e2f3a4b5c6d7e8f",
|
||||
"type": "text",
|
||||
"x": 300,
|
||||
"y": 200,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "2f3a4b5c6d7e8f9a",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Literature/Paper A.pdf"
|
||||
},
|
||||
{
|
||||
"id": "3a4b5c6d7e8f9a0b",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Notes/Meeting Notes.md",
|
||||
"subpath": "#Key Insights"
|
||||
},
|
||||
{
|
||||
"id": "4b5c6d7e8f9a0b1c",
|
||||
"type": "link",
|
||||
"x": 0,
|
||||
"y": 400,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"url": "https://example.com/research"
|
||||
},
|
||||
{
|
||||
"id": "5c6d7e8f9a0b1c2d",
|
||||
"type": "file",
|
||||
"x": 750,
|
||||
"y": 150,
|
||||
"width": 300,
|
||||
"height": 250,
|
||||
"file": "Attachments/diagram.png"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "6d7e8f9a0b1c2d3e",
|
||||
"fromNode": "2f3a4b5c6d7e8f9a",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "supports"
|
||||
},
|
||||
{
|
||||
"id": "7e8f9a0b1c2d3e4f",
|
||||
"fromNode": "3a4b5c6d7e8f9a0b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "informs"
|
||||
},
|
||||
{
|
||||
"id": "8f9a0b1c2d3e4f5a",
|
||||
"fromNode": "4b5c6d7e8f9a0b1c",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "6"
|
||||
},
|
||||
{
|
||||
"id": "9a0b1c2d3e4f5a6b",
|
||||
"fromNode": "1e2f3a4b5c6d7e8f",
|
||||
"fromSide": "right",
|
||||
"toNode": "5c6d7e8f9a0b1c2d",
|
||||
"toSide": "left",
|
||||
"label": "visualized by"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Flowchart
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "a0b1c2d3e4f5a6b7",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**Start**",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "b1c2d3e4f5a6b7c8",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Step 1:\nGather data"
|
||||
},
|
||||
{
|
||||
"id": "c2d3e4f5a6b7c8d9",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"text": "**Decision**\n\nIs data valid?",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "d3e4f5a6b7c8d9e0",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Process data"
|
||||
},
|
||||
{
|
||||
"id": "e4f5a6b7c8d9e0f1",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Request new data",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "f5a6b7c8d9e0f1a2",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 320,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**End**",
|
||||
"color": "4"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "a6b7c8d9e0f1a2b3",
|
||||
"fromNode": "a0b1c2d3e4f5a6b7",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "b7c8d9e0f1a2b3c4",
|
||||
"fromNode": "b1c2d3e4f5a6b7c8",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "c2d3e4f5a6b7c8d9",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "c8d9e0f1a2b3c4d5",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "right",
|
||||
"toNode": "d3e4f5a6b7c8d9e0",
|
||||
"toSide": "left",
|
||||
"label": "Yes",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "d9e0f1a2b3c4d5e6",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "left",
|
||||
"toNode": "e4f5a6b7c8d9e0f1",
|
||||
"toSide": "right",
|
||||
"label": "No",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "e0f1a2b3c4d5e6f7",
|
||||
"fromNode": "e4f5a6b7c8d9e0f1",
|
||||
"fromSide": "top",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "f1a2b3c4d5e6f7a8",
|
||||
"fromNode": "d3e4f5a6b7c8d9e0",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "f5a6b7c8d9e0f1a2",
|
||||
"toSide": "top"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Preset color values are intentionally undefined -- applications use their own brand colors.
|
||||
|
||||
## ID Generation
|
||||
|
||||
Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
|
||||
Generate 16-character lowercase hexadecimal strings (64-bit random value):
|
||||
|
||||
```json
|
||||
"id": "6f0ad84f44ce9c17"
|
||||
"id": "a3b2c1d0e9f8g7h6"
|
||||
"id": "1234567890abcdef"
|
||||
```
|
||||
|
||||
This format is a 16-character lowercase hex string (64-bit random value).
|
||||
"6f0ad84f44ce9c17"
|
||||
"a3b2c1d0e9f8a7b6"
|
||||
```
|
||||
|
||||
## Layout Guidelines
|
||||
|
||||
### Positioning
|
||||
|
||||
- Coordinates can be negative (canvas extends infinitely)
|
||||
- `x` increases to the right
|
||||
- `y` increases downward
|
||||
- Position refers to top-left corner of node
|
||||
|
||||
### Recommended Sizes
|
||||
- `x` increases right, `y` increases down; position is the top-left corner
|
||||
- Space nodes 50-100px apart; leave 20-50px padding inside groups
|
||||
- Align to grid (multiples of 10 or 20) for cleaner layouts
|
||||
|
||||
| Node Type | Suggested Width | Suggested Height |
|
||||
|-----------|-----------------|------------------|
|
||||
@@ -631,24 +218,25 @@ This format is a 16-character lowercase hex string (64-bit random value).
|
||||
| Large text | 400-600 | 300-500 |
|
||||
| File preview | 300-500 | 200-400 |
|
||||
| Link preview | 250-400 | 100-200 |
|
||||
| Group | Varies | Varies |
|
||||
|
||||
### Spacing
|
||||
## Validation Checklist
|
||||
|
||||
- Leave 20-50px padding inside groups
|
||||
- Space nodes 50-100px apart for readability
|
||||
- Align nodes to grid (multiples of 10 or 20) for cleaner layouts
|
||||
After creating or editing a canvas file, verify:
|
||||
|
||||
## Validation Rules
|
||||
1. All `id` values are unique across both nodes and edges
|
||||
2. Every `fromNode` and `toNode` references an existing node ID
|
||||
3. Required fields are present for each node type (`text` for text nodes, `file` for file nodes, `url` for link nodes)
|
||||
4. `type` is one of: `text`, `file`, `link`, `group`
|
||||
5. `fromSide`/`toSide` values are one of: `top`, `right`, `bottom`, `left`
|
||||
6. `fromEnd`/`toEnd` values are one of: `none`, `arrow`
|
||||
7. Color presets are `"1"` through `"6"` or valid hex (e.g., `"#FF0000"`)
|
||||
8. JSON is valid and parseable
|
||||
|
||||
1. All `id` values must be unique across nodes and edges
|
||||
2. `fromNode` and `toNode` must reference existing node IDs
|
||||
3. Required fields must be present for each node type
|
||||
4. `type` must be one of: `text`, `file`, `link`, `group`
|
||||
5. `backgroundStyle` must be one of: `cover`, `ratio`, `repeat`
|
||||
6. `fromSide`, `toSide` must be one of: `top`, `right`, `bottom`, `left`
|
||||
7. `fromEnd`, `toEnd` must be one of: `none`, `arrow`
|
||||
8. Color presets must be `"1"` through `"6"` or valid hex color
|
||||
If validation fails, check for duplicate IDs, dangling edge references, or malformed JSON strings (especially unescaped newlines in text content).
|
||||
|
||||
## Complete Examples
|
||||
|
||||
See [references/EXAMPLES.md](references/EXAMPLES.md) for full canvas examples including mind maps, project boards, research canvases, and flowcharts.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# JSON Canvas Complete Examples
|
||||
|
||||
## Simple Canvas with Text and Connections
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "8a9b0c1d2e3f4a5b",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 150,
|
||||
"text": "# Main Idea\n\nThis is the central concept."
|
||||
},
|
||||
{
|
||||
"id": "1a2b3c4d5e6f7a8b",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": -100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point A\n\nDetails here."
|
||||
},
|
||||
{
|
||||
"id": "2b3c4d5e6f7a8b9c",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point B\n\nMore details."
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "3c4d5e6f7a8b9c0d",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1a2b3c4d5e6f7a8b",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "4d5e6f7a8b9c0d1e",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "2b3c4d5e6f7a8b9c",
|
||||
"toSide": "left"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Project Board with Groups
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "5e6f7a8b9c0d1e2f",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "To Do",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "6f7a8b9c0d1e2f3a",
|
||||
"type": "group",
|
||||
"x": 350,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "In Progress",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "7a8b9c0d1e2f3a4b",
|
||||
"type": "group",
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "Done",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "8b9c0d1e2f3a4b5c",
|
||||
"type": "text",
|
||||
"x": 20,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 1\n\nImplement feature X"
|
||||
},
|
||||
{
|
||||
"id": "9c0d1e2f3a4b5c6d",
|
||||
"type": "text",
|
||||
"x": 370,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 2\n\nReview PR #123",
|
||||
"color": "2"
|
||||
},
|
||||
{
|
||||
"id": "0d1e2f3a4b5c6d7e",
|
||||
"type": "text",
|
||||
"x": 720,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 3\n\n~~Setup CI/CD~~"
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
```
|
||||
|
||||
## Research Canvas with Files and Links
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "1e2f3a4b5c6d7e8f",
|
||||
"type": "text",
|
||||
"x": 300,
|
||||
"y": 200,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "2f3a4b5c6d7e8f9a",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Literature/Paper A.pdf"
|
||||
},
|
||||
{
|
||||
"id": "3a4b5c6d7e8f9a0b",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Notes/Meeting Notes.md",
|
||||
"subpath": "#Key Insights"
|
||||
},
|
||||
{
|
||||
"id": "4b5c6d7e8f9a0b1c",
|
||||
"type": "link",
|
||||
"x": 0,
|
||||
"y": 400,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"url": "https://example.com/research"
|
||||
},
|
||||
{
|
||||
"id": "5c6d7e8f9a0b1c2d",
|
||||
"type": "file",
|
||||
"x": 750,
|
||||
"y": 150,
|
||||
"width": 300,
|
||||
"height": 250,
|
||||
"file": "Attachments/diagram.png"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "6d7e8f9a0b1c2d3e",
|
||||
"fromNode": "2f3a4b5c6d7e8f9a",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "supports"
|
||||
},
|
||||
{
|
||||
"id": "7e8f9a0b1c2d3e4f",
|
||||
"fromNode": "3a4b5c6d7e8f9a0b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "informs"
|
||||
},
|
||||
{
|
||||
"id": "8f9a0b1c2d3e4f5a",
|
||||
"fromNode": "4b5c6d7e8f9a0b1c",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "6"
|
||||
},
|
||||
{
|
||||
"id": "9a0b1c2d3e4f5a6b",
|
||||
"fromNode": "1e2f3a4b5c6d7e8f",
|
||||
"fromSide": "right",
|
||||
"toNode": "5c6d7e8f9a0b1c2d",
|
||||
"toSide": "left",
|
||||
"label": "visualized by"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Flowchart
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "a0b1c2d3e4f5a6b7",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**Start**",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "b1c2d3e4f5a6b7c8",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Step 1:\nGather data"
|
||||
},
|
||||
{
|
||||
"id": "c2d3e4f5a6b7c8d9",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"text": "**Decision**\n\nIs data valid?",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "d3e4f5a6b7c8d9e0",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Process data"
|
||||
},
|
||||
{
|
||||
"id": "e4f5a6b7c8d9e0f1",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Request new data",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "f5a6b7c8d9e0f1a2",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 320,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**End**",
|
||||
"color": "4"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "a6b7c8d9e0f1a2b3",
|
||||
"fromNode": "a0b1c2d3e4f5a6b7",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "b7c8d9e0f1a2b3c4",
|
||||
"fromNode": "b1c2d3e4f5a6b7c8",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "c2d3e4f5a6b7c8d9",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "c8d9e0f1a2b3c4d5",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "right",
|
||||
"toNode": "d3e4f5a6b7c8d9e0",
|
||||
"toSide": "left",
|
||||
"label": "Yes",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "d9e0f1a2b3c4d5e6",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "left",
|
||||
"toNode": "e4f5a6b7c8d9e0f1",
|
||||
"toSide": "right",
|
||||
"label": "No",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "e0f1a2b3c4d5e6f7",
|
||||
"fromNode": "e4f5a6b7c8d9e0f1",
|
||||
"fromSide": "top",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "f1a2b3c4d5e6f7a8",
|
||||
"fromNode": "d3e4f5a6b7c8d9e0",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "f5a6b7c8d9e0f1a2",
|
||||
"toSide": "top"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
+64
-218
@@ -5,17 +5,18 @@ description: Create and edit Obsidian Bases (.base files) with views, filters, f
|
||||
|
||||
# Obsidian Bases Skill
|
||||
|
||||
This skill enables skills-compatible agents to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.
|
||||
## Workflow
|
||||
|
||||
## Overview
|
||||
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
|
||||
|
||||
Obsidian Bases are YAML-based files that define dynamic views of notes in an Obsidian vault. A Base file can contain multiple views, global filters, formulas, property configurations, and custom summaries.
|
||||
## Schema
|
||||
|
||||
## File Format
|
||||
|
||||
Base files use the `.base` extension and contain valid YAML. They can also be embedded in Markdown code blocks.
|
||||
|
||||
## Complete Schema
|
||||
Base files use the `.base` extension and contain valid YAML.
|
||||
|
||||
```yaml
|
||||
# Global filters apply to ALL views in the base
|
||||
@@ -171,71 +172,33 @@ formulas:
|
||||
days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
|
||||
```
|
||||
|
||||
## Functions Reference
|
||||
## Key Functions
|
||||
|
||||
### Global 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. Format: `YYYY-MM-DD HH:mm:ss` |
|
||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
||||
| `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 |
|
||||
| `link()` | `link(path, display?): Link` | Create a link |
|
||||
|
||||
### Duration Type
|
||||
|
||||
When subtracting two dates, the result is a **Duration** type (not a number). Duration has its own properties and methods.
|
||||
When subtracting two dates, the result is a **Duration** type (not a number).
|
||||
|
||||
**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 |
|
||||
**Duration Fields:** `duration.days`, `duration.hours`, `duration.minutes`, `duration.seconds`, `duration.milliseconds`
|
||||
|
||||
**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. You must access a numeric field first (like `.days`), then apply number functions.
|
||||
**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
|
||||
|
||||
# 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
|
||||
@@ -246,105 +209,12 @@ When subtracting two dates, the result is a **Duration** type (not a number). Du
|
||||
```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)"
|
||||
"now() - file.ctime" # Returns Duration
|
||||
"(now() - file.ctime).days" # Get days as number
|
||||
```
|
||||
|
||||
### 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 |
|
||||
|
||||
## View Types
|
||||
|
||||
### Table View
|
||||
@@ -512,48 +382,6 @@ views:
|
||||
- formula.reading_time
|
||||
```
|
||||
|
||||
### Project Notes Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Projects")
|
||||
- 'file.ext == "md"'
|
||||
|
||||
formulas:
|
||||
last_updated: 'file.mtime.relative()'
|
||||
link_count: 'file.links.length'
|
||||
|
||||
summaries:
|
||||
avgLinks: 'values.filter(value.isType("number")).mean().round(1)'
|
||||
|
||||
properties:
|
||||
formula.last_updated:
|
||||
displayName: "Updated"
|
||||
formula.link_count:
|
||||
displayName: "Links"
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "All Projects"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- formula.last_updated
|
||||
- formula.link_count
|
||||
summaries:
|
||||
formula.link_count: avgLinks
|
||||
groupBy:
|
||||
property: status
|
||||
direction: ASC
|
||||
|
||||
- type: list
|
||||
name: "Quick List"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
```
|
||||
|
||||
### Daily Notes Index
|
||||
|
||||
```yaml
|
||||
@@ -600,47 +428,64 @@ Embed in Markdown files:
|
||||
- Use double quotes for simple strings: `"My View Name"`
|
||||
- Escape nested quotes properly in complex expressions
|
||||
|
||||
## Common Patterns
|
||||
## Troubleshooting
|
||||
|
||||
### YAML Syntax Errors
|
||||
|
||||
**Unquoted special characters**: Strings containing `:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` `` must be quoted.
|
||||
|
||||
### Filter by Tag
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.hasTag("project")
|
||||
# WRONG - colon in unquoted string
|
||||
displayName: Status: Active
|
||||
|
||||
# CORRECT
|
||||
displayName: "Status: Active"
|
||||
```
|
||||
|
||||
### Filter by Folder
|
||||
**Mismatched quotes in formulas**: When a formula contains double quotes, wrap the entire formula in single quotes.
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Notes")
|
||||
# WRONG - double quotes inside double quotes
|
||||
formulas:
|
||||
label: "if(done, "Yes", "No")"
|
||||
|
||||
# CORRECT - single quotes wrapping double quotes
|
||||
formulas:
|
||||
label: 'if(done, "Yes", "No")'
|
||||
```
|
||||
|
||||
### Filter by Date Range
|
||||
### Common Formula Errors
|
||||
|
||||
**Duration math without field access**: Subtracting dates returns a Duration, not a number. Always access `.days`, `.hours`, etc.
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- 'file.mtime > now() - "7d"'
|
||||
# WRONG - Duration is not a number
|
||||
"(now() - file.ctime).round(0)"
|
||||
|
||||
# CORRECT - access .days first, then round
|
||||
"(now() - file.ctime).days.round(0)"
|
||||
```
|
||||
|
||||
### Filter by Property Value
|
||||
**Missing null checks**: Properties may not exist on all notes. Use `if()` to guard.
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- 'status == "active"'
|
||||
- 'priority >= 3'
|
||||
# WRONG - crashes if due_date is empty
|
||||
"(date(due_date) - today()).days"
|
||||
|
||||
# CORRECT - guard with if()
|
||||
'if(due_date, (date(due_date) - today()).days, "")'
|
||||
```
|
||||
|
||||
### Combine Multiple Conditions
|
||||
**Referencing undefined formulas**: Ensure every `formula.X` in `order` or `properties` has a matching entry in `formulas`.
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
or:
|
||||
- and:
|
||||
- file.hasTag("important")
|
||||
- 'status != "done"'
|
||||
- and:
|
||||
- 'priority == 1'
|
||||
- 'due != ""'
|
||||
# This will fail silently if 'total' is not defined in formulas
|
||||
order:
|
||||
- formula.total
|
||||
|
||||
# Fix: define it
|
||||
formulas:
|
||||
total: "price * quantity"
|
||||
```
|
||||
|
||||
## References
|
||||
@@ -649,3 +494,4 @@ filters:
|
||||
- [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)
|
||||
|
||||
@@ -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 |
|
||||
@@ -61,11 +61,29 @@ Use `--copy` on any command to copy output to clipboard. Use `silent` to prevent
|
||||
|
||||
## Plugin development
|
||||
|
||||
Reload a plugin after code changes — essential for the develop/test cycle:
|
||||
### Develop/test cycle
|
||||
|
||||
```bash
|
||||
obsidian plugin:reload id=my-plugin
|
||||
```
|
||||
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:
|
||||
|
||||
@@ -73,24 +91,9 @@ Run JavaScript in the app context:
|
||||
obsidian eval code="app.vault.getFiles().length"
|
||||
```
|
||||
|
||||
Check for errors and console output:
|
||||
Inspect CSS values:
|
||||
|
||||
```bash
|
||||
obsidian dev:errors
|
||||
obsidian dev:console
|
||||
obsidian dev:console level=error
|
||||
```
|
||||
|
||||
Take a screenshot for visual testing:
|
||||
|
||||
```bash
|
||||
obsidian dev:screenshot path=screenshot.png
|
||||
```
|
||||
|
||||
Inspect DOM and CSS:
|
||||
|
||||
```bash
|
||||
obsidian dev:dom selector=".workspace-leaf" text
|
||||
obsidian dev:css selector=".workspace-leaf" prop=background-color
|
||||
```
|
||||
|
||||
|
||||
@@ -5,390 +5,130 @@ description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds,
|
||||
|
||||
# Obsidian Flavored Markdown Skill
|
||||
|
||||
This skill enables skills-compatible agents to create and edit valid Obsidian Flavored Markdown, including all Obsidian-specific syntax extensions.
|
||||
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.
|
||||
|
||||
## Overview
|
||||
## Workflow: Creating an Obsidian Note
|
||||
|
||||
Obsidian uses a combination of Markdown flavors:
|
||||
- [CommonMark](https://commonmark.org/)
|
||||
- [GitHub Flavored Markdown](https://github.github.com/gfm/)
|
||||
- [LaTeX](https://www.latex-project.org/) for math
|
||||
- Obsidian-specific extensions (wikilinks, callouts, embeds, etc.)
|
||||
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.
|
||||
|
||||
## Basic Formatting
|
||||
|
||||
### Paragraphs and Line Breaks
|
||||
|
||||
```markdown
|
||||
This is a paragraph.
|
||||
|
||||
This is another paragraph (blank line between creates separate paragraphs).
|
||||
|
||||
For a line break within a paragraph, add two spaces at the end
|
||||
or use Shift+Enter.
|
||||
```
|
||||
|
||||
### Headings
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
#### Heading 4
|
||||
##### Heading 5
|
||||
###### Heading 6
|
||||
```
|
||||
|
||||
### Text Formatting
|
||||
|
||||
| Style | Syntax | Example | Output |
|
||||
|-------|--------|---------|--------|
|
||||
| Bold | `**text**` or `__text__` | `**Bold**` | **Bold** |
|
||||
| Italic | `*text*` or `_text_` | `*Italic*` | *Italic* |
|
||||
| Bold + Italic | `***text***` | `***Both***` | ***Both*** |
|
||||
| Strikethrough | `~~text~~` | `~~Striked~~` | ~~Striked~~ |
|
||||
| Highlight | `==text==` | `==Highlighted==` | ==Highlighted== |
|
||||
| Inline code | `` `code` `` | `` `code` `` | `code` |
|
||||
|
||||
### Escaping Formatting
|
||||
|
||||
Use backslash to escape special characters:
|
||||
```markdown
|
||||
\*This won't be italic\*
|
||||
\#This won't be a heading
|
||||
1\. This won't be a list item
|
||||
```
|
||||
|
||||
Common characters to escape: `\*`, `\_`, `\#`, `` \` ``, `\|`, `\~`
|
||||
> 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)
|
||||
|
||||
### Basic Links
|
||||
|
||||
```markdown
|
||||
[[Note Name]]
|
||||
[[Note Name.md]]
|
||||
[[Note Name|Display Text]]
|
||||
[[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
|
||||
```
|
||||
|
||||
### Link to Headings
|
||||
Define a block ID by appending `^block-id` to any paragraph:
|
||||
|
||||
```markdown
|
||||
[[Note Name#Heading]]
|
||||
[[Note Name#Heading|Custom Text]]
|
||||
[[#Heading in same note]]
|
||||
[[##Search all headings in vault]]
|
||||
This paragraph can be linked to. ^my-block-id
|
||||
```
|
||||
|
||||
### Link to Blocks
|
||||
For lists and quotes, place the block ID on a separate line after the block:
|
||||
|
||||
```markdown
|
||||
[[Note Name#^block-id]]
|
||||
[[Note Name#^block-id|Custom Text]]
|
||||
```
|
||||
|
||||
Define a block ID by adding `^block-id` at the end of a paragraph:
|
||||
```markdown
|
||||
This is a paragraph that can be linked to. ^my-block-id
|
||||
```
|
||||
|
||||
For lists and quotes, add the block ID on a separate line:
|
||||
```markdown
|
||||
> This is a quote
|
||||
> With multiple lines
|
||||
> A quote block
|
||||
|
||||
^quote-id
|
||||
```
|
||||
|
||||
### Search Links
|
||||
|
||||
```markdown
|
||||
[[##heading]] Search for headings containing "heading"
|
||||
[[^^block]] Search for blocks containing "block"
|
||||
```
|
||||
|
||||
## Markdown-Style Links
|
||||
|
||||
```markdown
|
||||
[Display Text](Note%20Name.md)
|
||||
[Display Text](Note%20Name.md#Heading)
|
||||
[Display Text](https://example.com)
|
||||
[Note](obsidian://open?vault=VaultName&file=Note.md)
|
||||
```
|
||||
|
||||
Note: Spaces must be URL-encoded as `%20` in Markdown links.
|
||||
|
||||
## Embeds
|
||||
|
||||
### Embed Notes
|
||||
Prefix any wikilink with `!` to embed its content inline:
|
||||
|
||||
```markdown
|
||||
![[Note Name]]
|
||||
![[Note Name#Heading]]
|
||||
![[Note Name#^block-id]]
|
||||
![[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
|
||||
```
|
||||
|
||||
### 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 been defined with a block ID:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
^list-id
|
||||
```
|
||||
|
||||
### Embed Search Results
|
||||
|
||||
````markdown
|
||||
```query
|
||||
tag:#project status:done
|
||||
```
|
||||
````
|
||||
See [EMBEDS.md](references/EMBEDS.md) for audio, video, search embeds, and external images.
|
||||
|
||||
## Callouts
|
||||
|
||||
### Basic Callout
|
||||
|
||||
```markdown
|
||||
> [!note]
|
||||
> This is a note callout.
|
||||
> Basic callout.
|
||||
|
||||
> [!info] Custom Title
|
||||
> This callout has a custom title.
|
||||
> [!warning] Custom Title
|
||||
> Callout with 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.
|
||||
> Foldable callout (- collapsed, + expanded).
|
||||
```
|
||||
|
||||
### Nested Callouts
|
||||
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
|
||||
> [!question] Outer callout
|
||||
> > [!note] Inner callout
|
||||
> > Nested content
|
||||
#tag Inline tag
|
||||
#nested/tag Nested tag with hierarchy
|
||||
```
|
||||
|
||||
### Supported Callout Types
|
||||
Tags can contain letters, numbers (not first character), underscores, hyphens, and forward slashes. Tags can also be defined in frontmatter under the `tags` property.
|
||||
|
||||
| Type | Aliases | Description |
|
||||
|------|---------|-------------|
|
||||
| `note` | - | Blue, pencil icon |
|
||||
| `abstract` | `summary`, `tldr` | Teal, clipboard icon |
|
||||
| `info` | - | Blue, info icon |
|
||||
| `todo` | - | Blue, checkbox icon |
|
||||
| `tip` | `hint`, `important` | Cyan, flame icon |
|
||||
| `success` | `check`, `done` | Green, checkmark icon |
|
||||
| `question` | `help`, `faq` | Yellow, question mark |
|
||||
| `warning` | `caution`, `attention` | Orange, warning icon |
|
||||
| `failure` | `fail`, `missing` | Red, X icon |
|
||||
| `danger` | `error` | Red, zap icon |
|
||||
| `bug` | - | Red, bug icon |
|
||||
| `example` | - | Purple, list icon |
|
||||
| `quote` | `cite` | Gray, quote icon |
|
||||
|
||||
### Custom Callouts (CSS)
|
||||
|
||||
```css
|
||||
.callout[data-callout="custom-type"] {
|
||||
--callout-color: 255, 0, 0;
|
||||
--callout-icon: lucide-alert-circle;
|
||||
}
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
### Unordered Lists
|
||||
## Comments
|
||||
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Nested item
|
||||
- Another nested
|
||||
- Item 3
|
||||
This is visible %%but this is hidden%% text.
|
||||
|
||||
* Also works with asterisks
|
||||
+ Or plus signs
|
||||
%%
|
||||
This entire block is hidden in reading view.
|
||||
%%
|
||||
```
|
||||
|
||||
### Ordered Lists
|
||||
## Obsidian-Specific Formatting
|
||||
|
||||
```markdown
|
||||
1. First item
|
||||
2. Second item
|
||||
1. Nested numbered
|
||||
2. Another nested
|
||||
3. Third item
|
||||
|
||||
1) Alternative syntax
|
||||
2) With parentheses
|
||||
```
|
||||
|
||||
### Task Lists
|
||||
|
||||
```markdown
|
||||
- [ ] Incomplete task
|
||||
- [x] Completed task
|
||||
- [ ] Task with sub-tasks
|
||||
- [ ] Subtask 1
|
||||
- [x] Subtask 2
|
||||
```
|
||||
|
||||
## Quotes
|
||||
|
||||
```markdown
|
||||
> This is a blockquote.
|
||||
> It can span multiple lines.
|
||||
>
|
||||
> And include multiple paragraphs.
|
||||
>
|
||||
> > Nested quotes work too.
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
### Inline Code
|
||||
|
||||
```markdown
|
||||
Use `backticks` for inline code.
|
||||
Use double backticks for ``code with a ` backtick inside``.
|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
|
||||
````markdown
|
||||
```
|
||||
Plain code block
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Syntax highlighted code block
|
||||
function hello() {
|
||||
console.log("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python example
|
||||
def greet(name):
|
||||
print(f"Hello, {name}!")
|
||||
```
|
||||
````
|
||||
|
||||
### Nesting Code Blocks
|
||||
|
||||
Use more backticks or tildes for the outer block:
|
||||
|
||||
`````markdown
|
||||
````markdown
|
||||
Here's how to create a code block:
|
||||
```js
|
||||
console.log("Hello")
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
## Tables
|
||||
|
||||
```markdown
|
||||
| Header 1 | Header 2 | Header 3 |
|
||||
|----------|----------|----------|
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
```
|
||||
|
||||
### Alignment
|
||||
|
||||
```markdown
|
||||
| Left | Center | Right |
|
||||
|:---------|:--------:|---------:|
|
||||
| Left | Center | Right |
|
||||
```
|
||||
|
||||
### Using Pipes in Tables
|
||||
|
||||
Escape pipes with backslash:
|
||||
```markdown
|
||||
| Column 1 | Column 2 |
|
||||
|----------|----------|
|
||||
| [[Link\|Display]] | ![[Image\|100]] |
|
||||
==Highlighted text== Highlight syntax
|
||||
```
|
||||
|
||||
## Math (LaTeX)
|
||||
|
||||
### Inline Math
|
||||
|
||||
```markdown
|
||||
This is inline math: $e^{i\pi} + 1 = 0$
|
||||
```
|
||||
Inline: $e^{i\pi} + 1 = 0$
|
||||
|
||||
### Block Math
|
||||
|
||||
```markdown
|
||||
Block:
|
||||
$$
|
||||
\begin{vmatrix}
|
||||
a & b \\
|
||||
c & d
|
||||
\end{vmatrix} = ad - bc
|
||||
\frac{a}{b} = c
|
||||
$$
|
||||
```
|
||||
|
||||
### Common Math Syntax
|
||||
|
||||
```markdown
|
||||
$x^2$ Superscript
|
||||
$x_i$ Subscript
|
||||
$\frac{a}{b}$ Fraction
|
||||
$\sqrt{x}$ Square root
|
||||
$\sum_{i=1}^{n}$ Summation
|
||||
$\int_a^b$ Integral
|
||||
$\alpha, \beta$ Greek letters
|
||||
```
|
||||
|
||||
## Diagrams (Mermaid)
|
||||
|
||||
````markdown
|
||||
@@ -397,147 +137,19 @@ graph TD
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Do this]
|
||||
B -->|No| D[Do that]
|
||||
C --> E[End]
|
||||
D --> E
|
||||
```
|
||||
````
|
||||
|
||||
### Sequence Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hello Bob
|
||||
Bob-->>Alice: Hi Alice
|
||||
```
|
||||
````
|
||||
|
||||
### Linking in Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Biology]
|
||||
B[Chemistry]
|
||||
A --> B
|
||||
class A,B internal-link;
|
||||
```
|
||||
````
|
||||
To link Mermaid nodes to Obsidian notes, add `class NodeName internal-link;`.
|
||||
|
||||
## Footnotes
|
||||
|
||||
```markdown
|
||||
This sentence has a footnote[^1].
|
||||
Text with a footnote[^1].
|
||||
|
||||
[^1]: This is the footnote content.
|
||||
[^1]: Footnote content.
|
||||
|
||||
You can also use named footnotes[^note].
|
||||
|
||||
[^note]: Named footnotes still appear as numbers.
|
||||
|
||||
Inline footnotes are also supported.^[This is an inline footnote.]
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
```markdown
|
||||
This is visible %%but this is hidden%% text.
|
||||
|
||||
%%
|
||||
This entire block is hidden.
|
||||
It won't appear in reading view.
|
||||
%%
|
||||
```
|
||||
|
||||
## Horizontal Rules
|
||||
|
||||
```markdown
|
||||
---
|
||||
***
|
||||
___
|
||||
- - -
|
||||
* * *
|
||||
```
|
||||
|
||||
## Properties (Frontmatter)
|
||||
|
||||
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
|
||||
- `aliases` - Alternative names for the note
|
||||
- `cssclasses` - CSS classes applied to the note
|
||||
|
||||
## Tags
|
||||
|
||||
```markdown
|
||||
#tag
|
||||
#nested/tag
|
||||
#tag-with-dashes
|
||||
#tag_with_underscores
|
||||
|
||||
In frontmatter:
|
||||
---
|
||||
tags:
|
||||
- tag1
|
||||
- nested/tag2
|
||||
---
|
||||
```
|
||||
|
||||
Tags can contain:
|
||||
- Letters (any language)
|
||||
- Numbers (not as first character)
|
||||
- Underscores `_`
|
||||
- Hyphens `-`
|
||||
- Forward slashes `/` (for nesting)
|
||||
|
||||
## HTML Content
|
||||
|
||||
Obsidian supports HTML within Markdown:
|
||||
|
||||
```markdown
|
||||
<div class="custom-container">
|
||||
<span style="color: red;">Colored text</span>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
Hidden content here.
|
||||
</details>
|
||||
|
||||
<kbd>Ctrl</kbd> + <kbd>C</kbd>
|
||||
Inline footnote.^[This is inline.]
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
@@ -550,13 +162,10 @@ tags:
|
||||
- project
|
||||
- active
|
||||
status: in-progress
|
||||
priority: high
|
||||
---
|
||||
|
||||
# Project Alpha
|
||||
|
||||
## Overview
|
||||
|
||||
This project aims to [[improve workflow]] using modern techniques.
|
||||
|
||||
> [!important] Key Deadline
|
||||
@@ -565,54 +174,21 @@ This project aims to [[improve workflow]] using modern techniques.
|
||||
## Tasks
|
||||
|
||||
- [x] Initial planning
|
||||
- [x] Resource allocation
|
||||
- [ ] Development phase
|
||||
- [ ] Backend implementation
|
||||
- [ ] Frontend design
|
||||
- [ ] Testing
|
||||
- [ ] Deployment
|
||||
|
||||
## Technical Notes
|
||||
## Notes
|
||||
|
||||
The main algorithm uses the formula $O(n \log n)$ for sorting.
|
||||
The algorithm uses $O(n \log n)$ sorting. See [[Algorithm Notes#Sorting]] for details.
|
||||
|
||||
```python
|
||||
def process_data(items):
|
||||
return sorted(items, key=lambda x: x.priority)
|
||||
```
|
||||
![[Architecture Diagram.png|600]]
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Input] --> B[Process]
|
||||
B --> C[Output]
|
||||
B --> D[Cache]
|
||||
```
|
||||
|
||||
## Related Documents
|
||||
|
||||
- ![[Meeting Notes 2024-01-10#Decisions]]
|
||||
- [[Budget Allocation|Budget]]
|
||||
- [[Team Members]]
|
||||
|
||||
## References
|
||||
|
||||
For more details, see the official documentation[^1].
|
||||
|
||||
[^1]: https://example.com/docs
|
||||
|
||||
%%
|
||||
Internal notes:
|
||||
- Review with team on Friday
|
||||
- Consider alternative approaches
|
||||
%%
|
||||
Reviewed in [[Meeting Notes 2024-01-10#Decisions]].
|
||||
````
|
||||
|
||||
## References
|
||||
|
||||
- [Basic formatting syntax](https://help.obsidian.md/syntax)
|
||||
- [Advanced formatting syntax](https://help.obsidian.md/advanced-syntax)
|
||||
- [Obsidian Flavored Markdown](https://help.obsidian.md/obsidian-flavored-markdown)
|
||||
- [Internal links](https://help.obsidian.md/links)
|
||||
- [Embed files](https://help.obsidian.md/embeds)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
````
|
||||
@@ -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
|
||||
---
|
||||
```
|
||||
Reference in New Issue
Block a user