Pin messages sent by attendees (#62)

This commit is contained in:
haruncurak
2023-11-23 13:58:44 +01:00
committed by GitHub
parent 92eafa9af2
commit 3712efc558
14 changed files with 954 additions and 1575 deletions

2
.tool-versions Normal file
View File

@@ -0,0 +1,2 @@
erlang 26.1.2
elixir 1.15.7-otp-26

View File

@@ -35,12 +35,19 @@ export class Presenter {
if (data.value) { if (data.value) {
document.getElementById("post-list").classList.remove("animate__animated", "animate__fadeOutLeft") document.getElementById("post-list").classList.remove("animate__animated", "animate__fadeOutLeft")
document.getElementById("post-list").classList.add("animate__animated", "animate__fadeInLeft") document.getElementById("post-list").classList.add("animate__animated", "animate__fadeInLeft")
document.getElementById("pinned-post-list").classList.remove("animate__animated", "animate__fadeOutLeft")
document.getElementById("pinned-post-list").classList.add("animate__animated", "animate__fadeInLeft")
} else { } else {
document.getElementById("post-list").classList.remove("animate__animated", "animate__fadeInLeft") document.getElementById("post-list").classList.remove("animate__animated", "animate__fadeInLeft")
document.getElementById("post-list").classList.add("animate__animated", "animate__fadeOutLeft") document.getElementById("post-list").classList.add("animate__animated", "animate__fadeOutLeft")
document.getElementById("pinned-post-list").classList.remove("animate__animated", "animate__fadeInLeft")
document.getElementById("pinned-post-list").classList.add("animate__animated", "animate__fadeOutLeft")
} }
}) })
this.context.handleEvent('poll-visible', data => { this.context.handleEvent('poll-visible', data => {
if (data.value) { if (data.value) {
document.getElementById("poll").classList.remove("animate__animated", "animate__fadeOut") document.getElementById("poll").classList.remove("animate__animated", "animate__fadeOut")

1557
assets/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"devDependencies": { "devDependencies": {
"alpinejs": "^3.13.1", "alpinejs": "^3.13.1",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"esbuild": "^0.14.14", "esbuild": "^0.14.54",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"postcss": "^8.4.29", "postcss": "^8.4.29",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",

View File

@@ -24,6 +24,38 @@ defmodule Claper.Posts do
|> Repo.preload(preload) |> Repo.preload(preload)
end end
@doc """
Get only the pinned event posts.
"""
def list_pinned_posts(event_id, preload \\ []) do
from(p in Post,
join: e in Claper.Events.Event,
on: p.event_id == e.id,
select: p,
# Only pinned posts
where: e.uuid == ^event_id and p.pinned == true,
order_by: [asc: p.id]
)
|> Repo.all()
|> Repo.preload(preload)
end
@doc """
Get only the unpinned event posts.
"""
def list_unpinned_posts(event_id, preload \\ []) do
from(p in Post,
join: e in Claper.Events.Event,
on: p.event_id == e.id,
select: p,
# Only unpinned posts
where: e.uuid == ^event_id and p.pinned == false,
order_by: [asc: p.id]
)
|> Repo.all()
|> Repo.preload(preload)
end
def reacted_posts(event_id, user_id, icon) when is_number(user_id) do def reacted_posts(event_id, user_id, icon) when is_number(user_id) do
from(reaction in Claper.Posts.Reaction, from(reaction in Claper.Posts.Reaction,
join: post in Claper.Posts.Post, join: post in Claper.Posts.Post,
@@ -99,10 +131,41 @@ defmodule Claper.Posts do
""" """
def update_post(%Post{} = post, attrs) do def update_post(%Post{} = post, attrs) do
post changeset = Post.changeset(post, attrs)
|> Post.changeset(attrs)
|> Repo.update() result = changeset |> Repo.update()
|> broadcast(:post_updated)
result |> broadcast(:post_updated)
end
@doc """
Pins or unpins a post based on its current state.
## Examples
iex> toggle_pin_post(post)
{:ok, %Post{}}
iex> toggle_pin_post(invalid_post)
{:error, %Ecto.Changeset{}}
"""
def toggle_pin_post(%Post{} = post) do
# Toggling the pinned state
new_pinned_state = not post.pinned
changeset = Post.changeset(post, %{pinned: new_pinned_state})
result = changeset |> Repo.update()
# Broadcast the appropriate message based on the new state
broadcast_message =
if new_pinned_state do
:post_pinned
else
:post_unpinned
end
result |> broadcast(broadcast_message)
end end
@doc """ @doc """

View File

@@ -11,6 +11,7 @@ defmodule Claper.Posts.Post do
field :name, :string field :name, :string
field :attendee_identifier, :string field :attendee_identifier, :string
field :position, :integer, default: 0 field :position, :integer, default: 0
field :pinned, :boolean, default: false
belongs_to :event, Claper.Events.Event belongs_to :event, Claper.Events.Event
belongs_to :user, Claper.Accounts.User belongs_to :user, Claper.Accounts.User
@@ -30,7 +31,8 @@ defmodule Claper.Posts.Post do
:love_count, :love_count,
:lol_count, :lol_count,
:name, :name,
:position :position,
:pinned
]) ])
|> validate_required([:body, :position]) |> validate_required([:body, :position])
|> validate_length(:body, min: 2, max: 255) |> validate_length(:body, min: 2, max: 255)

View File

@@ -10,6 +10,7 @@ defmodule Claper.Presentations.PresentationState do
field :chat_enabled, :boolean field :chat_enabled, :boolean
field :anonymous_chat_enabled, :boolean field :anonymous_chat_enabled, :boolean
field :banned, {:array, :string}, default: [] field :banned, {:array, :string}, default: []
field :show_only_pinned, :boolean, default: false
belongs_to :presentation_file, Claper.Presentations.PresentationFile belongs_to :presentation_file, Claper.Presentations.PresentationFile
@@ -27,7 +28,8 @@ defmodule Claper.Presentations.PresentationState do
:banned, :banned,
:presentation_file_id, :presentation_file_id,
:chat_enabled, :chat_enabled,
:anonymous_chat_enabled :anonymous_chat_enabled,
:show_only_pinned
]) ])
|> validate_required([]) |> validate_required([])
end end

View File

@@ -41,7 +41,8 @@ defmodule ClaperWeb.EventLive.Manage do
|> assign(:attendees_nb, 1) |> assign(:attendees_nb, 1)
|> assign(:event, event) |> assign(:event, event)
|> assign(:state, event.presentation_file.presentation_state) |> assign(:state, event.presentation_file.presentation_state)
|> assign(:posts, list_posts(socket, event.uuid)) |> assign(:pinned_posts, list_pinned_posts(socket, event.uuid))
|> assign(:all_posts, list_all_posts(socket, event.uuid))
|> assign(:polls, list_polls(socket, event.presentation_file.id)) |> assign(:polls, list_polls(socket, event.presentation_file.id))
|> assign(:forms, list_forms(socket, event.presentation_file.id)) |> assign(:forms, list_forms(socket, event.presentation_file.id))
|> assign(:embeds, list_embeds(socket, event.presentation_file.id)) |> assign(:embeds, list_embeds(socket, event.presentation_file.id))
@@ -60,6 +61,16 @@ defmodule ClaperWeb.EventLive.Manage do
end end
end end
defp delete_post_from_list(posts, deleted_post) do
Enum.reject(posts, fn post -> post.id == deleted_post.id end)
end
defp update_post_in_list(posts, updated_post) do
Enum.map(posts, fn post ->
if post.id == updated_post.id, do: updated_post, else: post
end)
end
defp is_leader(%{assigns: %{current_user: current_user}} = _socket, event) do defp is_leader(%{assigns: %{current_user: current_user}} = _socket, event) do
Claper.Events.is_leaded_by(current_user.email, event) || event.user.id == current_user.id Claper.Events.is_leaded_by(current_user.email, event) || event.user.id == current_user.id
end end
@@ -75,27 +86,50 @@ defmodule ClaperWeb.EventLive.Manage do
@impl true @impl true
def handle_info({:post_created, post}, socket) do def handle_info({:post_created, post}, socket) do
{:noreply, {:noreply,
socket |> update(:posts, fn posts -> [post | posts] end) |> push_event("scroll", %{})} socket
|> assign(:all_posts, [post | socket.assigns.all_posts])
|> push_event("scroll", %{})}
end end
@impl true @impl true
def handle_info({:post_updated, post}, socket) do def handle_info({:post_updated, updated_post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} updated_posts = update_post_in_list(socket.assigns.all_posts, updated_post)
updated_pinned_posts = update_post_in_list(socket.assigns.pinned_posts, updated_post)
{:noreply,
socket
|> assign(:all_posts, updated_posts)
|> assign(:pinned_posts, updated_pinned_posts)}
end end
@impl true @impl true
def handle_info({:reaction_added, post}, socket) do def handle_info({:post_deleted, deleted_post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} {:noreply,
socket
|> update(:all_posts, fn posts -> [deleted_post | posts] end)
|> update(:pinned_posts, fn pinned_posts ->
delete_post_from_list(pinned_posts, deleted_post)
end)}
end end
@impl true @impl true
def handle_info({:reaction_removed, post}, socket) do def handle_info({:post_pinned, _post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} updated_socket =
socket
|> update(:all_posts, fn _all_posts -> socket.assigns.all_posts end)
|> update(:pinned_posts, fn _pinned_posts -> socket.assigns.pinned_posts end)
{:noreply, updated_socket}
end end
@impl true @impl true
def handle_info({:post_deleted, post}, socket) do def handle_info({:post_unpinned, _post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} updated_socket =
socket
|> update(:all_posts, fn _all_posts -> socket.assigns.all_posts end)
|> update(:pinned_posts, fn _pinned_posts -> socket.assigns.pinned_posts end)
{:noreply, updated_socket}
end end
@impl true @impl true
@@ -386,6 +420,12 @@ defmodule ClaperWeb.EventLive.Manage do
ban(attendee_identifier, socket) ban(attendee_identifier, socket)
end end
@impl true
def handle_event("pin", %{"id" => id}, socket) do
post = Claper.Posts.get_post!(id, [:event])
pin(post, socket)
end
@impl true @impl true
def handle_event( def handle_event(
"ban", "ban",
@@ -465,6 +505,23 @@ defmodule ClaperWeb.EventLive.Manage do
{:noreply, socket |> assign(:state, new_state)} {:noreply, socket |> assign(:state, new_state)}
end end
@impl true
def handle_event(
"checked",
%{"key" => "show_only_pinned", "value" => value},
%{assigns: %{event: _event, state: state}} = socket
) do
{:ok, new_state} =
Claper.Presentations.update_presentation_state(
state,
%{
:show_only_pinned => value
}
)
{:noreply, socket |> assign(:state, new_state)}
end
@impl true @impl true
def handle_event( def handle_event(
"checked", "checked",
@@ -483,11 +540,19 @@ defmodule ClaperWeb.EventLive.Manage do
end end
@impl true @impl true
def handle_event("delete", %{"event-id" => event_id, "id" => id}, socket) do def handle_event("delete", %{"id" => id}, socket) do
post = Claper.Posts.get_post!(id, [:event]) post = Claper.Posts.get_post!(id, [:event])
{:ok, _} = Claper.Posts.delete_post(post) {:ok, _} = Claper.Posts.delete_post(post)
{:noreply, assign(socket, :posts, list_posts(socket, event_id))} updated_socket =
if post.pinned do
assign(socket, :pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
assign(socket, :all_posts, list_all_posts(socket, socket.assigns.event.uuid))
else
assign(socket, :all_posts, list_all_posts(socket, socket.assigns.event.uuid))
end
{:noreply, updated_socket}
end end
@impl true @impl true
@@ -510,7 +575,9 @@ defmodule ClaperWeb.EventLive.Manage do
socket = socket =
case tab do case tab do
"posts" -> "posts" ->
assign(socket, :posts, list_posts(socket, socket.assigns.event.uuid)) socket
|> assign(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
|> assign(:all_posts, list_all_posts(socket, socket.assigns.event.uuid))
"forms" -> "forms" ->
assign( assign(
@@ -518,6 +585,12 @@ defmodule ClaperWeb.EventLive.Manage do
:form_submits, :form_submits,
list_form_submits(socket, socket.assigns.event.presentation_file.id) list_form_submits(socket, socket.assigns.event.presentation_file.id)
) )
"pinned_posts" ->
socket
|> assign(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
end end
{:noreply, socket} {:noreply, socket}
@@ -663,6 +736,17 @@ defmodule ClaperWeb.EventLive.Manage do
end end
end end
defp pin(post, socket) do
{:ok, _updated_post} = Claper.Posts.toggle_pin_post(post)
updated_socket =
socket
|> assign(:all_posts, list_all_posts(socket, socket.assigns.event.uuid))
|> assign(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
{:noreply, updated_socket}
end
defp embed_at_position( defp embed_at_position(
%{assigns: %{event: event, state: state}} = socket, %{assigns: %{event: event, state: state}} = socket,
broadcast \\ true broadcast \\ true
@@ -699,7 +783,11 @@ defmodule ClaperWeb.EventLive.Manage do
{:noreply, socket |> assign(:state, new_state)} {:noreply, socket |> assign(:state, new_state)}
end end
defp list_posts(_socket, event_id) do defp list_pinned_posts(_socket, event_id) do
Claper.Posts.list_pinned_posts(event_id, [:event, :reactions])
end
defp list_all_posts(_socket, event_id) do
Claper.Posts.list_posts(event_id, [:event, :reactions]) Claper.Posts.list_posts(event_id, [:event, :reactions])
end end

View File

@@ -1,7 +1,8 @@
<div <div
id="manager" id="manager"
class="h-screen max-h-screen flex flex-col" class="h-screen max-h-screen flex flex-col"
x-data={"{date: moment.utc('#{@event.expired_at}').local().format('lll')}"} x-data={"{date:
moment.utc('#{@event.expired_at}').local().format('lll')}"}
phx-hook="Manager" phx-hook="Manager"
data-max-page={@event.presentation_file.length} data-max-page={@event.presentation_file.length}
data-current-page={@state.position} data-current-page={@state.position}
@@ -117,7 +118,8 @@
<div <div
id="add-modal" id="add-modal"
class={"#{if !@create, do: 'hidden'} fixed z-30 inset-0 overflow-y-auto p-4 sm:p-6 md:p-24 transform transition-all duration-150"} class={"#{if !@create, do: 'hidden' } fixed z-30 inset-0 overflow-y-auto p-4 sm:p-6 md:p-24
transform transition-all duration-150"}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
> >
@@ -145,7 +147,7 @@
</button> </button>
<div id="modal-content" class="bg-gray-100"> <div id="modal-content" class="bg-gray-100">
<%= if @create == nil do %> <%= if @create==nil do %>
<ul class="scroll-py-3 overflow-y-auto p-3" id="options" role="listbox"> <ul class="scroll-py-3 overflow-y-auto p-3" id="options" role="listbox">
<li id="option-1" role="option" tabindex="-1"> <li id="option-1" role="option" tabindex="-1">
<a <a
@@ -171,7 +173,9 @@
</svg> </svg>
</div> </div>
<div class="ml-4 flex-auto text-left"> <div class="ml-4 flex-auto text-left">
<p class="font-medium text-gray-700"><%= gettext("Poll") %></p> <p class="font-medium text-gray-700">
<%= gettext("Poll") %>
</p>
<p class="text-gray-500"> <p class="text-gray-500">
<%= gettext("Add poll to know opinion of your public.") %> <%= gettext("Add poll to know opinion of your public.") %>
</p> </p>
@@ -208,7 +212,9 @@
</svg> </svg>
</div> </div>
<div class="ml-4 flex-auto text-left"> <div class="ml-4 flex-auto text-left">
<p class="font-medium text-gray-700"><%= gettext("Form") %></p> <p class="font-medium text-gray-700">
<%= gettext("Form") %>
</p>
<p class="text-gray-500"> <p class="text-gray-500">
<%= gettext("Add form to collect data from your public.") %> <%= gettext("Add form to collect data from your public.") %>
</p> </p>
@@ -248,7 +254,7 @@
</li> </li>
</ul> </ul>
<%= if (length @polls) == 0 && (length @forms) == 0 do %> <%= if (length @polls)==0 && (length @forms)==0 do %>
<div class="mt-10"> <div class="mt-10">
<a <a
data-phx-link="patch" data-phx-link="patch"
@@ -279,7 +285,7 @@
<% end %> <% end %>
<% end %> <% end %>
<%= if @create == "poll" do %> <%= if @create=="poll" do %>
<div class="scroll-py-3 overflow-y-auto bg-gray-100 p-3"> <div class="scroll-py-3 overflow-y-auto bg-gray-100 p-3">
<p class="text-xl font-bold"> <p class="text-xl font-bold">
<%= case @create_action do <%= case @create_action do
@@ -300,7 +306,7 @@
</div> </div>
<% end %> <% end %>
<%= if @create == "form" do %> <%= if @create=="form" do %>
<div class="scroll-py-3 overflow-y-auto bg-gray-100 p-3"> <div class="scroll-py-3 overflow-y-auto bg-gray-100 p-3">
<p class="text-xl font-bold"> <p class="text-xl font-bold">
<%= case @create_action do <%= case @create_action do
@@ -344,7 +350,9 @@
<%= if @create == "import" do %> <%= if @create == "import" do %>
<div class="scroll-py-3 overflow-y-auto bg-gray-100 p-3"> <div class="scroll-py-3 overflow-y-auto bg-gray-100 p-3">
<p class="text-xl font-bold"><%= gettext("Select presentation") %></p> <p class="text-xl font-bold">
<%= gettext("Select presentation") %>
</p>
<ul> <ul>
<%= for event <- @events do %> <%= for event <- @events do %>
<li class="my-3"> <li class="my-3">
@@ -367,7 +375,9 @@
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
/> />
</svg> </svg>
<span><%= event.name %></span> <span>
<%= event.name %>
</span>
</button> </button>
</li> </li>
<% end %> <% end %>
@@ -382,7 +392,7 @@
<div class="bg-gray-100 pb-10 md:col-span-2 overflow-y-auto"> <div class="bg-gray-100 pb-10 md:col-span-2 overflow-y-auto">
<div class="flex flex-col justify-center items-center text-center"> <div class="flex flex-col justify-center items-center text-center">
<%= for index <- 0..@event.presentation_file.length-1 do %> <%= for index <- 0..@event.presentation_file.length-1 do %>
<%= if @state.position == index && @state.position > 0 do %> <%= if @state.position==index && @state.position> 0 do %>
<button <button
phx-click="current-page" phx-click="current-page"
phx-value-page={index - 1} phx-value-page={index - 1}
@@ -402,7 +412,8 @@
<% end %> <% end %>
<div <div
class={"#{if @state.position == index, do: 'shadow-xl bg-white', else: 'opacity-50 bg-gray-100'} transition-all pb-5"} class={"#{if @state.position==index, do: 'shadow-xl bg-white' , else: 'opacity-50 bg-gray-100' }
transition-all pb-5"}
id={"slide-preview-#{index}"} id={"slide-preview-#{index}"}
> >
<button <button
@@ -418,7 +429,9 @@
<% else %> <% else %>
<img <img
class="w-1/3 mx-auto" class="w-1/3 mx-auto"
src={"https://#{Application.get_env(:claper, :presentations) |> Keyword.get(:aws_bucket)}.s3.#{Application.get_env(:ex_aws, :region)}.amazonaws.com/presentations/#{@event.presentation_file.hash}/#{index+1}.jpg"} src={"https://#{Application.get_env(:claper, :presentations) |>
Keyword.get(:aws_bucket)}.s3.#{Application.get_env(:ex_aws,
:region)}.amazonaws.com/presentations/#{@event.presentation_file.hash}/#{index+1}.jpg"}
/> />
<% end %> <% end %>
</button> </button>
@@ -444,9 +457,11 @@
</div> </div>
<div class="flex space-x-2"> <div class="flex space-x-2">
<span> <span>
<span class="font-semibold"><%= gettext "Poll" %></span>: <%= poll.title %> <span class="font-semibold">
<%= gettext "Poll" %>
</span>: <%= poll.title %>
</span> </span>
<%= if @state.position == index do %> <%= if @state.position==index do %>
<%= if poll.enabled do %> <%= if poll.enabled do %>
<button <button
phx-click="poll-set-inactive" phx-click="poll-set-inactive"
@@ -524,9 +539,11 @@
</div> </div>
<div class="flex space-x-2"> <div class="flex space-x-2">
<span> <span>
<span class="font-semibold"><%= gettext "Form" %></span>: <%= form.title %> <span class="font-semibold">
<%= gettext "Form" %>
</span>: <%= form.title %>
</span> </span>
<%= if @state.position == index do %> <%= if @state.position==index do %>
<%= if form.enabled do %> <%= if form.enabled do %>
<button <button
phx-click="form-set-inactive" phx-click="form-set-inactive"
@@ -656,14 +673,14 @@
<% end %> <% end %>
</div> </div>
<%= if @state.position == index do %> <%= if @state.position==index do %>
<button class="underline" phx-click={toggle_add_modal()}> <button class="underline" phx-click={toggle_add_modal()}>
<%= gettext("Add interaction") %> <%= gettext("Add interaction") %>
</button> </button>
<% end %> <% end %>
</div> </div>
<%= if @state.position == index && @state.position < @event.presentation_file.length - 1 do %> <%= if @state.position==index && @state.position < @event.presentation_file.length - 1 do %>
<button <button
phx-click="current-page" phx-click="current-page"
phx-value-page={index + 1} phx-value-page={index + 1}
@@ -685,13 +702,19 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-1 grid-rows-2 md:grid-rows-3"> <div class="grid grid-cols-1 grid-rows-2 md:grid-rows-3" style="height: 100%;">
<div class="bg-gray-200 md:row-span-2"> <div class="bg-gray-200 md:row-span-2 border-2">
<ul class="fixed z-20 flex items-center bg-gray-200 space-x-3 px-2 w-full py-2"> <ul class="fixed z-20 flex items-center bg-gray-200 space-x-3 px-2 w-full py-2">
<li class={"rounded-md #{if @list_tab == :posts, do: 'bg-secondary-600 text-white', else: 'bg-white text-gray-600' } px-2 py-0.5 text-sm shadow-sm"}> <li class={"rounded-md #{if @list_tab==:posts, do: 'bg-secondary-600 text-white' ,
else: 'bg-white text-gray-600' } px-2 py-0.5 text-sm shadow-sm"}>
<%= link(gettext("Messages"), to: "#", phx_click: "list-tab", phx_value_tab: :posts) %> <%= link(gettext("Messages"), to: "#", phx_click: "list-tab", phx_value_tab: :posts) %>
</li> </li>
<li class={"rounded-md #{if @list_tab == :forms, do: 'bg-secondary-600 text-white', else: 'bg-white text-gray-600' } px-2 py-0.5 text-sm shadow-sm"}> <li class={"rounded-md #{if @list_tab==:pinned_posts, do: 'bg-secondary-600 text-white' ,
else: 'bg-white text-gray-600' } px-2 py-0.5 text-sm shadow-sm"}>
<%= link(gettext("Pinned messages") <> " (#{ length(@pinned_posts)})", to: "#", phx_click: "list-tab", phx_value_tab: :pinned_posts) %>
</li>
<li class={"rounded-md #{if @list_tab==:forms, do: 'bg-secondary-600 text-white' ,
else: 'bg-white text-gray-600' } px-2 py-0.5 text-sm shadow-sm"}>
<%= link(gettext("Form submissions"), <%= link(gettext("Form submissions"),
to: "#", to: "#",
phx_click: "list-tab", phx_click: "list-tab",
@@ -699,9 +722,13 @@
) %> ) %>
</li> </li>
</ul> </ul>
<%= if @list_tab == :posts do %>
<%= if Enum.count(@posts) == 0 do %> <%= if @list_tab==:posts do %>
<div class="text-center h-full flex flex-col space-y-5 items-center justify-center text-gray-400"> <%= if Enum.count(@all_posts)==0 && Enum.count(@pinned_posts)==0 do %>
<div
class="text-center flex flex-col space-y-5 items-center justify-center text-gray-400"
style="height: 100%;"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-36 w-36" class="h-36 w-36"
@@ -717,112 +744,346 @@
/> />
</svg> </svg>
<p class="text-lg"><%= gettext("Messages from attendees will appear here.") %></p> <p class="text-lg">
<%= gettext("Messages from attendees will appear here.") %>
</p>
</div> </div>
<% end %> <% end %>
<div <div
id="post-list" id="x"
class={"overflow-y-auto #{if Enum.count(@posts) > 0, do: 'max-h-full'} pb-5 pt-8 px-5"} class={"overflow-y-auto #{if Enum.any?(@pinned_posts) or Enum.any?(@all_posts), do: 'h-full', else: 'h-1/2'}"}
phx-update="append"
data-posts-nb={Enum.count(@posts)}
phx-hook="ScrollIntoDiv"
data-target="#post-list"
> >
<%= for post <- @posts do %>
<div class={if post.__meta__.state == :deleted, do: "hidden"} id={"#{post.id}-post"}> <div
<div class="px-4 pb-2 pt-3 rounded-b-lg rounded-tr-lg bg-white relative shadow-md text-black break-all mt-4"> id="post-list"
<div class="float-right mr-1"> class={"overflow-y-auto #{if Enum.count(@all_posts)> 0, do: ''} pb-5 pt-8 px-5"}
<%= if post.attendee_identifier do %> phx-update="append"
data-posts-nb={Enum.count(@all_posts)}
phx-hook="ScrollIntoDiv"
data-target="#post-list"
>
<%= for post <- @all_posts do %>
<div
class={if post.__meta__.state == :deleted, do: "hidden"}
id={"#{post.id}-post"}
>
<div class="px-4 pb-2 pt-3 rounded-b-lg rounded-tr-lg bg-white relative shadow-md text-black break-all mt-4">
<div class="float-right mr-1">
<%= if post.attendee_identifier do %>
<span class="text-yellow-500">
<%= link(
if post.pinned do
gettext("Unpin")
else
gettext("Pin")
end,
to: "#",
phx_click: "pin",
phx_value_id: post.uuid,
phx_value_event_id: @event.uuid
) %>
</span>
/
<span class="text-red-500">
<%= link(gettext("Ban"),
to: "#",
phx_click: "ban",
phx_value_attendee_identifier: post.attendee_identifier,
data: [
confirm:
gettext(
"Blocking this user will delete all his messages and he will not be able to join again, confirm ?"
)
]
) %>
</span>
/
<% else %>
<span class="text-yellow-500">
<%= link(
if post.pinned do
gettext("Unpin")
else
gettext("Pin")
end,
to: "#",
phx_click: "pin",
phx_value_id: post.uuid,
phx_value_event_id: @event.uuid
) %>
</span>
/
<span class="text-red-500">
<%= link(gettext("Ban"),
to: "#",
phx_click: "ban",
phx_value_user_id: post.user_id,
data: [
confirm:
gettext(
"Blocking this user will delete all his messages and he will not be able to join again, confirm ?"
)
]
) %>
</span>
/
<% end %>
<span class="text-red-500"> <span class="text-red-500">
<%= link(gettext("Ban"), <%= link(gettext("Delete"),
to: "#", to: "#",
phx_click: "ban", phx_click: "delete",
phx_value_attendee_identifier: post.attendee_identifier, phx_value_id: post.uuid,
data: [ phx_value_event_id: @event.uuid
confirm:
gettext(
"Blocking this user will delete all his messages and he will not be able to join again, confirm ?"
)
]
) %> ) %>
</span> </span>
/ </div>
<% else %>
<span class="text-red-500">
<%= link(gettext("Ban"),
to: "#",
phx_click: "ban",
phx_value_user_id: post.user_id,
data: [
confirm:
gettext(
"Blocking this user will delete all his messages and he will not be able to join again, confirm ?"
)
]
) %>
</span>
/
<% end %>
<span class="text-red-500">
<%= link(gettext("Delete"),
to: "#",
phx_click: "delete",
phx_value_id: post.uuid,
phx_value_event_id: @event.uuid
) %>
</span>
</div>
<div class="flex space-x-3 items-center"> <div class="flex space-x-3 items-center">
<%= if post.attendee_identifier do %> <%= if post.attendee_identifier do %>
<img <img
class="h-8 w-8" class="h-8 w-8"
src={"https://avatars.dicebear.com/api/identicon/#{post.attendee_identifier}.svg"} src={"https://avatars.dicebear.com/api/identicon/#{post.attendee_identifier}.svg"}
/> />
<% else %> <% else %>
<img <img
class="h-8 w-8" class="h-8 w-8"
src={"https://avatars.dicebear.com/api/identicon/#{post.user_id}.svg"} src={"https://avatars.dicebear.com/api/identicon/#{post.user_id}.svg"}
/> />
<% end %>
<div class="flex flex-col">
<%= if post.name do %>
<p class="text-black text-sm font-semibold mr-2"><%= post.name %></p>
<% end %> <% end %>
<p class="text-xl"><%= post.body %></p> <div class="flex flex-col">
<%= if post.name do %>
<p class="text-black text-sm font-semibold mr-2">
<%= post.name %>
</p>
<% end %>
<p class="text-xl">
<%= post.body %>
</p>
</div>
</div>
<%= if post.like_count> 0 || post.love_count > 0 || post.lol_count > 0 do %>
<div class="flex h-6 space-x-2 text-base text-gray-500 pb-3 items-center mt-5">
<div class="flex items-center">
<%= if post.like_count> 0 do %>
<img src="/images/icons/thumb.svg" class="h-4" />
<span class="ml-1">
<%= post.like_count %>
</span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.love_count> 0 do %>
<img src="/images/icons/heart.svg" class="h-4" />
<span class="ml-1">
<%= post.love_count %>
</span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.lol_count> 0 do %>
<img src="/images/icons/laugh.svg" class="h-4" />
<span class="ml-1">
<%= post.lol_count %>
</span>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
</div>
<!-- The div below encompasses the 2 post lists -->
</div>
<% end %>
<%= if @list_tab==:pinned_posts do %>
<%= if Enum.count(@pinned_posts)==0 do %>
<div
class="text-center flex flex-col space-y-5 items-center justify-center text-gray-400"
style="height: 100%;"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-36 w-36"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"
/>
</svg>
<p class="text-lg">
<%= gettext("Pinned messages will appear here.") %>
</p>
</div>
<% end %>
<div
id="x"
class={"overflow-y-auto #{if Enum.any?(@pinned_posts) or Enum.any?(@all_posts), do: 'h-full', else: 'h-1/2'}"}
>
<%= if Enum.any?(@pinned_posts) do %>
<div
id="pinned-post-list"
class={"overflow-y-auto #{if Enum.count(@pinned_posts)> 0, do: ''}
pt-8 px-5"}
phx-update="replace"
data-posts-nb={Enum.count(@pinned_posts)}
phx-hook="ScrollIntoDiv"
data-target="#pinned-post-list"
>
<%= for post <- @pinned_posts do %>
<div
class={if post.__meta__.state == :deleted, do: "hidden"}
id={"#{post.id}-post"}
>
<div class="px-4 pb-2 pt-3 rounded-b-lg rounded-tr-lg bg-white relative shadow-md text-black break-all mt-4">
<div class="float-right mr-1">
<%= if post.attendee_identifier do %>
<span class="text-yellow-500">
<%= link(
if post.pinned do
gettext("Unpin")
else
gettext("Pin")
end,
to: "#",
phx_click: "pin",
phx_value_id: post.uuid,
phx_value_event_id: @event.uuid
) %>
</span>
/
<span class="text-red-500">
<%= link(gettext("Ban"),
to: "#",
phx_click: "ban",
phx_value_attendee_identifier: post.attendee_identifier,
data: [
confirm:
gettext(
"Blocking this user will delete all his messages and he will not be able to join again, confirm ?"
)
]
) %>
</span>
/
<% else %>
<span class="text-yellow-500">
<%= link(
if post.pinned do
gettext("Unpin")
else
gettext("Pin")
end,
to: "#",
phx_click: "pin",
phx_value_id: post.uuid,
phx_value_event_id: @event.uuid
) %>
</span>
/
<span class="text-red-500">
<%= link(gettext("Ban"),
to: "#",
phx_click: "ban",
phx_value_user_id: post.user_id,
data: [
confirm:
gettext(
"Blocking this user will delete all his messages and he will not be able to join again, confirm ?"
)
]
) %>
</span>
/
<% end %>
<span class="text-red-500">
<%= link(gettext("Delete"),
to: "#",
phx_click: "delete",
phx_value_id: post.uuid,
phx_value_event_id: @event.uuid
) %>
</span>
</div>
<div class="flex space-x-3 items-center">
<%= if post.attendee_identifier do %>
<img
class="h-8 w-8"
src={"https://avatars.dicebear.com/api/identicon/#{post.attendee_identifier}.svg"}
/>
<% else %>
<img
class="h-8 w-8"
src={"https://avatars.dicebear.com/api/identicon/#{post.user_id}.svg"}
/>
<% end %>
<div class="flex flex-col">
<%= if post.name do %>
<p class="text-black text-sm font-semibold mr-2">
<%= post.name %>
</p>
<% end %>
<p class="text-xl">
<%= post.body %>
</p>
</div>
</div>
<%= if post.like_count> 0 || post.love_count > 0 || post.lol_count > 0 do %>
<div class="flex h-6 space-x-2 text-base text-gray-500 pb-3 items-center mt-5">
<div class="flex items-center">
<%= if post.like_count> 0 do %>
<img src="/images/icons/thumb.svg" class="h-4" />
<span class="ml-1">
<%= post.like_count %>
</span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.love_count> 0 do %>
<img src="/images/icons/heart.svg" class="h-4" />
<span class="ml-1">
<%= post.love_count %>
</span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.lol_count> 0 do %>
<img src="/images/icons/laugh.svg" class="h-4" />
<span class="ml-1">
<%= post.lol_count %>
</span>
<% end %>
</div>
</div>
<% end %>
</div> </div>
</div> </div>
<% end %>
<%= if post.like_count > 0 || post.love_count > 0 || post.lol_count > 0 do %>
<div class="flex h-6 space-x-2 text-base text-gray-500 pb-3 items-center mt-5">
<div class="flex items-center">
<%= if post.like_count > 0 do %>
<img src="/images/icons/thumb.svg" class="h-4" />
<span class="ml-1"><%= post.like_count %></span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.love_count > 0 do %>
<img src="/images/icons/heart.svg" class="h-4" />
<span class="ml-1"><%= post.love_count %></span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.lol_count > 0 do %>
<img src="/images/icons/laugh.svg" class="h-4" />
<span class="ml-1"><%= post.lol_count %></span>
<% end %>
</div>
</div>
<% end %>
</div>
</div> </div>
<% end %> <% end %>
</div> </div>
<% else %> <% end %>
<%= if Enum.count(@form_submits) == 0 do %>
<%= if @list_tab==:forms do %>
<%= if Enum.count(@form_submits)==0 do %>
<div class="text-center h-full flex flex-col space-y-5 items-center justify-center text-gray-400"> <div class="text-center h-full flex flex-col space-y-5 items-center justify-center text-gray-400">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -850,7 +1111,8 @@
<% end %> <% end %>
<div <div
id="form-list" id="form-list"
class={"overflow-y-auto #{if Enum.count(@form_submits) > 0, do: 'max-h-full'} pb-5 pt-8 px-5"} class={"overflow-y-auto #{if Enum.count(@form_submits)> 0, do: 'max-h-full'}
pb-5 pt-8 px-5"}
phx-update="append" phx-update="append"
data-forms-nb={Enum.count(@form_submits)} data-forms-nb={Enum.count(@form_submits)}
phx-hook="ScrollIntoDiv" phx-hook="ScrollIntoDiv"
@@ -889,7 +1151,12 @@
<div> <div>
<%= for res <- submission.response do %> <%= for res <- submission.response do %>
<p><strong><%= elem(res, 0) %>:</strong> <%= elem(res, 1) %></p> <p>
<strong>
<%= elem(res, 0) %>:
</strong>
<%= elem(res, 1) %>
</p>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -903,7 +1170,9 @@
<div class="w-full shadow-lg"> <div class="w-full shadow-lg">
<div class="px-5 py-3 grid grid-cols-1 sm:grid-cols-2"> <div class="px-5 py-3 grid grid-cols-1 sm:grid-cols-2">
<div> <div>
<span class="font-semibold text-lg"><%= gettext("On screen settings") %></span> <span class="font-semibold text-lg">
<%= gettext("On screen settings") %>
</span>
<div class="flex space-x-2 items-center mt-3"> <div class="flex space-x-2 items-center mt-3">
<ClaperWeb.Component.Input.check <ClaperWeb.Component.Input.check
@@ -923,7 +1192,17 @@
<span><%= gettext("Show messages") %><code class="pl-1">(W)</code></span> <span><%= gettext("Show messages") %><code class="pl-1">(W)</code></span>
</div> </div>
<div class={"#{if !@current_poll, do: 'opacity-50'} flex space-x-2 items-center mt-3"}> <div class="flex space-x-2 items-center mt-3">
<ClaperWeb.Component.Input.check
key={:show_only_pinned}
checked={@state.show_only_pinned}
/>
<span>
<%= gettext("Show only pinned messages") %>
</span>
</div>
<div class={"#{if !@current_poll, do: 'opacity-50' } flex space-x-2 items-center mt-3"}>
<ClaperWeb.Component.Input.check <ClaperWeb.Component.Input.check
key={:poll_visible} key={:poll_visible}
disabled={!@current_poll} disabled={!@current_poll}
@@ -935,7 +1214,9 @@
</div> </div>
<div> <div>
<span class="font-semibold text-lg"><%= gettext("Attendees settings") %></span> <span class="font-semibold text-lg">
<%= gettext("Attendees settings") %>
</span>
<div class="flex space-x-2 items-center mt-3"> <div class="flex space-x-2 items-center mt-3">
<ClaperWeb.Component.Input.check <ClaperWeb.Component.Input.check

View File

@@ -33,6 +33,32 @@ defmodule ClaperWeb.EventLive.PostComponent do
<span><%= gettext("Host") %></span> <span><%= gettext("Host") %></span>
</div> </div>
<% end %> <% end %>
<%= if is_pinned(@post) do %>
<div class="inline-flex items-center space-x-1 justify-center px-3 py-0.5 rounded-full text-xs font-medium bg-supporting-yellow-100 text-supporting-yellow-800 mb-2 ml-1">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-pin-filled"
width="12"
height="12"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M15.113 3.21l.094 .083l5.5 5.5a1 1 0 0 1 -1.175 1.59l-3.172 3.171l-1.424 3.797a1 1 0 0 1 -.158 .277l-.07 .08l-1.5 1.5a1 1 0 0 1 -1.32 .082l-.095 -.083l-2.793 -2.792l-3.793 3.792a1 1 0 0 1 -1.497 -1.32l.083 -.094l3.792 -3.793l-2.792 -2.793a1 1 0 0 1 -.083 -1.32l.083 -.094l1.5 -1.5a1 1 0 0 1 .258 -.187l.098 -.042l3.796 -1.425l3.171 -3.17a1 1 0 0 1 1.497 -1.26z"
stroke-width="0"
fill="currentColor"
>
</path>
</svg>
<span><%= gettext("Pinned") %></span>
</div>
<% end %>
</div> </div>
<% end %> <% end %>
@@ -124,6 +150,32 @@ defmodule ClaperWeb.EventLive.PostComponent do
</div> </div>
<% end %> <% end %>
<%= if is_pinned(@post) do %>
<div class="inline-flex items-center space-x-1 justify-center px-3 py-0.5 rounded-full text-xs font-medium bg-supporting-yellow-100 text-supporting-yellow-800 mb-2 ml-1">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-pin-filled"
width="12"
height="12"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M15.113 3.21l.094 .083l5.5 5.5a1 1 0 0 1 -1.175 1.59l-3.172 3.171l-1.424 3.797a1 1 0 0 1 -.158 .277l-.07 .08l-1.5 1.5a1 1 0 0 1 -1.32 .082l-.095 -.083l-2.793 -2.792l-3.793 3.792a1 1 0 0 1 -1.497 -1.32l.083 -.094l3.792 -3.793l-2.792 -2.793a1 1 0 0 1 -.083 -1.32l.083 -.094l1.5 -1.5a1 1 0 0 1 .258 -.187l.098 -.042l3.796 -1.425l3.171 -3.17a1 1 0 0 1 1.497 -1.26z"
stroke-width="0"
fill="currentColor"
>
</path>
</svg>
<span><%= gettext("Pinned") %></span>
</div>
<% end %>
<p><%= @post.body %></p> <p><%= @post.body %></p>
<div class="flex h-6 text-xs float-right space-x-2"> <div class="flex h-6 text-xs float-right space-x-2">
@@ -218,4 +270,8 @@ defmodule ClaperWeb.EventLive.PostComponent do
leader.user_id == post.user_id leader.user_id == post.user_id
end)) end))
end end
defp is_pinned(post) do
post.pinned == true
end
end end

View File

@@ -46,15 +46,23 @@ defmodule ClaperWeb.EventLive.Presenter do
|> assign(:event, event) |> assign(:event, event)
|> assign(:state, event.presentation_file.presentation_state) |> assign(:state, event.presentation_file.presentation_state)
|> assign(:posts, list_posts(socket, event.uuid)) |> assign(:posts, list_posts(socket, event.uuid))
|> assign(:pinned_posts, list_pinned_posts(socket, event.uuid))
|> assign(:show_only_pinned, event.presentation_file.presentation_state.show_only_pinned)
|> assign(:reacts, []) |> assign(:reacts, [])
|> poll_at_position |> poll_at_position
|> form_at_position |> form_at_position
|> embed_at_position |> embed_at_position
{:ok, socket, temporary_assigns: [posts: []]} {:ok, socket, temporary_assigns: []}
end end
end end
defp update_post_in_list(posts, updated_post) do
Enum.map(posts, fn post ->
if post.id == updated_post.id, do: updated_post, else: post
end)
end
defp is_leader(%{assigns: %{current_user: current_user}} = _socket, event) do defp is_leader(%{assigns: %{current_user: current_user}} = _socket, event) do
Claper.Events.is_leaded_by(current_user.email, event) || event.user.id == current_user.id Claper.Events.is_leaded_by(current_user.email, event) || event.user.id == current_user.id
end end
@@ -74,6 +82,22 @@ defmodule ClaperWeb.EventLive.Presenter do
|> update(:posts, fn posts -> [post | posts] end)} |> update(:posts, fn posts -> [post | posts] end)}
end end
@impl true
def handle_info({:post_pinned, post}, socket) do
{:noreply,
socket
|> update(:pinned_posts, fn pinned_posts -> [post | pinned_posts] end)}
end
@impl true
def handle_info({:post_unpinned, post}, socket) do
{:noreply,
socket
|> update(:pinned_posts, fn pinned_posts ->
Enum.reject(pinned_posts, fn p -> p.id == post.id end)
end)}
end
@impl true @impl true
def handle_info({:state_updated, state}, socket) do def handle_info({:state_updated, state}, socket) do
{:noreply, {:noreply,
@@ -86,23 +110,23 @@ defmodule ClaperWeb.EventLive.Presenter do
end end
@impl true @impl true
def handle_info({:post_updated, post}, socket) do def handle_info({:post_updated, updated_post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} updated_posts = update_post_in_list(socket.assigns.posts, updated_post)
end updated_pinned_posts = update_post_in_list(socket.assigns.pinned_posts, updated_post)
@impl true {:noreply,
def handle_info({:reaction_added, post}, socket) do socket
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} |> assign(:posts, updated_posts)
end |> assign(:pinned_posts, updated_pinned_posts)}
@impl true
def handle_info({:reaction_removed, post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
end end
@impl true @impl true
def handle_info({:post_deleted, post}, socket) do def handle_info({:post_deleted, post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} updated_posts = Enum.reject(socket.assigns.posts, fn p -> p.id == post.id end)
updated_pinned_posts = Enum.reject(socket.assigns.pinned_posts, fn p -> p.id == post.id end)
{:noreply,
socket |> assign(:posts, updated_posts) |> assign(:pinned_posts, updated_pinned_posts)}
end end
@impl true @impl true
@@ -173,6 +197,14 @@ defmodule ClaperWeb.EventLive.Presenter do
|> update(:chat_visible, fn _chat_visible -> value end)} |> update(:chat_visible, fn _chat_visible -> value end)}
end end
@impl true
def handle_info({:show_only_pinned, value}, socket) do
{:noreply,
socket
|> push_event("show_only_pinned", %{value: value})
|> update(:show_only_pinned, fn _show_only_pinned -> value end)}
end
@impl true @impl true
def handle_info({:poll_visible, value}, socket) do def handle_info({:poll_visible, value}, socket) do
{:noreply, {:noreply,
@@ -267,4 +299,8 @@ defmodule ClaperWeb.EventLive.Presenter do
defp list_posts(_socket, event_id) do defp list_posts(_socket, event_id) do
Claper.Posts.list_posts(event_id, [:event, :reactions]) Claper.Posts.list_posts(event_id, [:event, :reactions])
end end
defp list_pinned_posts(_socket, event_id) do
Claper.Posts.list_pinned_posts(event_id, [:event, :reactions])
end
end end

View File

@@ -89,40 +89,80 @@
> >
<div <div
class={"#{if @state.chat_visible, do: 'opacity-100 w-3/12 px-4 showed', else: 'opacity-0 w-0 p-0'} transition-all duration-150 flex flex-col h-screen py-5 justify-end max-h-screen bg-black"} class={"#{if @state.chat_visible, do: 'opacity-100 w-3/12 px-4 showed', else: 'opacity-0 w-0 p-0'} transition-all duration-150 flex flex-col h-screen py-5 justify-end max-h-screen bg-black"}
id="post-list" id="test"
phx-update="append" phx-update="replace"
> >
<%= for post <- @posts do %> <%= if @state.show_only_pinned == false do %>
<div class={if post.__meta__.state == :deleted, do: "hidden"} id={"#{post.id}-post"}> <div id="post-list" phx-update="replace">
<div class="px-4 pb-2 pt-3 rounded-b-lg rounded-tr-lg bg-white shadow-md text-black break-word mt-4"> <%= for post <- @posts do %>
<%= if post.name do %> <div class={if post.__meta__.state == :deleted, do: "hidden"} id={"#{post.id}-post"}>
<p class="text-gray-400 text-lg font-semibold mb-2 mr-2"><%= post.name %></p> <div class="px-4 pb-2 pt-3 rounded-b-lg rounded-tr-lg bg-white shadow-md text-black break-word mt-4">
<% end %> <%= if post.name do %>
<p class="text-3xl"><%= post.body %></p> <p class="text-gray-400 text-lg font-semibold mb-2 mr-2"><%= post.name %></p>
<% end %>
<p class="text-3xl"><%= post.body %></p>
<%= if post.like_count > 0 || post.love_count > 0 || post.lol_count > 0 do %> <%= if post.like_count > 0 || post.love_count > 0 || post.lol_count > 0 do %>
<div class="flex h-6 space-x-2 text-lg text-gray-500 pb-3 items-center mt-5"> <div class="flex h-6 space-x-2 text-lg text-gray-500 pb-3 items-center mt-5">
<div class="flex items-center"> <div class="flex items-center">
<%= if post.like_count > 0 do %> <%= if post.like_count > 0 do %>
<img src="/images/icons/thumb.svg" class="h-7" /> <img src="/images/icons/thumb.svg" class="h-7" />
<span class="ml-1"><%= post.like_count %></span> <span class="ml-1"><%= post.like_count %></span>
<% end %> <% end %>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<%= if post.love_count > 0 do %> <%= if post.love_count > 0 do %>
<img src="/images/icons/heart.svg" class="h-7" /> <img src="/images/icons/heart.svg" class="h-7" />
<span class="ml-1"><%= post.love_count %></span> <span class="ml-1"><%= post.love_count %></span>
<% end %> <% end %>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<%= if post.lol_count > 0 do %> <%= if post.lol_count > 0 do %>
<img src="/images/icons/laugh.svg" class="h-7" /> <img src="/images/icons/laugh.svg" class="h-7" />
<span class="ml-1"><%= post.lol_count %></span> <span class="ml-1"><%= post.lol_count %></span>
<% end %> <% end %>
</div> </div>
</div>
<% end %>
</div> </div>
<% end %> </div>
</div> <% end %>
</div>
<% else %>
<div id="pinned-posts" phx-update="replace">
<%= for post <- @pinned_posts do %>
<div class={if post.__meta__.state == :deleted, do: "hidden"} id={"#{post.id}-post"}>
<div class="px-4 pb-2 pt-3 rounded-b-lg rounded-tr-lg bg-white shadow-md text-black break-word mt-4">
<%= if post.name do %>
<p class="text-gray-400 text-lg font-semibold mb-2 mr-2"><%= post.name %></p>
<% end %>
<p class="text-3xl"><%= post.body %></p>
<%= if post.like_count > 0 || post.love_count > 0 || post.lol_count > 0 do %>
<div class="flex h-6 space-x-2 text-lg text-gray-500 pb-3 items-center mt-5">
<div class="flex items-center">
<%= if post.like_count > 0 do %>
<img src="/images/icons/thumb.svg" class="h-7" />
<span class="ml-1"><%= post.like_count %></span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.love_count > 0 do %>
<img src="/images/icons/heart.svg" class="h-7" />
<span class="ml-1"><%= post.love_count %></span>
<% end %>
</div>
<div class="flex items-center">
<%= if post.lol_count > 0 do %>
<img src="/images/icons/laugh.svg" class="h-7" />
<span class="ml-1"><%= post.lol_count %></span>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
</div> </div>
<% end %> <% end %>
</div> </div>

View File

@@ -259,6 +259,16 @@ defmodule ClaperWeb.EventLive.Show do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} {:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
end end
@impl true
def handle_info({:post_pinned, post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
end
@impl true
def handle_info({:post_unpinned, post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
end
@impl true @impl true
def handle_info({:reaction_added, post}, socket) do def handle_info({:reaction_added, post}, socket) do
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)} {:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}

View File

@@ -0,0 +1,13 @@
defmodule Claper.Repo.Migrations.AddPinnedToPostsAndPresentationStates do
use Ecto.Migration
def change do
alter table(:presentation_states) do
add :show_only_pinned, :boolean, default: false
end
alter table(:posts) do
add :pinned, :boolean, default: false
end
end
end