diff --git a/lib/chiya/channels/channel.ex b/lib/chiya/channels/channel.ex index 5d18d16..5447108 100644 --- a/lib/chiya/channels/channel.ex +++ b/lib/chiya/channels/channel.ex @@ -1,12 +1,13 @@ defmodule Chiya.Channels.Channel do use Ecto.Schema import Ecto.Changeset + alias Chiya.Channels.ChannelSlug @derive {Jason.Encoder, only: [:id, :name, :content, :slug, :visibility]} schema "channels" do field :content, :string field :name, :string - field :slug, :string + field :slug, ChannelSlug.Type field :visibility, Ecto.Enum, values: [:public, :private, :unlisted] many_to_many :notes, Chiya.Notes.Note, @@ -20,8 +21,9 @@ defmodule Chiya.Channels.Channel do def changeset(channel, attrs) do channel |> cast(attrs, [:name, :content, :visibility, :slug]) + |> ChannelSlug.maybe_generate_slug() + |> ChannelSlug.unique_constraint() |> validate_required([:name, :content, :visibility, :slug]) |> validate_exclusion(:slug, ~w(admin user dev)) - |> unique_constraint(:slug) end end diff --git a/lib/chiya/channels/channel_slug.ex b/lib/chiya/channels/channel_slug.ex new file mode 100644 index 0000000..69caba3 --- /dev/null +++ b/lib/chiya/channels/channel_slug.ex @@ -0,0 +1,3 @@ +defmodule Chiya.Channels.ChannelSlug do + use EctoAutoslugField.Slug, from: :name, to: :slug +end diff --git a/lib/chiya/notes/note.ex b/lib/chiya/notes/note.ex index 6a49a82..dc85047 100644 --- a/lib/chiya/notes/note.ex +++ b/lib/chiya/notes/note.ex @@ -1,6 +1,9 @@ defmodule Chiya.Notes.Note do use Ecto.Schema import Ecto.Changeset + alias Chiya.Notes.NoteSlug + + @reserved_slugs [] @derive {Jason.Encoder, only: [:id, :name, :content, :slug, :channels]} schema "notes" do @@ -8,7 +11,7 @@ defmodule Chiya.Notes.Note do field :kind, Ecto.Enum, values: [:post, :bookmark], default: :post field :name, :string field :published_at, :naive_datetime - field :slug, :string + field :slug, NoteSlug.Type field :url, :string many_to_many :channels, Chiya.Channels.Channel, @@ -27,7 +30,9 @@ defmodule Chiya.Notes.Note do |> Chiya.Notes.preload_note() |> cast(attrs, [:name, :content, :slug, :published_at, :kind, :url]) |> put_assoc(:channels, attrs["channels"] || []) + |> NoteSlug.maybe_generate_slug() + |> NoteSlug.unique_constraint() |> validate_required([:name, :content, :slug, :kind]) - |> unique_constraint(:slug) + |> validate_exclusion(:slug, @reserved_slugs) end end diff --git a/lib/chiya/notes/note_slug.ex b/lib/chiya/notes/note_slug.ex new file mode 100644 index 0000000..ad082d1 --- /dev/null +++ b/lib/chiya/notes/note_slug.ex @@ -0,0 +1,3 @@ +defmodule Chiya.Notes.NoteSlug do + use EctoAutoslugField.Slug, from: :name, to: :slug +end diff --git a/lib/chiya_web/components/layouts/public.html.heex b/lib/chiya_web/components/layouts/public.html.heex index 7c2a7a0..2372c82 100644 --- a/lib/chiya_web/components/layouts/public.html.heex +++ b/lib/chiya_web/components/layouts/public.html.heex @@ -3,7 +3,7 @@
<%= for channel <- @channels do %> <.link - href={~p"/#{channel.slug}"} + href={~p"/c/#{channel.slug}"} class="text-xs font-semibold leading-6 text-theme-base hover:text-theme-base/75" > <%= channel.name %> diff --git a/lib/chiya_web/controllers/admin_html/home.html.heex b/lib/chiya_web/controllers/admin_html/home.html.heex index d8310bb..b4c2cbe 100644 --- a/lib/chiya_web/controllers/admin_html/home.html.heex +++ b/lib/chiya_web/controllers/admin_html/home.html.heex @@ -13,7 +13,7 @@
- + <.icon name="hero-document-plus" /> New Note
\ No newline at end of file diff --git a/lib/chiya_web/controllers/channel_html/show.html.heex b/lib/chiya_web/controllers/channel_html/show.html.heex index c41c009..f76f99e 100644 --- a/lib/chiya_web/controllers/channel_html/show.html.heex +++ b/lib/chiya_web/controllers/channel_html/show.html.heex @@ -5,6 +5,9 @@ <.link href={~p"/admin/channels/#{@channel}/edit"}> <.button>Edit channel + <.link href={~p"/c/#{@channel.slug}"}> + <.button>Preview + diff --git a/lib/chiya_web/controllers/page_html/channel.html.heex b/lib/chiya_web/controllers/page_html/channel.html.heex index 681f6a0..6a9b1c9 100644 --- a/lib/chiya_web/controllers/page_html/channel.html.heex +++ b/lib/chiya_web/controllers/page_html/channel.html.heex @@ -10,7 +10,7 @@
<%= for note <- @channel.notes do %> <%= note.name %> diff --git a/lib/chiya_web/controllers/page_html/home.html.heex b/lib/chiya_web/controllers/page_html/home.html.heex index 14f12d2..8d7ec51 100644 --- a/lib/chiya_web/controllers/page_html/home.html.heex +++ b/lib/chiya_web/controllers/page_html/home.html.heex @@ -27,7 +27,7 @@
<%= for note <- @channel.notes do %> diff --git a/lib/chiya_web/live/note_show_live.ex b/lib/chiya_web/live/note_show_live.ex index eb06d75..b28c633 100644 --- a/lib/chiya_web/live/note_show_live.ex +++ b/lib/chiya_web/live/note_show_live.ex @@ -17,7 +17,7 @@ defmodule ChiyaWeb.NoteShowLive do <.link href={~p"/admin/notes/#{@note}/edit"}> <.button>Edit note - <.link href={~p"/n/#{@note.slug}"}> + <.link href={~p"/#{@note.slug}"}> <.button>Preview <.link href={~p"/admin/notes/#{@note}/raw"}> diff --git a/lib/chiya_web/markdown.ex b/lib/chiya_web/markdown.ex index cad2760..030cd81 100644 --- a/lib/chiya_web/markdown.ex +++ b/lib/chiya_web/markdown.ex @@ -2,7 +2,8 @@ defmodule ChiyaWeb.Markdown do @options [ footnotes: true, breaks: true, - escape: true + escape: true, + wikilinks: true ] def render(markdown) do diff --git a/lib/chiya_web/router.ex b/lib/chiya_web/router.ex index b0ebd06..16eec7d 100644 --- a/lib/chiya_web/router.ex +++ b/lib/chiya_web/router.ex @@ -109,8 +109,8 @@ defmodule ChiyaWeb.Router do scope "/", ChiyaWeb do pipe_through [:browser, :public] - get "/n/:slug", PageController, :note - get "/:slug", PageController, :channel + get "/:slug", PageController, :note + get "/c/:slug", PageController, :channel get "/", PageController, :home end end diff --git a/mix.exs b/mix.exs index d85e629..3492b15 100644 --- a/mix.exs +++ b/mix.exs @@ -36,6 +36,7 @@ defmodule Chiya.MixProject do {:phoenix, "~> 1.7.1"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.6"}, + {:ecto_autoslug_field, "~> 3.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 3.3"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/mix.lock b/mix.lock index 5c4ed10..8b9ed29 100644 --- a/mix.lock +++ b/mix.lock @@ -12,6 +12,7 @@ "earmark": {:hex, :earmark, "1.4.37", "56ce845c543393aa3f9b294c818c3d783452a4a67e4ab18c4303a954a8b59363", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "d86d5e12868db86d5321b00e62a4bbcb4150346e4acc9a90a041fb188a5cb106"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, + "ecto_autoslug_field": {:hex, :ecto_autoslug_field, "3.0.0", "37fbc2f07e6691136afff246f2cf5b159ad395b665a55d06db918975fd2397db", [:mix], [{:ecto, ">= 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.3.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm", "8ec252c7cf85f13132062f56a484d6a0ef1f981f7be9ce4ad7e9546dd8c0cc0f"}, "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 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", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, "elixir_make": {:hex, :elixir_make, "0.7.5", "784cc00f5fa24239067cc04d449437dcc5f59353c44eb08f188b2b146568738a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "c3d63e8d5c92fa3880d89ecd41de59473fa2e83eeb68148155e25e8b95aa2887"}, "esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"}, @@ -45,6 +46,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, "tailwind": {:hex, :tailwind, "0.2.0", "95f9e4a32020c5bec480f1d6a43a49ac8030b13183127b577605f506d6e13a66", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "385e939fcd7fe4654be5130b187e358aaabade385513f9d200ffecdbb9552a9e"}, diff --git a/test/chiya/channels_test.exs b/test/chiya/channels_test.exs index 76a0979..5d14092 100644 --- a/test/chiya/channels_test.exs +++ b/test/chiya/channels_test.exs @@ -23,14 +23,13 @@ defmodule Chiya.ChannelsTest do valid_attrs = %{ content: "some content", name: "some name", - slug: "some slug", visibility: :public } assert {:ok, %Channel{} = channel} = Channels.create_channel(valid_attrs) assert channel.content == "some content" assert channel.name == "some name" - assert channel.slug == "some slug" + assert channel.slug == "some-name" assert channel.visibility == :public end diff --git a/test/chiya/notes_test.exs b/test/chiya/notes_test.exs index c1366e3..6e9bd0b 100644 --- a/test/chiya/notes_test.exs +++ b/test/chiya/notes_test.exs @@ -25,7 +25,6 @@ defmodule Chiya.NotesTest do kind: "post", name: "some name", published_at: ~N[2023-03-04 16:22:00], - slug: "some slug", url: "some url" } @@ -34,7 +33,7 @@ defmodule Chiya.NotesTest do assert note.kind == :post assert note.name == "some name" assert note.published_at == ~N[2023-03-04 16:22:00] - assert note.slug == "some slug" + assert note.slug == "some-name" assert note.url == "some url" end