Literature

Literature is a blog content management system with core blog features such as Publications, Posts, Authors, and Tags. Content can be published with static html files through a static pages generator that uses Phoenix.Components. It comes with a WYSIWYG post editor, image optimization, reusable SEO tags, and seamless integration to an existing Phoenix application.

Installation

def deps do
  [
    {:literature, "~> 0.5"}
  ]
end

Quick Setup

Literature comes with built in database tables that can be added through a migration.

  1. Setup config including Ecto repo for Literature to use and directory where static pages will be stored.
config :literature,
  repo: MyApp.Repo,
  static_pages_storage_dir: "/tmp/literature/static_pages"
  1. Setup Literature database tables by creating a new migration. This will create all necessary tables Publication, Post, Author, and Tag.
defmodule MyApp.Repo.Migrations.SetupLiterature do
  use Ecto.Migration

  def up, do: Literature.Migrations.up([])
  def down, do: Literature.Migrations.down([])
end
  1. Setup content management dashboard in your router. Use Literature.Router and add literature_assets/1 for styling and JS files and literature_dashboard/1 for the dashboard LiveView pages.
defmodule MyAppWeb.Router do
  ...
  use Literature.Router

  literature_assets("/literature")
  literature_dashboard("/literature")
end
  1. You can separate content by main topic by creating a publication for each. Create a publication e.g. Blog then create authors and tags. You can now start creating Posts with a WYSIWYG editor.

Images

Literature tables have a few image columns such as feature and profile images. Post content could also have images, both are handled with

Setup literature config for waffle, e.g. for local config

config :literature,
  storage: Waffle.Storage.Local,
  storage_dir_prefix: "/tmp/literature/",
  asset_host: "/tmp/literature"

WSYIWYG Post content editor

Post editor supports content formatting and is built with Milkdown.js markdown editor. Content is saved both as markdown, and also converted to html for easy rendering with image tags converted to responsive images. Currently supported features for the editor are Headings, Images, Lists, Blockquote, Tables, and various formatting such as setting hyperlinks, bold, italic, and underline.

editor screenshot

Current rendered styling in the editor is styled with Tailwind Typography prose. Content styling can be customized on the templates it will be used on and can be rendered through Phoenix.HTML.raw/1

<div class="prose">
  <%= raw(post.html) %>
</div>

Generating Static pages

Literature.StaticPages.Generator, Literature.StaticPages.Layout, and Literature.StaticPages.Templates will be used to generate your static HTML files.

  1. Create your templates module using the built in layout. Here you can fully customize the content of your page and add more assigns using Phoenix.Components.
defmodule MyAppWeb.Blog.Templates do
  use Phoenix.Component
  @behavior Literature.StaticPages.Templates

  import Literature.StaticPages.Layout, only: [layout: 1]

  @impl true
  def index(assigns) do
    ~H"""
    <.layout {assigns}>
      <h1>{@publication.name}</h1>
      <h2>Posts</h2>
      <ul>
        <li :for={post <- @posts}>
          {post.title}
        </li>
      </ul>
    </.layout>
    """
  end

  def show(assigns) do
    ~H"""
    <.layout {assigns}>
      <h1>{@post.title}</h1>
      <p>
        {raw(@post.html)}
      </p>
    </.layout>
    """
  end
end
  1. Create your own generator file
defmodule MyAppWeb.Blog.Generator do
  alias Literature.StaticPages.Generator, as: LiteratureGenerator

  def generate_index_page do
    opts =
      [
        publication_slug: "blog",
        base_url: "http://localhost:4000",
        templates: MyAppWeb.Blog.Templates,
        write_to: :file
      ]

    LiteratureGenerator.generate(:index, opts)
  end
end

Calling MyAppWeb.Blog.Generator.generate_index_page() should generate an index.html inside your set static_pages_storage_dir. See Literature.StaticPages.Generator for more information.

Generated files can be served through Plug.Static or through a controller.

defmodule MyAppWeb.BlogController do
  use Phoenix.Controller

  def index(conn, _params) do
    static_file_path = "/tmp/literature/static_pages/index.html"

    conn
    |> put_resp_header("content-type", "text/html; charset=utf-8")
    |> Conn.send_file(200, static_file_path)
  end
end