mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
Merge pull request #321 from philomena-dev/notifications-v2
New notifications UI: separated by category
This commit is contained in:
commit
41219bb217
12 changed files with 276 additions and 39 deletions
|
@ -480,6 +480,7 @@ span.stat {
|
|||
@import "views/filters";
|
||||
@import "views/galleries";
|
||||
@import "views/images";
|
||||
@import "views/notifications";
|
||||
@import "views/pages";
|
||||
@import "views/polls";
|
||||
@import "views/posts";
|
||||
|
|
11
assets/css/views/_notifications.scss
Normal file
11
assets/css/views/_notifications.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
.notification-type-block:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.notification:not(:last-child) {
|
||||
border-bottom: 0;
|
||||
}
|
|
@ -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 """
|
||||
|
@ -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)
|
||||
|
|
93
lib/philomena/notifications/category.ex
Normal file
93
lib/philomena/notifications/category.ex
Normal file
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
5
lib/philomena_web/views/notification/category_view.ex
Normal file
5
lib/philomena_web/views/notification/category_view.ex
Normal file
|
@ -0,0 +1,5 @@
|
|||
defmodule PhilomenaWeb.Notification.CategoryView do
|
||||
use PhilomenaWeb, :view
|
||||
|
||||
defdelegate name_of_type(type), to: PhilomenaWeb.NotificationView
|
||||
end
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue