| 818 |
818
|
|
"""
|
| 819 |
819
|
|
end
|
| 820 |
820
|
|
|
|
821
|
+ |
@doc """
|
|
822
|
+ |
A repository's file table: the ref bar, the latest commit, and the entries.
|
|
823
|
+ |
|
|
824
|
+ |
Adapted from the GitHub-shaped clones catalogued in
|
|
825
|
+ |
`docs/audits/2026-08-19-github-clone-harvest-candidates.md` -- `gh-next`
|
|
826
|
+ |
(MIT, Fredkiss3/gh-next) for the Tailwind repo home's header and rail, and
|
|
827
|
+ |
Gitea's `view_list.tmpl` for what a tree row must actually carry.
|
|
828
|
+ |
|
|
829
|
+ |
Directories sort above files, which is what `Browse.tree/3` already returns
|
|
830
|
+ |
and what makes a deep repository scannable: a reader looks for the folder
|
|
831
|
+ |
first and only then for the file.
|
|
832
|
+ |
|
|
833
|
+ |
The latest-commit bar sits above the table rather than inside it. It
|
|
834
|
+ |
describes the tree as a whole, and a row that describes the whole table but
|
|
835
|
+ |
looks like a row is the reason GitHub's own version of this reads oddly on
|
|
836
|
+ |
first sight.
|
|
837
|
+ |
|
|
838
|
+ |
Per-row commit messages are deliberately absent. GitHub fills them by
|
|
839
|
+ |
walking history once per path, which is one process per file; when we have
|
|
840
|
+ |
that cheaply the slot is `meta` and it costs no markup change.
|
|
841
|
+ |
"""
|
|
842
|
+ |
attr :owner, :string, required: true
|
|
843
|
+ |
attr :repo, :string, required: true
|
|
844
|
+ |
attr :ref, :string, required: true
|
|
845
|
+ |
attr :path, :string, default: ""
|
|
846
|
+ |
attr :entries, :list, required: true, doc: "`[%{name, kind, size}]` from `Browse.tree/3`"
|
|
847
|
+ |
attr :branches, :integer, default: nil
|
|
848
|
+ |
attr :tags, :integer, default: nil
|
|
849
|
+ |
attr :class, :any, default: nil
|
|
850
|
+ |
slot :commit, doc: "the latest commit, shown above the table"
|
|
851
|
+ |
slot :actions, doc: "controls at the trailing edge of the ref bar"
|
|
852
|
+ |
|
|
853
|
+ |
def file_table(assigns) do
|
|
854
|
+ |
~H"""
|
|
855
|
+ |
<div class={["file-table", @class]}>
|
|
856
|
+ |
<div class="file-table__bar">
|
|
857
|
+ |
<span class="file-table__ref">
|
|
858
|
+ |
<.icon name="branch" /> {@ref}
|
|
859
|
+ |
</span>
|
|
860
|
+ |
<span :if={@branches} class="file-table__count">
|
|
861
|
+ |
<strong>{@branches}</strong> {plural(@branches, "Branch", "Branches")}
|
|
862
|
+ |
</span>
|
|
863
|
+ |
<span :if={@tags} class="file-table__count">
|
|
864
|
+ |
<strong>{@tags}</strong> {plural(@tags, "Tag", "Tags")}
|
|
865
|
+ |
</span>
|
|
866
|
+ |
<span :if={@actions != []} class="file-table__actions">{render_slot(@actions)}</span>
|
|
867
|
+ |
</div>
|
|
868
|
+ |
|
|
869
|
+ |
<div :if={@commit != []} class="file-table__commit">{render_slot(@commit)}</div>
|
|
870
|
+ |
|
|
871
|
+ |
<table class="file-table__list">
|
|
872
|
+ |
<caption class="visually-hidden">
|
|
873
|
+ |
Files in {(@path == "" && "the repository root") || @path}
|
|
874
|
+ |
</caption>
|
|
875
|
+ |
<tbody>
|
|
876
|
+ |
<tr :for={entry <- @entries} class="file-row" data-kind={entry.kind}>
|
|
877
|
+ |
<td class="file-row__name">
|
|
878
|
+ |
<.link navigate={entry_path(@owner, @repo, @ref, @path, entry)} class="file-row__link">
|
|
879
|
+ |
<.icon name={if entry.kind == "tree", do: "folder", else: "file-document"} />
|
|
880
|
+ |
{entry.name}
|
|
881
|
+ |
</.link>
|
|
882
|
+ |
</td>
|
|
883
|
+ |
<td class="file-row__size">{size_label(entry)}</td>
|
|
884
|
+ |
</tr>
|
|
885
|
+ |
</tbody>
|
|
886
|
+ |
</table>
|
|
887
|
+ |
|
|
888
|
+ |
<p :if={@entries == []} class="file-table__empty">Nothing at this path.</p>
|
|
889
|
+ |
</div>
|
|
890
|
+ |
"""
|
|
891
|
+ |
end
|
|
892
|
+ |
|
|
893
|
+ |
# A directory goes to `tree`, a file to `blob`. GitHub's grammar, which the
|
|
894
|
+ |
# harvest audit treats as the compatibility target rather than a style
|
|
895
|
+ |
# choice: an existing link, bookmark, or `gh`-shaped tool should keep working.
|
|
896
|
+ |
defp entry_path(owner, repo, ref, path, %{kind: kind, name: name}) do
|
|
897
|
+ |
noun = if kind == "tree", do: "tree", else: "blob"
|
|
898
|
+ |
joined = if path == "", do: name, else: path <> "/" <> name
|
|
899
|
+ |
"/#{owner}/#{repo}/#{noun}/#{ref}/#{joined}"
|
|
900
|
+ |
end
|
|
901
|
+ |
|
|
902
|
+ |
# `UI` is not a gettext backend, and these two words are the only plurals it
|
|
903
|
+ |
# needs.
|
|
904
|
+ |
defp plural(1, singular, _plural), do: singular
|
|
905
|
+ |
defp plural(_count, _singular, plural), do: plural
|
|
906
|
+ |
|
|
907
|
+ |
defp size_label(%{kind: "tree"}), do: nil
|
|
908
|
+ |
defp size_label(%{size: nil}), do: nil
|
|
909
|
+ |
|
|
910
|
+ |
defp size_label(%{size: bytes}) when is_integer(bytes) do
|
|
911
|
+ |
cond do
|
|
912
|
+ |
bytes < 1_024 -> "#{bytes} B"
|
|
913
|
+ |
bytes < 1_048_576 -> "#{Float.round(bytes / 1_024, 1)} KB"
|
|
914
|
+ |
true -> "#{Float.round(bytes / 1_048_576, 1)} MB"
|
|
915
|
+ |
end
|
|
916
|
+ |
end
|
|
917
|
+ |
|
|
918
|
+ |
defp size_label(_entry), do: nil
|
|
919
|
+ |
|
|
920
|
+ |
@doc """
|
|
921
|
+ |
The rail beside a repository: what it is, how it is licensed, what it is made of.
|
|
922
|
+ |
|
|
923
|
+ |
Adapted from `gh-next`'s repo home. Every row is optional, and an absent one
|
|
924
|
+ |
renders nothing rather than a placeholder -- a rail that says "no description"
|
|
925
|
+ |
is louder than one that simply does not mention it.
|
|
926
|
+ |
"""
|
|
927
|
+ |
attr :description, :string, default: nil
|
|
928
|
+ |
attr :license, :string, default: nil
|
|
929
|
+ |
attr :class, :any, default: nil
|
|
930
|
+ |
|
|
931
|
+ |
slot :link, doc: "one related destination" do
|
|
932
|
+ |
attr :icon, :string
|
|
933
|
+ |
attr :navigate, :string
|
|
934
|
+ |
attr :href, :string
|
|
935
|
+ |
end
|
|
936
|
+ |
|
|
937
|
+ |
slot :stat, doc: "one count" do
|
|
938
|
+ |
attr :icon, :string
|
|
939
|
+ |
end
|
|
940
|
+ |
|
|
941
|
+ |
slot :language, doc: "one language" do
|
|
942
|
+ |
attr :percent, :float, required: true
|
|
943
|
+ |
end
|
|
944
|
+ |
|
|
945
|
+ |
def repo_about(assigns) do
|
|
946
|
+ |
~H"""
|
|
947
|
+ |
<aside class={["repo-about", @class]} aria-label="About this repository">
|
|
948
|
+ |
<h2 class="repo-about__title">About</h2>
|
|
949
|
+ |
<p :if={@description} class="repo-about__description">{@description}</p>
|
|
950
|
+ |
|
|
951
|
+ |
<ul :if={@link != [] or @license} class="repo-about__links">
|
|
952
|
+ |
<li :if={@license}>
|
|
953
|
+ |
<.icon name="scales" /> {@license}
|
|
954
|
+ |
</li>
|
|
955
|
+ |
<li :for={link <- @link}>
|
|
956
|
+ |
<.link navigate={link[:navigate]} href={link[:href]}>
|
|
957
|
+ |
<.icon :if={link[:icon]} name={link.icon} /> {render_slot(link)}
|
|
958
|
+ |
</.link>
|
|
959
|
+ |
</li>
|
|
960
|
+ |
</ul>
|
|
961
|
+ |
|
|
962
|
+ |
<ul :if={@stat != []} class="repo-about__stats">
|
|
963
|
+ |
<li :for={stat <- @stat}>
|
|
964
|
+ |
<.icon :if={stat[:icon]} name={stat.icon} /> {render_slot(stat)}
|
|
965
|
+ |
</li>
|
|
966
|
+ |
</ul>
|
|
967
|
+ |
|
|
968
|
+ |
<div :if={@language != []} class="repo-about__languages">
|
|
969
|
+ |
<h3>Languages</h3>
|
|
970
|
+ |
<%!-- One bar, not a stack of bars: the proportions are the point, and
|
|
971
|
+ |
they only read as proportions when they share a length. --%>
|
|
972
|
+ |
<div class="language-bar" role="img" aria-label="Language breakdown">
|
|
973
|
+ |
<span
|
|
974
|
+ |
:for={{language, index} <- Enum.with_index(@language)}
|
|
975
|
+ |
class="language-bar__segment"
|
|
976
|
+ |
data-index={rem(index, 6)}
|
|
977
|
+ |
style={"width: #{language.percent}%"}
|
|
978
|
+ |
></span>
|
|
979
|
+ |
</div>
|
|
980
|
+ |
<ul class="repo-about__language-list">
|
|
981
|
+ |
<li :for={{language, index} <- Enum.with_index(@language)}>
|
|
982
|
+ |
<span class="language-dot" data-index={rem(index, 6)} aria-hidden="true"></span>
|
|
983
|
+ |
{render_slot(language)} <span class="repo-about__percent">{language.percent}%</span>
|
|
984
|
+ |
</li>
|
|
985
|
+ |
</ul>
|
|
986
|
+ |
</div>
|
|
987
|
+ |
</aside>
|
|
988
|
+ |
"""
|
|
989
|
+ |
end
|
|
990
|
+ |
|
| 821 |
991
|
|
@doc """
|
| 822 |
992
|
|
One file's diff: a header, its hunks, and every line numbered on both sides.
|
| 823 |
993
|
|
|