Getting Started with Quillon

Copy Markdown View Source

This guide will walk you through the basics of creating and manipulating documents with Quillon.

Installation

Add Quillon to your mix.exs dependencies:

def deps do
  [
    {:quillon, "~> 0.1.0"}
  ]
end

Then run:

mix deps.get

Creating Your First Document

Quillon documents are trees of nodes. Each node is a tuple: {type, attrs, children}.

Let's create a simple document with a heading and a paragraph:

doc = Quillon.document([
  Quillon.heading(1, "Welcome to Quillon"),
  Quillon.paragraph("This is your first document.")
])

This creates:

{:document, %{},
 [
   {:heading, %{level: 1},
    [{:text, %{text: "Welcome to Quillon", marks: []}, []}]},
   {:paragraph, %{},
    [{:text, %{text: "This is your first document.", marks: []}, []}]}
 ]}

Working with Blocks

Quillon supports many block types. Here are some common ones:

Paragraphs and Headings

# Empty paragraph
Quillon.paragraph()

# Paragraph with text
Quillon.paragraph("Hello world")

# Headings (levels 1-6)
Quillon.heading(1, "Main Title")
Quillon.heading(2, "Subtitle")

Lists

# Bullet list
Quillon.bullet_list([
  Quillon.list_item([Quillon.paragraph("First item")]),
  Quillon.list_item([Quillon.paragraph("Second item")])
])

# Ordered list (starts at 1 by default)
Quillon.ordered_list([
  Quillon.list_item([Quillon.paragraph("Step one")]),
  Quillon.list_item([Quillon.paragraph("Step two")])
])

# Ordered list starting at 5
Quillon.ordered_list([
  Quillon.list_item([Quillon.paragraph("Item five")])
], 5)

Tables

Quillon.table([
  Quillon.table_row([
    Quillon.table_cell([Quillon.paragraph("Name")]),
    Quillon.table_cell([Quillon.paragraph("Age")])
  ], header: true),
  Quillon.table_row([
    Quillon.table_cell([Quillon.paragraph("Alice")]),
    Quillon.table_cell([Quillon.paragraph("30")])
  ])
])

Other Blocks

# Divider (horizontal rule)
Quillon.divider()
Quillon.divider(:dashed)  # :solid, :dashed, or :dotted

# Blockquote
Quillon.blockquote([
  Quillon.paragraph("To be or not to be.")
], "Shakespeare")

# Callout (info, warning, success, error)
Quillon.callout(:warning, [
  Quillon.paragraph("This action cannot be undone.")
], "Warning")

# Code block
Quillon.code_block("def hello, do: :world", "elixir")

# Image
Quillon.image("/images/photo.jpg", "A beautiful sunset")

# Video
Quillon.video("/videos/intro.mp4", poster: "/images/poster.jpg")

Applying Formatting (Marks)

Marks apply formatting to text. Quillon supports two types:

Simple Marks

Simple marks are atoms: :bold, :italic, :underline, :strike, :code, :subscript, :superscript

# Create text with marks
Quillon.text("Bold text", [:bold])
Quillon.text("Bold and italic", [:bold, :italic])

# Apply marks to a range in a paragraph
para = Quillon.paragraph("Hello world")
para = Quillon.toggle_bold(para, 0, 5)  # Makes "Hello" bold

Attributed Marks

Attributed marks have additional data:

# Link
Quillon.text("Click here", [{:link, %{href: "https://example.com"}}])

# Highlight
Quillon.text("Important", [{:highlight, %{color: "yellow"}}])

# Font color
Quillon.text("Red text", [{:font_color, %{color: "#ff0000"}}])

# Mention
Quillon.text("@alice", [{:mention, %{id: "user_123", type: "user", label: "@alice"}}])

Formatting Commands

Use commands to toggle formatting on text ranges:

para = Quillon.paragraph("Hello world")

# Toggle simple marks
para = Quillon.toggle_bold(para, 0, 5)
para = Quillon.toggle_italic(para, 6, 11)
para = Quillon.toggle_code(para, 0, 11)

# Set/unset links
para = Quillon.set_link(para, 0, 5, "https://example.com")
para = Quillon.unset_link(para, 0, 5)

# Set/unset highlight
para = Quillon.set_highlight(para, 0, 5, "yellow")
para = Quillon.unset_highlight(para, 0, 5)

# Clear all formatting
para = Quillon.clear_formatting(para, 0, 11)

# Check if selection has a mark
Quillon.selection_has_mark?(para, 0, 5, :bold)  # => true/false

JSON Serialization

Documents can be converted to and from JSON:

doc = Quillon.document([
  Quillon.paragraph("Hello world")
])

# Convert to JSON-friendly map
json = Quillon.to_json(doc)
# => %{"type" => "document", "attrs" => %{}, "children" => [...]}

# Parse from JSON
{:ok, doc} = Quillon.from_json(json)

# Parse with exception on error
doc = Quillon.from_json!(json)

Validating Documents

Quillon can validate documents against a schema:

doc = Quillon.document([
  Quillon.paragraph("Valid document")
])

# Validate (returns {:ok, doc} or {:error, errors})
{:ok, doc} = Quillon.validate(doc)

# Validate with exception on error
doc = Quillon.validate!(doc)

# Invalid document example
invalid = {:document, %{}, ["not a node"]}
{:error, errors} = Quillon.validate(invalid)

Use paths to navigate and modify the document tree. A path is a list of indices:

doc = Quillon.document([
  Quillon.paragraph("First"),   # path: [0]
  Quillon.paragraph("Second")   # path: [1]
])

# Get node at path
{:ok, para} = Quillon.get(doc, [0])

# Update node at path
{:ok, updated} = Quillon.update(doc, [0], fn {type, attrs, children} ->
  {type, Map.put(attrs, :id, "para_1"), children}
end)

# Insert at path
{:ok, doc} = Quillon.insert(doc, [1], Quillon.paragraph("Middle"))

# Delete at path
{:ok, doc} = Quillon.delete(doc, [0])

# Move node
{:ok, doc} = Quillon.move(doc, [0], [1])

ID-based Operations

If your nodes have :id attributes, you can use ID-based operations:

doc = Quillon.document([
  {:paragraph, %{id: "intro"}, [{:text, %{text: "Hello", marks: []}, []}]}
])

# Find path to node by ID
{:ok, [0]} = Quillon.find_path(doc, "intro")

# Get node by ID
{:ok, para} = Quillon.get_by_id(doc, "intro")

# Update node by ID
{:ok, updated} = Quillon.update_by_id(doc, "intro", fn node ->
  # modify node
  node
end)

Next Steps