devel #38
11 changed files with 123 additions and 61 deletions
|
@ -85,4 +85,12 @@ a[rel] svg {
|
||||||
/* Set width and color for identity icons */
|
/* Set width and color for identity icons */
|
||||||
svg {
|
svg {
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
@apply p-3 mt-3 rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
@apply bg-red-100 text-red-500 dark:bg-red-950 dark:text-red-500;
|
||||||
}
|
}
|
|
@ -35,8 +35,3 @@
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightbox span img {
|
|
||||||
width: 75%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ const groupNameStyle = {
|
||||||
padding: "8px 16px",
|
padding: "8px 16px",
|
||||||
fontSize: "10px",
|
fontSize: "10px",
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
opacity: 0.5,
|
opacity: 0.75,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ function RenderResults() {
|
||||||
items={results}
|
items={results}
|
||||||
onRender={({ item, active }) =>
|
onRender={({ item, active }) =>
|
||||||
typeof item === "string" ? (
|
typeof item === "string" ? (
|
||||||
<div style={groupNameStyle}>{item}</div>
|
<div style={groupNameStyle} class="dark:text-white">{item}</div>
|
||||||
) : (
|
) : (
|
||||||
<ResultItem
|
<ResultItem
|
||||||
action={item}
|
action={item}
|
||||||
|
@ -88,7 +88,7 @@ const ResultItem = React.forwardRef(
|
||||||
"border-l-2 border-emerald-300": active
|
"border-l-2 border-emerald-300": active
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center text-sm">
|
<div className="flex gap-3 items-center text-sm dark:text-white">
|
||||||
{action.icon && action.icon}
|
{action.icon && action.icon}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -149,7 +149,7 @@ defmodule Chiya.Notes do
|
||||||
|> Repo.insert() do
|
|> Repo.insert() do
|
||||||
{:ok, note_image} ->
|
{:ok, note_image} ->
|
||||||
note_image
|
note_image
|
||||||
|> change_note_image(attrs)
|
|> NoteImage.update_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
|
@ -157,6 +157,12 @@ defmodule Chiya.Notes do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_note_image(%NoteImage{} = note_image, attrs) do
|
||||||
|
note_image
|
||||||
|
|> NoteImage.update_changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
def delete_note_image(%NoteImage{} = note_image) do
|
def delete_note_image(%NoteImage{} = note_image) do
|
||||||
{:ok, _} = Repo.delete(note_image)
|
{:ok, _} = Repo.delete(note_image)
|
||||||
:ok = ChiyaWeb.Uploaders.NoteImage.delete({note_image.path, note_image})
|
:ok = ChiyaWeb.Uploaders.NoteImage.delete({note_image.path, note_image})
|
||||||
|
@ -167,6 +173,6 @@ defmodule Chiya.Notes do
|
||||||
Returns an `%Ecto.Changeset{}` for tracking note_image changes.
|
Returns an `%Ecto.Changeset{}` for tracking note_image changes.
|
||||||
"""
|
"""
|
||||||
def change_note_image(%NoteImage{} = note_image, attrs \\ %{}) do
|
def change_note_image(%NoteImage{} = note_image, attrs \\ %{}) do
|
||||||
NoteImage.changeset(note_image, attrs)
|
NoteImage.update_changeset(note_image, attrs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,22 +7,23 @@ defmodule Chiya.Notes.NoteImage do
|
||||||
field :content, :string, default: ""
|
field :content, :string, default: ""
|
||||||
field :path, ChiyaWeb.Uploaders.NoteImage.Type
|
field :path, ChiyaWeb.Uploaders.NoteImage.Type
|
||||||
field :note_id, :id
|
field :note_id, :id
|
||||||
|
field :featured, :boolean, default: false
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
|
||||||
def changeset(note_image, attrs) do
|
|
||||||
note_image
|
|
||||||
|> cast(attrs, [:content, :note_id])
|
|
||||||
|> cast_attachments(attrs, [:path], allow_paths: true)
|
|
||||||
|> validate_required([:path, :note_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def insert_changeset(note_image, attrs) do
|
def insert_changeset(note_image, attrs) do
|
||||||
note_image
|
note_image
|
||||||
|> cast(attrs, [:note_id])
|
|> cast(attrs, [:note_id])
|
||||||
|> validate_required([:note_id])
|
|> validate_required([:note_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def update_changeset(note_image, attrs) do
|
||||||
|
note_image
|
||||||
|
|> cast(attrs, [:content, :note_id, :featured])
|
||||||
|
|> cast_attachments(attrs, [:path], allow_paths: true)
|
||||||
|
|> validate_required([:path, :note_id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,21 +34,26 @@ defmodule ChiyaWeb.AdminComponents do
|
||||||
<article class="upload-entry">
|
<article class="upload-entry">
|
||||||
<figure>
|
<figure>
|
||||||
<.live_img_preview entry={entry} />
|
<.live_img_preview entry={entry} />
|
||||||
<figcaption><%= entry.client_name %></figcaption>
|
<figcaption class="dark:text-gray-100"><%= entry.client_name %></figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<%!-- entry.progress will update automatically for in-flight entries --%>
|
<div class="flex">
|
||||||
<progress value={entry.progress} max="100"><%= entry.progress %>%</progress>
|
<%!-- entry.progress will update automatically for in-flight entries --%>
|
||||||
|
<progress value={entry.progress} max="100" class="w-full">
|
||||||
|
<%= entry.progress %>%
|
||||||
|
</progress>
|
||||||
|
|
||||||
<%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%>
|
<%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%>
|
||||||
<button
|
<button
|
||||||
type="button"
|
class="px-2 dark:text-white"
|
||||||
phx-click="cancel-upload"
|
type="button"
|
||||||
phx-value-ref={entry.ref}
|
phx-click="cancel-upload"
|
||||||
aria-label="cancel"
|
phx-value-ref={entry.ref}
|
||||||
>
|
aria-label="cancel"
|
||||||
×
|
>
|
||||||
</button>
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%>
|
<%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%>
|
||||||
<%= for err <- upload_errors(@upload, entry) do %>
|
<%= for err <- upload_errors(@upload, entry) do %>
|
||||||
|
|
|
@ -69,7 +69,11 @@ defmodule ChiyaWeb.CoreComponents do
|
||||||
phx-remove={hide_modal(@id)}
|
phx-remove={hide_modal(@id)}
|
||||||
class="relative z-50 hidden"
|
class="relative z-50 hidden"
|
||||||
>
|
>
|
||||||
<div id={"#{@id}-bg"} class="fixed inset-0 bg-gray-50/90 transition-opacity" aria-hidden="true" />
|
<div
|
||||||
|
id={"#{@id}-bg"}
|
||||||
|
class="fixed inset-0 bg-gray-50/90 dark:bg-gray-900/90 transition-opacity"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 overflow-y-auto"
|
class="fixed inset-0 overflow-y-auto"
|
||||||
aria-labelledby={"#{@id}-title"}
|
aria-labelledby={"#{@id}-title"}
|
||||||
|
@ -86,7 +90,7 @@ defmodule ChiyaWeb.CoreComponents do
|
||||||
phx-window-keydown={hide_modal(@on_cancel, @id)}
|
phx-window-keydown={hide_modal(@on_cancel, @id)}
|
||||||
phx-key="escape"
|
phx-key="escape"
|
||||||
phx-click-away={hide_modal(@on_cancel, @id)}
|
phx-click-away={hide_modal(@on_cancel, @id)}
|
||||||
class="hidden relative rounded-2xl bg-white p-14 shadow-lg shadow-gray-700/10 ring-1 ring-gray-700/10 transition"
|
class="hidden relative rounded-2xl bg-white p-14 shadow-lg shadow-gray-700/10 ring-1 ring-gray-700/10 transition dark:bg-gray-900"
|
||||||
>
|
>
|
||||||
<div class="absolute top-6 right-5">
|
<div class="absolute top-6 right-5">
|
||||||
<button
|
<button
|
||||||
|
@ -95,7 +99,7 @@ defmodule ChiyaWeb.CoreComponents do
|
||||||
class="-m-3 flex-none p-3 opacity-20 hover:opacity-40"
|
class="-m-3 flex-none p-3 opacity-20 hover:opacity-40"
|
||||||
aria-label={gettext("close")}
|
aria-label={gettext("close")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark-solid" class="w-5 h-5" />
|
<.icon name="hero-x-mark-solid" class="w-5 h-5 dark:text-gray-50" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id={"#{@id}-content"}>
|
<div id={"#{@id}-content"}>
|
||||||
|
|
|
@ -27,10 +27,14 @@
|
||||||
</a>
|
</a>
|
||||||
<a href="#images" class="lightbox" id={"image-#{image.id}"}>
|
<a href="#images" class="lightbox" id={"image-#{image.id}"}>
|
||||||
<span>
|
<span>
|
||||||
<img src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)} loading="lazy" />
|
<img
|
||||||
|
class="md:max-w-screen-sm lg:max-w-screen-md xl:max-w-screen-lg max-h-screen mx-auto"
|
||||||
|
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<span class="text-theme-background text-center"><%= image.content %></span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
defmodule ChiyaWeb.Exim do
|
defmodule ChiyaWeb.Exim do
|
||||||
alias Chiya.Notes.Note
|
alias Chiya.Notes.Note
|
||||||
|
|
||||||
defp frontmatter(%Note{name: title, channels: channels}) do
|
defp frontmatter(%Note{name: title, channels: channels}) do
|
||||||
channels_raw = Enum.map_join(channels, "], [", fn c -> "\"#{c.name}\"" end)
|
channels_raw = Enum.map_join(channels, "], [", fn c -> "\"#{c.name}\"" end)
|
||||||
"title: \"#{title}\"\ncategories: [#{channels_raw}]"
|
"title: \"#{title}\"\ncategories: [#{channels_raw}]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def export_note(%Note{content: content} = note) do
|
def export_note(%Note{content: content} = note) do
|
||||||
"---\n#{frontmatter(note)}\n---\n#{content}"
|
"---\n#{frontmatter(note)}\n---\n#{content}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule ChiyaWeb.NoteShowLive do
|
||||||
use ChiyaWeb, :live_view
|
use ChiyaWeb, :live_view
|
||||||
|
|
||||||
alias Chiya.Notes
|
alias Chiya.Notes
|
||||||
|
alias Chiya.Notes.NoteImage
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
|
@ -41,28 +42,39 @@ defmodule ChiyaWeb.NoteShowLive do
|
||||||
<div class="flex flex-wrap gap-3" id="images">
|
<div class="flex flex-wrap gap-3" id="images">
|
||||||
<%= for image <- @note.images do %>
|
<%= for image <- @note.images do %>
|
||||||
<article>
|
<article>
|
||||||
<a href={"#image-#{image.id}"}>
|
<a href="#" phx-click={show_modal("image-edit-modal-#{image.id}")} phx-value-id={image.id}>
|
||||||
<img
|
<img
|
||||||
class="rounded-lg border border-theme-dim w-28"
|
class="rounded-lg border border-theme-dim w-28"
|
||||||
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)}
|
src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :thumb_dithered)}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<p class="text-center text-xs text-gray-700 dark:text-gray-300">
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
phx-click="delete_image"
|
|
||||||
phx-value-id={image.id}
|
|
||||||
data-confirm="Are you sure?"
|
|
||||||
>
|
|
||||||
Delete image
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<a href="#images" class="lightbox" id={"image-#{image.id}"}>
|
<.modal id={"image-edit-modal-#{image.id}"}>
|
||||||
<span>
|
<.simple_form
|
||||||
<img src={ChiyaWeb.Uploaders.NoteImage.url({image.path, image}, :full_dithered)} />
|
:let={f}
|
||||||
</span>
|
for={to_form(Notes.change_note_image(image))}
|
||||||
</a>
|
id={"image-edit-form-#{image.id}"}
|
||||||
|
phx-submit="update_edit_image"
|
||||||
|
phx-change="validate_edit_image"
|
||||||
|
>
|
||||||
|
<.input field={f[:id]} type="hidden" value={image.id} />
|
||||||
|
<.input field={f[:content]} type="textarea" label="Content" />
|
||||||
|
<.input field={f[:featured]} type="checkbox" label="Featured" />
|
||||||
|
|
||||||
|
<:actions>
|
||||||
|
<.button type="submit" phx-click={hide_modal("image-edit-modal-#{image.id}")}>
|
||||||
|
Save
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
phx-click="delete_image"
|
||||||
|
phx-value-id={image.id}
|
||||||
|
data-confirm="Are you sure?"
|
||||||
|
>
|
||||||
|
Delete Image
|
||||||
|
</.button>
|
||||||
|
</:actions>
|
||||||
|
</.simple_form>
|
||||||
|
</.modal>
|
||||||
</article>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,12 +107,13 @@ defmodule ChiyaWeb.NoteShowLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(%{"id" => note_id}, _session, socket) do
|
def mount(%{"id" => note_id}, _session, socket) do
|
||||||
image_changeset = Notes.change_note_image(%Chiya.Notes.NoteImage{})
|
image_changeset = Notes.change_note_image(%NoteImage{})
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:note, Notes.get_note_preloaded!(note_id))
|
|> assign(:note, Notes.get_note_preloaded!(note_id))
|
||||||
|> assign(:uploaded_files, [])
|
|> assign(:uploaded_files, [])
|
||||||
|
|> assign(:image_edit_form, to_form(image_changeset))
|
||||||
|> assign(:image_form, to_form(image_changeset))
|
|> assign(:image_form, to_form(image_changeset))
|
||||||
|> allow_upload(:note_images, accept: ~w(.jpg .jpeg .gif .png), max_entries: 100)}
|
|> allow_upload(:note_images, accept: ~w(.jpg .jpeg .gif .png), max_entries: 100)}
|
||||||
end
|
end
|
||||||
|
@ -133,6 +146,23 @@ defmodule ChiyaWeb.NoteShowLive do
|
||||||
|> assign(:note, Notes.get_note_preloaded!(socket.assigns.note.id))}
|
|> assign(:note, Notes.get_note_preloaded!(socket.assigns.note.id))}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("validate_edit_image", assigns, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(
|
||||||
|
:image_edit_form,
|
||||||
|
to_form(Notes.change_note_image(%NoteImage{}, assigns))
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("update_edit_image", %{"id" => id} = assigns, socket) do
|
||||||
|
id
|
||||||
|
|> Notes.get_note_image!()
|
||||||
|
|> Notes.update_note_image(assigns)
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
@impl Phoenix.LiveView
|
@impl Phoenix.LiveView
|
||||||
def handle_event("delete_image", %{"id" => id}, socket) do
|
def handle_event("delete_image", %{"id" => id}, socket) do
|
||||||
:ok =
|
:ok =
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Chiya.Repo.Migrations.AddFeaturedFlagToNoteImage do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:note_images) do
|
||||||
|
add :featured, :boolean
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue