joplin-publisher

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Joplin Publisher Skill

Joplin 内容发布技能

Purpose

用途

Import markdown documents and mermaid diagrams into Joplin using the Joplin CLI.
使用Joplin CLI将Markdown文档和Mermaid图表导入Joplin。

When to Use

适用场景

  • Exporting research to Joplin notebooks
  • Creating documentation in Joplin
  • Generating diagrams for Joplin knowledge base
  • Syncing analysis results to Joplin
  • 将研究内容导出到Joplin笔记本
  • 在Joplin中创建文档
  • 为Joplin知识库生成图表
  • 将分析结果同步到Joplin

Overview

概述

Joplin uses a database-backed system. Content is imported via the Joplin CLI or API. This skill focuses on CLI-based imports.
Joplin采用基于数据库的系统。内容可通过Joplin CLI或API导入。本技能专注于基于CLI的导入方式。

Prerequisites

前置条件

Install Joplin CLI

安装Joplin CLI

bash
undefined
bash
undefined

Using npm

使用npm安装

npm install -g joplin
npm install -g joplin

Verify installation

验证安装

joplin version
undefined
joplin version
undefined

Configure Joplin CLI

配置Joplin CLI

bash
undefined
bash
undefined

Set sync target (if needed)

设置同步目标(如有需要)

joplin config sync.target 2 # Filesystem joplin config sync.2.path /path/to/sync/folder
joplin config sync.target 2 # 文件系统 joplin config sync.2.path /path/to/sync/folder

Or connect to Joplin Server/Cloud

或连接到Joplin Server/Cloud

joplin config sync.target 9 joplin config sync.9.path https://your-joplin-server.com joplin config sync.9.username your-username
undefined
joplin config sync.target 9 joplin config sync.9.path https://your-joplin-server.com joplin config sync.9.username your-username
undefined

Joplin CLI Commands

Joplin CLI 命令

List Notebooks

列出笔记本

bash
joplin ls /          # List top-level notebooks
joplin ls /Notebook  # List notes in notebook
bash
joplin ls /          # 列出顶级笔记本
joplin ls /Notebook  # 列出笔记本中的笔记

Create Notebook

创建笔记本

bash
joplin mkbook "Notebook Name"
joplin mkbook "Parent/Child"  # Nested notebook
bash
joplin mkbook "Notebook Name"
joplin mkbook "Parent/Child"  # 嵌套笔记本

Import Markdown

导入Markdown

bash
undefined
bash
undefined

Import single file to notebook

将单个文件导入笔记本

joplin import /path/to/file.md --notebook "Notebook Name"
joplin import /path/to/file.md --notebook "Notebook Name"

Import directory

导入目录

joplin import /path/to/folder --notebook "Notebook Name"
joplin import /path/to/folder --notebook "Notebook Name"

Import with format specification

指定格式导入

joplin import /path/to/file.md --format md --notebook "Notebook Name"
undefined
joplin import /path/to/file.md --format md --notebook "Notebook Name"
undefined

Create Note Directly

直接创建笔记

bash
undefined
bash
undefined

Create note from stdin

从标准输入创建笔记

echo "# Title
Content" | joplin mknote "Note Title" --notebook "Notebook Name"
echo "# Title
Content" | joplin mknote "Note Title" --notebook "Notebook Name"

Create from file content

从文件内容创建笔记

cat file.md | joplin mknote "Note Title" --notebook "Notebook Name"
undefined
cat file.md | joplin mknote "Note Title" --notebook "Notebook Name"
undefined

Publishing Workflow

发布工作流

Step 1: Validate Notebook Exists

步骤1:验证笔记本是否存在

python
import subprocess

def notebook_exists(notebook: str) -> bool:
    """Check if a Joplin notebook exists."""
    result = subprocess.run(
        ["joplin", "ls", "/"],
        capture_output=True,
        text=True
    )
    notebooks = result.stdout.strip().split('
')
    return notebook in notebooks

def create_notebook_if_missing(notebook: str):
    """Create notebook if it doesn't exist."""
    if not notebook_exists(notebook):
        subprocess.run(
            ["joplin", "mkbook", notebook],
            check=True
        )
python
import subprocess

def notebook_exists(notebook: str) -> bool:
    """检查Joplin笔记本是否存在。"""
    result = subprocess.run(
        ["joplin", "ls", "/"],
        capture_output=True,
        text=True
    )
    notebooks = result.stdout.strip().split('\n')
    return notebook in notebooks

def create_notebook_if_missing(notebook: str):
    """如果笔记本不存在则创建。"""
    if not notebook_exists(notebook):
        subprocess.run(
            ["joplin", "mkbook", notebook],
            check=True
        )

Step 2: Prepare Content

步骤2:准备内容

python
def prepare_markdown(
    title: str,
    content: str,
    tags: list = None
) -> str:
    """
    Prepare markdown content for Joplin import.

    Joplin supports YAML frontmatter for metadata.
    """
    lines = [f"# {title}", ""]

    if tags:
        lines.extend([
            "---",
            f"tags: {', '.join(tags)}",
            "---",
            ""
        ])

    lines.append(content)
    return '
'.join(lines)
python
def prepare_markdown(
    title: str,
    content: str,
    tags: list = None
) -> str:
    """
    准备用于Joplin导入的Markdown内容。

    Joplin支持使用YAML前置元数据。
    """
    lines = [f"# {title}", ""]

    if tags:
        lines.extend([
            "---",
            f"tags: {', '.join(tags)}",
            "---",
            ""
        ])

    lines.append(content)
    return '\n'.join(lines)

Step 3: Write Temporary File

步骤3:写入临时文件

python
import tempfile
from pathlib import Path

def write_temp_markdown(content: str, filename: str) -> Path:
    """Write content to a temporary markdown file."""
    temp_dir = Path(tempfile.mkdtemp())
    file_path = temp_dir / f"{filename}.md"
    file_path.write_text(content, encoding='utf-8')
    return file_path
python
import tempfile
from pathlib import Path

def write_temp_markdown(content: str, filename: str) -> Path:
    """将内容写入临时Markdown文件。"""
    temp_dir = Path(tempfile.mkdtemp())
    file_path = temp_dir / f"{filename}.md"
    file_path.write_text(content, encoding='utf-8')
    return file_path

Step 4: Import to Joplin

步骤4:导入到Joplin

python
def import_to_joplin(
    file_path: Path,
    notebook: str
) -> bool:
    """Import markdown file to Joplin notebook."""
    result = subprocess.run(
        [
            "joplin", "import",
            str(file_path),
            "--notebook", notebook
        ],
        capture_output=True,
        text=True
    )

    if result.returncode != 0:
        raise JoplinImportError(f"Import failed: {result.stderr}")

    return True
python
def import_to_joplin(
    file_path: Path,
    notebook: str
) -> bool:
    """将Markdown文件导入Joplin笔记本。"""
    result = subprocess.run(
        [
            "joplin", "import",
            str(file_path),
            "--notebook", notebook
        ],
        capture_output=True,
        text=True
    )

    if result.returncode != 0:
        raise JoplinImportError(f"导入失败: {result.stderr}")

    return True

Complete Publishing Function

完整发布函数

python
def publish_to_joplin(
    notebook: str,
    title: str,
    content: str,
    tags: list = None
) -> bool:
    """
    Publish markdown content to a Joplin notebook.

    Args:
        notebook: Target notebook name
        title: Note title
        content: Markdown content (can include mermaid)
        tags: Optional list of tags

    Returns:
        True if successful
    """
    # Ensure notebook exists
    create_notebook_if_missing(notebook)

    # Prepare content
    full_content = prepare_markdown(title, content, tags)

    # Write to temp file
    temp_file = write_temp_markdown(full_content, title)

    try:
        # Import to Joplin
        import_to_joplin(temp_file, notebook)
        return True
    finally:
        # Cleanup
        temp_file.unlink()
        temp_file.parent.rmdir()
python
def publish_to_joplin(
    notebook: str,
    title: str,
    content: str,
    tags: list = None
) -> bool:
    """
    将Markdown内容发布到Joplin笔记本。

    参数:
        notebook: 目标笔记本名称
        title: 笔记标题
        content: Markdown内容(可包含Mermaid代码)
        tags: 可选标签列表

    返回:
        成功则返回True
    """
    # 确保笔记本存在
    create_notebook_if_missing(notebook)

    # 准备内容
    full_content = prepare_markdown(title, content, tags)

    # 写入临时文件
    temp_file = write_temp_markdown(full_content, title)

    try:
        # 导入到Joplin
        import_to_joplin(temp_file, notebook)
        return True
    finally:
        # 清理临时文件
        temp_file.unlink()
        temp_file.parent.rmdir()

Document Formats

文档格式

Basic Note

基础笔记

markdown
undefined
markdown
undefined

Note Title

笔记标题

Content goes here.
内容写在这里。

Section

章节

More content.
undefined
更多内容。
undefined

Note with Tags

带标签的笔记

markdown
undefined
markdown
undefined

Note Title

笔记标题

Content here.
undefined
内容在这里。
undefined

Note with Mermaid

带Mermaid图表的笔记

Joplin supports mermaid diagrams natively in markdown:
markdown
undefined
Joplin原生支持在Markdown中使用Mermaid图表:
markdown
undefined

System Architecture

系统架构

mermaid
flowchart TD
    A[Client] --> B[Server]
    B --> C[(Database)]
mermaid
flowchart TD
    A[客户端] --> B[服务器]
    B --> C[(数据库)]

Description

说明

The system consists of...
undefined
该系统由...组成
undefined

Notebook Organization

笔记本组织方式

Flat Structure

扁平结构

Notebooks/
├── Research
├── Projects
├── Meetings
└── Archive
Notebooks/
├── 研究
├── 项目
├── 会议
└── 归档

Nested Structure

嵌套结构

Notebooks/
├── Work/
│   ├── Project Alpha
│   └── Project Beta
├── Personal/
│   ├── Notes
│   └── Ideas
└── Archive/
Create nested notebooks:
bash
joplin mkbook "Work"
joplin mkbook "Work/Project Alpha"
Notebooks/
├── 工作/
│   ├── Alpha项目
│   └── Beta项目
├── 个人/
│   ├── 笔记
│   └── 想法
└── 归档/
创建嵌套笔记本:
bash
joplin mkbook "Work"
joplin mkbook "Work/Project Alpha"

Usage Examples

使用示例

Publish Research Note

发布研究笔记

python
publish_to_joplin(
    notebook="Research",
    title="API Design Patterns",
    content="""
python
publish_to_joplin(
    notebook="Research",
    title="API设计模式",
    content="""

Overview

概述

Key findings from API design research.
API设计研究的关键发现。

REST Best Practices

REST最佳实践

  1. Use nouns for resources
  2. Use HTTP methods correctly
  3. Version your API
  1. 对资源使用名词
  2. 正确使用HTTP方法
  3. 为API添加版本

GraphQL Considerations

GraphQL注意事项

  • Schema-first design
  • Query optimization """, tags=["api", "research", "design"] )
undefined
  • 优先采用Schema-first设计
  • 查询优化 """, tags=["api", "research", "design"] )
undefined

Publish Diagram

发布图表

python
publish_to_joplin(
    notebook="Architecture",
    title="System Overview Diagram",
    content="""
python
publish_to_joplin(
    notebook="Architecture",
    title="系统概览图",
    content="""

Architecture Diagram

架构图

mermaid
flowchart TB
    subgraph Frontend
        A[Web App]
        B[Mobile App]
    end

    subgraph Backend
        C[API Server]
        D[Worker]
    end

    subgraph Data
        E[(PostgreSQL)]
        F[(Redis)]
    end

    A --> C
    B --> C
    C --> E
    C --> F
    D --> E
mermaid
flowchart TB
    subgraph 前端
        A[Web应用]
        B[移动应用]
    end

    subgraph 后端
        C[API服务器]
        D[工作节点]
    end

    subgraph 数据层
        E[(PostgreSQL)]
        F[(Redis)]
    end

    A --> C
    B --> C
    C --> E
    C --> F
    D --> E

Components

组件说明

ComponentTechnologyPurpose
Web AppReactUser interface
API ServerFastAPIREST API
WorkerCeleryBackground jobs
""",
tags=["architecture", "diagram"]
)
undefined
组件技术栈用途
Web应用React用户界面
API服务器FastAPIREST API
工作节点Celery后台任务
""",
tags=["architecture", "diagram"]
)
undefined

Publish Meeting Notes

发布会议笔记

python
publish_to_joplin(
    notebook="Meetings/2024",
    title="Project Sync - Jan 15",
    content="""
python
publish_to_joplin(
    notebook="Meetings/2024",
    title="项目同步会 - 1月15日",
    content="""

Attendees

参会人员

  • Alice
  • Bob
  • Charlie
  • 爱丽丝
  • 鲍勃
  • 查理

Agenda

议程

  1. Sprint review
  2. Blockers
  3. Next steps
  1. 迭代回顾
  2. 阻塞问题
  3. 下一步计划

Notes

会议记录

Sprint Review

迭代回顾

  • Feature X completed
  • Bug Y in progress
  • 功能X已完成
  • 漏洞Y处理中

Blockers

阻塞问题

  • Waiting on API access
  • 等待API访问权限

Action Items

行动项

  • Alice: Follow up on API access
  • Bob: Complete bug fix
  • Charlie: Update documentation """, tags=["meeting", "project-alpha"] )
undefined
  • 爱丽丝:跟进API访问权限
  • 鲍勃:完成漏洞修复
  • 查理:更新文档 """, tags=["meeting", "project-alpha"] )
undefined

Batch Import

批量导入

For importing multiple documents:
python
def batch_import(
    notebook: str,
    documents: list[dict]
) -> dict:
    """
    Import multiple documents to Joplin.

    Args:
        notebook: Target notebook
        documents: List of {title, content, tags} dicts

    Returns:
        {success: int, failed: int, errors: list}
    """
    results = {"success": 0, "failed": 0, "errors": []}

    for doc in documents:
        try:
            publish_to_joplin(
                notebook=notebook,
                title=doc["title"],
                content=doc["content"],
                tags=doc.get("tags", [])
            )
            results["success"] += 1
        except Exception as e:
            results["failed"] += 1
            results["errors"].append({
                "title": doc["title"],
                "error": str(e)
            })

    return results
如需导入多个文档:
python
def batch_import(
    notebook: str,
    documents: list[dict]
) -> dict:
    """
    将多个文档导入Joplin。

    参数:
        notebook: 目标笔记本
        documents: 包含{title, content, tags}的字典列表

    返回:
        {success: 成功数量, failed: 失败数量, errors: 错误列表}
    """
    results = {"success": 0, "failed": 0, "errors": []}

    for doc in documents:
        try:
            publish_to_joplin(
                notebook=notebook,
                title=doc["title"],
                content=doc["content"],
                tags=doc.get("tags", [])
            )
            results["success"] += 1
        except Exception as e:
            results["failed"] += 1
            results["errors"].append({
                "title": doc["title"],
                "error": str(e)
            })

    return results

Error Handling

错误处理

python
class JoplinError(Exception):
    """Base exception for Joplin operations."""
    pass

class JoplinNotInstalledError(JoplinError):
    """Joplin CLI not found."""
    pass

class JoplinImportError(JoplinError):
    """Failed to import content."""
    pass

class NotebookNotFoundError(JoplinError):
    """Notebook does not exist."""
    pass

def check_joplin_installed():
    """Verify Joplin CLI is available."""
    result = subprocess.run(
        ["joplin", "version"],
        capture_output=True
    )
    if result.returncode != 0:
        raise JoplinNotInstalledError(
            "Joplin CLI not found. Install with: npm install -g joplin"
        )
python
class JoplinError(Exception):
    """Joplin操作的基础异常类。"""
    pass

class JoplinNotInstalledError(JoplinError):
    """未找到Joplin CLI。"""
    pass

class JoplinImportError(JoplinError):
    """内容导入失败。"""
    pass

class NotebookNotFoundError(JoplinError):
    """笔记本不存在。"""
    pass

def check_joplin_installed():
    """验证Joplin CLI是否可用。"""
    result = subprocess.run(
        ["joplin", "version"],
        capture_output=True
    )
    if result.returncode != 0:
        raise JoplinNotInstalledError(
            "未找到Joplin CLI。请使用以下命令安装:npm install -g joplin"
        )

Joplin API Alternative

Joplin API 替代方案

For more control, use the Joplin Data API:
python
import requests

class JoplinAPI:
    def __init__(self, token: str, port: int = 41184):
        self.base_url = f"http://localhost:{port}"
        self.token = token

    def create_note(
        self,
        title: str,
        body: str,
        parent_id: str = None
    ) -> dict:
        """Create a note via Joplin API."""
        response = requests.post(
            f"{self.base_url}/notes",
            params={"token": self.token},
            json={
                "title": title,
                "body": body,
                "parent_id": parent_id
            }
        )
        response.raise_for_status()
        return response.json()
Enable the API in Joplin Desktop: Options → Web Clipper → Enable
如需更多控制,可使用Joplin数据API:
python
import requests

class JoplinAPI:
    def __init__(self, token: str, port: int = 41184):
        self.base_url = f"http://localhost:{port}"
        self.token = token

    def create_note(
        self,
        title: str,
        body: str,
        parent_id: str = None
    ) -> dict:
        """通过Joplin API创建笔记。"""
        response = requests.post(
            f"{self.base_url}/notes",
            params={"token": self.token},
            json={
                "title": title,
                "body": body,
                "parent_id": parent_id
            }
        )
        response.raise_for_status()
        return response.json()
在Joplin桌面端启用API:选项 → 网页剪辑器 → 启用

Checklist

检查清单

Before publishing:
  • Joplin CLI is installed and configured
  • Target notebook exists or will be created
  • Content is valid markdown
  • Mermaid diagrams use correct syntax
  • Tags are properly formatted
  • Sync is configured (if using cloud/server)
发布前请确认:
  • Joplin CLI已安装并配置完成
  • 目标笔记本已存在或会自动创建
  • 内容为合法的Markdown格式
  • Mermaid图表语法正确
  • 标签格式正确
  • 已配置同步(如使用云端/服务器)