This commit is contained in:
Zach Daniel 2022-03-29 12:12:28 -04:00
parent c6c05c0fa1
commit de97413240
26 changed files with 1829 additions and 157 deletions

View file

@ -11,8 +11,10 @@
"noreply", "noreply",
"ossp", "ossp",
"plainto", "plainto",
"postprocessor",
"setweight", "setweight",
"tailwindcss", "tailwindcss",
"topbar",
"trgm", "trgm",
"tsquery", "tsquery",
"tsvector", "tsvector",

View file

@ -97,63 +97,3 @@
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
} }
.fade-in-scale {
animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys;
}
.fade-out-scale {
animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys;
}
.fade-in {
animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys;
}
.fade-out {
animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys;
}
@keyframes fade-in-scale-keys {
0% {
scale: 0.95;
opacity: 0;
}
100% {
scale: 1.0;
opacity: 1;
}
}
@keyframes fade-out-scale-keys {
0% {
scale: 1.0;
opacity: 1;
}
100% {
scale: 0.95;
opacity: 0;
}
}
@keyframes fade-in-keys {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-out-keys {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View file

@ -22,8 +22,8 @@ import "phoenix_html"
import {Socket} from "phoenix" import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view" import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar" import topbar from "../vendor/topbar"
import mermaid from "mermaid"
window.matchMedia('(prefers-color-scheme: dark)').matches mermaid.init(".mermaid")
const Hooks = {}; const Hooks = {};
@ -42,6 +42,16 @@ Hooks.ColorTheme = {
} }
} }
Hooks.Docs = {
mounted() {
console.log(this.el)
mermaid.init(".mermaid")
},
beforeUpdate() {
console.log(this.el)
},
}
Hooks.CmdK = { Hooks.CmdK = {
mounted() { mounted() {
window.addEventListener("keydown", (event) => { window.addEventListener("keydown", (event) => {
@ -115,6 +125,7 @@ window.addEventListener("phx:selected-versions", (e) => {
// connect if there are any LiveViews on the page // connect if there are any LiveViews on the page
liveSocket.connect() liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation: // expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug() // >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session

1444
assets/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/typography": "^0.5.2", "@tailwindcss/typography": "^0.5.2",
"mermaid": "^8.14.0",
"smooth-scroll-into-view-if-needed": "^1.1.33" "smooth-scroll-into-view-if-needed": "^1.1.33"
} }
} }

View file

@ -6,7 +6,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do
if Ash.Changeset.changing_attribute?(changeset, opts[:source]) do if Ash.Changeset.changing_attribute?(changeset, opts[:source]) do
source = Ash.Changeset.get_attribute(changeset, opts[:source]) source = Ash.Changeset.get_attribute(changeset, opts[:source])
case Earmark.as_html(source) do case AshHq.Docs.Extensions.RenderMarkdown.as_html(source) do
{:error, _, error_messages} -> {:error, _, error_messages} ->
Ash.Changeset.add_error( Ash.Changeset.add_error(
changeset, changeset,
@ -18,6 +18,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do
{:ok, html_doc, _} -> {:ok, html_doc, _} ->
html_doc = AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight(html_doc) html_doc = AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight(html_doc)
Ash.Changeset.force_change_attribute(changeset, opts[:destination], html_doc) Ash.Changeset.force_change_attribute(changeset, opts[:destination], html_doc)
end end
else else

View file

@ -41,7 +41,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
formatter_options: [highlight_tag: "span"] formatter_options: [highlight_tag: "span"]
) )
~s(<pre><code class="makeup #{lang}">#{highlighted}</code></pre>) ~s(<pre><code class="makeup #{lang} highlight">#{highlighted}</code></pre>)
end end
entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}] entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]

View file

@ -15,20 +15,78 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown do
sections: [@render_markdown], sections: [@render_markdown],
transformers: [AshHq.Docs.Extensions.RenderMarkdown.Transformers.AddRenderMarkdownStructure] transformers: [AshHq.Docs.Extensions.RenderMarkdown.Transformers.AddRenderMarkdownStructure]
def attributes(resource) do def render_attributes(resource) do
Ash.Dsl.Extension.get_opt(resource, [:render_markdown], :attributes, []) Ash.Dsl.Extension.get_opt(resource, [:render_markdown], :render_attributes, [])
end end
def render!(%resource{} = record, key, on_demand? \\ false) do def render!(%resource{} = record, key, on_demand? \\ false) do
cond do cond do
attributes(resource)[key] -> render_attributes(resource)[key] ->
Map.get(record, attributes(resource)[key]) Map.get(record, render_attributes(resource)[key])
on_demand? -> on_demand? ->
Earmark.as_html!(Map.get(record, key) || "") as_html!(Map.get(record, key) || "")
true -> true ->
raise "#{resource} dos not render #{key} as markdown. Pass the `on_demand?` argument as `true` to render it dynamically." raise "#{resource} dos not render #{key} as markdown. Pass the `on_demand?` argument as `true` to render it dynamically."
end end
end end
def as_html!(text) do
text
|> Earmark.as_html!(postprocessor: &add_ids/1)
|> AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight()
end
def as_html(text) do
text
|> Earmark.as_html(postprocessor: &add_ids/1)
|> case do
{:ok, html_doc, errors} ->
{:ok, AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight(html_doc), errors}
{:error, html_doc, errors} ->
{:error, html_doc, errors}
end
end
defp add_ids({tag, attrs, [contents], meta} = node)
when tag in ["h1", "h2", "h3", "h4", "h5", "h6"] and is_binary(contents) do
if meta[:handled] do
node
else
new_attrs = Enum.reject(attrs, fn {key, _} -> key == "id" end)
id = String.downcase(String.replace(contents, ~r/[^A-Za-z0-9_]/, "-"))
new_attrs = [{"id", id} | new_attrs]
{"div", [{"class", "flex flex-row items-center"}],
[
{"a", [{"href", "##{id}"}],
[
{"svg",
[
{"xmlns", "http://www.w3.org/2000/svg"},
{"class", "h-6 w-6"},
{"fill", "none"},
{"viewBox", "0 0 24 24"},
{"stroke", "currentColor"},
{"stroke-width", "2"}
],
[
{"path",
[
{"stroke-linecap", "round"},
{"stroke-linejoin", "round"},
{"d",
"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"}
], [], %{}}
], %{}}
], %{}},
{tag, new_attrs, [contents], %{handled: true}}
], %{}}
end
end
defp add_ids(other), do: other
end end

View file

@ -4,15 +4,18 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Transformers.AddRenderMarkdownStr
def transform(resource, dsl) do def transform(resource, dsl) do
resource resource
|> AshHq.Docs.Extensions.RenderMarkdown.attributes() |> AshHq.Docs.Extensions.RenderMarkdown.render_attributes()
|> Enum.reduce({:ok, dsl}, fn {source, destination}, {:ok, dsl} -> |> Enum.reduce({:ok, dsl}, fn {source, destination}, {:ok, dsl} ->
{:ok, {:ok,
dsl dsl
|> allow_nil_input(resource, destination) |> allow_nil_input(resource, destination)
|> Transformer.add_entity([:changes], :change, |> Transformer.add_entity(
[:changes],
Transformer.build_entity!(Ash.Resource.Dsl, [:changes], :change,
change: change:
{AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown, {AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown,
source: source, destination: destination} source: source, destination: destination}
)
)} )}
end) end)
end end

View file

@ -7,7 +7,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do
def transform(resource, dsl_state) do def transform(resource, dsl_state) do
config = %{ config = %{
name_attribute: AshHq.Docs.Extensions.Search.name_attribute(resource), name_attribute: AshHq.Docs.Extensions.Search.name_attribute(resource),
doc_attribute: AshHq.Docs.Extensions.Search.name_attribute(resource), doc_attribute: AshHq.Docs.Extensions.Search.doc_attribute(resource),
library_version_attribute: AshHq.Docs.Extensions.Search.library_version_attribute(resource) library_version_attribute: AshHq.Docs.Extensions.Search.library_version_attribute(resource)
} }
@ -75,7 +75,7 @@ defmodule AshHq.Docs.Extensions.Search.Transformers.AddSearchStructure do
calculation: calculation:
Ash.Query.expr( Ash.Query.expr(
fragment( fragment(
"ts_headline('simple', ?, plainto_tsquery('simple', ?), 'StartSel=\"<span class=\"\"search-hit\"\">\", StopSel=</span>')", "ts_headline('english', ?, plainto_tsquery('english', ?), 'MaxFragments=3,StartSel=\"<span class=\"\"search-hit\"\">\", StopSel=</span>')",
^ref(config.doc_attribute), ^ref(config.doc_attribute),
^arg(:query) ^arg(:query)
) )

View file

@ -4,7 +4,7 @@ defmodule AshHq.Docs.Dsl do
extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown]
render_markdown do render_markdown do
render_attributes doc: :html_doc render_attributes doc: :doc_html
end end
search do search do

View file

@ -4,12 +4,12 @@ defmodule AshHq.Docs.Extension do
extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown]
render_markdown do render_markdown do
render_attributes doc: :html_doc render_attributes doc: :doc_html
end end
# search :load_for_search do search do
load_for_search library_version: [:library_display_name, :library_name]
# end end
postgres do postgres do
table "extensions" table "extensions"

View file

@ -45,7 +45,7 @@ defmodule AshHq.Docs.Guide do
end end
calculations do calculations do
calculate :url_safe_name, :string, expr(fragment("replace(?, ' ', '-')", name)) calculate :url_safe_name, :string, expr(fragment("lower(replace(?, ' ', '-'))", name))
end end
relationships do relationships do

View file

@ -4,7 +4,7 @@ defmodule AshHq.Docs.Option do
extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown] extensions: [AshHq.Docs.Extensions.Search, AshHq.Docs.Extensions.RenderMarkdown]
render_markdown do render_markdown do
render_attributes doc: :html_doc render_attributes doc: :doc_html
end end
search do search do

View file

@ -24,7 +24,7 @@ defmodule AshHqWeb.Components.DocSidebar do
to={Routes.library_link(library, selected_version_name(library, @selected_versions))} to={Routes.library_link(library, selected_version_name(library, @selected_versions))}
class={ class={
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700", "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700",
"dark:bg-gray-600": !@extension && @library && library.id == @library.id "dark:bg-gray-600": !@guide && !@extension && @library && library.id == @library.id
} }
> >
<Heroicons.Outline.CollectionIcon class="w-6 h-6" /> <Heroicons.Outline.CollectionIcon class="w-6 h-6" />

View file

@ -0,0 +1,49 @@
defmodule AshHqWeb.Components.ProgressiveHeading do
use Surface.Component
prop depth, :integer, required: true
slot default, required: true
def render(assigns) do
~F"""
{#case @depth}
{#match 1}
<h1>
<#slot />
</h1>
{#match 2}
<h2>
<#slot />
</h2>
{#match 3}
<h3>
<#slot />
</h3>
{#match 4}
<h4>
<#slot />
</h4>
{#match 5}
<h5>
<#slot />
</h5>
{#match 6}
<h6>
<#slot />
</h6>
{#match 7}
<span>
<#slot />
</span>
{#match 8}
<span>
<#slot />
</span>
{#match 9}
<span>
<#slot />
</span>
{/case}
"""
end
end

View file

@ -23,7 +23,7 @@ defmodule AshHqWeb.Components.Search do
<div <div
id={@id} id={@id}
style="display: none;" style="display: none;"
class="absolute flex justify-center align-middle w-screen h-screen backdrop-blur-sm pb-8 bg-white bg-opacity-10" class="absolute flex justify-center align-middle w-screen h-full backdrop-blur-sm pb-8 bg-white bg-opacity-10"
phx-hook="CmdK" phx-hook="CmdK"
> >
<div <div
@ -59,9 +59,9 @@ defmodule AshHqWeb.Components.Search do
<Label field={library.id}> <Label field={library.id}>
{library.display_name} {library.display_name}
</Label> </Label>
<div> <div class="pb-2">
<Select <Select
class="text-black" class="text-black form-select rounded-md pt-1 py-2 w-3/4"
name={"versions[#{library.id}]"} name={"versions[#{library.id}]"}
selected={Map.get(@selected_versions, library.id)} selected={Map.get(@selected_versions, library.id)}
options={Enum.map(library.versions, &{&1.version, &1.id})} options={Enum.map(library.versions, &{&1.version, &1.id})}
@ -229,6 +229,11 @@ defmodule AshHqWeb.Components.Search do
} -> } ->
"#{library_display_name} #{version}" "#{library_display_name} #{version}"
%AshHq.Docs.Extension{
library_version: %{version: version, library_display_name: library_display_name}
} ->
"#{library_display_name} #{version}"
%AshHq.Docs.LibraryVersion{library_display_name: library_display_name, version: version} -> %AshHq.Docs.LibraryVersion{library_display_name: library_display_name, version: version} ->
"#{library_display_name} #{version}" "#{library_display_name} #{version}"
end) end)

View file

@ -0,0 +1,14 @@
defmodule AshHqWeb.Components.Tag do
use Surface.Component
prop color, :atom, values: [:red]
slot default
def render(assigns) do
~F"""
<div class={"rounded-xl p-1", "bg-red-300 text-black": @color == :red}>
<#slot/>
</div>
"""
end
end

View file

@ -2,7 +2,8 @@ defmodule AshHqWeb.Pages.Docs do
use Surface.LiveComponent use Surface.LiveComponent
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS
alias AshHqWeb.Components.DocSidebar alias AshHqWeb.Components.{CalloutText, DocSidebar, ProgressiveHeading, Tag}
alias AshHqWeb.Routes
prop params, :map, required: true prop params, :map, required: true
prop change_versions, :event, required: true prop change_versions, :event, required: true
@ -15,12 +16,13 @@ defmodule AshHqWeb.Pages.Docs do
data library_version, :any data library_version, :any
data guide, :any data guide, :any
data doc_path, :list, default: [] data doc_path, :list, default: []
data dsls, :list, default: []
@spec render(any) :: Phoenix.LiveView.Rendered.t() @spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do def render(assigns) do
~F""" ~F"""
<div class="h-full w-full flex flex-col bg-light-grid dark:bg-dark-grid"> <div class="grow h-full w-full flex flex-col">
<div class="md:hidden flex flex-row justify-start space-x-12 mt-2 items-center border-b border-t border-gray-600 py-3 mb-10"> <div class="lg:hidden flex flex-row justify-start space-x-12 items-center border-b border-t border-gray-600 py-3">
<button class="dark:hover:text-gray-600" phx-click={show_sidebar()}> <button class="dark:hover:text-gray-600" phx-click={show_sidebar()}>
<Heroicons.Outline.MenuIcon class="w-8 h-8 ml-4" /> <Heroicons.Outline.MenuIcon class="w-8 h-8 ml-4" />
</button> </button>
@ -33,55 +35,169 @@ defmodule AshHqWeb.Pages.Docs do
</div> </div>
{#match path} {#match path}
{#for item <- :lists.droplast(path)} {#for item <- :lists.droplast(path)}
<span class="text-gray-500"> <span class="text-gray-400">
{item}</span> {item}
</span>
<Heroicons.Outline.ChevronRightIcon class="w-3 h-3" /> <Heroicons.Outline.ChevronRightIcon class="w-3 h-3" />
{/for} {/for}
<span class="dark:text-white" /> <span class="dark:text-white">
<CalloutText>{List.last(path)}</CalloutText>
</span>
{/case} {/case}
</div> </div>
{/if} {/if}
</div> </div>
<div id="mobile-sidebar-container" class="hidden md:hidden relative w-screen h-full backdrop-blur-lg transition"> <span class="lg:hidden">
<div> <div
id="mobile-sidebar-container"
class="hidden fixed w-min h-full bg-primary-black transition"
>
<DocSidebar <DocSidebar
id="mobile-sidebar" id="mobile-sidebar"
class="absolute left-0 top-0"
libraries={@libraries} libraries={@libraries}
extension={@extension} extension={@extension}
dsls={@dsls}
guide={@guide} guide={@guide}
library={@library} library={@library}
library_version={@library_version} library_version={@library_version}
selected_versions={@selected_versions} selected_versions={@selected_versions}
/> />
</div> </div>
</div> </span>
<div class="grow flex flex-row h-full justify-center space-x-12"> <div class="grow flex flex-row h-full justify-center space-x-12">
<DocSidebar <DocSidebar
id="sidebar" id="sidebar"
class="hidden md:block" class="hidden lg:block mt-10"
libraries={@libraries} libraries={@libraries}
extension={@extension} extension={@extension}
dsls={@dsls}
guide={@guide} guide={@guide}
library={@library} library={@library}
library_version={@library_version} library_version={@library_version}
selected_versions={@selected_versions} selected_versions={@selected_versions}
/> />
<div class="grow w-full prose prose-zinc md:prose-lg lg:prose-xl dark:prose-invert"> <div
id="docs-window"
class="grow w-full prose lg:max-w-3xl xl:max-w-5xl dark:prose-invert overflow-y-scroll overflow-x-visible"
phx-hook="Docs"
>
{raw(@docs)} {raw(@docs)}
{#if !Enum.empty?(@dsls)}
<h1>Dsl Documentation</h1>
{render_dsl_docs(assigns, @dsls)}
{/if}
</div> </div>
</div> </div>
</div> </div>
""" """
end end
defp render_dsl_docs(assigns, dsls, path \\ [], depth \\ nil) do
count = Enum.count(path)
depth = depth || count + 4
~F"""
{#for dsl <- Enum.filter(dsls, &(&1.path == path)) |> Enum.sort_by(& &1.order)}
<div class={"w-full pl-2 border-gray-800 border-l", "ml-2": count > 0}>
<div id={path_to_name(dsl.path, dsl.name)} class="flex flex-row items-center">
<a href={"##{path_to_name(dsl.path, dsl.name)}"}><Heroicons.Outline.LinkIcon class="h-4 w-4" /></a>
{render_path(assigns, dsl.path, dsl.name)}
</div>
{render_options(assigns, get_options(dsl, @options), depth)}
{raw(AshHq.Docs.Extensions.RenderMarkdown.render!(dsl, :doc))}
{render_dsl_docs(assigns, dsls, path ++ [dsl.name], depth + 1)}
</div>
{/for}
"""
end
def path_to_name(path, name) do
Enum.map_join(path ++ [name], "-", &Routes.sanitize_name/1)
end
defp render_options(assigns, [], _) do
~F"""
"""
end
defp render_options(assigns, options, depth) do
~F"""
<div class="ml-2">
<table>
{#for option <- options}
<tr id={path_to_name(option.path, option.name)}>
<td>
<div class="flex flex-row items-baseline">
<a href={"##{path_to_name(option.path, option.name)}"}>
<Heroicons.Outline.LinkIcon class="h-3 m-3" />
</a>
<CalloutText>{option.name}</CalloutText>
</div>
</td>
<td>
{option.type}
</td>
<td>
{render_tags(assigns, option)}
</td>
<td>
{raw(AshHq.Docs.Extensions.RenderMarkdown.render!(option, :doc))}
</td>
</tr>
{/for}
</table>
</div>
"""
end
defp render_path(assigns, path, name) do
~F"""
<div class="flex flex-row space-x-1 items-center">
{#for item <- path}
<span class="text-gray-400">
{item}</span>
<Heroicons.Outline.ChevronRightIcon class="w-3 h-3" />
{/for}
<span class="dark:text-white">
<CalloutText>
{name}
</CalloutText>
</span>
</div>
"""
end
defp render_tags(assigns, option) do
~F"""
{#if option.required}
<Tag color={:red}>
Required
</Tag>
{/if}
"""
end
defp get_options(dsl, options) do
Enum.filter(options, fn option ->
List.starts_with?(option.path, dsl.path ++ [dsl.name]) &&
Enum.count(option.path) - Enum.count(dsl.path) == 1
end)
end
def show_sidebar() do def show_sidebar() do
%JS{} %JS{}
|> JS.toggle( |> JS.toggle(
to: "#mobile-sidebar-container", to: "#mobile-sidebar-container",
in: {"fade-in duration-100 transition", "hidden", "block"}, in: {
out: {"fade-out duration-100 transition", "block", "hidden"}, "transition ease-in duration-100",
time: 100 "opacity-0",
"opacity-100"
},
out: {
"transition ease-out duration-75",
"opacity-100",
"opacity-0"
}
) )
end end
@ -109,24 +225,31 @@ defmodule AshHqWeb.Pages.Docs do
cond do cond do
socket.assigns.extension -> socket.assigns.extension ->
assign(socket, assign(socket,
docs: Earmark.as_html!(socket.assigns.extension.doc), docs: AshHq.Docs.Extensions.RenderMarkdown.render!(socket.assigns.extension, :doc),
doc_path: [socket.assigns.library.name, socket.assigns.extension.name] doc_path: [socket.assigns.library.name, socket.assigns.extension.name],
dsls: socket.assigns.extension.dsls,
options: socket.assigns.extension.options
) )
socket.assigns.guide -> socket.assigns.guide ->
assign(socket, assign(socket,
docs: Earmark.as_html!(socket.assigns.guide.text), docs: AshHq.Docs.Extensions.RenderMarkdown.render!(socket.assigns.guide, :text),
doc_path: [socket.assigns.library.name, socket.assigns.guide.name] doc_path: [socket.assigns.library.name, socket.assigns.guide.name],
dsls: [],
options: []
) )
socket.assigns.library_version -> socket.assigns.library_version ->
assign(socket, assign(socket,
docs: Earmark.as_html!(socket.assigns.library_version.doc), docs:
doc_path: [socket.assigns.library.name] AshHq.Docs.Extensions.RenderMarkdown.render!(socket.assigns.library_version, :doc),
doc_path: [socket.assigns.library.name],
dsls: [],
options: []
) )
true -> true ->
assign(socket, docs: "", doc_path: []) assign(socket, docs: "", doc_path: [], dsls: [])
end end
end end
@ -137,7 +260,7 @@ defmodule AshHqWeb.Pages.Docs do
assign(socket, assign(socket,
extension: extension:
Enum.find(extensions, fn extension -> Enum.find(extensions, fn extension ->
extension.name == socket.assigns[:params]["extension"] Routes.sanitize_name(extension.name) == socket.assigns[:params]["extension"]
end) end)
) )
else else
@ -149,16 +272,6 @@ defmodule AshHqWeb.Pages.Docs do
{:ok, socket} {:ok, socket}
end end
defp selected_version(library, selected_versions) do
case Enum.find(library.versions, &(&1.id == selected_versions[library.id])) do
nil ->
nil
version ->
version.version
end
end
defp get_extensions(library, selected_versions) do defp get_extensions(library, selected_versions) do
case Enum.find(library.versions, &(&1.id == selected_versions[library.id])) do case Enum.find(library.versions, &(&1.id == selected_versions[library.id])) do
nil -> nil ->
@ -171,8 +284,11 @@ defmodule AshHqWeb.Pages.Docs do
defp assign_library(socket) do defp assign_library(socket) do
if !socket.assigns[:library] || if !socket.assigns[:library] ||
socket.assigns.params["library"] != socket.assigns.library.name do socket.assigns.params["library"] != Routes.sanitize_name(socket.assigns.library.name) do
case Enum.find(socket.assigns.libraries, &(&1.name == socket.assigns.params["library"])) do case Enum.find(
socket.assigns.libraries,
&(Routes.sanitize_name(&1.name) == socket.assigns.params["library"])
) do
nil -> nil ->
assign(socket, library: nil, library_version: nil) assign(socket, library: nil, library_version: nil)
@ -180,7 +296,10 @@ defmodule AshHqWeb.Pages.Docs do
socket = socket =
if socket.assigns[:params]["version"] do if socket.assigns[:params]["version"] do
library_version = library_version =
Enum.find(library.versions, &(&1.version == socket.assigns[:params]["version"])) Enum.find(
library.versions,
&(Routes.sanitize_name(&1.version) == socket.assigns[:params]["version"])
)
if library_version do if library_version do
new_selected_versions = new_selected_versions =

View file

@ -26,8 +26,8 @@ defmodule AshHqWeb.Pages.Home do
<div> K</div> <div> K</div>
</div> </div>
</button> </button>
<div class="pt-6 pb-6 w-full hidden md:block"> <div class="pt-6 pb-6 w-full hidden sm:block">
<div class="flex flex-row justify-center space-x-24 xl:space-x-32"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-10">
<CodeExample <CodeExample
id="define-a-resource" id="define-a-resource"
class="grow min-w-fit max-w-[1000px]" class="grow min-w-fit max-w-[1000px]"

View file

@ -1,34 +1,49 @@
defmodule AshHqWeb.Routes do defmodule AshHqWeb.Routes do
def guide_link(library, version, guide) do def guide_link(library, version, guide) do
"/docs/guides/#{library.name}/#{version}/#{guide}" "/docs/guides/#{sanitize_name(library.name)}/#{sanitize_name(version)}/#{sanitize_name(guide)}"
end
def library_link(library, nil) do
"/docs/dsl/#{sanitize_name(library.name)}"
end end
def library_link(library, name) do def library_link(library, name) do
"/docs/dsl/#{library.name}/#{name}" "/docs/dsl/#{sanitize_name(library.name)}/#{sanitize_name(name)}"
end end
def extension_link(library, name, extension) do def extension_link(library, name, extension) do
"/docs/dsl/#{library.name}/#{name}/#{extension}" "/docs/dsl/#{sanitize_name(library.name)}/#{sanitize_name(name)}/#{sanitize_name(extension)}"
end end
def doc_link(%AshHq.Docs.Guide{ def doc_link(%AshHq.Docs.Guide{
url_safe_name: url_safe_name, url_safe_name: url_safe_name,
library_version: %{library_name: library_name, version: version} library_version: %{library_name: library_name, version: version}
}) do }) do
"/docs/guides/#{library_name}/#{version}/#{url_safe_name}" "/docs/guides/#{sanitize_name(library_name)}/#{sanitize_name(version)}/#{url_safe_name}"
end end
def doc_link(%AshHq.Docs.LibraryVersion{library_name: library_name, version: version}) do def doc_link(%AshHq.Docs.LibraryVersion{library_name: library_name, version: version}) do
"/docs/dsl/#{library_name}/#{version}" "/docs/dsl/#{sanitize_name(library_name)}/#{sanitize_name(version)}"
end
def doc_link(%AshHq.Docs.Extension{
library_version: %{library_name: library_name, version: version},
name: name
}) do
"/docs/dsl/#{sanitize_name(library_name)}/#{sanitize_name(version)}/#{sanitize_name(name)}"
end end
def doc_link(item) do def doc_link(item) do
case item.path do case item.path do
[] -> [] ->
"/docs/dsl/#{item.library_name}/#{item.version_name}/#{item.extension_name}" "/docs/dsl/#{sanitize_name(item.library_name)}/#{sanitize_name(item.version_name)}/#{sanitize_name(item.extension_name)}##{sanitize_name(item.name)}"
path -> path ->
"/docs/dsl/#{item.library_name}/#{item.version_name}/#{item.extension_name}?path=#{Enum.join(path, ".")}" "/docs/dsl/#{sanitize_name(item.library_name)}/#{sanitize_name(item.version_name)}/#{sanitize_name(item.extension_name)}##{Enum.map_join(path ++ [item.name], "-", &sanitize_name/1)}"
end end
end end
def sanitize_name(name) do
String.downcase(String.replace(name, ~r/[^A-Za-z0-9_]/, "-"))
end
end end

View file

@ -7,9 +7,11 @@
<%= csrf_meta_tag() %> <%= csrf_meta_tag() %>
<%= live_title_tag assigns[:page_title] || "Ash Framework" %> <%= live_title_tag assigns[:page_title] || "Ash Framework" %>
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/> <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
</head> </head>
<body class="h-full"> <body class="h-full">
<%= @inner_content %> <%= @inner_content %>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.init(".mermaid")</script>
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
</body> </body>
</html> </html>

View file

@ -29,9 +29,9 @@ defmodule AshHqWeb.AppViewLive do
<button id="search-button" class="hidden" phx-click={AshHqWeb.AppViewLive.toggle_search()} /> <button id="search-button" class="hidden" phx-click={AshHqWeb.AppViewLive.toggle_search()} />
<div <div
id="main-container" id="main-container"
class="h-full bg-white dark:bg-primary-black dark:text-silver-phoenix overflow-x-hidden" class="h-screen flex flex-col bg-white dark:bg-primary-black dark:text-silver-phoenix overflow-x-hidden overflow-clip"
> >
<div class="flex justify-between pt-4 px-4"> <div class={"flex justify-between pt-4 px-4", "w-full border-b bg-white dark:bg-primary-black pb-4 top-0": @live_action == :docs_dsl}>
<div class="flex flex-row align-baseline"> <div class="flex flex-row align-baseline">
<a href="/"> <a href="/">
<img class="h-10 hidden dark:block" src="/images/ash-framework-dark.png"> <img class="h-10 hidden dark:block" src="/images/ash-framework-dark.png">
@ -40,7 +40,7 @@ defmodule AshHqWeb.AppViewLive do
</div> </div>
<div class="flex flex-row align-middle items-center space-x-2"> <div class="flex flex-row align-middle items-center space-x-2">
<a <a
href="/docs/guides/ash/main/Getting-Started" href="/docs/guides/ash/main/getting-started"
class="dark:text-gray-400 dark:hover:text-gray-200 hover:text-gray-600" class="dark:text-gray-400 dark:hover:text-gray-200 hover:text-gray-600"
>Get Started</a> >Get Started</a>
<div>|</div> <div>|</div>
@ -198,9 +198,17 @@ defmodule AshHqWeb.AppViewLive do
js js
|> JS.dispatch("js:noscroll-main", to: "#search-box") |> JS.dispatch("js:noscroll-main", to: "#search-box")
|> JS.toggle( |> JS.toggle(
in: "fade-in transition", to: "#search-box",
out: "fade-out transition", in: {
to: "#search-box" "transition ease-in duration-100",
"opacity-0",
"opacity-100"
},
out: {
"transition ease-out duration-75",
"opacity-100",
"opacity-0"
}
) )
|> JS.dispatch("js:focus", to: "#search-input") |> JS.dispatch("js:focus", to: "#search-input")
end end

View file

@ -38,7 +38,7 @@ defmodule AshHq.MixProject do
# {:ash_postgres, github: "ash-project/ash_postgres"}, # {:ash_postgres, github: "ash-project/ash_postgres"},
{:ash_postgres, path: "../ash_postgres"}, {:ash_postgres, path: "../ash_postgres"},
{:ash_phoenix, github: "ash-project/ash_phoenix"}, {:ash_phoenix, github: "ash-project/ash_phoenix"},
{:earmark, "~> 1.4"}, {:earmark, "~> 1.5.0-pre1"},
{:ecto, git: "https://github.com/elixir-ecto/ecto.git", override: true}, {:ecto, git: "https://github.com/elixir-ecto/ecto.git", override: true},
{:surface, "~> 0.7.3"}, {:surface, "~> 0.7.3"},
{:surface_heroicons, "~> 0.6.0"}, {:surface_heroicons, "~> 0.6.0"},

View file

@ -12,7 +12,7 @@
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"earmark": {:hex, :earmark, "1.4.24", "1923e201c3742af421860b983560967cc3e3deacc59c12966bc991a5435565e6", [:mix], [{:earmark_parser, "~> 1.4.25", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "9724242f241f2ad634756d8f2bb57a3d0992cedd10c51842fa655703b4da7c67"}, "earmark": {:hex, :earmark, "1.5.0-pre1", "e04aca73692bc3cda3429d6df99c8dae2bf76411e5e76d006a4bc04ac81ef1c1", [:mix], [{:earmark_parser, "~> 1.4.21", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "26ec0473ad2ef995b9672f89309a7a4952887f69b78cfc7af14e320bc6546bfa"},
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"}, "earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "cda1172063753dea764e9c8ad80757dae5d1a4a7", []}, "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "cda1172063753dea764e9c8ad80757dae5d1a4a7", []},
"ecto_sql": {:hex, :ecto_sql, "3.7.2", "55c60aa3a06168912abf145c6df38b0295c34118c3624cf7a6977cd6ce043081", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c218ea62f305dcaef0b915fb56583195e7b91c91dcfb006ba1f669bfacbff2a"}, "ecto_sql": {:hex, :ecto_sql, "3.7.2", "55c60aa3a06168912abf145c6df38b0295c34118c3624cf7a6977cd6ce043081", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c218ea62f305dcaef0b915fb56583195e7b91c91dcfb006ba1f669bfacbff2a"},

View file

@ -13,8 +13,8 @@ defmodule AshHq.Repo.Migrations.AddTsvectorIndices do
for {table, {header, text}} <- @config do for {table, {header, text}} <- @config do
execute """ execute """
CREATE INDEX #{table}_search_index ON #{table} USING GIN(( CREATE INDEX #{table}_search_index ON #{table} USING GIN((
setweight(to_tsvector('simple', #{header}), 'A') || setweight(to_tsvector('english', #{header}), 'A') ||
setweight(to_tsvector('simple', #{text}), 'D') setweight(to_tsvector('english', #{text}), 'D')
)); ));
""", """,
""" """