Topic

Implementing Shortcodes

Replace template strings in your content with rendered HTML using item processors.

Overview

Shortcodes let you embed dynamic content in your markdown files using a simple syntax like {{youtube id="dQw4w9WgXcQ"}}. Since Saga’s item processors run after the reader parses markdown into HTML, you can search and replace these patterns in the item’s body.

Define a shortcode syntax

Choose a pattern that won’t conflict with markdown. A common choice is double curly braces:

---
title: My Article
---

Check out this video:

{{youtube id="dQw4w9WgXcQ"}}

And here's a note:

{{note}}
This is important information.
{{/note}}

Write an item processor

Create a processor that finds and replaces shortcode patterns:

import Foundation
import Saga

func processShortcodes(item: Item<ArticleMetadata>) async {
  // Self-closing shortcodes: {{youtube id="..."}}
  item.body = item.body.replacingOccurrences(
    of: #"\{\{youtube id="([^"]+)"\}\}"#,
    with: """ <div class="video-embed"> <iframe src="https://www.youtube.com/embed/$1" \ frameborder="0" allowfullscreen></iframe> </div> """,
    options: .regularExpression
  )

  // Block shortcodes: {{note}}...{{/note}}
  item.body = item.body.replacingOccurrences(
    of: #"\{\{note\}\}(.*?)\{\{/note\}\}"#,
    with: "<aside class=\"note\">$1</aside>",
    options: [.regularExpression, .dotMatchesLineSeparators]
  )
}

Register the processor

Pass your shortcode processor as the itemProcessor, or chain it with other processors using sequence(_:):

try await Saga(input: "content", output: "deploy")
  .register(
    folder: "articles",
    metadata: ArticleMetadata.self,
    readers: [.parsleyMarkdownReader],
    itemProcessor: processShortcodes,
    writers: [.itemWriter(swim(renderArticle))]
  )
  .run()

To combine multiple processors:

try await Saga(input: "content", output: "deploy")
  .register(
    folder: "articles",
    metadata: ArticleMetadata.self,
    readers: [.parsleyMarkdownReader],
    itemProcessor: sequence(processShortcodes, anotherProcessor),
    writers: [.itemWriter(swim(renderArticle))]
  )
  .run()

Tip For more complex transformations that need to work with the HTML DOM rather than string replacement, consider using SagaUtils which provides SwiftSoup-based HTML transformations.