blocks 0.1.0

A high-performance Rust library for block-based content editing with JSON, Markdown, and HTML support
Documentation
//! JSON API Example - Backend for Block Editors
//!
//! This example demonstrates how to use the blocks library as a backend
//! for a block-based editor (like TinyMCE, Editor.js, etc.)
//!
//! The workflow is:
//! 1. Frontend sends JSON commands
//! 2. Backend parses JSON → Block/Document
//! 3. Backend processes/converts
//! 4. Backend returns JSON response

use blocks::{
    block::{ButtonStyle, CalloutType},
    Block, BlockType, ConversionFormat, Convertible, Document, JsonSerializable,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("🔌 Blocks Library - JSON API for Editor Backends\n");

    // ═══════════════════════════════════════════════════════════════════
    // 1. RECEIVING BLOCKS FROM FRONTEND (JSON → Block)
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("1. PARSING BLOCKS FROM FRONTEND JSON");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // Simulating JSON received from a frontend editor
    let block_json = r#"{
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "block_type": { "Header": { "level": 2 } },
        "content": "Welcome to My Blog",
        "metadata": {},
        "created_at": 1703001600,
        "updated_at": 1703001600
    }"#;

    println!("Received JSON from frontend:");
    println!("{}\n", block_json);

    let block = Block::from_json(block_json)?;
    println!("Parsed block: {:?}", block.block_type);
    println!("Content: {}\n", block.content);

    // ═══════════════════════════════════════════════════════════════════
    // 2. RECEIVING FULL DOCUMENT FROM FRONTEND
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("2. PARSING FULL DOCUMENT FROM FRONTEND");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    let doc_json = r#"{
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "title": "My Blog Post",
        "blocks": [
            {
                "id": "550e8400-e29b-41d4-a716-446655440002",
                "block_type": { "Header": { "level": 1 } },
                "content": "Introduction",
                "metadata": {},
                "created_at": 1703001600,
                "updated_at": 1703001600
            },
            {
                "id": "550e8400-e29b-41d4-a716-446655440003",
                "block_type": "Text",
                "content": "This is the first paragraph of my blog post.",
                "metadata": {},
                "created_at": 1703001600,
                "updated_at": 1703001600
            },
            {
                "id": "550e8400-e29b-41d4-a716-446655440004",
                "block_type": { "List": { "list_type": "Unordered" } },
                "content": "Point one\nPoint two\nPoint three",
                "metadata": {},
                "created_at": 1703001600,
                "updated_at": 1703001600
            }
        ],
        "metadata": { "author": "John Doe" },
        "created_at": 1703001600,
        "updated_at": 1703001600
    }"#;

    let doc = Document::from_json(doc_json)?;
    println!("Parsed document: '{}'", doc.title);
    println!("Blocks: {}", doc.blocks.len());
    for (i, block) in doc.blocks.iter().enumerate() {
        println!(
            "  {}. {:?} - '{}'",
            i + 1,
            block.block_type,
            truncate(&block.content, 30)
        );
    }
    println!();

    // ═══════════════════════════════════════════════════════════════════
    // 3. CREATING DOCUMENT AND SENDING TO FRONTEND
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("3. CREATING DOCUMENT → SENDING JSON TO FRONTEND");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    let mut new_doc = Document::with_title("New Article".to_string());
    new_doc.add_block(Block::new(
        BlockType::Header { level: 1 },
        "Getting Started with Rust".to_string(),
    ));
    new_doc.add_block(Block::new(
        BlockType::Text,
        "Rust is a systems programming language focused on safety.".to_string(),
    ));
    new_doc.add_block(Block::new(
        BlockType::Code {
            language: Some("rust".to_string()),
        },
        "fn main() {\n    println!(\"Hello, world!\");\n}".to_string(),
    ));
    new_doc.add_block(Block::new(
        BlockType::Callout {
            callout_type: CalloutType::Info,
            title: Some("Note".to_string()),
        },
        "Rust requires cargo to manage dependencies.".to_string(),
    ));

    // Send as JSON to frontend
    let json_response = new_doc.to_json_pretty()?;
    println!("JSON response for frontend (first 500 chars):");
    println!("{}\n", truncate(&json_response, 500));

    // ═══════════════════════════════════════════════════════════════════
    // 4. CONVERT AND EXPORT (Backend Processing)
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("4. BACKEND CONVERSIONS (JSON → Markdown/HTML)");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // Frontend sends JSON, backend converts to different formats
    let markdown = new_doc.to_markdown()?;
    let html = new_doc.to_html()?;
    let plain = new_doc.to_plain_text()?;

    println!("📝 Markdown output:");
    println!("{}\n", truncate(&markdown, 300));

    println!("🌐 HTML output:");
    println!("{}\n", truncate(&html, 300));

    println!("📄 Plain text output:");
    println!("{}\n", truncate(&plain, 200));

    // ═══════════════════════════════════════════════════════════════════
    // 5. PARTIAL UPDATES (Single Block Operations)
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("5. SINGLE BLOCK OPERATIONS");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // Create a block and convert to JSON
    let button = Block::new(
        BlockType::Button {
            text: "Subscribe".to_string(),
            url: "https://newsletter.example.com".to_string(),
            style: ButtonStyle::Primary,
        },
        String::new(),
    );

    let block_json = button.to_json()?;
    println!("Block as JSON: {}\n", truncate(&block_json, 200));

    // Convert single block to formats
    let block_html = button.to_format(ConversionFormat::Html)?;
    let block_md = button.to_format(ConversionFormat::Markdown)?;

    println!("Block as HTML: {}", block_html.trim());
    println!("Block as Markdown: {}\n", block_md.trim());

    // ═══════════════════════════════════════════════════════════════════
    // 6. BATCH OPERATIONS (Multiple Blocks)
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("6. BATCH BLOCK OPERATIONS");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // Simulate receiving array of blocks from frontend
    let blocks_json = r#"[
        {"id":"550e8400-e29b-41d4-a716-446655440010","block_type":"Text","content":"First","metadata":{},"created_at":0,"updated_at":0},
        {"id":"550e8400-e29b-41d4-a716-446655440011","block_type":"Text","content":"Second","metadata":{},"created_at":0,"updated_at":0},
        {"id":"550e8400-e29b-41d4-a716-446655440012","block_type":"Text","content":"Third","metadata":{},"created_at":0,"updated_at":0}
    ]"#;

    let blocks = blocks::Converter::blocks_from_json(blocks_json)?;
    println!("Parsed {} blocks from JSON array", blocks.len());

    // Convert batch to JSON
    let batch_json = blocks.to_json()?;
    println!("Batch JSON (compact): {}\n", truncate(&batch_json, 100));

    // ═══════════════════════════════════════════════════════════════════
    // 7. IMPORT FROM MARKDOWN/HTML (User Paste)
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("7. IMPORT FROM MARKDOWN/HTML (User Paste)");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // User pastes markdown, backend converts to blocks, returns JSON
    let pasted_markdown = r#"# Pasted Title

Some pasted content with **bold** text.

- List item 1
- List item 2
"#;

    let imported_doc = Document::from_markdown(pasted_markdown)?;
    let import_json = imported_doc.to_json()?;

    println!("User pasted markdown, returning JSON:");
    println!("  Blocks imported: {}", imported_doc.blocks.len());
    println!("  JSON size: {} bytes\n", import_json.len());

    // ═══════════════════════════════════════════════════════════════════
    // 8. API RESPONSE EXAMPLES
    // ═══════════════════════════════════════════════════════════════════
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!("8. TYPICAL API RESPONSES");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // GET /documents/{id} → return JSON
    println!("GET /documents/123 →");
    println!("  Content-Type: application/json");
    println!("  Body: {}\n", truncate(&new_doc.to_json()?, 80));

    // POST /documents/{id}/export?format=markdown
    println!("POST /documents/123/export?format=markdown →");
    println!("  Content-Type: text/markdown");
    println!("  Body: {}\n", truncate(&new_doc.to_markdown()?, 80));

    // POST /documents/{id}/export?format=html
    println!("POST /documents/123/export?format=html →");
    println!("  Content-Type: text/html");
    println!("  Body: {}\n", truncate(&new_doc.to_html()?, 80));

    println!("✅ JSON API examples completed!");

    Ok(())
}

fn truncate(s: &str, max: usize) -> String {
    if s.len() <= max {
        s.to_string()
    } else {
        format!("{}...", &s[..max])
    }
}