Merge pull request 'devel' (#200) from devel into main

Reviewed-on: #200
This commit is contained in:
inhji 2023-07-09 20:02:47 +02:00
commit ee3c23e5ae
18 changed files with 171 additions and 98 deletions

View file

@ -14,14 +14,16 @@ defmodule Chiya.Channels.Channel do
:public, :public,
:private, :private,
:unlisted :unlisted
] ],
default: :private
field :layout, Ecto.Enum, field :layout, Ecto.Enum,
values: [ values: [
:default, :default,
:microblog, :microblog,
:gallery :gallery
] ],
default: :default
many_to_many :notes, Chiya.Notes.Note, many_to_many :notes, Chiya.Notes.Note,
join_through: "channels_notes", join_through: "channels_notes",

View file

@ -46,18 +46,20 @@ defmodule Chiya.Notes do
|> Repo.preload(@preloads) |> Repo.preload(@preloads)
end end
def list_notes_by_channel_published(%Chiya.Channels.Channel{} = channel) do def list_notes_by_channel_published(%Chiya.Channels.Channel{} = channel, count \\ 10) do
list_notes_by_channel_query(channel) list_notes_by_channel_query(channel)
|> order_by([n], desc: n.published_at) |> order_by([n], desc: n.published_at)
|> where([n], not is_nil(n.published_at)) |> where([n], not is_nil(n.published_at))
|> limit(^count)
|> Repo.all() |> Repo.all()
|> Repo.preload(@preloads) |> Repo.preload(@preloads)
end end
def list_notes_by_channel_updated(%Chiya.Channels.Channel{} = channel) do def list_notes_by_channel_updated(%Chiya.Channels.Channel{} = channel, count \\ 10) do
list_notes_by_channel_query(channel) list_notes_by_channel_query(channel)
|> order_by([n], desc: n.published_at) |> order_by([n], desc: n.published_at)
|> where([n], not is_nil(n.published_at)) |> where([n], not is_nil(n.published_at))
|> limit(^count)
|> Repo.all() |> Repo.all()
|> Repo.preload(@preloads) |> Repo.preload(@preloads)
end end

View file

@ -36,7 +36,7 @@
</head> </head>
<body class="text-theme-base bg-theme-background | h-feed hfeed"> <body class="text-theme-base bg-theme-background | h-feed hfeed">
<aside class="block"> <aside class="block">
<%= raw @settings.custom_html %> <%= raw(@settings.custom_html) %>
</aside> </aside>
<header class="my-8 block px-3"> <header class="my-8 block px-3">
<nav class="mx-auto max-w-2xl"> <nav class="mx-auto max-w-2xl">
@ -116,12 +116,15 @@
</footer> </footer>
<%= if @profile do %> <%= if @profile do %>
<section class="hidden | hcard h-card"> <section class="hidden | hcard h-card">
<a href="/" rel="me" class="u-url"><%= @profile.name %></a> <a href="/" rel="me" class="u-url"><%= @profile.name %></a>
<img class="u-photo" src={ChiyaWeb.Uploaders.UserImage.url({@profile.user_image, @profile}, :thumb)} /> <img
<span class="p-nickname"><%= @profile.handle %></span> class="u-photo"
<span class="p-note"><%= @profile.bio %></span> src={ChiyaWeb.Uploaders.UserImage.url({@profile.user_image, @profile}, :thumb)}
</section> />
<span class="p-nickname"><%= @profile.handle %></span>
<span class="p-note"><%= @profile.bio %></span>
</section>
<% end %> <% end %>
<section class="flex h-1 w-full flex-row"> <section class="flex h-1 w-full flex-row">

View file

@ -87,16 +87,18 @@ defmodule ChiyaWeb.PublicComponents do
attr :class_title, :string, default: nil attr :class_title, :string, default: nil
attr :class_subtitle, :string, default: nil attr :class_subtitle, :string, default: nil
slot :title, required: true
slot :subtitle, required: false slot :subtitle, required: false
def header(assigns) do def header(assigns) do
~H""" ~H"""
<header class={["p-8 rounded bg-theme-background1", @class]}> <header class={["p-8 rounded bg-theme-background1", @class]}>
<h1 class={["text-3xl leading-10 text-theme-base", @class_title]}> <h1 class={["text-3xl leading-10 text-theme-base", @class_title]}>
<%= render_slot(@title) %> <%= render_slot(@inner_block) %>
</h1> </h1>
<p :if={@subtitle != []} class={["mt-4 leading-7 font-semibold text-theme-base/75", @class_subtitle]}> <p
:if={@subtitle != []}
class={["mt-4 leading-7 font-semibold text-theme-base/75", @class_subtitle]}
>
<%= render_slot(@subtitle) %> <%= render_slot(@subtitle) %>
</p> </p>
</header> </header>
@ -119,12 +121,16 @@ defmodule ChiyaWeb.PublicComponents do
end end
end end
attr :notes, :list, required: true
def note_list_headers(assigns) do def note_list_headers(assigns) do
~H""" ~H"""
<section class="note-list default | mt-6 sm:w-auto flex flex-col gap-3"> <section class="note-list default | mt-6 sm:w-auto flex flex-col gap-3">
<%= for note <- assigns.notes do %> <%= for note <- assigns.notes do %>
<a href={~p"/note/#{note.slug}"} <a
class="rounded-lg px-6 pt-4 pb-5 border border-theme-background1 hover:bg-theme-background1 transition"> href={~p"/note/#{note.slug}"}
class="rounded-lg px-6 pt-4 pb-5 border border-theme-background1 hover:bg-theme-background1 transition"
>
<header> <header>
<span class="text-theme-secondary text-lg font-semibold leading-8"> <span class="text-theme-secondary text-lg font-semibold leading-8">
<%= note.name %> <%= note.name %>
@ -134,7 +140,9 @@ defmodule ChiyaWeb.PublicComponents do
</span> </span>
</header> </header>
<p class="text-theme-base"><%= String.slice(note.content, 0..150) %></p> <p class="text-theme-base">
<%= String.slice(note.content, 0..150) %>
</p>
</a> </a>
<% end %> <% end %>
</section> </section>
@ -158,8 +166,10 @@ defmodule ChiyaWeb.PublicComponents do
<%= pretty_datetime(note.published_at) %> <%= pretty_datetime(note.published_at) %>
</time> </time>
<.dot /> <.dot />
<.tags note={note} /> <%= if not Enum.empty?(note.tags) do %>
<.dot /> <.tags note={note} />
<.dot />
<% end %>
<a href={~p"/note/#{note.slug}"} class="text-theme-base/75">Permalink</a> <a href={~p"/note/#{note.slug}"} class="text-theme-base/75">Permalink</a>
<%= if not Enum.empty?(note.images) do %> <%= if not Enum.empty?(note.images) do %>
<.dot /> <.dot />

View file

@ -1,14 +1,14 @@
<html> <html>
<head> <head>
<title>Not found</title> <title>Not found</title>
<link rel="preconnect" href="https://rsms.me/" /> <link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
</head> </head>
<body class="bg-theme-background text-theme-base"> <body class="bg-theme-background text-theme-base">
<.header class="mx-auto max-w-2xl"> <.header class="mx-auto max-w-2xl">
Not found Not found
<:subtitle>This page went away and never came back.</:subtitle> <:subtitle>This page went away and never came back.</:subtitle>
</.header> </.header>
</body> </body>
</html> </html>

View file

@ -1,14 +1,14 @@
<html> <html>
<head> <head>
<title>Infernal Server Error</title> <title>Infernal Server Error</title>
<link rel="preconnect" href="https://rsms.me/" /> <link rel="preconnect" href="https://rsms.me/" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
</head> </head>
<body class="bg-theme-background text-theme-base"> <body class="bg-theme-background text-theme-base">
<.header class="mx-auto max-w-2xl"> <.header class="mx-auto max-w-2xl">
Infernal Server Error Infernal Server Error
<:subtitle>Server got hot and went to hell.</:subtitle> <:subtitle>Server got hot and went to hell.</:subtitle>
</.header> </.header>
</body> </body>
</html> </html>

View file

@ -70,11 +70,12 @@ defmodule ChiyaWeb.PageController do
[channel, notes_updated, notes_published] = [channel, notes_updated, notes_published] =
case settings.wiki_channel_id do case settings.wiki_channel_id do
nil -> nil ->
[nil, nil] [nil, nil, nil]
id -> id ->
channel = Chiya.Channels.get_channel!(id) channel = Chiya.Channels.get_channel!(id)
updated = Chiya.Notes.list_notes_by_channel_updated(channel) updated = Chiya.Notes.list_notes_by_channel_updated(channel, 5)
published = Chiya.Notes.list_notes_by_channel_published(channel) published = Chiya.Notes.list_notes_by_channel_published(channel, 5)
[channel, updated, published] [channel, updated, published]
end end

View file

@ -1,21 +1,14 @@
<section class="max-w-2xl mx-auto"> <section class="max-w-2xl mx-auto">
<article class="h-card hcard"> <article class="h-card hcard">
<section class="p-10 bg-theme-background1 flex gap-3 items-start"> <img
<div> class="rounded-lg block text-center w-28 | u-photo"
<img src={ChiyaWeb.Uploaders.UserImage.url({@user.user_image, @current_user}, :thumb)}
class="rounded-lg w-28 | u-photo" />
src={ChiyaWeb.Uploaders.UserImage.url({@user.user_image, @current_user}, :thumb)}
/> <.header class="mt-8">
</div> <span class="p-name"><%= @user.name %></span>
<div> <:subtitle><%= @user.bio %></:subtitle>
<h1 class="text-3xl font-extrabold leading-10 tracking-tight text-theme-primary | p-name"> </.header>
<%= @user.name %>
</h1>
<section class="mx-auto mt-8 prose prose-gruvbox | p-note">
<%= Markdown.render(@user.bio) |> raw() %>
</section>
</div>
</section>
<%= if @note do %> <%= if @note do %>
<section class="mx-auto mt-8 prose prose-gruvbox md:prose-lg lg:prose-xl | p-summary e-content"> <section class="mx-auto mt-8 prose prose-gruvbox md:prose-lg lg:prose-xl | p-summary e-content">

View file

@ -1,6 +1,6 @@
<section class="max-w-2xl mx-auto"> <section class="max-w-2xl mx-auto">
<.header> <.header>
<:title><%= @channel.name %></:title> <%= @channel.name %>
<:subtitle><%= @channel.content %></:subtitle> <:subtitle><%= @channel.content %></:subtitle>
</.header> </.header>

View file

@ -1,6 +1,6 @@
<section class="max-w-2xl mx-auto"> <section class="max-w-2xl mx-auto">
<.header class_title="text-theme-primary p-name" class_subtitle="p-summary"> <.header class_title="text-theme-primary p-name" class_subtitle="p-summary">
<:title><%= @settings.title %></:title> <%= @settings.title %>
<:subtitle><%= @settings.subtitle %></:subtitle> <:subtitle><%= @settings.subtitle %></:subtitle>
</.header> </.header>

View file

@ -1,6 +1,6 @@
<article class="h-entry hentry"> <article class="h-entry hentry">
<.header class="max-w-2xl mx-auto" class_title="p-name"> <.header class="max-w-2xl mx-auto" class_title="p-name">
<:title><%= @note.name %></:title> <%= @note.name %>
</.header> </.header>
<section class="mt-8 mx-auto prose prose-gruvbox md:prose-lg lg:prose-xl | p-summary e-content"> <section class="mt-8 mx-auto prose prose-gruvbox md:prose-lg lg:prose-xl | p-summary e-content">
@ -55,4 +55,3 @@
<% end %> <% end %>
</section> </section>
</article> </article>

View file

@ -1,6 +1,6 @@
<section class="max-w-2xl mx-auto"> <section class="max-w-2xl mx-auto">
<.header> <.header>
<:title>Tagged with &ldquo;<%= @tag.name %>&rdquo;</:title> Tagged with &ldquo;<%= @tag.name %>&rdquo;
<:subtitle><%= @tag.content %></:subtitle> <:subtitle><%= @tag.content %></:subtitle>
</.header> </.header>

View file

@ -1,25 +1,35 @@
<section class="mx-auto max-w-2xl">
<.header>
<:title><%= @channel.name %></:title>
</.header>
<section class="prose prose-gruvbox mt-6">
<%= Markdown.render(@channel.content) |> raw %>
</section>
</section>
<%= if @channel do %> <%= if @channel do %>
<section class="flex flex-col md:flex-row gap-3 mt-6 w-full px-3"> <section class="mx-auto max-w-2xl">
<div class="w-full mt-6 sm:w-auto flex flex-1 flex-col gap-1.5"> <.header>
<h2 class="text-xl text-theme-base">Recently Updated</h2> <%= @channel.name %>
</.header>
<.note_list notes={@notes_updated} layout={@channel.layout} /> <section class="prose prose-gruvbox mt-6">
</div> <%= Markdown.render(@channel.content) |> raw %>
</section>
</section>
<div class="w-full mt-6 sm:w-auto flex flex-1 flex-col gap-1.5"> <section class="flex flex-col md:flex-row gap-3 mt-6 w-full sm:px-3">
<h2 class="text-xl text-theme-base">Recently Published</h2> <div class="w-full mt-6 sm:w-auto flex flex-1 flex-col gap-1.5">
<h2 class="text-xl text-theme-base">Recently Updated</h2>
<.note_list notes={@notes_published} layout={@channel.layout} /> <.note_list notes={@notes_updated} layout={@channel.layout} />
</div> </div>
</section>
<div class="w-full mt-6 sm:w-auto flex flex-1 flex-col gap-1.5">
<h2 class="text-xl text-theme-base">Recently Published</h2>
<.note_list notes={@notes_published} layout={@channel.layout} />
</div>
</section>
<% else %>
<section class="mx-auto max-w-2xl">
<.header>
Wiki
</.header>
<section class="prose prose-gruvbox mt-6">
Wiki is not set up.
</section>
</section>
<% end %> <% end %>

21
lib/chiya_web/outline.ex Normal file
View file

@ -0,0 +1,21 @@
defmodule ChiyaWeb.Outline do
@outline_regex ~r/\#{1,6}\s.+/
@heading_regex ~r/^(#+)\s(.+)$/
def get(markdown) do
headings =
@outline_regex
|> Regex.scan(markdown, capture: :all)
Enum.chunk_by(headings, fn h -> nil end)
end
def level(heading) do
Regex.scan(@heading_regex, heading)
|> Enum.map(fn [_, level, heading] ->
[level_from_string(level), heading]
end)
end
defp level_from_string(string), do: String.length(string)
end

View file

@ -5,10 +5,10 @@ defmodule ChiyaWeb.ErrorHTMLTest do
import Phoenix.Template import Phoenix.Template
test "renders 404.html" do test "renders 404.html" do
assert render_to_string(ChiyaWeb.ErrorHTML, "404", "html", []) == "Not Found" assert render_to_string(ChiyaWeb.ErrorHTML, "404", "html", []) =~ "Not Found"
end end
test "renders 500.html" do test "renders 500.html" do
assert render_to_string(ChiyaWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" assert render_to_string(ChiyaWeb.ErrorHTML, "500", "html", []) =~ "Internal Server Error"
end end
end end

View file

@ -0,0 +1,20 @@
defmodule ChiyaWeb.OutlineTest do
use Chiya.SimpleCase
alias ChiyaWeb.Outline
describe "extract_outline/1" do
test "extracts headlines from markdown" do
markdown = "# Heading\nsome paragraph\n## Sub Heading\n# Second Heading"
assert [{1, "Heading", [{2, "Sub Heading", []}]}, {1, "Second Heading", []}] =
Outline.get(markdown)
end
end
describe "outline_level/1" do
test "extracts outline level" do
assert {1, "Heading"} = Outline.level("# Heading")
end
end
end

View file

@ -0,0 +1,12 @@
defmodule Chiya.SimpleCase do
use ExUnit.CaseTemplate
using do
quote do
# The default endpoint for testing
@endpoint ChiyaWeb.Endpoint
use ChiyaWeb, :verified_routes
end
end
end