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"}
]
endThen 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" boldAttributed 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/falseJSON 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)Navigating Documents (Path Operations)
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
- See the Cheatsheet for a quick reference
- Learn about the Marks system for text formatting
- Understand Schema & Validation for document structure
- Find common patterns in the Cookbook
- Browse the API documentation for all available functions