lib/forage_web/assets.ex

defmodule ForageWeb.Assets do
  @moduledoc """
  Utilities to deal with static assets required by Forage's
  more advanced widgets, namely the select widgets.

  Forage uses Bootstrap v4 instead of the newer Bootstrap v5.
  """

  @external_resource "lib/forage_web/assets/activate-forage-select.js"

  # Extract the list of supported themes from the ones in the `priv/` directory
  bootswatch_themes =
    File.ls!("priv/static/themes")
    |> List.delete("default")
    |> Enum.sort()

  # Ensure the "default" theme comes first
  bootstrap_themes = ["default" | bootswatch_themes]

  for theme <- bootstrap_themes do
    path = "priv/static/themes/#{theme}/bootstrap.min.css"
    @external_resource path
  end

  for file <- File.ls!("priv/static/select/") do
    path = "priv/select/#{file}"
    @external_resource path
  end

  # Add the supported themes to a module attribute
  @bootstrap_themes bootstrap_themes

  url_for_theme = fn
    # Not strictly a Bootswatch theme, but including it here is useful
    "default" -> "https://getbootstrap.com/docs/4.6/getting-started/introduction/"
    # Link to Boostwatch v4
    theme -> "https://bootswatch.com/4/#{theme}/"
  end

  # Build a markdown excerpt containing a list of suppoered theme
  # and their respective themes
  theme_list_markdown =
    bootstrap_themes
    |> Enum.map(fn theme -> "  - [#{theme}](#{url_for_theme.(theme)})" end)
    |> Enum.join("\n")

  @activate_select (
    "<script>" <>
    File.read!("lib/forage_web/assets/activate-forage-select.js") <>
    "</script>"
  )


  @forage_select_assets_from_cdn """
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"/>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ttskch/select2-bootstrap4-theme/dist/select2-bootstrap4.min.css"/>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
  """

  @forage_select_assets_from_app """
  <link rel="stylesheet" href="/_forage/select/select2.min.css"/>
  <link rel="stylesheet" href="/_forage/select/select2-bootstrap4.min.css"/>
  <script src="/_forage/select/jquery.min.js"></script>
  <script src="/_forage/select/select2.min.js"></script>
  """


  @doc """
  Adds a `<script>` tag to the webpage with the javascript required to run
  the forage select widgets.
  Even after adding this code, you need to activate the select widget using
  `ForageWeb.Assets.activate_forage_select()`

  The Javascript is served from an external CDN.
  """
  def forage_select_assets_from_cdn() do
    {:safe, @forage_select_assets_from_cdn}
  end


  @doc """
  Adds a `<script>` tag to the webpage with the javascript required to run
  the forage select widgets.
  Even after adding this code, you need to activate the select widget using
  `ForageWeb.Assets.activate_forage_select()`

  The Javascript is served from the phoenix application.
  """
  def forage_select_assets_from_app() do
    {:safe, @forage_select_assets_from_app}
  end


  @doc """
  Add to the end of the webpage in order to activate the forage select widget.
  Adds a `<script>` tag to the webpage.
  """
  def activate_forage_select() do
    {:safe, @activate_select}
  end

  @doc """
  Renders the HTML `<link>` tag necessary to use a given Bootswatch theme,
  which are compatible with the default bootstrap classes.
  The theme is served from an external CDN.

  This function includes the *default* theme, which is the default
  bootstrap theme and not strictly part of bootswatch.
  The following themes are supported:

  #{theme_list_markdown}

  This is meant to be used inside the `<head>` tag of your application.

  Note: no specific endorsement of Bootswatch themes is intended.
  They are included here simply because they are compatible with
  the default theme and may be a simple way of adding variety
  to your website.

  The themes are served from the
  [https://cdn.jsdelivr.net/](https://cdn.jsdelivr.net/) CDN.

  ## Examples

  ```heex
  <%= ForageWeb.Assets.theme_from_cdn("superhero") %>
  ```
  """
  def theme_from_cdn("default") do
    {:safe,
     ~S[<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">]}
  end

  def theme_from_cdn(theme) when theme in @bootstrap_themes do
    {:safe,
     [
       ~S[<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.6.2/dist/],
       theme,
       ~S[/bootstrap.min.css">]
     ]}
  end

  @doc """
  Renders the HTML `<link>` tag necessary to use a given Bootswatch theme,
  which are compatible with the default bootstrap classes.
  The theme is served from an external CDN.

  This function includes the *default* theme, which is the default
  bootstrap theme and not strictly part of bootswatch.
  The following themes are supported:

  #{theme_list_markdown}

  This is meant to be used inside the `<head>` tag of your application.

  Note: no specific endorsement of Bootswatch themes is intended.
  They are included here simply because they are compatible with
  the default theme and may be a simple way of adding variety
  to your website.

  ## Examples

  ```heex
  <%= ForageWeb.Assets.theme_from_app("superhero") %>
  ```
  """
  def theme_from_app(theme) when theme in @bootstrap_themes do
    {:safe,
     [
       ~S[<link rel="stylesheet" href="/_forage/themes/],
       theme,
       ~S[/bootstrap.min.css">]
     ]}
  end

  @doc """
  Enable serving forage assets from the phoenix application
  (instead of serving from a CDN).

  It's meant to be used in the Phoenix application's endpoint
  (just like the `Plug.Static`).

  ## Example

    	defmodule MyAppWeb.Endpoint do
        use Phoenix.Endpoint, otp_app: :my_app
        # ...

        plug Plug.Static, ...

        use ForageWeb.Assets

        # ...
      end
  """
  defmacro __using__(_opts) do
    quote do
      plug Plug.Static,
        at: "_forage/",
        from: {:forage, "priv/static"},
        gzip: false,
        headers: [{"access-control-allow-origin", "*"}]
    end
  end

  @doc """
  Lists all supported bootstrap themes.

  Currently, supported bootstrap themes are:

  #{theme_list_markdown}
  """
  def supported_bootstrap_themes() do
    @bootstrap_themes
  end
end