improvement: make LivePatch work again

improvement: add some experimental class helpers
This commit is contained in:
Zach Daniel 2022-09-12 16:47:14 -04:00
parent 92fd65982f
commit 5c4bfda6a4
15 changed files with 614 additions and 434 deletions

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
elixir 1.14.0

View file

@ -228,6 +228,6 @@ defmodule AshHq.Accounts.User do
end
validations do
validate match(:email, ~r/^[^\s]+@[^\s]+$/, "must have the @ sign and no spaces")
validate match(:email, ~r/^[^\s]+@[^\s]+$/), message: "must have the @ sign and no spaces"
end
end

View file

@ -1,10 +1,34 @@
defmodule AshHq.Colors do
defmodule AshHq.Classes do
@moduledoc "Static values for tailwind colors"
@colors "assets/tailwind.colors.json"
|> File.read!()
|> Jason.decode!()
def classes(classes) when is_list(classes) do
classes
|> Enum.filter(fn
{_classes, condition} ->
condition
_ ->
true
end)
|> Enum.join(" ")
end
def classes(classes) when is_binary(classes) do
classes
end
def classes(base, classes) do
merge(classes(base), classes(classes))
end
def merge(base, extension) do
AshHq.Tailwind.merge(base, extension)
end
for {key, value} when is_binary(value) <- @colors do
# sobelow_skip ["DOS.BinToAtom"]
def unquote(:"#{String.replace(key, "-", "_")}")() do

View file

@ -33,6 +33,6 @@ defmodule AshHq.MailingList.Email do
end
validations do
validate match(:email, ~r/^[^\s]+@[^\s]+$/, "must have the @ sign and no spaces")
validate match(:email, ~r/^[^\s]+@[^\s]+$/), message: "must have the @ sign and no spaces"
end
end

144
lib/ash_hq/tailwind.ex Normal file
View file

@ -0,0 +1,144 @@
defmodule AshHq.Tailwind do
defstruct [:p, :m, :w, classes: MapSet.new()]
defmodule Directions do
defstruct [:l, :r, :t, :b, :x, :y, :all]
@type t :: %__MODULE__{
l: String.t(),
r: String.t(),
t: String.t(),
b: String.t(),
x: String.t(),
y: String.t()
}
end
@type directional_value :: String.t() | Directions.t()
@type t :: %__MODULE__{
p: directional_value(),
m: directional_value(),
w: String.t()
}
def new(classes) do
merge(%__MODULE__{}, classes)
end
@doc """
Semantically merges two lists of tailwind classes, treating the first as a base and the second as overrides
Examples
iex> merge("p-4", "p-2") |> to_string()
"p-2"
iex> merge("p-2", "p-4") |> to_string()
"p-4"
iex> merge("p-4", "px-2") |> to_string()
"px-2 py-4"
"""
def merge(tailwind, classes) when is_binary(tailwind) do
merge(new(tailwind), classes)
end
def merge(tailwind, classes) when is_binary(classes) do
classes
|> String.split()
|> Enum.reduce(tailwind, &merge_class(&2, &1))
end
def merge(tailwind, %__MODULE__{} = classes) do
merge(tailwind, to_string(classes))
end
def merge(_tailwind, value) do
raise "Cannot merge #{inspect(value)}"
end
@directional ~w(p m)a
for class <- @directional do
string_class = to_string(class)
def merge_class(tailwind, unquote(string_class) <> "-" <> value) do
Map.put(tailwind, unquote(class), value)
end
def merge_class(%{unquote(class) => nil} = tailwind, unquote(string_class) <> "x-" <> value) do
Map.put(tailwind, unquote(class), %Directions{x: value})
end
def merge_class(%{unquote(class) => all} = tailwind, unquote(string_class) <> "x-" <> value)
when is_binary(all) do
Map.put(tailwind, unquote(class), %Directions{y: all, x: value})
end
def merge_class(
%{unquote(class) => %Directions{} = directions} = tailwind,
unquote(string_class) <> "x" <> value
) do
Map.put(tailwind, unquote(class), %{directions | x: value, l: nil, r: nil})
end
def merge_class(%{unquote(class) => nil} = tailwind, unquote(string_class) <> "y-" <> value) do
Map.put(tailwind, unquote(class), %Directions{y: value})
end
def merge_class(%{unquote(class) => all} = tailwind, unquote(string_class) <> "y-" <> value)
when is_binary(all) do
Map.put(tailwind, unquote(class), %Directions{x: all, y: value})
end
def merge_class(
%{unquote(class) => %Directions{} = directions} = tailwind,
unquote(string_class) <> "y-" <> value
) do
Map.put(tailwind, unquote(class), %{directions | y: value, t: nil, b: nil})
end
end
def merge_class(tailwind, class) do
%{tailwind | classes: MapSet.put(tailwind.classes, class)}
end
defimpl String.Chars do
def to_string(tailwind) do
[
directional(tailwind.p, :p),
directional(tailwind.m, :m),
Enum.join(tailwind.classes, " ")
]
|> Enum.filter(& &1)
|> Enum.join(" ")
end
defp directional(nil, _key), do: nil
defp directional(value, key) when is_binary(value) do
"#{key}-#{value}"
end
defp directional(%Directions{l: l, r: r, t: t, b: b, x: x, y: y}, key) do
[
direction(t, :t, key),
direction(b, :b, key),
direction(l, :l, key),
direction(r, :r, key),
direction(x, :x, key),
direction(y, :y, key)
]
|> Enum.filter(& &1)
|> Enum.join(" ")
end
defp direction(nil, _, _), do: nil
defp direction(value, suffix, prefix), do: "#{prefix}#{suffix}-#{value}"
end
defimpl Inspect do
def inspect(tailwind, _opts) do
"Tailwind.new(\"#{to_string(tailwind)}\")"
end
end
end

View file

@ -62,7 +62,7 @@ defmodule AshHqWeb do
def component do
quote do
use Surface.Component
import AshHq.Colors
import AshHq.Classes
unquote(view_helpers())
end

View file

@ -0,0 +1,85 @@
defmodule AshHqWeb.Components.AppView.TopBar do
use Surface.Component
prop live_action, :atom, required: true
prop toggle_theme, :event, required: true
prop configured_theme, :string, required: true
alias AshHqWeb.Components.SearchBar
def render(assigns) do
~F"""
<div id="top-bar" class={
"flex justify-between items-center py-4 px-4 h-min sticky",
"border-b bg-white dark:bg-base-dark-900": @live_action == :docs_dsl
}>
<div class="flex flex-row align-baseline">
<a href="/">
<img class="h-6 md:h-10 hidden dark:block" src="/images/ash-framework-dark.png">
<img class="h-6 md:h-10 dark:hidden" src="/images/ash-framework-light.png">
</a>
</div>
{#if @live_action == :docs_dsl}
<SearchBar class="hidden lg:block" />
{/if}
<div class="flex flex-row align-middle items-center space-x-2">
<a href="/docs/guides/ash/latest/tutorials/get-started.md" target="_blank">
<Heroicons.Solid.BookOpenIcon class="w-8 h-8 dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600" />
</a>
<a href="https://github.com/ash-project" target="_blank">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600"
viewBox="0 0 24 24"
><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /></svg>
</a>
<a href="https://discord.gg/D7FNG2q" target="_blank">
<svg
class="w-6 h-6 fill-black dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600"
viewBox="0 0 71 55"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0)">
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" />
</g>
<defs>
<clipPath id="clip0">
<rect width="71" height="55" fill="white" />
</clipPath>
</defs>
</svg>
</a>
<a href="https://twitter.com/ashframework" target="_blank">
<svg
class="w-6 h-6 dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600"
version="1.1"
viewBox="0 0 248 204"
style="enable-background:new 0 0 248 204;"
>
<g id="Logo_1_">
<path d="M221.95,51.29c0.15,2.17,0.15,4.34,0.15,6.53c0,66.73-50.8,143.69-143.69,143.69v-0.04
C50.97,201.51,24.1,193.65,1,178.83c3.99,0.48,8,0.72,12.02,0.73c22.74,0.02,44.83-7.61,62.72-21.66
c-21.61-0.41-40.56-14.5-47.18-35.07c7.57,1.46,15.37,1.16,22.8-0.87C27.8,117.2,10.85,96.5,10.85,72.46c0-0.22,0-0.43,0-0.64
c7.02,3.91,14.88,6.08,22.92,6.32C11.58,63.31,4.74,33.79,18.14,10.71c25.64,31.55,63.47,50.73,104.08,52.76
c-4.07-17.54,1.49-35.92,14.61-48.25c20.34-19.12,52.33-18.14,71.45,2.19c11.31-2.23,22.15-6.38,32.07-12.26
c-3.77,11.69-11.66,21.62-22.2,27.93c10.01-1.18,19.79-3.86,29-7.95C240.37,35.29,231.83,44.14,221.95,51.29z" />
</g>
</svg>
</a>
<div>|</div>
<button :on-click={@toggle_theme}>
{#case @configured_theme}
{#match "light"}
<Heroicons.Solid.SunIcon class="w-6 h-6 hover:text-base-light-600" />
{#match "system"}
<Heroicons.Solid.DesktopComputerIcon class="w-6 h-6 fill-base-light-400 dark:text-black dark:hover:text-base-dark-600 hover:text-base-light-600" />
{#match _}
<Heroicons.Solid.MoonIcon class="w-6 h-6 fill-base-light-400 hover:fill-base-light-200 hover:text-base-light-200" />
{/case}
</button>
</div>
</div>
"""
end
end

View file

@ -2,7 +2,7 @@ defmodule AshHqWeb.Components.CalloutText do
@moduledoc "Highlights some text on the page"
use Surface.Component
import AshHq.Colors
import AshHq.Classes
prop text, :string, required: true

View file

@ -3,24 +3,24 @@ defmodule AshHqWeb.Components.DocSidebar do
use Surface.Component
alias AshHqWeb.DocRoutes
alias Surface.Components.Link
alias Surface.Components.LivePatch
prop(class, :css_class, default: "")
prop(libraries, :list, required: true)
prop(extension, :any, default: nil)
prop(guide, :any, default: nil)
prop(library, :any, default: nil)
prop(library_version, :any, default: nil)
prop(selected_versions, :map, default: %{})
prop(id, :string, required: true)
prop(dsl, :any, required: true)
prop(module, :any, required: true)
prop(sidebar_state, :map, required: true)
prop(collapse_sidebar, :event, required: true)
prop(expand_sidebar, :event, required: true)
prop(add_version, :event, required: true)
prop(remove_version, :event, required: true)
prop(change_version, :event, required: true)
prop class, :css_class, default: ""
prop libraries, :list, required: true
prop extension, :any, default: nil
prop guide, :any, default: nil
prop library, :any, default: nil
prop library_version, :any, default: nil
prop selected_versions, :map, default: %{}
prop id, :string, required: true
prop dsl, :any, required: true
prop module, :any, required: true
prop sidebar_state, :map, required: true
prop collapse_sidebar, :event, required: true
prop expand_sidebar, :event, required: true
prop add_version, :event, required: true
prop remove_version, :event, required: true
prop change_version, :event, required: true
@spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do
@ -81,7 +81,7 @@ defmodule AshHqWeb.Components.DocSidebar do
<ul>
{#for guide <- guides}
<li class="ml-1">
<Link
<LivePatch
to={DocRoutes.doc_link(guide, @selected_versions)}
class={
"flex items-center p-1 text-base font-normal text-base-light-900 rounded-lg dark:text-base-dark-200 hover:bg-base-light-100 dark:hover:bg-base-dark-700",
@ -90,7 +90,7 @@ defmodule AshHqWeb.Components.DocSidebar do
>
<Heroicons.Outline.BookOpenIcon class="h-4 w-4" />
<span class="ml-3 mr-2">{guide.name}</span>
</Link>
</LivePatch>
</li>
{/for}
</ul>
@ -126,7 +126,7 @@ defmodule AshHqWeb.Components.DocSidebar do
<ul>
{#for extension <- extensions}
<li class="ml-1">
<Link
<LivePatch
to={DocRoutes.doc_link(extension, @selected_versions)}
class={
"flex items-center p-1 text-base font-normal text-base-light-900 rounded-lg dark:text-base-dark-200 hover:bg-base-light-100 dark:hover:bg-base-dark-700",
@ -135,7 +135,7 @@ defmodule AshHqWeb.Components.DocSidebar do
>
{render_icon(assigns, extension.type)}
<span class="ml-3 mr-2">{extension.name}</span>
</Link>
</LivePatch>
{#if @extension && @extension.id == extension.id && !Enum.empty?(extension.dsls)}
{render_dsls(assigns, extension.dsls, [])}
{/if}
@ -164,7 +164,7 @@ defmodule AshHqWeb.Components.DocSidebar do
</div>
{#for module <- modules}
<li class="ml-4">
<Link
<LivePatch
to={DocRoutes.doc_link(module, @selected_versions)}
class={
"flex items-center space-x-2 pt-1 text-base font-normal text-base-light-900 rounded-lg dark:text-base-dark-100 hover:bg-base-light-100 dark:hover:bg-base-light-700",
@ -173,7 +173,7 @@ defmodule AshHqWeb.Components.DocSidebar do
>
<Heroicons.Outline.CodeIcon class="h-4 w-4" />
<span class="">{module.name}</span>
</Link>
</LivePatch>
</li>
{/for}
{/for}
@ -210,7 +210,7 @@ defmodule AshHqWeb.Components.DocSidebar do
{/if}
{/if}
{/if}
<Link
<LivePatch
to={DocRoutes.doc_link(dsl, @selected_versions)}
class={
"flex items-center p-1 text-base font-normal rounded-lg hover:text-primary-light-300",
@ -218,7 +218,7 @@ defmodule AshHqWeb.Components.DocSidebar do
}
>
{dsl.name}
</Link>
</LivePatch>
</div>
{#if @sidebar_state[dsl.id] == "open" ||
(@dsl && List.starts_with?(@dsl.path ++ [@dsl.name], path ++ [dsl.name]))}

View file

@ -6,22 +6,22 @@ defmodule AshHqWeb.Components.Search do
alias AshHqWeb.Components.CalloutText
alias AshHqWeb.DocRoutes
alias Surface.Components.{Form, LiveRedirect}
alias Surface.Components.{Form, LivePatch}
alias Surface.Components.Form.{Checkbox, Label}
prop(open, :boolean, default: false)
prop(close, :event, required: true)
prop(libraries, :list, required: true)
prop(selected_versions, :map, required: true)
prop(change_versions, :event, required: true)
prop(selected_types, :list, required: true)
prop(change_types, :event, required: true)
prop(uri, :string, required: true)
prop open, :boolean, default: false
prop close, :event, required: true
prop libraries, :list, required: true
prop selected_versions, :map, required: true
prop change_versions, :event, required: true
prop selected_types, :list, required: true
prop change_types, :event, required: true
prop uri, :string, required: true
data(versions, :map, default: %{})
data(search, :string, default: "")
data(item_list, :list, default: [])
data(selected_item, :string)
data versions, :map, default: %{}
data search, :string, default: ""
data item_list, :list, default: []
data selected_item, :string
def render(assigns) do
~F"""
@ -100,7 +100,7 @@ defmodule AshHqWeb.Components.Search do
defp render_items(assigns, items) do
~F"""
{#for item <- items}
<LiveRedirect to={DocRoutes.doc_link(item, @selected_versions)} opts={id: item.id}>
<LivePatch to={DocRoutes.doc_link(item, @selected_versions)} opts={id: item.id}>
<div class={
"rounded-lg mb-4 py-2 px-2 hover:bg-base-dark-300 dark:hover:bg-base-dark-700",
"bg-base-light-400 dark:bg-base-dark-600": @selected_item.id == item.id,
@ -133,7 +133,7 @@ defmodule AshHqWeb.Components.Search do
{raw(item.search_headline)}
</div>
</div>
</LiveRedirect>
</LivePatch>
{/for}
"""
end

View file

@ -1,35 +1,35 @@
defmodule AshHqWeb.Pages.Docs do
@moduledoc "The page for showing documentation"
use Surface.Component
use Surface.LiveComponent
alias AshHqWeb.Components.{CalloutText, DocSidebar, RightNav, Tag}
alias AshHqWeb.DocRoutes
alias Phoenix.LiveView.JS
require Logger
prop(change_versions, :event, required: true)
prop(selected_versions, :map, required: true)
prop(libraries, :list, default: [])
prop(uri, :string)
prop(sidebar_state, :map, required: true)
prop(collapse_sidebar, :event, required: true)
prop(expand_sidebar, :event, required: true)
prop change_versions, :event, required: true
prop selected_versions, :map, required: true
prop libraries, :list, default: []
prop uri, :string
prop sidebar_state, :map, required: true
prop collapse_sidebar, :event, required: true
prop expand_sidebar, :event, required: true
prop remove_version, :event
prop add_version, :event
prop change_version, :event
prop params, :map, required: true
prop(library, :any)
prop(extension, :any)
prop(docs, :any)
prop(library_version, :any)
prop(guide, :any)
prop(doc_path, :list, default: [])
prop(dsls, :list, default: [])
prop(dsl, :any)
prop(options, :list, default: [])
prop(module, :any)
prop(remove_version, :event)
prop(add_version, :event)
prop(change_version, :event)
data(positional_options, :list)
data library, :any
data extension, :any
data docs, :any
data library_version, :any
data guide, :any
data doc_path, :list, default: []
data dsls, :list, default: []
data dsl, :any
data options, :list, default: []
data module, :any
data positional_options, :list
@spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do
@ -272,6 +272,10 @@ defmodule AshHqWeb.Pages.Docs do
"""
end
def update(assigns, socket) do
{:ok, socket |> assign(assigns) |> load_docs()}
end
defp render_source_code_link(assigns, module_or_function, library, library_version) do
~F"""
{#if module_or_function.file}
@ -562,4 +566,276 @@ defmodule AshHqWeb.Pages.Docs do
end
end
end
defp load_docs(socket) do
new_libraries =
socket.assigns.libraries
|> Enum.map(fn library ->
latest_version = AshHqWeb.Helpers.latest_version(library)
Map.update!(library, :versions, fn versions ->
Enum.map(versions, fn version ->
if (socket.assigns[:selected_versions][library.id] in ["latest", nil, ""] &&
latest_version &&
version.id == latest_version.id) ||
version.id == socket.assigns[:selected_versions][library.id] do
dsls_query =
AshHq.Docs.Dsl
|> Ash.Query.sort(order: :asc)
|> load_for_search(socket.assigns[:params]["dsl_path"])
options_query =
AshHq.Docs.Option
|> Ash.Query.sort(order: :asc)
|> load_for_search(socket.assigns[:params]["dsl_path"])
functions_query =
AshHq.Docs.Function
|> Ash.Query.sort(name: :asc, arity: :asc)
|> load_for_search(socket.assigns[:params]["module"])
guides_query =
AshHq.Docs.Guide
|> Ash.Query.new()
|> load_for_search(socket.assigns[:params]["guide"])
modules_query =
AshHq.Docs.Module
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(functions: functions_query)
|> load_for_search(socket.assigns[:params]["module"])
extensions_query =
AshHq.Docs.Extension
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(options: options_query, dsls: dsls_query)
|> load_for_search(socket.assigns[:params]["extension"])
AshHq.Docs.load!(version,
extensions: extensions_query,
guides: guides_query,
modules: modules_query
)
else
version
end
end)
end)
end)
socket
|> assign(:libraries, new_libraries)
|> assign_library()
|> assign_extension()
|> assign_guide()
|> assign_module()
|> assign_dsl()
|> assign_docs()
end
defp load_for_search(query, docs_for) do
query
|> Ash.Query.load(AshHq.Docs.Extensions.Search.load_for_search(query.resource))
|> deselect_doc_attributes()
|> load_docs_for(docs_for)
end
defp load_docs_for(query, nil), do: query
defp load_docs_for(query, []), do: query
defp load_docs_for(query, true) do
query.resource
|> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce(query, fn {source, target}, query ->
Ash.Query.select(query, [source, target])
end)
end
defp load_docs_for(query, name) when is_list(name) do
Ash.Query.load(query, html_for: %{for: Enum.join(name, "/")})
end
defp load_docs_for(query, name) do
Ash.Query.load(query, html_for: %{for: name})
end
defp deselect_doc_attributes(query) do
query.resource
|> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce(query, fn {source, target}, query ->
Ash.Query.deselect(query, [source, target])
end)
end
defp assign_library(socket) do
case Enum.find(
socket.assigns.libraries,
&(&1.name == socket.assigns.params["library"])
) do
nil ->
socket
|> assign(:library, nil)
|> assign(:library_version, nil)
library ->
socket =
if socket.assigns[:params]["version"] do
library_version =
case socket.assigns[:params]["version"] do
"latest" ->
AshHqWeb.Helpers.latest_version(library)
version ->
Enum.find(
library.versions,
&(&1.version == version)
)
end
if library_version do
socket =
assign(
socket,
library_version: library_version
)
if socket.assigns.params["version"] != "latest" &&
(!socket.assigns[:library] ||
socket.assigns.params["library"] !=
socket.assigns.library.name) do
new_selected_versions =
Map.put(socket.assigns.selected_versions, library.id, library_version.id)
socket
|> assign(selected_versions: new_selected_versions)
|> push_event("selected-versions", new_selected_versions)
else
socket
end
else
assign(socket, :library_version, nil)
end
else
assign(socket, :library_version, nil)
end
assign(socket, :library, library)
end
end
defp assign_extension(socket) do
if socket.assigns.library_version && socket.assigns[:params]["extension"] do
extensions = socket.assigns.library_version.extensions
assign(socket,
extension:
Enum.find(extensions, fn extension ->
extension.sanitized_name == socket.assigns[:params]["extension"]
end)
)
else
assign(socket, :extension, nil)
end
end
defp assign_guide(socket) do
guide =
if socket.assigns[:params]["guide"] && socket.assigns.library_version do
Enum.find(socket.assigns.library_version.guides, fn guide ->
guide.route == Enum.join(socket.assigns[:params]["guide"], "/")
end)
else
nil
end
assign(socket, :guide, guide)
end
defp assign_dsl(socket) do
case socket.assigns[:params]["dsl_path"] do
nil ->
assign(socket, :dsl, nil)
path ->
path = Enum.join(path, "/")
dsl =
Enum.find(
socket.assigns.extension.dsls,
&(&1.sanitized_path == path)
)
new_state = Map.put(socket.assigns.sidebar_state, dsl.id, "open")
unless socket.assigns.sidebar_state[dsl.id] == "open" do
send(self(), {:new_sidebar_state, new_state})
end
socket
|> assign(
:dsl,
dsl
)
end
end
defp assign_module(socket) do
if socket.assigns.library && socket.assigns.library_version &&
socket.assigns[:params]["module"] do
module =
Enum.find(
socket.assigns.library_version.modules,
&(&1.sanitized_name == socket.assigns[:params]["module"])
)
assign(socket,
module: module
)
else
assign(socket, :module, nil)
end
end
defp assign_docs(socket) do
cond do
socket.assigns.module ->
assign(socket,
docs: socket.assigns.module.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.module.name],
options: []
)
socket.assigns.dsl ->
assign(socket,
docs: socket.assigns.dsl.html_for,
doc_path:
[
socket.assigns.library.name,
socket.assigns.extension.name
] ++ socket.assigns.dsl.path ++ [socket.assigns.dsl.name],
options:
Enum.filter(
socket.assigns.extension.options,
&(&1.path == socket.assigns.dsl.path ++ [socket.assigns.dsl.name])
)
)
socket.assigns.extension ->
assign(socket,
docs: socket.assigns.extension.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.extension.name],
options: []
)
socket.assigns.guide ->
assign(socket,
docs: socket.assigns.guide.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.guide.name],
options: []
)
true ->
assign(socket, docs: "", doc_path: [], dsls: [], options: [])
end
end
end

View file

@ -38,8 +38,8 @@
</head>
<body class="h-full">
<%= @inner_content %>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.init(".mermaid")</script>
<script defer src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script defer>mermaid.init(".mermaid")</script>
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/assets/app.js") %>"></script>
</body>
</html>

View file

@ -3,7 +3,8 @@ defmodule AshHqWeb.AppViewLive do
use Surface.LiveView,
container: {:div, class: "h-full"}
alias AshHqWeb.Components.{Search, SearchBar}
alias AshHqWeb.Components.Search
alias AshHqWeb.Components.AppView.TopBar
alias AshHqWeb.Pages.{Docs, Home, LogIn, Register, ResetPassword, UserSettings}
alias Phoenix.LiveView.JS
alias Surface.Components.Context
@ -63,77 +64,7 @@ defmodule AshHqWeb.AppViewLive do
"overflow-hidden": @live_action == :docs_dsl
}
>
<div id="top-bar" class={
"flex justify-between items-center py-4 px-4 h-min sticky",
"border-b bg-white dark:bg-base-dark-900": @live_action == :docs_dsl
}>
<div class="flex flex-row align-baseline">
<a href="/">
<img class="h-6 md:h-10 hidden dark:block" src="/images/ash-framework-dark.png">
<img class="h-6 md:h-10 dark:hidden" src="/images/ash-framework-light.png">
</a>
</div>
{#if @live_action == :docs_dsl}
<SearchBar class="hidden lg:block" />
{/if}
<div class="flex flex-row align-middle items-center space-x-2">
<a href="/docs/guides/ash/latest/tutorials/get-started.md" target="_blank">
<Heroicons.Solid.BookOpenIcon class="w-8 h-8 dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600" />
</a>
<a href="https://github.com/ash-project" target="_blank">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600"
viewBox="0 0 24 24"
><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /></svg>
</a>
<a href="https://discord.gg/D7FNG2q" target="_blank">
<svg
class="w-6 h-6 fill-black dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600"
viewBox="0 0 71 55"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0)">
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" />
</g>
<defs>
<clipPath id="clip0">
<rect width="71" height="55" fill="white" />
</clipPath>
</defs>
</svg>
</a>
<a href="https://twitter.com/ashframework" target="_blank">
<svg
class="w-6 h-6 dark:fill-base-dark-400 dark:hover:fill-base-dark-200 hover:fill-base-light-600"
version="1.1"
viewBox="0 0 248 204"
style="enable-background:new 0 0 248 204;"
>
<g id="Logo_1_">
<path d="M221.95,51.29c0.15,2.17,0.15,4.34,0.15,6.53c0,66.73-50.8,143.69-143.69,143.69v-0.04
C50.97,201.51,24.1,193.65,1,178.83c3.99,0.48,8,0.72,12.02,0.73c22.74,0.02,44.83-7.61,62.72-21.66
c-21.61-0.41-40.56-14.5-47.18-35.07c7.57,1.46,15.37,1.16,22.8-0.87C27.8,117.2,10.85,96.5,10.85,72.46c0-0.22,0-0.43,0-0.64
c7.02,3.91,14.88,6.08,22.92,6.32C11.58,63.31,4.74,33.79,18.14,10.71c25.64,31.55,63.47,50.73,104.08,52.76
c-4.07-17.54,1.49-35.92,14.61-48.25c20.34-19.12,52.33-18.14,71.45,2.19c11.31-2.23,22.15-6.38,32.07-12.26
c-3.77,11.69-11.66,21.62-22.2,27.93c10.01-1.18,19.79-3.86,29-7.95C240.37,35.29,231.83,44.14,221.95,51.29z" />
</g>
</svg>
</a>
<div>|</div>
<button phx-click="toggle_theme">
{#case @configured_theme}
{#match "light"}
<Heroicons.Solid.SunIcon class="w-6 h-6 hover:text-base-light-600" />
{#match "system"}
<Heroicons.Solid.DesktopComputerIcon class="w-6 h-6 fill-base-light-400 dark:text-black dark:hover:text-base-dark-600 hover:text-base-light-600" />
{#match _}
<Heroicons.Solid.MoonIcon class="w-6 h-6 fill-base-light-400 hover:fill-base-light-200 hover:text-base-light-200" />
{/case}
</button>
</div>
</div>
<TopBar live_action={@live_action} toggle_theme="toggle_theme" configured_theme={@configured_theme}/>
{#for flash <- List.wrap(live_flash(@flash, :error))}
<p class="alert alert-warning" role="alert">{flash}</p>
{/for}
@ -145,7 +76,9 @@ defmodule AshHqWeb.AppViewLive do
<Home id="home" />
{#match :docs_dsl}
<Docs
id="docs"
uri={@uri}
params={@params}
change_version="change_version"
remove_version="remove_version"
add_version="add_version"
@ -155,16 +88,6 @@ defmodule AshHqWeb.AppViewLive do
change_versions="change-versions"
selected_versions={@selected_versions}
libraries={@libraries}
library={@library}
extension={@extension}
docs={@docs}
library_version={@library_version}
guide={@guide}
doc_path={@doc_path}
dsls={@dsls}
dsl={@dsl}
options={@options}
module={@module}
/>
{#match :user_settings}
<UserSettings id="user_settings" current_user={@current_user} />
@ -256,8 +179,7 @@ defmodule AshHqWeb.AppViewLive do
def handle_params(params, uri, socket) do
{:noreply,
socket
|> assign(params: params, uri: uri)
|> load_docs()}
|> assign(params: params, uri: uri)}
end
def handle_event("remove_version", %{"library" => library}, socket) do
@ -266,8 +188,7 @@ defmodule AshHqWeb.AppViewLive do
{:noreply,
socket
|> assign(:selected_versions, new_selected_versions)
|> push_event("selected-versions", new_selected_versions)
|> load_docs()}
|> push_event("selected-versions", new_selected_versions)}
end
def handle_event("add_version", %{"library" => library}, socket) do
@ -286,8 +207,7 @@ defmodule AshHqWeb.AppViewLive do
{:noreply,
socket
|> assign(:selected_versions, new_selected_versions)
|> push_event("selected-versions", new_selected_versions)
|> load_docs()}
|> push_event("selected-versions", new_selected_versions)}
end
def handle_event("collapse_sidebar", %{"id" => id}, socket) do
@ -308,7 +228,6 @@ defmodule AshHqWeb.AppViewLive do
{:noreply,
socket
|> assign(:selected_versions, versions)
|> load_docs()
|> push_event("selected-versions", versions)}
end
@ -353,74 +272,6 @@ defmodule AshHqWeb.AppViewLive do
socket |> assign(:sidebar_state, new_state) |> push_event("sidebar-state", new_state)}
end
defp load_docs(%{assigns: %{live_action: :docs_dsl}} = socket) do
new_libraries =
socket.assigns.libraries
|> Enum.map(fn library ->
latest_version = AshHqWeb.Helpers.latest_version(library)
Map.update!(library, :versions, fn versions ->
Enum.map(versions, fn version ->
if (socket.assigns[:selected_versions][library.id] in ["latest", nil, ""] &&
latest_version &&
version.id == latest_version.id) ||
version.id == socket.assigns[:selected_versions][library.id] do
dsls_query =
AshHq.Docs.Dsl
|> Ash.Query.sort(order: :asc)
|> load_for_search(socket.assigns[:params]["dsl_path"])
options_query =
AshHq.Docs.Option
|> Ash.Query.sort(order: :asc)
|> load_for_search(socket.assigns[:params]["dsl_path"])
functions_query =
AshHq.Docs.Function
|> Ash.Query.sort(name: :asc, arity: :asc)
|> load_for_search(socket.assigns[:params]["module"])
guides_query =
AshHq.Docs.Guide
|> Ash.Query.new()
|> load_for_search(socket.assigns[:params]["guide"])
modules_query =
AshHq.Docs.Module
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(functions: functions_query)
|> load_for_search(socket.assigns[:params]["module"])
extensions_query =
AshHq.Docs.Extension
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(options: options_query, dsls: dsls_query)
|> load_for_search(socket.assigns[:params]["extension"])
AshHq.Docs.load!(version,
extensions: extensions_query,
guides: guides_query,
modules: modules_query
)
else
version
end
end)
end)
end)
socket
|> assign(:libraries, new_libraries)
|> assign_library()
|> assign_extension()
|> assign_guide()
|> assign_module()
|> assign_dsl()
|> assign_docs()
end
defp load_docs(socket), do: socket
def mount(_params, session, socket) do
socket = Context.put(socket, platform: socket.assigns.platform)
configured_theme = session["theme"] || "system"
@ -531,210 +382,4 @@ defmodule AshHqWeb.AppViewLive do
)
|> JS.dispatch("js:focus", to: "#search-input")
end
defp load_for_search(query, docs_for) do
query
|> Ash.Query.load(AshHq.Docs.Extensions.Search.load_for_search(query.resource))
|> deselect_doc_attributes()
|> load_docs_for(docs_for)
end
defp load_docs_for(query, nil), do: query
defp load_docs_for(query, []), do: query
defp load_docs_for(query, true) do
query.resource
|> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce(query, fn {source, target}, query ->
Ash.Query.select(query, [source, target])
end)
end
defp load_docs_for(query, name) when is_list(name) do
Ash.Query.load(query, html_for: %{for: Enum.join(name, "/")})
end
defp load_docs_for(query, name) do
Ash.Query.load(query, html_for: %{for: name})
end
defp deselect_doc_attributes(query) do
query.resource
|> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce(query, fn {source, target}, query ->
Ash.Query.deselect(query, [source, target])
end)
end
defp assign_guide(socket) do
guide =
if socket.assigns[:params]["guide"] && socket.assigns.library_version do
Enum.find(socket.assigns.library_version.guides, fn guide ->
guide.route == Enum.join(socket.assigns[:params]["guide"], "/")
end)
else
nil
end
assign(socket, :guide, guide)
end
defp assign_dsl(socket) do
case socket.assigns[:params]["dsl_path"] do
nil ->
assign(socket, :dsl, nil)
path ->
path = Enum.join(path, "/")
dsl =
Enum.find(
socket.assigns.extension.dsls,
&(&1.sanitized_path == path)
)
new_state = Map.put(socket.assigns.sidebar_state, dsl.id, "open")
unless socket.assigns.sidebar_state[dsl.id] == "open" do
send(self(), {:new_sidebar_state, new_state})
end
socket
|> assign(
:dsl,
dsl
)
end
end
defp assign_module(socket) do
if socket.assigns.library && socket.assigns.library_version &&
socket.assigns[:params]["module"] do
module =
Enum.find(
socket.assigns.library_version.modules,
&(&1.sanitized_name == socket.assigns[:params]["module"])
)
assign(socket,
module: module
)
else
assign(socket, :module, nil)
end
end
defp assign_docs(socket) do
cond do
socket.assigns.module ->
assign(socket,
docs: socket.assigns.module.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.module.name],
options: []
)
socket.assigns.dsl ->
assign(socket,
docs: socket.assigns.dsl.html_for,
doc_path:
[
socket.assigns.library.name,
socket.assigns.extension.name
] ++ socket.assigns.dsl.path ++ [socket.assigns.dsl.name],
options:
Enum.filter(
socket.assigns.extension.options,
&(&1.path == socket.assigns.dsl.path ++ [socket.assigns.dsl.name])
)
)
socket.assigns.extension ->
assign(socket,
docs: socket.assigns.extension.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.extension.name],
options: []
)
socket.assigns.guide ->
assign(socket,
docs: socket.assigns.guide.html_for,
doc_path: [socket.assigns.library.name, socket.assigns.guide.name],
options: []
)
true ->
assign(socket, docs: "", doc_path: [], dsls: [], options: [])
end
end
defp assign_extension(socket) do
if socket.assigns.library_version && socket.assigns[:params]["extension"] do
extensions = socket.assigns.library_version.extensions
assign(socket,
extension:
Enum.find(extensions, fn extension ->
extension.sanitized_name == socket.assigns[:params]["extension"]
end)
)
else
assign(socket, :extension, nil)
end
end
defp assign_library(socket) do
case Enum.find(
socket.assigns.libraries,
&(&1.name == socket.assigns.params["library"])
) do
nil ->
socket
|> assign(:library, nil)
|> assign(:library_version, nil)
library ->
socket =
if socket.assigns[:params]["version"] do
library_version =
case socket.assigns[:params]["version"] do
"latest" ->
AshHqWeb.Helpers.latest_version(library)
version ->
Enum.find(
library.versions,
&(&1.version == version)
)
end
if library_version do
socket =
assign(
socket,
library_version: library_version
)
if socket.assigns.params["version"] != "latest" &&
(!socket.assigns[:library] ||
socket.assigns.params["library"] !=
socket.assigns.library.name) do
new_selected_versions =
Map.put(socket.assigns.selected_versions, library.id, library_version.id)
socket
|> assign(selected_versions: new_selected_versions)
|> push_event("selected-versions", new_selected_versions)
else
socket
end
else
assign(socket, :library_version, nil)
end
else
assign(socket, :library_version, nil)
end
assign(socket, :library, library)
end
end
end

View file

@ -1,5 +1,5 @@
%{
"ash": {:hex, :ash, "2.0.0-rc.4", "bfab2443b69a7e4ff178db747df4ee9572be704bae73b9fc0cc20b3dc8a54ef2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:spark, "~> 0.1 and >= 0.1.9", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21aa47b1524d9f008d44ef8212a21dba2b1e7d5b9561a5ceb8f2958fef839af7"},
"ash": {:hex, :ash, "2.0.0-rc.4", "6c184706a5de2fab19f3b51b07cf81e6d157a7d7884d0e1bc57fe3c8ae535cc5", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:spark, "~> 0.1 and >= 0.1.9", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6e5a34aa924feda8ac9bbaaa67faf38c9bf09a970ef6872cb3b56a88588b2ca"},
"ash_phoenix": {:hex, :ash_phoenix, "1.0.0-rc.0", "7c5d780c6ebf239977ec0a3f40675ee3f3a5d6eaa4f28a95b4bc6701b61bbd67", [:mix], [{:ash, "~> 2.0.0-rc.0", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "e47246d2c7630b68a0d78d12824d73cd57d0948fe59dab736c26f79bb68b0ff3"},
"ash_postgres": {:hex, :ash_postgres, "1.0.0-rc.3", "e18e7a1c1d601e8a73b91e6f75e8f4f99f4ca58083c29354dee19942ccb0f9f7", [:mix], [{:ash, "~> 2.0.0-rc.3", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.8", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "92691ab7f2cf773aa1d5b0a79050b32549eb9de8203baf6b787b3d5681b6a1d1"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},

5
test/tailwind_test.exs Normal file
View file

@ -0,0 +1,5 @@
defmodule AshHq.Test.TailwindTest do
use ExUnit.Case, async: true
doctest AshHq.Tailwind, import: true
end