Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
liamwhite committed Jul 8, 2024
2 parents b677f1b + 41219bb commit 10afa05
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 39 deletions.
1 change: 1 addition & 0 deletions assets/css/common/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ span.stat {
@import "views/galleries";
@import "views/game";
@import "views/images";
@import "views/notifications";
@import "views/pages";
@import "views/polls";
@import "views/posts";
Expand Down
11 changes: 11 additions & 0 deletions assets/css/views/_notifications.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.notification-type-block:not(:last-child) {
margin-bottom: 20px;
}

.notification {
margin-bottom: 0;
}

.notification:not(:last-child) {
border-bottom: 0;
}
73 changes: 67 additions & 6 deletions lib/philomena/notifications.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,82 @@ defmodule Philomena.Notifications do
import Ecto.Query, warn: false
alias Philomena.Repo

alias Philomena.Notifications.Category
alias Philomena.Notifications.Notification
alias Philomena.Notifications.UnreadNotification
alias Philomena.Polymorphic

@doc """
Returns the list of notifications.
Returns the list of unread notifications of the given type.
The set of valid types is `t:Philomena.Notifications.Category.t/0`.
## Examples
iex> list_notifications()
iex> unread_notifications_for_user_and_type(user, :image_comment, ...)
[%Notification{}, ...]
"""
def list_notifications do
Repo.all(Notification)
def unread_notifications_for_user_and_type(user, type, pagination) do
notifications =
user
|> unread_query_for_type(type)
|> Repo.paginate(pagination)

put_in(notifications.entries, load_associations(notifications.entries))
end

@doc """
Gather up and return the top N notifications for the user, for each type of
unread notification currently existing.
## Examples
iex> unread_notifications_for_user(user)
[
forum_topic: [%Notification{...}, ...],
forum_post: [%Notification{...}, ...],
image_comment: [%Notification{...}, ...]
]
"""
def unread_notifications_for_user(user, n) do
Category.types()
|> Enum.map(fn type ->
q =
user
|> unread_query_for_type(type)
|> limit(^n)

# Use a subquery to ensure the order by is applied to the
# subquery results only, and not the main query results
from(n in subquery(q))
end)
|> union_all_queries()
|> Repo.all()
|> load_associations()
|> Enum.group_by(&Category.notification_type/1)
|> Enum.sort_by(fn {k, _v} -> k end)
end

defp unread_query_for_type(user, type) do
from n in Category.query_for_type(type),
join: un in UnreadNotification,
on: un.notification_id == n.id,
where: un.user_id == ^user.id,
order_by: [desc: :updated_at]
end

defp union_all_queries([query | rest]) do
Enum.reduce(rest, query, fn q, acc -> union_all(acc, ^q) end)
end

defp load_associations(notifications) do
Polymorphic.load_polymorphic(
notifications,
actor: [actor_id: :actor_type],
actor_child: [actor_child_id: :actor_child_type]
)
end

@doc """
Expand Down Expand Up @@ -102,8 +165,6 @@ defmodule Philomena.Notifications do
Notification.changeset(notification, %{})
end

alias Philomena.Notifications.UnreadNotification

def count_unread_notifications(user) do
UnreadNotification
|> where(user_id: ^user.id)
Expand Down
93 changes: 93 additions & 0 deletions lib/philomena/notifications/category.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
defmodule Philomena.Notifications.Category do
@moduledoc """
Notification category determination.
"""

import Ecto.Query, warn: false
alias Philomena.Notifications.Notification

@type t ::
:channel_live
| :forum_post
| :forum_topic
| :gallery_image
| :image_comment
| :image_merge

@doc """
Return a list of all supported types.
"""
def types do
[
:channel_live,
:forum_topic,
:gallery_image,
:image_comment,
:image_merge,
:forum_post
]
end

@doc """
Determine the type of a `m:Philomena.Notifications.Notification`.
"""
def notification_type(n) do
case {n.actor_type, n.actor_child_type} do
{"Channel", _} ->
:channel_live

{"Gallery", _} ->
:gallery_image

{"Image", "Comment"} ->
:image_comment

{"Image", _} ->
:image_merge

{"Topic", "Post"} ->
if n.action == "posted a new reply in" do
:forum_post
else
:forum_topic
end
end
end

@doc """
Returns an `m:Ecto.Query` that finds notifications for the given type.
"""
def query_for_type(type) do
base = from(n in Notification)

case type do
:channel_live ->
where(base, [n], n.actor_type == "Channel")

:gallery_image ->
where(base, [n], n.actor_type == "Gallery")

:image_comment ->
where(base, [n], n.actor_type == "Image" and n.actor_child_type == "Comment")

:image_merge ->
where(base, [n], n.actor_type == "Image" and is_nil(n.actor_child_type))

:forum_topic ->
where(
base,
[n],
n.actor_type == "Topic" and n.actor_child_type == "Post" and
n.action != "posted a new reply in"
)

:forum_post ->
where(
base,
[n],
n.actor_type == "Topic" and n.actor_child_type == "Post" and
n.action == "posted a new reply in"
)
end
end
end
33 changes: 33 additions & 0 deletions lib/philomena_web/controllers/notification/category_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule PhilomenaWeb.Notification.CategoryController do
use PhilomenaWeb, :controller

alias Philomena.Notifications

def show(conn, params) do
type = category(params)

notifications =
Notifications.unread_notifications_for_user_and_type(
conn.assigns.current_user,
type,
conn.assigns.scrivener
)

render(conn, "show.html",
title: "Notification Area",
notifications: notifications,
type: type
)
end

defp category(params) do
case params["id"] do
"channel_live" -> :channel_live
"gallery_image" -> :gallery_image
"image_comment" -> :image_comment
"image_merge" -> :image_merge
"forum_topic" -> :forum_topic
_ -> :forum_post
end
end
end
27 changes: 2 additions & 25 deletions lib/philomena_web/controllers/notification_controller.ex
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
defmodule PhilomenaWeb.NotificationController do
use PhilomenaWeb, :controller

alias Philomena.Notifications.{UnreadNotification, Notification}
alias Philomena.Polymorphic
alias Philomena.Repo
import Ecto.Query
alias Philomena.Notifications

def index(conn, _params) do
user = conn.assigns.current_user

notifications =
from n in Notification,
join: un in UnreadNotification,
on: un.notification_id == n.id,
where: un.user_id == ^user.id

notifications =
notifications
|> order_by(desc: :updated_at)
|> Repo.paginate(conn.assigns.scrivener)

entries =
notifications.entries
|> Polymorphic.load_polymorphic(
actor: [actor_id: :actor_type],
actor_child: [actor_child_id: :actor_child_type]
)

notifications = %{notifications | entries: entries}
notifications = Notifications.unread_notifications_for_user(conn.assigns.current_user, 15)

render(conn, "index.html", title: "Notification Area", notifications: notifications)
end
Expand Down
1 change: 1 addition & 0 deletions lib/philomena_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ defmodule PhilomenaWeb.Router do

scope "/notifications", Notification, as: :notification do
resources "/unread", UnreadController, only: [:index]
resources "/categories", CategoryController, only: [:show]
end

resources "/notifications", NotificationController, only: [:index, :delete]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
= if @notification.actor do
.block.block--fixed.flex id="notification-#{@notification.id}"
.block.block--fixed.flex.notification id="notification-#{@notification.id}"
= if @notification.actor_type == "Image" and @notification.actor do
.flex.flex--centered.flex__fixed.thumb-tiny-container.spacing-right
= render PhilomenaWeb.ImageView, "_image_container.html", image: @notification.actor, size: :thumb_tiny, conn: @conn
Expand Down
28 changes: 28 additions & 0 deletions lib/philomena_web/templates/notification/category/show.html.slime
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
h1 Notification Area
.walloftext
= cond do
- Enum.any?(@notifications) ->
- route = fn p -> ~p"/notifications/categories/#{@type}?#{p}" end
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @notifications, route: route, conn: @conn

.block.notification-type-block
.block__header
span.block__header__title = name_of_type(@type)
.block__header.block__header__sub
= pagination

div
= for notification <- @notifications do
= render PhilomenaWeb.NotificationView, "_notification.html", notification: notification, conn: @conn

.block__header.block__header--light
= pagination

- true ->
p You currently have no notifications of this category.
p
' To get notifications on new comments and forum posts, click the
' 'Subscribe' button in the bar at the top of an image or forum topic.

a.button href=~p"/notifications"
' View all notifications
19 changes: 12 additions & 7 deletions lib/philomena_web/templates/notification/index.html.slime
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
- route = fn p -> ~p"/notifications?#{p}" end

h1 Notification Area
.walloftext
.block__header
= render PhilomenaWeb.PaginationView, "_pagination.html", page: @notifications, route: route, conn: @conn

= cond do
- Enum.any?(@notifications) ->
= for notification <- @notifications do
= render PhilomenaWeb.NotificationView, "_notification.html", notification: notification, conn: @conn
= for {type, notifications} <- @notifications do
.block.notification-type-block
.block__header
span.block__header__title = name_of_type(type)

div
= for notification <- notifications do
= render PhilomenaWeb.NotificationView, "_notification.html", notification: notification, conn: @conn

.block__header.block__header--light
a href=~p"/notifications/categories/#{type}"
| View category

- true ->
p
Expand Down
5 changes: 5 additions & 0 deletions lib/philomena_web/views/notification/category_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule PhilomenaWeb.Notification.CategoryView do
use PhilomenaWeb, :view

defdelegate name_of_type(type), to: PhilomenaWeb.NotificationView
end
22 changes: 22 additions & 0 deletions lib/philomena_web/views/notification_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,26 @@ defmodule PhilomenaWeb.NotificationView do
def notification_template_path(actor_type) do
@template_paths[actor_type]
end

def name_of_type(notification_type) do
case notification_type do
:channel_live ->
"Live channels"

:forum_post ->
"New replies in topics"

:forum_topic ->
"New topics"

:gallery_image ->
"Updated galleries"

:image_comment ->
"New replies on images"

:image_merge ->
"Image merges"
end
end
end

0 comments on commit 10afa05

Please sign in to comment.