Initial chat box; man phoenix does not have the best docs

This commit is contained in:
Logan Hunt 2022-04-20 16:03:17 -06:00
parent 3cf9f4a364
commit 763ea5331b
Signed by untrusted user who does not match committer: simponic
GPG Key ID: 52B3774857EB24B1
7 changed files with 207 additions and 66 deletions

View File

@ -121,3 +121,103 @@
max-width: 100px; max-width: 100px;
max-height: 100px; max-height: 100px;
} }
.post-body {
white-space: pre-line;
}
/* Chat css from: https://www.bootdey.com/snippets/view/card-chat-list#html */
.chat-list {
padding: 0;
font-size: .8rem;
}
.chat-list li {
margin-bottom: 10px;
overflow: auto;
color: #ffffff;
}
.chat-list .chat-message {
-webkit-border-radius: 50px;
-moz-border-radius: 50px;
border-radius: 50px;
background: #5a99ee;
display: inline-block;
padding: 10px 20px;
position: relative;
}
.chat-list .chat-message:before {
content: "";
position: absolute;
top: 15px;
width: 0;
height: 0;
}
.chat-list .chat-message h5 {
margin: 0 0 5px 0;
font-weight: 600;
line-height: 100%;
font-size: .9rem;
}
.chat-list .chat-message p {
line-height: 18px;
margin: 0;
padding: 0;
}
.chat-list .chat-card-body {
margin-left: 20px;
float: left;
width: 70%;
}
.chat-list .in .chat-message:before {
left: -12px;
border-bottom: 20px solid transparent;
border-right: 20px solid #5a99ee;
}
.chat-list .out .chat-img {
float: right;
}
.chat-list .out .chat-body {
float: right;
margin-right: 20px;
text-align: right;
}
.chat-list .out .chat-message {
background: #fc6d4c;
}
.chat-list .out .chat-message:before {
right: -12px;
border-bottom: 20px solid transparent;
border-left: 20px solid #fc6d4c;
}
.chat-card {
overflow-y: scroll;
max-height: 300px;
}
.chat-card .card-header:first-child {
-webkit-border-radius: 0.3rem 0.3rem 0 0;
-moz-border-radius: 0.3rem 0.3rem 0 0;
border-radius: 0.3rem 0.3rem 0 0;
}
.chat-card .card-header {
background: #17202b;
border: 0;
font-size: 1rem;
padding: .65rem 1rem;
position: relative;
font-weight: 600;
color: #ffffff;
}

View File

@ -1,46 +1,11 @@
let RoomChat = { let RoomChat = {
init(socket, postId) { connect(socket, postId) {
console.log(postId);
console.log(socket);
let channel = socket.channel(`post:${postId}`) let channel = socket.channel(`post:${postId}`)
channel.join() channel.join()
.receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("ok", resp => { console.log("Joined successfully: ", resp) })
.receive("error", resp => { console.log("Unable to join", resp) }) .receive("error", resp => { console.log("Unable to join: ", resp) })
this.listenForChats(channel) return channel;
}, },
addMessage(user, message) {
// let body = `<span class="username"><b>${user}</b></span>: ${message}<br>`
// if (message.match(new RegExp(`@${window.userName}`, "ig"))) {
// $("#chat-box").append('<p class="chat-entry"><span class="mentioned">' + body + '</span></p>')
// } else {
// $("#chat-box").append('<p class="chat-entry">' + body + '</p>')
// }
},
scrollBottom() {
// $("#chat-box").animate({ scrollTop: $('#chat-box').prop("scrollHeight")}, 200)
},
listenForChats(channel) {
channel.push('send', { body: "HELLO"});
// $(() => {
// $("#chat-form").on("submit", function(ev) {
// ev.preventDefault()
//
// let userMsg = $('#user-msg').val()
// channel.push('send', {body: userMsg})
//
// $("#user-msg").val("")
// })
// channel.on('shout', function(payload) {
// console.log(payload)
// RoomChat.addMessage(payload.name, payload.body)
// RoomChat.scrollBottom()
// })
// })
channel.on('shout', function(payload) {
console.log(payload)
});
}
} }
export default RoomChat; export default RoomChat;

View File

@ -94,7 +94,7 @@ defmodule Aggiedit.Rooms do
end end
def vote_count(post) do def vote_count(post) do
votes = post post
|> Repo.preload(:votes) |> Repo.preload(:votes)
|> Map.get(:votes) |> Map.get(:votes)
|> Enum.map(fn vote -> if vote.is_up, do: 1, else: -1 end) |> Enum.map(fn vote -> if vote.is_up, do: 1, else: -1 end)
@ -105,11 +105,17 @@ defmodule Aggiedit.Rooms do
is_up = direction == "upvote" is_up = direction == "upvote"
vote = %Vote{is_up: is_up, user: user, post: post} vote = %Vote{is_up: is_up, user: user, post: post}
|> Repo.insert(on_conflict: [set: [is_up: is_up]], conflict_target: [:user_id, :post_id]) |> Repo.insert(on_conflict: [set: [is_up: is_up]], conflict_target: [:user_id, :post_id])
post = change_post(post, %{score: vote_count(post)}) post = change_post(post, %{score: vote_count(post)})
|> Repo.update() |> Repo.update()
broadcast_post_over_room(post, :post_voted) broadcast_post_over_room(post, :post_voted)
end end
def comment_post(%Post{} = post, %User{} = user, comment) do
Repo.insert(%Comment{comment: comment, user: user, post: post})
end
defp broadcast_post_over_room({:error, _reason}=error, _event), do: error defp broadcast_post_over_room({:error, _reason}=error, _event), do: error
defp broadcast_post_over_room({:ok, post}, event) do defp broadcast_post_over_room({:ok, post}, event) do
PubSub.broadcast(Aggiedit.PubSub, "room:#{post.room_id}:posts", {event, post}) PubSub.broadcast(Aggiedit.PubSub, "room:#{post.room_id}:posts", {event, post})

View File

@ -17,4 +17,8 @@ defmodule Aggiedit.Post.Comment do
|> cast(attrs, [:comment]) |> cast(attrs, [:comment])
|> validate_required([:comment]) |> validate_required([:comment])
end end
def serialize(c) do
%{"body" => c.comment, "user" => c.user.username, "id" => c.id, "inserted_at" => c.inserted_at}
end
end end

View File

@ -2,21 +2,35 @@ defmodule AggieditWeb.PostChannel do
use AggieditWeb, :channel use AggieditWeb, :channel
alias Aggiedit.Roles alias Aggiedit.Roles
alias Aggiedit.Repo
alias Aggiedit.Rooms alias Aggiedit.Rooms
@impl true @impl true
def join("post:" <> post_id, _payload, socket) do def join("post:" <> post_id, _payload, socket) do
post = Rooms.get_post!(post_id) post = Rooms.get_post!(post_id)
if Roles.guard?(socket.assigns.current_user, :show, post) do if Roles.guard?(socket.assigns.current_user, :show, post) do
{:ok, socket} send(self(), :after_join)
{:ok, assign(socket, %{:post => post})}
else else
{:error, "You do not have permission to view this post."} {:error, "You do not have permission to view this post."}
end end
end end
@impl true @impl true
def handle_in("send", body, socket) do def handle_info(:after_join, socket) do
broadcast!(socket, "shout", body) comments = socket.assigns.post
|> Repo.preload(comments: [:user])
|> Map.get(:comments)
|> Enum.map(fn c -> Aggiedit.Post.Comment.serialize(c) end)
push(socket, "initial-comments", %{:comments => comments})
{:noreply, socket}
end
@impl true
def handle_in("send", %{"body" => comment}=body, socket) do
{:ok, comment} = Rooms.comment_post(socket.assigns.post, socket.assigns.current_user, comment)
broadcast!(socket, "shout", Aggiedit.Post.Comment.serialize(comment))
{:reply, :ok, socket} {:reply, :ok, socket}
end end
end end

View File

@ -15,10 +15,10 @@ defmodule AggieditWeb.PostLive.Show do
post = Rooms.get_post!(id) post = Rooms.get_post!(id)
|> Repo.preload(:upload) |> Repo.preload(:upload)
if Roles.guard?(socket.assigns.current_user, socket.assigns.live_action, post) do if Roles.guard?(socket.assigns.current_user, socket.assigns.live_action, post) do
{:noreply, socket = (if socket.assigns.live_action == :show, do: push_event(socket, "initial-post", %{:id => post.id}), else: socket)
socket
|> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:post, post)} |> assign(:post, post)
{:noreply, socket}
else else
{:noreply, socket |> put_flash(:error, "You don't have permission to do that.") |> redirect(to: Routes.post_show_path(socket, :show, socket.assigns.room, post))} {:noreply, socket |> put_flash(:error, "You don't have permission to do that.") |> redirect(to: Routes.post_show_path(socket, :show, socket.assigns.room, post))}
end end

View File

@ -1,4 +1,37 @@
<h1>Show Post</h1>
<div class="d-flex justify-content-center">
<div class="container">
<div>
<h1><%= @post.title %></h1>
</div>
<div>
<%= if Ecto.assoc_loaded?(@post.upload) && !is_nil(@post.upload) do %>
<img src={Routes.static_path(@socket, "/uploads/#{@post.upload.file}")} class="img-fluid"/>
<% end %>
</div>
<div class="post-body">
<%= @post.body %>
</div>
<%= if Aggiedit.Roles.guard?(@current_user, :edit, @post) do %>
<span><%= live_patch "Edit", to: Routes.post_show_path(@socket, :edit, @room, @post), class: "button" %></span> |
<% end %>
<span><%= live_redirect "Back", to: Routes.post_index_path(@socket, :index, @room) %></span>
</div>
</div>
<!-- chat container from https://www.bootdey.com/snippets/view/card-chat-list#html -->
<div class="container content mt-2">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12">
<div class="chat-card">
<div class="chat-card-body">
<ul class="chat-list" id="chat" phx-update="ignore">
</ul>
</div>
</div>
</div>
</div>
</div>
<%= if @live_action in [:edit] do %> <%= if @live_action in [:edit] do %>
<.modal return_to={Routes.post_show_path(@socket, :show, @room, @post)}> <.modal return_to={Routes.post_show_path(@socket, :show, @room, @post)}>
@ -14,24 +47,43 @@
</.modal> </.modal>
<% end %> <% end %>
<ul>
<li>
<strong>Title:</strong>
<%= @post.title %>
</li>
<li>
<strong>Body:</strong>
<%= @post.body %>
</li>
</ul>
<span><%= live_patch "Edit", to: Routes.post_show_path(@socket, :edit, @room, @post), class: "button" %></span> |
<span><%= live_redirect "Back", to: Routes.post_index_path(@socket, :index, @room) %></span>
<script> <script>
const scrollToBottom = (element) => {
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
};
const appendComment = ({user, body, id, inserted_at}, element) => {
const messageElement = document.createElement("div");
messageElement.innerHTML = `
<li class="in" id=${id}>
<div class="chat-card-body">
<div class="chat-message">
<h5>${user}</h5>
<p>${body}</p>
</div>
</div>
</li>
`;
element.appendChild(messageElement);
scrollToBottom(element);
};
window.addEventListener('phx:initial-post', (e) => {
const chatWindow = document.getElementById("chat");
window.userSocket.connect(); window.userSocket.connect();
window.RoomChat.init(window.userSocket, <%= @post.id %>) let channel = window.RoomChat.connect(window.userSocket, e.detail.id);
channel.on('shout', (comment) => {
appendComment(comment, chatWindow);
});
channel.on('initial-comments', ({comments}) => {
comments.forEach((comment) => {
appendComment(comment, chatWindow);
});
scrollToBottom(chatWindow);
});
channel.push("send", {body: "Hello!"});
});
</script> </script>