Script: Pair with Obsidian Canvas2Document to automatically convert canvas replacement cards into markdown files

To solve this problem I have tested the 13900K+3090, 7950x+4090, and 7950x3d+4090, and there is a serious issue with Canvas performance. No configuration can smoothly run a Canvas card with 26,000 characters of content, we can replace the text cards in the canvas with file cards, and then we can edit the cards in the markdown interface. Although writing too much will also cause lag in markdown editing, it is much better than in the canvas.

Pair with GitHub - slnsys/obsidian-canvas2document: Plugin for Obsidian to convert a complete Canvas to a long form document to automatically convert canvas replacement cards into markdown files. Instructions for use:

  • first, use Canvas2Document to convert all cards in the canvas file into md files.
  • Then use this Python script to generate a modified version of the canvas file, replacing cards with markdown files. It also enables one-way updates; cards added in the modified version can be converted into md files and replaced, and cards added in the original will also be synchronized and updated in the modified version, converting into md files.This script, in theory, should not modify the original version of the canvas file.
import json
import os

def find_obsidian_root(current_dir):
    # Search upwards from the current directory until the root directory containing the .obsidian folder is found
    while True:
        if os.path.exists(os.path.join(current_dir, '.obsidian')):
            return current_dir
        # Get the parent directory of the current directory
        current_dir = os.path.dirname(current_dir)
        # Stop searching if the root directory is reached
        if current_dir == os.path.dirname(current_dir):
            raise FileNotFoundError("Could not find the root directory containing the .obsidian folder")

def load_canvas_data(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        return json.load(file)

def save_canvas_data(file_path, data):
    with open(file_path, 'w', encoding='utf-8') as file:
        json.dump(data, file, ensure_ascii=False, indent=4)

def update_modified_canvas_with_new_nodes(original_canvas, modified_canvas, base_path, relative_base_path, obsidian_root):
    modified_node_ids = {node["id"] for node in modified_canvas.get("nodes", [])}
    for node in original_canvas.get("nodes", []):
        if node["id"] not in modified_node_ids:
            print(f"Syncing new node: {node}")
            if node.get("type") == "text":
                node_id = node["id"]
                new_file_path = os.path.join(base_path, f"newdoc-node_{node_id}_fromCanvas.md").replace("\\", "/")

                # Create the markdown file and copy content if it does not exist
                if not os.path.exists(new_file_path):
                    with open(new_file_path, 'w', encoding='utf-8') as md_file:
                        md_file.write(node["text"])

                node["type"] = "file"
                node["file"] = os.path.relpath(new_file_path, obsidian_root).replace("\\", "/")
                del node["text"]
                
                print(f"Synced node: {node}")
            
            modified_canvas["nodes"].append(node)

def replace_canvas_content():
    # Get the directory of the script
    script_dir = os.path.dirname(os.path.abspath(__file__))
    print(f"Script directory: {script_dir}")
    
    # Find the root directory containing the .obsidian folder
    obsidian_root = find_obsidian_root(script_dir)
    print(f"Found .obsidian root directory: {obsidian_root}")
    
    # Get all .canvas files in the script directory
    canvas_files = [f for f in os.listdir(script_dir) if f.endswith('.canvas') and not f.endswith('_modified.canvas')]
    print(f"Found .canvas files: {canvas_files}")

    for canvas_file in canvas_files:
        modified_canvas_file = canvas_file.replace('.canvas', '_modified.canvas')
        
        # Skip processing if the modified file exists
        if os.path.exists(os.path.join(script_dir, modified_canvas_file)):
            print(f"Found modified file: {modified_canvas_file}, skipping {canvas_file}")
            continue

        print(f"\nProcessing file: {canvas_file}")
        
        canvas_file_path = os.path.join(script_dir, canvas_file).replace("\\", "/")
        base_name = os.path.splitext(canvas_file)[0]
        base_path = os.path.join(script_dir, f"{base_name}_canvas2doc-data").replace("\\", "/")
        
        # Skip processing if the corresponding folder does not exist
        if not os.path.exists(base_path):
            print(f"Warning: Folder {base_path} does not exist, skipping {canvas_file}")
            continue
        
        # Read the contents of the canvas file
        canvas_data = load_canvas_data(canvas_file_path)
        print(f"Read file content: {canvas_data}")

        # Calculate the relative path
        relative_base_path = os.path.relpath(base_path, obsidian_root).replace("\\", "/")
        print(f"Relative folder path: {relative_base_path}")

        # Iterate through each node in the canvas file and replace content
        for node in canvas_data.get("nodes", []):
            print(f"Processing node: {node}")
            
            if node.get("type") == "text":
                node_id = node["id"]
                new_file_path = os.path.join(base_path, f"newdoc-node_{node_id}_fromCanvas.md").replace("\\", "/")

                # Create the markdown file and copy content if it does not exist
                if not os.path.exists(new_file_path):
                    with open(new_file_path, 'w', encoding='utf-8') as md_file:
                        md_file.write(node["text"])

                node["type"] = "file"
                node["file"] = os.path.relpath(new_file_path, obsidian_root).replace("\\", "/")
                # Delete the original "text" field
                del node["text"]
                
                print(f"Modified node: {node}")

        # Save the modified content back to a new canvas file
        modified_canvas_file_path = os.path.join(script_dir, modified_canvas_file).replace("\\", "/")
        save_canvas_data(modified_canvas_file_path, canvas_data)

        print(f"Content successfully replaced and saved to: {modified_canvas_file_path}")

    # Process all _modified.canvas files
    modified_canvas_files = [f for f in os.listdir(script_dir) if f.endswith('_modified.canvas')]
    for modified_canvas_file in modified_canvas_files:
        print(f"\nProcessing modified file: {modified_canvas_file}")
        
        modified_canvas_file_path = os.path.join(script_dir, modified_canvas_file).replace("\\", "/")
        base_name = modified_canvas_file.replace('_modified.canvas', '')
        base_path = os.path.join(script_dir, f"{base_name}_canvas2doc-data").replace("\\", "/")
        original_canvas_file_path = os.path.join(script_dir, f"{base_name}.canvas").replace("\\", "/")

        # Read the contents of the modified canvas file
        modified_canvas_data = load_canvas_data(modified_canvas_file_path)
        original_canvas_data = load_canvas_data(original_canvas_file_path)
        print(f"Read file content: {modified_canvas_data}")

        # Calculate the relative path
        relative_base_path = os.path.relpath(base_path, obsidian_root).replace("\\", "/")
        print(f"Relative folder path: {relative_base_path}")

        # Sync new nodes
        update_modified_canvas_with_new_nodes(original_canvas_data, modified_canvas_data, base_path, relative_base_path, obsidian_root)

        # Iterate through each node in the modified canvas file and replace content
        for node in modified_canvas_data.get("nodes", []):
            print(f"Processing node: {node}")
            
            if node.get("type") == "text":
                node_id = node["id"]
                new_file_path = os.path.join(base_path, f"newdoc-node_{node_id}_fromCanvas.md").replace("\\", "/")
                
                # Write the text content to a new markdown file
                if not os.path.exists(new_file_path):
                    with open(new_file_path, 'w', encoding='utf-8') as md_file:
                        md_file.write(node["text"])

                node["type"] = "file"
                node["file"] = os.path.relpath(new_file_path, obsidian_root).replace("\\", "/")
                # Delete the original "text" field
                del node["text"]
                
                print(f"Modified node: {node}")

        # Save the modified content back to the modified canvas file
        save_canvas_data(modified_canvas_file_path, modified_canvas_data)

        print(f"Content successfully replaced and saved to: {modified_canvas_file_path}")

# Run the script
replace_canvas_content()