defmodule AshHqWeb.Components.CodeExample do use Surface.LiveComponent prop text, :string, required: true prop class, :css_class prop title, :string prop start_collapsed, :boolean, default: false prop collapsible, :boolean, default: false data collapsed, :string, default: false data code, :string, default: "" def render(assigns) do ~F"""
{#if @title}
{@title}
{#if @collapsible} {/if}
{/if}
{#if !@collapsed} {#for {_line, no} <- @code}
{no}
{/for} {#else} {/if}
{#for {line, _no} <- @code}
{Phoenix.HTML.raw(line)}
{/for}
""" end def handle_event("fold", _, socket) do {:noreply, assign(socket, :collapsed, !socket.assigns.collapsed)} end def update(assigns, socket) do if assigns.start_collapsed && assigns.collapsible do {:ok, socket |> assign(assigns) |> assign(:collapsed, true) |> assign(:code, to_code(assigns[:text]))} else {:ok, socket |> assign(assigns) |> assign(:collapsed, false) |> assign(:code, to_code(assigns[:text]))} end end defp to_code(text) do # TODO: do this at compile time lines = text # this is pretty naive, won't handle things like block comments |> String.split("\n") |> Enum.reverse() |> Enum.drop_while(&(&1 in [""])) |> Enum.reverse() |> Enum.map(fn "" -> "
" line -> Makeup.highlight(line) end) count = Enum.count(lines) padding = String.length(to_string(count)) lines |> Enum.with_index() |> Enum.map(fn {line, index} -> {line, String.pad_leading(to_string(index + 1), padding, " ")} end) end end