Skip to content
Elixir blog engine based on Phoenix powered by markdown
Elixir HTML CSS JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci
config
lib
priv
test
web
.editorconfig
.gitignore
.prettierrc
Procfile
README.md
coveralls.json
elixir_buildpack.config
jelly_shot.png
mix.exs
mix.lock
package.json
webpack.config.js
yarn.lock

README.md

title separator verticalSeparator theme revealOptions
Jelly Shot
<!--s-->
<!--v-->
black
controls transition
false
slide

CircleCI codecov

Jelly Shot

A semi static blog engine

starting point

Static markdown blog by @seilund

A million static site generators...

💁 let's write another one

  • Phoenix
  • No database
  • No static file generation

Let's take this further

  • Generator -> markdown |> struct
  • Repository -> Agent storing data
  • Watcher -> GenServer for auto update

Generator

defp compile_file(file) do
  with{:ok, matter, body} <- split_frontmatter(file),
      {:ok, html, _} <- Earmark.as_html(body),
  do: {:ok, into_post(file, matter, html)}
end
defp into_post(file, meta, html) do
  data = %{
    slug: file_to_slug(file),
    content: html,
  } |> Map.merge(meta)

  struct(JellyShot.Post, data)
end

Repository

def start_link do
  Agent.start_link(&get_initial_state/0, name: __MODULE__)
end
posts = File.ls!("priv/posts")
|> Enum.filter(&(Path.extname(&1) == ".md"))
|> Enum.map(&compile_async/1)
|> Enum.map(&Task.await/1)
|> Enum.reduce([], &valid_into_list/2)
|> Enum.sort(&sort/2)

Watcher

def init(state) do
  path = Path.expand("priv/posts")

  :fs.start_link(:fs_watcher, path)
  :fs.subscribe(:fs_watcher)

  {:ok, state}
end
def handle_info({_pid, {:fs, :file_event}, {path, ev}}, _) do
  new_state = cond do
    Enum.member?(ev, :modified) ->
      path
      |> JellyShot.Post.file_to_slug
      |> JellyShot.Repo.upsert_by_slug
  end
end

Integrating into Phoenix 🐣🔥

Listing posts

def index(conn, params) do
  {tmpl, headline, {:ok, posts}} = case params do
    %{"author" => author} ->
      {"list", "by author",  Repo.get_by_author(author)}
    %{"category" => category} ->
      {"list", "by category", Repo.get_by_category(category)}
    _ ->
      {"index", "recent posts", Repo.list()}
  end

  render conn, "#{tmpl}.html", head: head, posts: posts
end

JellySHot

Limitations

Filling the repository...

~ 250 sloc / file

  • 12 posts in 406ms 🐰
  • 384 posts in 3844ms 🐢
  • ... 🐌

We might hit a cap at some point

Anyway, I Learned a lot

  • Pattern matching
  • Agents
  • GenServer
  • with {:ok}

Thanks

https://github.com/erikmueller/jelly_shot

<style> .reveal code {font-family: hasklig, monospace} </style>
You can’t perform that action at this time.