devel #57
34 changed files with 524 additions and 67 deletions
|
@ -84,7 +84,7 @@
|
|||
/* Set width and color for identity icons */
|
||||
a[rel] svg {
|
||||
width: 1em;
|
||||
fill: rgb(var(--color-text-primary));
|
||||
fill: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
/* Set width and color for identity icons */
|
||||
|
|
|
@ -5,9 +5,9 @@ defmodule Chiya.Notes do
|
|||
|
||||
import Ecto.Query, warn: false
|
||||
alias Chiya.Repo
|
||||
alias Chiya.Notes.{Note, NoteImage, NoteNote, References}
|
||||
alias Chiya.Notes.{Note, NoteImage, NoteNote, NoteTag}
|
||||
|
||||
@preloads [:channels, :images, :links_from, :links_to]
|
||||
@preloads [:channels, :images, :links_from, :links_to, :tags]
|
||||
|
||||
@doc """
|
||||
Returns the list of notes.
|
||||
|
@ -158,6 +158,7 @@ defmodule Chiya.Notes do
|
|||
note
|
||||
|> Note.changeset(attrs)
|
||||
|> Repo.update()
|
||||
|> Chiya.Tags.TagUpdater.update_tags(attrs)
|
||||
|> Chiya.Notes.References.update_references(attrs)
|
||||
end
|
||||
|
||||
|
@ -248,4 +249,18 @@ defmodule Chiya.Notes do
|
|||
def change_note_image(%NoteImage{} = note_image, attrs \\ %{}) do
|
||||
NoteImage.update_changeset(note_image, attrs)
|
||||
end
|
||||
|
||||
def get_note_tag!(attrs \\ %{}) do
|
||||
Repo.get_by!(NoteTag, attrs)
|
||||
end
|
||||
|
||||
def create_note_tag(attrs \\ %{}) do
|
||||
%NoteTag{}
|
||||
|> NoteTag.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def delete_note_tag(%NoteTag{} = note_tag) do
|
||||
Repo.delete(note_tag)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,11 @@ defmodule Chiya.Notes.Note do
|
|||
@derive {Jason.Encoder, only: [:id, :name, :content, :slug, :channels]}
|
||||
schema "notes" do
|
||||
field :content, :string
|
||||
field :kind, Ecto.Enum, values: [:post, :bookmark], default: :post
|
||||
|
||||
field :kind, Ecto.Enum,
|
||||
values: [:post, :bookmark, :recipe],
|
||||
default: :post
|
||||
|
||||
field :name, :string
|
||||
field :published_at, :naive_datetime
|
||||
field :slug, NoteSlug.Type
|
||||
|
@ -27,8 +31,16 @@ defmodule Chiya.Notes.Note do
|
|||
join_through: Chiya.Notes.NoteNote,
|
||||
join_keys: [source_id: :id, target_id: :id]
|
||||
|
||||
many_to_many :tags, Chiya.Tags.Tag,
|
||||
join_through: Chiya.Notes.NoteTag,
|
||||
join_keys: [note_id: :id, tag_id: :id]
|
||||
|
||||
has_many :images, Chiya.Notes.NoteImage
|
||||
|
||||
field :tags_string, :string,
|
||||
virtual: true,
|
||||
default: ""
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
|
|
19
lib/chiya/notes/note_tag.ex
Normal file
19
lib/chiya/notes/note_tag.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
defmodule Chiya.Notes.NoteTag do
|
||||
@moduledoc """
|
||||
The NoteTag module
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "notes_tags" do
|
||||
belongs_to :note, Chiya.Notes.Note
|
||||
belongs_to :tag, Chiya.Tags.Tag
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(note_tag, attrs) do
|
||||
note_tag
|
||||
|> cast(attrs, [:note_id, :tag_id])
|
||||
|> validate_required([:note_id, :tag_id])
|
||||
end
|
||||
end
|
|
@ -75,7 +75,6 @@ defmodule Chiya.Notes.References do
|
|||
end)
|
||||
end
|
||||
|
||||
|
||||
def update_references({:ok, note}, attrs) do
|
||||
new_reference_slugs = get_reference_ids(attrs["content"])
|
||||
old_reference_slugs = Enum.map(note.links_from, fn n -> n.slug end)
|
||||
|
@ -118,6 +117,7 @@ defmodule Chiya.Notes.References do
|
|||
attrs = get_attrs(origin_note.id, linked_note.id)
|
||||
note_note = Chiya.Notes.get_note_note(attrs)
|
||||
|
||||
if note_note do
|
||||
case Chiya.Notes.delete_note_note(note_note) do
|
||||
{:ok, _note_note} ->
|
||||
Logger.info("Reference to '#{slug}' deleted")
|
||||
|
@ -125,6 +125,10 @@ defmodule Chiya.Notes.References do
|
|||
error ->
|
||||
Logger.warn(error)
|
||||
end
|
||||
else
|
||||
Logger.debug("Note '#{slug}' does not exist anymore.")
|
||||
end
|
||||
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
136
lib/chiya/tags.ex
Normal file
136
lib/chiya/tags.ex
Normal file
|
@ -0,0 +1,136 @@
|
|||
defmodule Chiya.Tags do
|
||||
@moduledoc """
|
||||
The Tags context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Chiya.Repo
|
||||
|
||||
alias Chiya.Tags.Tag
|
||||
|
||||
@preloads [:notes]
|
||||
defp with_preloads(query), do: preload(query, ^@preloads)
|
||||
|
||||
@doc """
|
||||
Preloads a tag with all defined preloads.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> preload_tag(tag)
|
||||
%Tag{}
|
||||
|
||||
"""
|
||||
def preload_tag(tag), do: Repo.preload(tag, @preloads)
|
||||
|
||||
@doc """
|
||||
Returns the list of tags.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_tags()
|
||||
[%Tag{}, ...]
|
||||
|
||||
"""
|
||||
def list_tags do
|
||||
Tag
|
||||
|> with_preloads()
|
||||
|> order_by(:title)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single tag.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Tag does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_tag!(123)
|
||||
%Tag{}
|
||||
|
||||
iex> get_tag!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_tag_by_slug!(slug), do: Repo.get_by!(Tag, slug: slug) |> preload_tag()
|
||||
|
||||
@doc """
|
||||
Gets a single tag.
|
||||
|
||||
Returns nil if the Tag does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_tag(123)
|
||||
%Tag{}
|
||||
|
||||
iex> get_tag(456)
|
||||
nil
|
||||
"""
|
||||
def get_tag_by_slug(slug), do: Repo.get_by(Tag, slug: slug) |> preload_tag()
|
||||
|
||||
@doc """
|
||||
Creates a tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_tag(%{field: value})
|
||||
{:ok, %Tag{}}
|
||||
|
||||
iex> create_tag(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_tag(attrs \\ %{}) do
|
||||
%Tag{}
|
||||
|> Tag.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_tag(tag, %{field: new_value})
|
||||
{:ok, %Tag{}}
|
||||
|
||||
iex> update_tag(tag, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_tag(%Tag{} = tag, attrs) do
|
||||
tag
|
||||
|> Tag.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_tag(tag)
|
||||
{:ok, %Tag{}}
|
||||
|
||||
iex> delete_tag(tag)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_tag(%Tag{} = tag) do
|
||||
Repo.delete(tag)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking tag changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_tag(tag)
|
||||
%Ecto.Changeset{data: %Tag{}}
|
||||
|
||||
"""
|
||||
def change_tag(%Tag{} = tag, attrs \\ %{}) do
|
||||
Tag.changeset(tag, attrs)
|
||||
end
|
||||
end
|
32
lib/chiya/tags/tag.ex
Normal file
32
lib/chiya/tags/tag.ex
Normal file
|
@ -0,0 +1,32 @@
|
|||
defmodule Chiya.Tags.Tag do
|
||||
@moduledoc """
|
||||
The Tag Schema
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Chiya.Tags.TagSlug
|
||||
|
||||
schema "tags" do
|
||||
field :name, :string
|
||||
field :slug, TagSlug.Type
|
||||
|
||||
field :content, :string
|
||||
|
||||
field :icon, :string
|
||||
field :regex, :string
|
||||
|
||||
many_to_many :notes, Chiya.Notes.Note, join_through: "notes_tags"
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(tag, attrs) do
|
||||
tag
|
||||
|> cast(attrs, [:name, :content, :icon, :regex])
|
||||
|> validate_required([:name])
|
||||
|> unique_constraint(:name)
|
||||
|> TagSlug.maybe_generate_slug()
|
||||
|> TagSlug.unique_constraint()
|
||||
end
|
||||
end
|
3
lib/chiya/tags/tag_slug.ex
Normal file
3
lib/chiya/tags/tag_slug.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule Chiya.Tags.TagSlug do
|
||||
use EctoAutoslugField.Slug, from: :name, to: :slug
|
||||
end
|
112
lib/chiya/tags/tag_updater.ex
Normal file
112
lib/chiya/tags/tag_updater.ex
Normal file
|
@ -0,0 +1,112 @@
|
|||
defmodule Chiya.Tags.TagUpdater do
|
||||
@moduledoc """
|
||||
Updates tags for a schema like notes by adding new ones and removing old ones.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Chiya.{Notes, Tags}
|
||||
alias Chiya.Notes.Note
|
||||
|
||||
def update_tags({:ok, %Note{} = note}, attrs) do
|
||||
update_tags(note, attrs)
|
||||
|
||||
{:ok, note}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the tags for the given schema
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_tags(note, "foo,bar")
|
||||
|
||||
"""
|
||||
def update_tags(schema, %{tags_string: new_tags} = attrs) when is_map(attrs) do
|
||||
update_tags(schema, new_tags)
|
||||
end
|
||||
|
||||
def update_tags(schema, %{"tags_string" => new_tags} = attrs) when is_map(attrs) do
|
||||
update_tags(schema, new_tags)
|
||||
end
|
||||
|
||||
def update_tags(schema, attrs) when is_map(attrs) do
|
||||
schema
|
||||
end
|
||||
|
||||
def update_tags(schema, new_tags) when is_binary(new_tags) do
|
||||
update_tags(schema, split_tags(new_tags))
|
||||
end
|
||||
|
||||
def update_tags(%{tags: tags} = schema, new_tags) when is_list(new_tags) do
|
||||
old_tags = Enum.map(tags, fn tag -> tag.name end)
|
||||
|
||||
Logger.info("Adding tags #{inspect(new_tags -- old_tags)}")
|
||||
Logger.info("Removing tags #{inspect(old_tags -- new_tags)}")
|
||||
|
||||
schema
|
||||
|> add_tags(new_tags -- old_tags)
|
||||
|> remove_tags(old_tags -- new_tags)
|
||||
end
|
||||
|
||||
defp split_tags(tags_string) when is_binary(tags_string) do
|
||||
tags_string
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.trim/1)
|
||||
|> Enum.filter(&(String.length(&1) > 0))
|
||||
end
|
||||
|
||||
defp add_tags(schema, tags) do
|
||||
Enum.each(tags, &add_tag(schema, &1))
|
||||
schema
|
||||
end
|
||||
|
||||
defp add_tag(%{id: schema_id} = schema, tag) when is_binary(tag) do
|
||||
slug = Slugger.slugify_downcase(tag)
|
||||
|
||||
Logger.debug("Looking up tag [#{tag}] with slug [#{slug}]")
|
||||
|
||||
{:ok, tag} =
|
||||
case Tags.get_tag(slug) do
|
||||
nil ->
|
||||
Logger.debug("Tag [#{tag}] does not exist. Creating.")
|
||||
Tags.create_tag(%{name: tag})
|
||||
|
||||
tag ->
|
||||
Logger.debug("Tag already exists. Returning.")
|
||||
{:ok, tag}
|
||||
end
|
||||
|
||||
case schema do
|
||||
%Note{} ->
|
||||
attrs = %{
|
||||
note_id: schema_id,
|
||||
tag_id: tag.id
|
||||
}
|
||||
|
||||
{:ok, _note_tag} = Notes.create_note_tag(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_tags(schema, tags) do
|
||||
Enum.each(tags, &remove_tag(schema, &1))
|
||||
schema
|
||||
end
|
||||
|
||||
defp remove_tag(schema, tag) do
|
||||
slug = Slugger.slugify_downcase(tag)
|
||||
|
||||
if tag = Tags.get_tag(slug) do
|
||||
case schema do
|
||||
%Note{} ->
|
||||
attrs = %{tag_id: tag.id, note_id: schema.id}
|
||||
note_tag = Notes.get_note_tag!(attrs)
|
||||
|
||||
{:ok, _} = Notes.delete_note_tag(note_tag)
|
||||
end
|
||||
else
|
||||
Logger.warn("Tag with slug #{slug} was not removed.")
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -321,8 +321,11 @@ defmodule ChiyaWeb.CoreComponents do
|
|||
attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
|
||||
attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
|
||||
attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
|
||||
attr :rest, :global, include: ~w(autocomplete accept cols disabled form max maxlength min minlength
|
||||
|
||||
attr :rest, :global,
|
||||
include: ~w(autocomplete accept cols disabled form max maxlength min minlength
|
||||
pattern placeholder readonly required rows size step)
|
||||
|
||||
slot :inner_block
|
||||
|
||||
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
|
||||
|
|
|
@ -32,8 +32,10 @@
|
|||
</main>
|
||||
|
||||
<footer class="px-4 py-1 sm:px-6 lg:px-8 bg-white/30 dark:bg-black/30 text-gray-900 dark:text-gray-100 text-xs leading-6 font-semibold">
|
||||
<span class="text-theme-primary">⌘</span> <span>Chiya</span> <span class="rounded-full bg-theme-primary/10 px-2 text-xs font-medium leading-6 text-theme-primary">
|
||||
v<%= Application.spec(:chiya, :vsn) %></span>
|
||||
<span class="text-theme-primary">⌘</span> <span>Chiya</span>
|
||||
<span class="rounded-full bg-theme-primary/10 px-2 text-xs font-medium leading-6 text-theme-primary">
|
||||
v<%= Application.spec(:chiya, :vsn) %>
|
||||
</span>
|
||||
</footer>
|
||||
|
||||
<%= raw(@settings.custom_html) %>
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
</section>
|
||||
|
||||
<section class="mt-4 flex gap-4">
|
||||
<a href="/admin/notes/new" class="p-4 rounded text-gray-700 bg-gray-100 shadow border border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 hover:text-gray-700/80 dark:hover:text-gray-200/80 transition hover:shadow-none">
|
||||
<a
|
||||
href="/admin/notes/new"
|
||||
class="p-4 rounded text-gray-700 bg-gray-100 shadow border border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 hover:text-gray-700/80 dark:hover:text-gray-200/80 transition hover:shadow-none"
|
||||
>
|
||||
<.icon name="hero-document-plus" /> New Note
|
||||
</a>
|
||||
</section>
|
|
@ -10,7 +10,9 @@
|
|||
|
||||
<.table id="channels" rows={@channels} row_click={&JS.navigate(~p"/admin/channels/#{&1}")}>
|
||||
<:col :let={channel} label="Name"><%= channel.name %></:col>
|
||||
<:col :let={channel} label="Visibility"><%= Chiya.Channels.Channel.icon(channel) %> <%= channel.visibility %></:col>
|
||||
<:col :let={channel} label="Visibility">
|
||||
<%= Chiya.Channels.Channel.icon(channel) %> <%= channel.visibility %>
|
||||
</:col>
|
||||
<:col :let={channel} label="Notes"><%= Enum.count(channel.notes) %></:col>
|
||||
<:col :let={channel} label="Slug"><%= channel.slug %></:col>
|
||||
<:action :let={channel}>
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
</.header>
|
||||
|
||||
<.table id="identities" rows={@identities} row_click={&JS.navigate(~p"/admin/identities/#{&1}")}>
|
||||
<:col :let={identity} label="Icon"><%= raw(identity.icon) %></:col>
|
||||
<:col :let={identity} label="Icon">
|
||||
<span class="fill-gray-900 dark:fill-gray-200"><%= raw(identity.icon) %></span>
|
||||
</:col>
|
||||
<:col :let={identity} label="Name"><%= identity.name %></:col>
|
||||
<:col :let={identity} label="Rel"><%= identity.rel %></:col>
|
||||
<:col :let={identity} label="Url"><%= identity.url %></:col>
|
||||
|
|
|
@ -24,7 +24,12 @@ defmodule ChiyaWeb.NoteController do
|
|||
|
||||
def new(conn, _params) do
|
||||
changeset = Notes.change_note(%Note{})
|
||||
render(conn, :new, changeset: changeset, channels: to_channel_options())
|
||||
|
||||
render(conn, :new,
|
||||
changeset: changeset,
|
||||
channels: to_channel_options(),
|
||||
tags: []
|
||||
)
|
||||
end
|
||||
|
||||
def create(conn, %{"note" => note_params}) do
|
||||
|
@ -50,7 +55,13 @@ defmodule ChiyaWeb.NoteController do
|
|||
def edit(conn, %{"id" => id}) do
|
||||
note = Notes.get_note_preloaded!(id)
|
||||
changeset = Notes.change_note(note)
|
||||
render(conn, :edit, note: note, changeset: changeset, channels: to_channel_options())
|
||||
|
||||
render(conn, :edit,
|
||||
note: note,
|
||||
changeset: changeset,
|
||||
channels: to_channel_options(),
|
||||
tags: note.tags
|
||||
)
|
||||
end
|
||||
|
||||
def update(conn, %{"id" => id, "note" => note_params}) do
|
||||
|
@ -64,7 +75,11 @@ defmodule ChiyaWeb.NoteController do
|
|||
|> redirect(to: ~p"/admin/notes/#{note}")
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, :edit, note: note, changeset: changeset, channels: to_channel_options())
|
||||
render(conn, :edit,
|
||||
note: note,
|
||||
changeset: changeset,
|
||||
channels: to_channel_options(),
|
||||
tags: note.tags)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,8 +9,13 @@ defmodule ChiyaWeb.NoteHTML do
|
|||
attr :changeset, Ecto.Changeset, required: true
|
||||
attr :action, :string, required: true
|
||||
attr :channels, :list, required: true
|
||||
attr :tags, :list, required: true
|
||||
|
||||
def note_form(assigns)
|
||||
|
||||
def selected_channels(changeset), do: Enum.map(changeset.data.channels, fn c -> c.id end)
|
||||
def selected_channels(changeset), do:
|
||||
Enum.map(changeset.data.channels, fn c -> c.id end)
|
||||
|
||||
def tags_to_string(tags), do:
|
||||
Enum.map_join(tags, ", ", fn t -> t.name end)
|
||||
end
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
<:subtitle>Use this form to manage note records in your database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.note_form changeset={@changeset} action={~p"/admin/notes/#{@note}"} channels={@channels} />
|
||||
<.note_form changeset={@changeset} action={~p"/admin/notes/#{@note}"} channels={@channels} tags={@tags} />
|
||||
|
||||
<.back navigate={~p"/admin/notes"}>Back to notes</.back>
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
<:subtitle>Use this form to manage note image records in your database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form :let={f} for={@changeset} action={~p"/admin/notes/#{@image.note.id}/image/#{@image.id}"}>
|
||||
<.simple_form
|
||||
:let={f}
|
||||
for={@changeset}
|
||||
action={~p"/admin/notes/#{@image.note.id}/image/#{@image.id}"}
|
||||
>
|
||||
<.error :if={@changeset.action}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
|
@ -17,5 +21,4 @@
|
|||
</:actions>
|
||||
</.simple_form>
|
||||
|
||||
|
||||
<.back navigate={~p"/admin/notes/#{@image.note_id}"}>Back to notes</.back>
|
|
@ -12,9 +12,20 @@
|
|||
</.header>
|
||||
|
||||
<section class="flex flex-row flex-wrap mt-4 gap-3">
|
||||
<a href={~p"/admin/notes"} class="text-sm dark:text-gray-300 rounded-full bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-2 py-1">All</a>
|
||||
<a
|
||||
href={~p"/admin/notes"}
|
||||
class="text-sm dark:text-gray-300 rounded-full bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-2 py-1"
|
||||
>
|
||||
All
|
||||
</a>
|
||||
<%= for channel <- @channels do %>
|
||||
<a href={~p"/admin/notes?channel=#{channel.slug}"} class="text-sm dark:text-gray-300 rounded-full bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-2 py-1"><%= channel.name %> <span class="text-gray-600 dark:text-gray-500">(<%= Enum.count(channel.notes) %>)</span></a>
|
||||
<a
|
||||
href={~p"/admin/notes?channel=#{channel.slug}"}
|
||||
class="text-sm dark:text-gray-300 rounded-full bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-2 py-1"
|
||||
>
|
||||
<%= channel.name %>
|
||||
<span class="text-gray-600 dark:text-gray-500">(<%= Enum.count(channel.notes) %>)</span>
|
||||
</a>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
<:subtitle>Use this form to manage note records in your database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.note_form changeset={@changeset} action={~p"/admin/notes"} channels={@channels} />
|
||||
<.note_form changeset={@changeset} action={~p"/admin/notes"} channels={@channels} tags={@tags} />
|
||||
|
||||
<.back navigate={~p"/admin/notes"}>Back to notes</.back>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
options={Ecto.Enum.values(Chiya.Notes.Note, :kind)}
|
||||
/>
|
||||
<.input field={f[:url]} type="text" label="Url" />
|
||||
<.input field={f[:tags_string]} type="text" label="Tags" value={tags_to_string(@tags)} />
|
||||
<.input
|
||||
field={f[:channels]}
|
||||
type="select"
|
||||
|
@ -22,7 +23,9 @@
|
|||
options={@channels}
|
||||
value={selected_channels(@changeset)}
|
||||
/>
|
||||
|
||||
<:actions>
|
||||
<.button>Save Note</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
|
||||
|
|
|
@ -29,6 +29,16 @@ defmodule ChiyaWeb.PageController do
|
|||
)
|
||||
end
|
||||
|
||||
def tag(conn, %{"slug" => tag_slug}) do
|
||||
tag = Chiya.Tags.get_tag_by_slug!(tag_slug)
|
||||
|
||||
render(conn, :tag,
|
||||
layout: {ChiyaWeb.Layouts, "public.html"},
|
||||
tag: tag,
|
||||
page_title: tag.name
|
||||
)
|
||||
end
|
||||
|
||||
def note(conn, %{"slug" => note_slug}) do
|
||||
note = Chiya.Notes.get_note_by_slug_preloaded!(note_slug)
|
||||
|
||||
|
|
|
@ -2,4 +2,7 @@ defmodule ChiyaWeb.PageHTML do
|
|||
use ChiyaWeb, :html_public
|
||||
|
||||
embed_templates "page_html/*"
|
||||
|
||||
def tag_list([]), do: "No Tags"
|
||||
def tag_list(tags), do: Enum.map_join(tags, ", ", fn t -> t.name end)
|
||||
end
|
||||
|
|
|
@ -4,9 +4,18 @@
|
|||
<%= @note.name %>
|
||||
</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-theme-base">
|
||||
<span>Published</span> <time class="text-theme-primary font-semibold"><%= pretty_date(@note.published_at) %></time>
|
||||
<span>Published</span>
|
||||
<time class="text-theme-primary font-semibold"><%= pretty_date(@note.published_at) %></time>
|
||||
<span>·</span>
|
||||
<span>Last Updated</span> <time class="text-theme-primary font-semibold"><%= pretty_date(@note.updated_at) %></time>
|
||||
<span>Last Updated</span>
|
||||
<time class="text-theme-primary font-semibold"><%= pretty_date(@note.updated_at) %></time>
|
||||
<span>·</span>
|
||||
<span>Tags</span>
|
||||
<span class="text-theme-primary font-semibold">
|
||||
<%= for tag <- @note.tags do %>
|
||||
<a href={~p"/t/#{tag.slug}"}><%= tag.name %></a>
|
||||
<% end %>
|
||||
</span>
|
||||
<%= if @current_user do %>
|
||||
<span>·</span>
|
||||
<a href={~p"/admin/notes/#{@note}/edit"} class="text-theme-primary font-semibold">Edit</a>
|
||||
|
@ -23,8 +32,16 @@
|
|||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<%= for image <- @note.images do %>
|
||||
<a href={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)} class="lightbox | w-28" data-gallery="note" data-description={ChiyaWeb.Markdown.render(image.content)}>
|
||||
<img src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)} class="rounded"/>
|
||||
<a
|
||||
href={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)}
|
||||
class="lightbox | w-28"
|
||||
data-gallery="note"
|
||||
data-description={ChiyaWeb.Markdown.render(image.content)}
|
||||
>
|
||||
<img
|
||||
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)}
|
||||
class="rounded"
|
||||
/>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
20
lib/chiya_web/controllers/page_html/tag.html.heex
Normal file
20
lib/chiya_web/controllers/page_html/tag.html.heex
Normal file
|
@ -0,0 +1,20 @@
|
|||
<div class="mx-auto max-w-xl mx-4 lg:mx-0">
|
||||
<h1 class="mt-16 text-[2rem] font-semibold font-serif leading-10 tracking-tighter text-theme-heading">
|
||||
Tagged with “<%= @tag.name %>”
|
||||
</h1>
|
||||
<p class="mt-4 text-base leading-7 text-theme-base">
|
||||
<%= @tag.content %>
|
||||
</p>
|
||||
|
||||
<div class="w-full mt-6 sm:w-auto flex flex-col gap-1.5">
|
||||
<%= for note <- @tag.notes do %>
|
||||
<a
|
||||
href={~p"/#{note.slug}"}
|
||||
class="rounded -mx-2 -my-0.5 px-2 py-0.5 hover:bg-theme-primary/10 transition"
|
||||
>
|
||||
<span class="text-theme-primary text-lg font-semibold leading-8"><%= note.name %></span>
|
||||
<span class="text-theme-base text-sm"><%= pretty_date(note.published_at) %></span>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -29,10 +29,13 @@ defmodule ChiyaWeb.NoteShowLive do
|
|||
</.header>
|
||||
|
||||
<.list>
|
||||
<:item title="Published at"><%= pretty_date(@note.published_at) %> <span>(<%= from_now(@note.published_at) %>)</span></:item>
|
||||
<:item title="Published at">
|
||||
<%= pretty_date(@note.published_at) %> <span>(<%= from_now(@note.published_at) %>)</span>
|
||||
</:item>
|
||||
<:item title="Channels"><%= @channels %></:item>
|
||||
<:item title="Kind"><%= @note.kind %></:item>
|
||||
<:item title="Url"><%= @note.url %></:item>
|
||||
<:item title="Tags"><%= note_tags(@note.tags) %></:item>
|
||||
<:item title="Links outgoing"><%= note_links(@note.links_to) %></:item>
|
||||
<:item title="Links incoming"><%= note_links(@note.links_from) %></:item>
|
||||
</.list>
|
||||
|
@ -48,11 +51,7 @@ defmodule ChiyaWeb.NoteShowLive do
|
|||
class="rounded-lg border border-theme-dim w-28"
|
||||
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)}
|
||||
/>
|
||||
<.button
|
||||
phx-click="delete_image"
|
||||
phx-value-id={image.id}
|
||||
data-confirm="Are you sure?"
|
||||
>
|
||||
<.button phx-click="delete_image" phx-value-id={image.id} data-confirm="Are you sure?">
|
||||
Delete Image
|
||||
</.button>
|
||||
</a>
|
||||
|
@ -153,7 +152,6 @@ defmodule ChiyaWeb.NoteShowLive do
|
|||
{:noreply, assign(socket, :note, Notes.get_note_preloaded!(socket.assigns.note.id))}
|
||||
end
|
||||
|
||||
defp note_links(notes) do
|
||||
Enum.map_join(notes, ", ",fn n -> n.name end)
|
||||
end
|
||||
defp note_links(notes), do: Enum.map_join(notes, ", ", fn n -> n.name end)
|
||||
defp note_tags(tags), do: Enum.map_join(tags, ", ", fn t -> t.name end)
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ defmodule ChiyaWeb.UserLoginLive do
|
|||
|
||||
<:actions>
|
||||
<.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
|
||||
<.link href={~p"/user/reset_password"} class="text-sm font-semibold">
|
||||
<.link href={~p"/user/reset_password"} class="text-sm font-semibold dark:text-gray-100">
|
||||
Forgot your password?
|
||||
</.link>
|
||||
</:actions>
|
||||
|
|
|
@ -117,6 +117,7 @@ defmodule ChiyaWeb.Router do
|
|||
|
||||
get "/:slug", PageController, :note
|
||||
get "/c/:slug", PageController, :channel
|
||||
get "/t/:slug", PageController, :tag
|
||||
get "/", PageController, :home
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,14 +2,16 @@ defmodule Chiya.Repo.Migrations.FixNoteImageReference do
|
|||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
drop constraint :note_images, "note_images_note_id_fkey"
|
||||
drop constraint(:note_images, "note_images_note_id_fkey")
|
||||
|
||||
alter table(:note_images) do
|
||||
modify :note_id, references(:notes, on_delete: :delete_all)
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
drop constraint :note_images, "note_images_note_id_fkey"
|
||||
drop constraint(:note_images, "note_images_note_id_fkey")
|
||||
|
||||
alter table(:note_images) do
|
||||
modify :note_id, references(:notes, on_delete: :nothing)
|
||||
end
|
||||
|
|
24
priv/repo/migrations/20230410153445_add_tags.exs
Normal file
24
priv/repo/migrations/20230410153445_add_tags.exs
Normal file
|
@ -0,0 +1,24 @@
|
|||
defmodule Chiya.Repo.Migrations.AddTags do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:tags) do
|
||||
add :name, :string
|
||||
add :slug, :string
|
||||
add :content, :text
|
||||
add :icon, :string
|
||||
add :regex, :string
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create table(:notes_tags) do
|
||||
add :note_id, references(:notes, on_delete: :delete_all)
|
||||
add :tag_id, references(:tags, on_delete: :delete_all)
|
||||
end
|
||||
|
||||
create unique_index(:tags, [:slug])
|
||||
create unique_index(:tags, [:name])
|
||||
create unique_index(:notes_tags, [:note_id, :tag_id])
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue