diff --git a/.tool-versions b/.tool-versions index ba05ca2..3216775 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,3 @@ erlang 25.2.3 elixir 1.14.3-otp-25 +nodejs 18.16.0 diff --git a/lib/chiya/channels.ex b/lib/chiya/channels.ex index f3afefd..021cf7b 100644 --- a/lib/chiya/channels.ex +++ b/lib/chiya/channels.ex @@ -53,6 +53,8 @@ defmodule Chiya.Channels do """ def get_channel!(id), do: Repo.get!(Channel, id) + def get_channel(id), do: Repo.get(Channel, id) + @doc """ Gets a single channel with all associated entities preloaded. """ diff --git a/lib/chiya/notes.ex b/lib/chiya/notes.ex index 414cb2a..b0ceb1f 100644 --- a/lib/chiya/notes.ex +++ b/lib/chiya/notes.ex @@ -218,6 +218,15 @@ defmodule Chiya.Notes do Repo.delete(note) end + def publish_note(%Note{} = note, published_at) do + {1, nil} = + Note + |> where([n], n.id == ^note.id) + |> Repo.update_all(set: [published_at: published_at]) + + {:ok, note} + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking note changes. diff --git a/lib/chiya/site/setting.ex b/lib/chiya/site/setting.ex index 7ae485b..a8561a1 100644 --- a/lib/chiya/site/setting.ex +++ b/lib/chiya/site/setting.ex @@ -31,7 +31,8 @@ defmodule Chiya.Site.Setting do :custom_css, :custom_html, :home_channel_id, - :default_channel_id + :default_channel_id, + :micropub_channel_id ]) |> validate_required([:title, :subtitle, :theme, :user_agent]) end diff --git a/lib/chiya_web.ex b/lib/chiya_web.ex index 6de6716..6a8b18d 100644 --- a/lib/chiya_web.ex +++ b/lib/chiya_web.ex @@ -58,7 +58,7 @@ defmodule ChiyaWeb do layout: {ChiyaWeb.Layouts, :app} # Import admin components - import ChiyaWeb.AdminComponents + import ChiyaWeb.CoreComponents unquote(html_helpers()) end @@ -69,7 +69,7 @@ defmodule ChiyaWeb do use Phoenix.LiveComponent # Import admin components - import ChiyaWeb.AdminComponents + import ChiyaWeb.CoreComponents unquote(html_helpers()) end @@ -84,7 +84,7 @@ defmodule ChiyaWeb do only: [get_csrf_token: 0, view_module: 1, view_template: 1] # Import admin components - import ChiyaWeb.AdminComponents + import ChiyaWeb.CoreComponents # Include general helpers for rendering HTML unquote(html_helpers()) @@ -112,7 +112,6 @@ defmodule ChiyaWeb do # HTML escaping functionality import Phoenix.HTML # Core UI components and translation - import ChiyaWeb.CoreComponents import ChiyaWeb.Gettext # Shortcut for generating JS commands diff --git a/lib/chiya_web/components/admin_components.ex b/lib/chiya_web/components/admin_components.ex deleted file mode 100644 index f29e041..0000000 --- a/lib/chiya_web/components/admin_components.ex +++ /dev/null @@ -1,126 +0,0 @@ -defmodule ChiyaWeb.AdminComponents do - use Phoenix.Component - - use Phoenix.VerifiedRoutes, - endpoint: ChiyaWeb.Endpoint, - router: ChiyaWeb.Router, - statics: ChiyaWeb.static_paths() - - import ChiyaWeb.CoreComponents - - @doc """ - Renders a horizontal line - """ - def line(assigns) do - ~H""" -
- """ - end - - @doc """ - Renders a UI for uploading files - """ - - attr :upload, :map, required: true - attr :cancel_upload, :string, default: "cancel-upload" - - def live_upload(assigns) do - ~H""" -
- <.live_file_input upload={@upload} class="dark:text-gray-300" /> - -
- <%= for entry <- @upload.entries do %> -
-
- <.live_img_preview entry={entry} /> -
<%= entry.client_name %>
-
- -
- <%!-- entry.progress will update automatically for in-flight entries --%> - - <%= entry.progress %>% - - - <%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%> - -
- - <%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%> - <%= for err <- upload_errors(@upload, entry) do %> -

<%= upload_error_to_string(err) %>

- <% end %> -
- <% end %> -
-
- """ - end - - @doc """ - Renders the admin menu bar - """ - - attr :current_user, :map, required: true - attr :settings, :map, required: true - - def admin_bar(assigns) do - ~H""" - - """ - end - - defp upload_error_to_string(:too_large), do: "Too large" - defp upload_error_to_string(:too_many_files), do: "You have selected too many files" - defp upload_error_to_string(:not_accepted), do: "You have selected an unacceptable file type" -end diff --git a/lib/chiya_web/components/core_components.ex b/lib/chiya_web/components/core_components.ex index d43e82a..4924063 100644 --- a/lib/chiya_web/components/core_components.ex +++ b/lib/chiya_web/components/core_components.ex @@ -653,6 +653,118 @@ defmodule ChiyaWeb.CoreComponents do """ end + @doc """ + Renders a horizontal line + """ + def line(assigns) do + ~H""" +
+ """ + end + + @doc """ + Renders a UI for uploading files + """ + + attr :upload, :map, required: true + attr :cancel_upload, :string, default: "cancel-upload" + + def live_upload(assigns) do + ~H""" +
+ <.live_file_input upload={@upload} class="dark:text-gray-300" /> + +
+ <%= for entry <- @upload.entries do %> +
+
+ <.live_img_preview entry={entry} /> +
<%= entry.client_name %>
+
+ +
+ <%!-- entry.progress will update automatically for in-flight entries --%> + + <%= entry.progress %>% + + + <%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%> + +
+ + <%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%> + <%= for err <- upload_errors(@upload, entry) do %> +

<%= upload_error_to_string(err) %>

+ <% end %> +
+ <% end %> +
+
+ """ + end + + @doc """ + Renders the admin menu bar + """ + + attr :current_user, :map, required: true + attr :settings, :map, required: true + + def admin_bar(assigns) do + ~H""" + + """ + end + ## JS Commands def show(js \\ %JS{}, selector) do @@ -734,4 +846,9 @@ defmodule ChiyaWeb.CoreComponents do def translate_errors(errors, field) when is_list(errors) do for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) end + + defp upload_error_to_string(:too_large), do: "Too large" + defp upload_error_to_string(:too_many_files), do: "You have selected too many files" + defp upload_error_to_string(:not_accepted), do: "You have selected an unacceptable file type" + end diff --git a/lib/chiya_web/components/layouts.ex b/lib/chiya_web/components/layouts.ex index f7fe4e1..2ff0c0c 100644 --- a/lib/chiya_web/components/layouts.ex +++ b/lib/chiya_web/components/layouts.ex @@ -1,7 +1,7 @@ defmodule ChiyaWeb.Layouts do use ChiyaWeb, :html - import ChiyaWeb.PublicComponents + import ChiyaWeb.PublicComponents, only: [divider: 1] embed_templates "layouts/*" end diff --git a/lib/chiya_web/components/public_components.ex b/lib/chiya_web/components/public_components.ex index 24f3f8a..dde1679 100644 --- a/lib/chiya_web/components/public_components.ex +++ b/lib/chiya_web/components/public_components.ex @@ -9,7 +9,35 @@ defmodule ChiyaWeb.PublicComponents do import ChiyaWeb.Format import ChiyaWeb.Markdown, only: [render: 1] import Phoenix.HTML, only: [raw: 1] - import ChiyaWeb.CoreComponents + + @doc """ + Renders a [Hero Icon](https://heroicons.com). + + Hero icons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid an mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from your `priv/hero_icons` directory and bundled + within your compiled app.css by the plugin in your `assets/tailwind.config.js`. + + ## Examples + + <.icon name="hero-cake" /> + <.icon name="hero-cake-solid" /> + <.icon name="hero-cake-mini" /> + <.icon name="hero-bolt" class="bg-blue-500 w-10 h-10" /> + """ + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end @doc """ Renders a middot as divider @@ -164,58 +192,58 @@ defmodule ChiyaWeb.PublicComponents do end end - def comment_form(assigns) do - ~H""" - <.simple_form :let={f} for={@changeset} action="" class="bg-theme-background -m-3"> - <.error :if={@changeset.action}> - Oops, something went wrong! Please check the errors below. - - <.input - field={f[:author_name]} - type="text" - placeholder="Name" - class="bg-theme-background dark:bg-theme-background border-theme-base/20 dark:border-theme-base/20 text-theme-base dark:text-theme-base placeholder-theme-base/40 dark:placeholder-theme-base/60 dark:focus:border-theme-base/60 dark:focus:border-theme-base/60" - /> - <.input - field={f[:content]} - type="textarea" - placeholder="Content" - rows="3" - class="bg-theme-background dark:bg-theme-background border-theme-base/20 dark:border-theme-base/20 text-theme-base dark:text-theme-base placeholder-theme-base/60 dark:placeholder-theme-base/60 focus:border-theme-base/60 dark:focus:border-theme-base/60" - /> - <.input field={f[:note_id]} type="hidden" /> - <:actions> - <.button>Submit Comment - - - """ - end + # def comment_form(assigns) do + # ~H""" + # <.simple_form :let={f} for={@changeset} action="" class="bg-theme-background -m-3"> + # <.error :if={@changeset.action}> + # Oops, something went wrong! Please check the errors below. + # + # <.input + # field={f[:author_name]} + # type="text" + # placeholder="Name" + # class="bg-theme-background dark:bg-theme-background border-theme-base/20 dark:border-theme-base/20 text-theme-base dark:text-theme-base placeholder-theme-base/40 dark:placeholder-theme-base/60 dark:focus:border-theme-base/60 dark:focus:border-theme-base/60" + # /> + # <.input + # field={f[:content]} + # type="textarea" + # placeholder="Content" + # rows="3" + # class="bg-theme-background dark:bg-theme-background border-theme-base/20 dark:border-theme-base/20 text-theme-base dark:text-theme-base placeholder-theme-base/60 dark:placeholder-theme-base/60 focus:border-theme-base/60 dark:focus:border-theme-base/60" + # /> + # <.input field={f[:note_id]} type="hidden" /> + # <:actions> + # <.button>Submit Comment + # + # + # """ + # end - def comment_list(assigns) do - ~H""" - <%= if not Enum.empty?(assigns.note.comments) do %> - <.line /> + # def comment_list(assigns) do + # ~H""" + # <%= if not Enum.empty?(assigns.note.comments) do %> + # <.line /> -

<%= Enum.count(assigns.note.comments) %> Comments

+ #

<%= Enum.count(assigns.note.comments) %> Comments

- - <% else %> - <.line /> + # + # <% else %> + # <.line /> -

No comments yet.

- <% end %> - """ - end + #

No comments yet.

+ # <% end %> + # """ + # end defp gallery_name(note), do: "gallery-#{note.id}" diff --git a/lib/chiya_web/controllers/note_controller.ex b/lib/chiya_web/controllers/note_controller.ex index 804030b..8be730b 100644 --- a/lib/chiya_web/controllers/note_controller.ex +++ b/lib/chiya_web/controllers/note_controller.ex @@ -105,10 +105,9 @@ defmodule ChiyaWeb.NoteController do end def publish(conn, %{"id" => id}) do - note_params = %{published_at: NaiveDateTime.local_now()} note = Notes.get_note_preloaded!(id) - case Notes.update_note(note, note_params) do + case Notes.publish_note(note, NaiveDateTime.local_now()) do {:ok, note} -> conn |> put_flash(:info, "Note published successfully.") @@ -125,10 +124,9 @@ defmodule ChiyaWeb.NoteController do end def unpublish(conn, %{"id" => id}) do - note_params = %{published_at: nil} note = Notes.get_note_preloaded!(id) - case Notes.update_note(note, note_params) do + case Notes.publish_note(note, nil) do {:ok, note} -> conn |> put_flash(:info, "Note un-published successfully.") diff --git a/lib/chiya_web/indie/micropub_handler.ex b/lib/chiya_web/indie/micropub_handler.ex index cdbd8e8..d094fe7 100644 --- a/lib/chiya_web/indie/micropub_handler.ex +++ b/lib/chiya_web/indie/micropub_handler.ex @@ -20,9 +20,12 @@ defmodule ChiyaWeb.Indie.MicropubHandler do Logger.info("Properties: #{inspect(properties)}") Logger.info("Type: #{type}") + settings = Chiya.Site.get_settings() + micropub_channel_id = settings.micropub_channel_id + with :ok <- verify_token(access_token), {:ok, post_type} <- Props.get_post_type(properties), - {:ok, note_attrs} <- get_attrs(type, post_type, properties), + {:ok, note_attrs} <- get_attrs(type, post_type, properties, micropub_channel_id), {:ok, note} <- Chiya.Notes.create_note(note_attrs) do Logger.info("Note created!") {:ok, :created, Chiya.Notes.Note.note_url(note)} @@ -157,16 +160,18 @@ defmodule ChiyaWeb.Indie.MicropubHandler do end end - defp get_attrs(type, post_type, properties) do + defp get_attrs(type, post_type, properties, default_channel_id) do Logger.info("Creating a #{type}/#{post_type}..") + channel = Chiya.Channels.get_channel(default_channel_id) + case post_type do - :note -> get_note_attrs(properties) + :note -> get_note_attrs(properties, channel) _ -> {:error, :insufficient_scope} end end - defp get_note_attrs(p) do + defp get_note_attrs(p, default_channel) do content = Props.get_content(p) name = Props.get_title(p) || String.slice(content, 0..15) tags = Props.get_tags(p) |> Enum.join(",") @@ -176,13 +181,19 @@ defmodule ChiyaWeb.Indie.MicropubHandler do do: NaiveDateTime.local_now(), else: nil - {:ok, - %{ - content: content, - name: name, - tags_string: tags, - published_at: published_at - }} + attrs = %{ + content: content, + name: name, + tags_string: tags, + published_at: published_at + } + + attrs = + if default_channel, + do: Map.put(attrs, :channel, default_channel), + else: attrs + + {:ok, attrs} end defp get_hostname(), diff --git a/lib/chiya_web/live/note_show_live.ex b/lib/chiya_web/live/note_show_live.ex index 9cb5d4f..45dd324 100644 --- a/lib/chiya_web/live/note_show_live.ex +++ b/lib/chiya_web/live/note_show_live.ex @@ -60,13 +60,21 @@ defmodule ChiyaWeb.NoteShowLive do
- <.button phx-click="delete_image" phx-value-id={image.id} data-confirm="Are you sure?"> - Delete Image - + /> +
+ <.button phx-click="delete_image" phx-value-id={image.id} data-confirm="Are you sure?"> + <.icon name="hero-trash" /> + + <.button phx-click="toggle_favorite" phx-value-id={image.id}> + <.icon name="hero-star-solid" /> + +
<% end %> @@ -138,6 +146,7 @@ defmodule ChiyaWeb.NoteShowLive do |> assign(:note, Notes.get_note_preloaded!(socket.assigns.note.id))} end + @impl Phoenix.LiveView def handle_event("validate_edit_image", assigns, socket) do {:noreply, socket @@ -147,6 +156,7 @@ defmodule ChiyaWeb.NoteShowLive do )} end + @impl Phoenix.LiveView def handle_event("update_edit_image", %{"id" => id} = assigns, socket) do id |> Notes.get_note_image!() @@ -164,6 +174,14 @@ defmodule ChiyaWeb.NoteShowLive do {:noreply, assign(socket, :note, Notes.get_note_preloaded!(socket.assigns.note.id))} end + @impl Phoenix.LiveView + def handle_event("toggle_favorite", %{"id" => id}, socket) do + image = Notes.get_note_image!(id) + Notes.update_note_image(image, %{featured: !image.featured}) + + {: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) defp note_tags(tags), do: Enum.map_join(tags, ", ", fn t -> t.name end) end