mirror of
https://github.com/ash-project/ash_hq.git
synced 2024-09-19 12:53:49 +12:00
improvement: make LivePatch
work again
improvement: add some experimental class helpers
This commit is contained in:
parent
92fd65982f
commit
5c4bfda6a4
15 changed files with 614 additions and 434 deletions
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
elixir 1.14.0
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
144
lib/ash_hq/tailwind.ex
Normal 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
|
|
@ -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
|
||||
|
|
85
lib/ash_hq_web/components/app_view/top_bar.ex
Normal file
85
lib/ash_hq_web/components/app_view/top_bar.ex
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]))}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -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
5
test/tailwind_test.exs
Normal file
|
@ -0,0 +1,5 @@
|
|||
defmodule AshHq.Test.TailwindTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest AshHq.Tailwind, import: true
|
||||
end
|
Loading…
Reference in a new issue