README.md

# Tableau

[![Discord](https://img.shields.io/badge/Discord-5865F3?style=flat&logo=discord&logoColor=white&link=https://discord.gg/nNDMwTJ8)](https://discord.gg/6XdGnxVA2A)
[![Hex.pm](https://img.shields.io/hexpm/v/tableau)](https://hex.pm/packages/tableau)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/tableau/)
[![GitHub Discussions](https://img.shields.io/github/discussions/elixir-tools/tableau)](https://github.com/elixir-tools/tableau/discussions)

Static Site Generator for Elixir.

## Goals

- [x] Good code and browser reloading on file change
- [x] Easy to use the current Node.js JS/CSS tooling
- [x] Extensions
- [x] Ability to work with "data" (either dynamic data or static files)
    - [x] YAML Files
    - [x] Elixir scripts (.exs files)
- [ ] Handles stuff like Posts, RSS, sitemap, SEO.
    - [x] Posts
    - [x] RSS
    - [x] Sitemap
    - [ ] SEO
- [x] Project generator

## Installation

The easiest way to get started is to generate a new project using the `tableau.new` mix task.

Currently the generator can create a website using several different template syntaxes and assets frameworks.

- Templates
  - [HEEx](https://github.com/phoenixframework/phoenix)
  - [Temple](https://github.com/mhanberg/temple)
  - EEx
- Assets
  - [TailwindCSS](https://tailwindcss.com)
  - Vanilla (Just a regular CSS file)

Please run `mix help tableau.new` or `mix tableau.new --help` to see all of the possible flags.

```
mix archive.install hex tableau_new

mix tableau.new my_awesome_site
```

Otherwise, you can just install Tableau into a new mix project.

```elixir
def deps do
  [
    {:tableau, "~> 0.17"}
  ]
end
```

Documentation can be found at <https://hexdocs.pm/tableau>.

## Built with Tableau

| Site                                                       | Template                                                                      | Styling    | Template | Source                                                                            |
|------------------------------------------------------------|-------------------------------------------------------------------------------|------------|----------|-----------------------------------------------------------------------------------|
| [www.elixir-tools.dev](https://www.elixir-tools.dev)       | [Temple](https://github.com/mhanberg/temple)                                  | Tailwind   |          | [elixir-tools/elixir-tools.dev](https://github.com/elixir-tools/elixir-tools.dev) |
| [www.mitchellhanberg.com](https://www.mitchellhanberg.com) | [Liquid](https://github.com/edgurgel/solid)                                   | Tailwind   |          | [mhanberg/blog](https://github.com/mhanberg/blog)                                 |
| [pdx.su](https://pdx.su)                                   | [Temple](https://github.com/mhanberg/temple)                                  | CSS        |          | [paradox460/pdx.su](https://github.com/paradox460/pdx.su)                         |
| [Xmeyers](https://andyl.github.io/xmeyers)                 | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | Tailwind   |          | [andyl/xmeyers](https://github.com/andyl/xmeyers)                                 |
| [0x7f](https://0x7f.dev)                                   | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | magick.css |          | [0x7fdev/site](https://github.com/0x7fdev/site)                                   |
| Hackery                                                    | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | Tailwind   | ✅       | [mhanberg/hackery](https://github.com/mhanberg/hackery)                           |
| [doneth.dev](https://doneth.dev)                           | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | Tailwind   |          | [JohnDoneth/doneth.dev](https://github.com/JohnDoneth/doneth.dev)                 |
| [joelkoch.dev](https://joelkoch.dev)                       | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | Tailwind   |          | [joelpaulkoch/joelkoch.dev](https://github.com/joelpaulkoch/joelkoch.dev)         |
| [www.ethangunderson.com](https://www.ethangunderson.com/)  | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | Tailwind   |          | [ethangunderson/website](https://github.com/ethangunderson/website)               |
| [https://adrienanselme.com/](https://adrienanselme.com/)   | [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) | Tailwind   |          | [adanselm/adanselm.github.io](https://github.com/adanselm/adanselm.github.io)     |

## Getting Started

The examples in the README use the [Temple](https://github.com/mhanberg/temple) library to demonstrate that Tableau can be used with any markup language of your choice. You could easily use the builtin EEx, or use HEEx, Surface, or HAML.

### Layouts

Layouts are modules that use the `use Tableau.Layout` macro.

Layouts have access to the `@site` and `@page` assign.

The `@site` assign contains your site's config.

The `@page` assign contains all the options passed to the `use Tableau.Page` macro.

```elixir
defmodule MySite.RootLayout do
  use Tableau.Layout

  import Temple

  def template(assigns) do
    temple do
      "<!DOCTYPE html>"

      html lang: "en" do
        head do
          meta charset: "utf-8"
          meta http_equiv: "X-UA-Compatible", content: "IE=edge"
          meta name: "viewport", content: "width=device-width, initial-scale=1.0"

          title do
            @page.some_assign
          end

          link rel: "stylesheet", href: "/css/site.css"
        end

        body class: "font-sans" do
          main class: "container mx-auto px-2" do
            div class: "border-4 border-green-500" do
              a class: "text-blue-500 hover:underline", href: "/about" do
                "About"
              end

              a class: "text-blue-500 hover:underline", href: "/posts" do
                "Posts"
              end

              render @inner_content
            end
          end
        end

        if Mix.env() == :dev do
          c &Tableau.live_reload/1
        end
      end
    end
  end
end
```

#### Page

Pages are modules that use the `use Tableau.Page` macro.

Required options:

* `:layout` - which layout module to use.
* `:permalink` - the permalink of the page

Any remaining options are arbitrary and will be available under the `@page` assign available to layout and page templates.

```elixir
defmodule MySite.AboutPage do
  use Tableau.Page,
    layout: MySite.RootLayout,
    permalink: "/about",
    some_assign: "foo"

  import Temple

  def template(assigns) do
    temple do
      span class: "text-red-500 font-bold" do
        "i'm a super cool and smart!"
      end
    end
  end
end
```

### Live Reloading

You can specify a set of directories/files to watch for changes, and the browser will automatically refresh.

```elixir
# config/config.exs
import Config

config :tableau, :reloader,
  patterns: [
    ~r"lib/layouts/.*.ex",
    ~r"lib/pages/.*.ex",
    ~r"lib/components.ex",
    ~r"_site/.*.css"
  ]
```

All you need to do is render the `Tableau.live_reload/1` component right after your `body` tag.

```elixir
# lib/layouts/app.ex

defmodule YourApp.Layouts.App do
  use Tableau.Layout

  import Temple

  def template(assigns) do
    temple do
      "<!DOCTYPE html>"

      html lang: "en" do
        head do
          meta charset: "utf-8"
          meta http_equiv: "X-UA-Compatible", content: "IE=edge"
          meta name: "viewport", content: "width=device-width, initial-scale=1.0"

          link rel: "stylesheet", href: "/css/site.css"
        end

        body class: "font-sans" do
          main class: "container mx-auto px-2" do
            render(@inner_content)
          end
        end

        if Mix.env() == :dev do
          c &Tableau.live_reload/1
        end
      end
    end
  end
end
```


### JS/CSS

You can arbitrarily start other build tools as "watchers". This is inspired by the way Phoenix does it.

```elixir
# config/config.exs

import Config

config :tableau, :assets,
  npx: [
    "tailwindcss",
    "-o",
    "_site/css/site.css",
    "--watch"
  ]

# or if you are using a package similar to the TailwindCSS hex package

config :tableau, :assets, tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}

import_config "#{config_env()}.exs"
```

This will start a long running process that will independently build your CSS as it sees files change.

These are started automatically when you run `mix tableau.server`.

### Static Assets

Other static assets can be copied into the "out" directory by placing them in an `extra` directory in the root of your project.

This directory can be configured.

```elixir
config :tableau, :config,
  include_dir: "static"
```

### Development

The dev server can be started with `mix tableau.server`. On file change, a browser reload will be triggered and the page your requesting will be re-built during the request.