devel #200
18 changed files with 171 additions and 98 deletions
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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">
|
||||||
|
@ -118,7 +118,10 @@
|
||||||
<%= 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
|
||||||
|
class="u-photo"
|
||||||
|
src={ChiyaWeb.Uploaders.UserImage.url({@profile.user_image, @profile}, :thumb)}
|
||||||
|
/>
|
||||||
<span class="p-nickname"><%= @profile.handle %></span>
|
<span class="p-nickname"><%= @profile.handle %></span>
|
||||||
<span class="p-note"><%= @profile.bio %></span>
|
<span class="p-note"><%= @profile.bio %></span>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -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 />
|
||||||
|
<%= if not Enum.empty?(note.tags) do %>
|
||||||
<.tags note={note} />
|
<.tags note={note} />
|
||||||
<.dot />
|
<.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 />
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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">
|
|
||||||
<div>
|
|
||||||
<img
|
<img
|
||||||
class="rounded-lg w-28 | u-photo"
|
class="rounded-lg block text-center w-28 | u-photo"
|
||||||
src={ChiyaWeb.Uploaders.UserImage.url({@user.user_image, @current_user}, :thumb)}
|
src={ChiyaWeb.Uploaders.UserImage.url({@user.user_image, @current_user}, :thumb)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
<.header class="mt-8">
|
||||||
<h1 class="text-3xl font-extrabold leading-10 tracking-tight text-theme-primary | p-name">
|
<span class="p-name"><%= @user.name %></span>
|
||||||
<%= @user.name %>
|
<:subtitle><%= @user.bio %></:subtitle>
|
||||||
</h1>
|
</.header>
|
||||||
<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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<section class="max-w-2xl mx-auto">
|
<section class="max-w-2xl mx-auto">
|
||||||
<.header>
|
<.header>
|
||||||
<:title>Tagged with “<%= @tag.name %>”</:title>
|
Tagged with “<%= @tag.name %>”
|
||||||
<:subtitle><%= @tag.content %></:subtitle>
|
<:subtitle><%= @tag.content %></:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<section class="mx-auto max-w-2xl">
|
<%= if @channel do %>
|
||||||
|
<section class="mx-auto max-w-2xl">
|
||||||
<.header>
|
<.header>
|
||||||
<:title><%= @channel.name %></:title>
|
<%= @channel.name %>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<section class="prose prose-gruvbox mt-6">
|
<section class="prose prose-gruvbox mt-6">
|
||||||
<%= Markdown.render(@channel.content) |> raw %>
|
<%= Markdown.render(@channel.content) |> raw %>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<%= if @channel do %>
|
<section class="flex flex-col md:flex-row gap-3 mt-6 w-full sm:px-3">
|
||||||
<section class="flex flex-col md:flex-row gap-3 mt-6 w-full px-3">
|
|
||||||
<div class="w-full mt-6 sm:w-auto flex flex-1 flex-col gap-1.5">
|
<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>
|
<h2 class="text-xl text-theme-base">Recently Updated</h2>
|
||||||
|
|
||||||
|
@ -21,5 +21,15 @@
|
||||||
|
|
||||||
<.note_list notes={@notes_published} layout={@channel.layout} />
|
<.note_list notes={@notes_published} layout={@channel.layout} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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
21
lib/chiya_web/outline.ex
Normal 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
|
|
@ -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
|
||||||
|
|
20
test/chiya_web/outline_test.exs
Normal file
20
test/chiya_web/outline_test.exs
Normal 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
|
12
test/support/simple_case.ex
Normal file
12
test/support/simple_case.ex
Normal 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
|
Loading…
Reference in a new issue