Merge branch 'develop' into oembed_provider

This commit is contained in:
raeno 2018-12-14 20:28:35 +01:00
commit 90b00701ff
59 changed files with 1584 additions and 138 deletions

View File

@ -30,6 +30,31 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used.
## Pleroma.Mailer
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
An example for Sendgrid adapter:
```
config :pleroma, Pleroma.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: "YOUR_API_KEY"
```
An example for SMTP adapter:
```
config :pleroma, Pleroma.Mailer,
adapter: Swoosh.Adapters.SMTP,
relay: "smtp.gmail.com",
username: "YOUR_USERNAME@gmail.com",
password: "YOUR_SMTP_PASSWORD",
port: 465,
ssl: true,
tls: :always,
auth: :always
```
## :uri_schemes
* `valid_schemes`: List of the scheme part that is considered valid to be an URL
@ -129,3 +154,11 @@ An example:
config :pleroma, :mrf_user_allowlist,
"example.org": ["https://example.org/users/admin"]
```
## :web_push_encryption, :vapid_details
Web Push Notifications configuration. You can use the mix task `mix web_push.gen.keypair` to generate it.
* ``subject``: a mailto link for the administrative contact. Its best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise cant respond, someone else on the list can.
* ``public_key``: VAPID public key
* ``private_key``: VAPID private key

View File

@ -17,6 +17,8 @@
check_origin: false,
watchers: []
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local
# ## SSL Support
#
# In order to use HTTPS in development, a self-signed

View File

@ -11,6 +11,8 @@
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Test
# Configure your database
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,

View File

@ -3,6 +3,14 @@ defmodule Pleroma.Activity do
alias Pleroma.{Repo, Activity, Notification}
import Ecto.Query
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@mastodon_notification_types %{
"Create" => "mention",
"Follow" => "follow",
"Announce" => "reblog",
"Like" => "favourite"
}
schema "activities" do
field(:data, :map)
field(:local, :boolean, default: true)
@ -88,4 +96,11 @@ def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_i
end
def get_in_reply_to_activity(_), do: nil
for {ap_type, type} <- @mastodon_notification_types do
def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
do: unquote(type)
end
def mastodon_notification_type(%Activity{}), do: nil
end

View File

@ -0,0 +1,3 @@
defmodule Pleroma.Mailer do
use Swoosh.Mailer, otp_app: :pleroma
end

View File

@ -0,0 +1,40 @@
defmodule Pleroma.UserEmail do
@moduledoc "User emails"
import Swoosh.Email
alias Pleroma.Web.{Endpoint, Router}
defp instance_config, do: Pleroma.Config.get(:instance)
defp instance_name, do: instance_config()[:name]
defp sender do
{instance_name(), instance_config()[:email]}
end
defp recipient(email, nil), do: email
defp recipient(email, name), do: {name, email}
def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
password_reset_url =
Router.Helpers.util_url(
Endpoint,
:show_password_reset,
password_reset_token
)
html_body = """
<h3>Reset your password at #{instance_name()}</h3>
<p>Someone has requested password change for your account at #{instance_name()}.</p>
<p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
"""
new()
|> to(recipient(user.email, user.name))
|> from(sender())
|> subject("Password reset")
|> html_body(html_body)
end
end

View File

@ -15,10 +15,10 @@ def init(options), do: options
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(conn, _) do
with {:ok, token} <- fetch_token(conn),
{:ok, user} <- fetch_user(token) do
with {:ok, token_str} <- fetch_token_str(conn),
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
conn
|> assign(:token, token)
|> assign(:token, token_record)
|> assign(:user, user)
else
_ -> conn
@ -27,12 +27,12 @@ def call(conn, _) do
# Gets user by token
#
@spec fetch_user(String.t()) :: {:ok, User.t()} | nil
defp fetch_user(token) do
@spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil
defp fetch_user_and_token(token) do
query = from(q in Token, where: q.token == ^token, preload: [:user])
with %Token{user: %{info: %{deactivated: false} = _} = user} <- Repo.one(query) do
{:ok, user}
with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do
{:ok, user, token_record}
end
end
@ -48,23 +48,23 @@ defp fetch_token_from_session(conn) do
# Gets token from headers
#
@spec fetch_token(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token(%Plug.Conn{} = conn) do
@spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token_str(%Plug.Conn{} = conn) do
headers = get_req_header(conn, "authorization")
with :no_token_found <- fetch_token(headers),
with :no_token_found <- fetch_token_str(headers),
do: fetch_token_from_session(conn)
end
@spec fetch_token(Keyword.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token([]), do: :no_token_found
@spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token_str([]), do: :no_token_found
defp fetch_token([token | tail]) do
defp fetch_token_str([token | tail]) do
trimmed_token = String.trim(token)
case Regex.run(@realm_reg, trimmed_token) do
[_, match] -> {:ok, String.trim(match)}
_ -> fetch_token(tail)
_ -> fetch_token_str(tail)
end
end
end

View File

@ -1,6 +1,7 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.{User, Repo, Activity, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Formatter
import Pleroma.Web.CommonAPI.Utils
@ -16,7 +17,8 @@ def delete(activity_id, user) do
def repeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity.data["object"]["id"]) do
object <- Object.normalize(activity.data["object"]["id"]),
nil <- Utils.get_existing_announce(user.ap_id, object) do
ActivityPub.announce(user, object)
else
_ ->
@ -36,7 +38,8 @@ def unrepeat(id_or_ap_id, user) do
def favorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity.data["object"]["id"]) do
object <- Object.normalize(activity.data["object"]["id"]),
nil <- Utils.get_existing_like(user.ap_id, object) do
ActivityPub.like(user, object)
else
_ ->

View File

@ -1055,52 +1055,37 @@ def empty_object(conn, _) do
def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
actor = User.get_cached_by_ap_id(activity.data["actor"])
parent_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
mastodon_type = Activity.mastodon_notification_type(activity)
created_at =
NaiveDateTime.to_iso8601(created_at)
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
response = %{
id: to_string(id),
type: mastodon_type,
created_at: CommonAPI.Utils.to_masto_date(created_at),
account: AccountView.render("account.json", %{user: actor, for: user})
}
id = id |> to_string
case activity.data["type"] do
"Create" ->
%{
id: id,
type: "mention",
created_at: created_at,
account: AccountView.render("account.json", %{user: actor, for: user}),
case mastodon_type do
"mention" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: activity, for: user})
}
})
"Like" ->
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
"favourite" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
})
%{
id: id,
type: "favourite",
created_at: created_at,
account: AccountView.render("account.json", %{user: actor, for: user}),
status: StatusView.render("status.json", %{activity: liked_activity, for: user})
}
"reblog" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
})
"Announce" ->
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
%{
id: id,
type: "reblog",
created_at: created_at,
account: AccountView.render("account.json", %{user: actor, for: user}),
status: StatusView.render("status.json", %{activity: announced_activity, for: user})
}
"Follow" ->
%{
id: id,
type: "follow",
created_at: created_at,
account: AccountView.render("account.json", %{user: actor, for: user})
}
"follow" ->
response
_ ->
nil
@ -1167,6 +1152,7 @@ def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
end
def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
true = Pleroma.Web.Push.enabled()
Pleroma.Web.Push.Subscription.delete_if_exists(user, token)
{:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
@ -1174,6 +1160,7 @@ def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, par
end
def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
true = Pleroma.Web.Push.enabled()
subscription = Pleroma.Web.Push.Subscription.get(user, token)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view)
@ -1183,12 +1170,14 @@ def update_push_subscription(
%{assigns: %{user: user, token: token}} = conn,
params
) do
true = Pleroma.Web.Push.enabled()
{:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view)
end
def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
true = Pleroma.Web.Push.enabled()
{:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token)
json(conn, %{})
end

View File

@ -5,7 +5,12 @@ def render("push_subscription.json", %{subscription: subscription}) do
%{
id: to_string(subscription.id),
endpoint: subscription.endpoint,
alerts: Map.get(subscription.data, "alerts")
alerts: Map.get(subscription.data, "alerts"),
server_key: server_key()
}
end
defp server_key do
Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key)
end
end

View File

@ -9,67 +9,99 @@ defmodule Pleroma.Web.Push do
@types ["Create", "Follow", "Announce", "Like"]
@gcm_api_key nil
def start_link() do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
case Application.get_env(:web_push_encryption, :vapid_details) do
nil ->
Logger.warn(
"VAPID key pair is not found. Please, add VAPID configuration to config. Run `mix web_push.gen.keypair` mix task to create a key pair"
)
def vapid_config() do
Application.get_env(:web_push_encryption, :vapid_details, [])
end
:ignore
_ ->
{:ok, %{}}
def enabled() do
case vapid_config() do
[] -> false
list when is_list(list) -> true
_ -> false
end
end
def send(notification) do
if Application.get_env(:web_push_encryption, :vapid_details) do
if enabled() do
GenServer.cast(Pleroma.Web.Push, {:send, notification})
end
end
def init(:ok) do
if !enabled() do
Logger.warn("""
VAPID key pair is not found. If you wish to enabled web push, please run
mix web_push.gen.keypair
and add the resulting output to your configuration file.
""")
:ignore
else
{:ok, nil}
end
end
def handle_cast(
{:send, %{activity: %{data: %{"type" => type}}, user_id: user_id} = notification},
state
)
when type in @types do
actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
body = notification |> format(actor) |> Jason.encode!()
type = Pleroma.Activity.mastodon_notification_type(notification.activity)
Subscription
|> where(user_id: ^user_id)
|> preload(:token)
|> Repo.all()
|> Enum.each(fn record ->
subscription = %{
|> Enum.filter(fn subscription ->
get_in(subscription.data, ["alerts", type]) || false
end)
|> Enum.each(fn subscription ->
sub = %{
keys: %{
p256dh: record.key_p256dh,
auth: record.key_auth
p256dh: subscription.key_p256dh,
auth: subscription.key_auth
},
endpoint: record.endpoint
endpoint: subscription.endpoint
}
case WebPushEncryption.send_web_push(body, subscription, @gcm_api_key) do
body =
Jason.encode!(%{
title: format_title(notification),
access_token: subscription.token.token,
body: format_body(notification, actor),
notification_id: notification.id,
notification_type: type,
icon: User.avatar_url(actor),
preferred_locale: "en"
})
case WebPushEncryption.send_web_push(
body,
sub,
Application.get_env(:web_push_encryption, :gcm_api_key)
) do
{:ok, %{status_code: code}} when 400 <= code and code < 500 ->
Logger.debug("Removing subscription record")
Repo.delete!(record)
Repo.delete!(subscription)
:ok
{:ok, %{status_code: code}} when 200 <= code and code < 300 ->
:ok
{:ok, %{status_code: code}} ->
Logger.error("Web Push Nonification failed with code: #{code}")
Logger.error("Web Push Notification failed with code: #{code}")
:error
_ ->
Logger.error("Web Push Nonification failed with unknown error")
Logger.error("Web Push Notification failed with unknown error")
:error
end
end)
@ -82,35 +114,21 @@ def handle_cast({:send, _}, state) do
{:noreply, state}
end
def format(%{activity: %{data: %{"type" => "Create"}}}, actor) do
%{
title: "New Mention",
body: "@#{actor.nickname} has mentiond you",
icon: User.avatar_url(actor)
}
defp format_title(%{activity: %{data: %{"type" => type}}}) do
case type do
"Create" -> "New Mention"
"Follow" -> "New Follower"
"Announce" -> "New Repeat"
"Like" -> "New Favorite"
end
end
def format(%{activity: %{data: %{"type" => "Follow"}}}, actor) do
%{
title: "New Follower",
body: "@#{actor.nickname} has followed you",
icon: User.avatar_url(actor)
}
end
def format(%{activity: %{data: %{"type" => "Announce"}}}, actor) do
%{
title: "New Announce",
body: "@#{actor.nickname} has announced your post",
icon: User.avatar_url(actor)
}
end
def format(%{activity: %{data: %{"type" => "Like"}}}, actor) do
%{
title: "New Like",
body: "@#{actor.nickname} has liked your post",
icon: User.avatar_url(actor)
}
defp format_body(%{activity: %{data: %{"type" => type}}}, actor) do
case type do
"Create" -> "@#{actor.nickname} has mentioned you"
"Follow" -> "@#{actor.nickname} has followed you"
"Announce" -> "@#{actor.nickname} has repeated your post"
"Like" -> "@#{actor.nickname} has favorited your post"
end
end
end

View File

@ -37,8 +37,8 @@ def create(
user_id: user.id,
token_id: token.id,
endpoint: endpoint,
key_auth: key_auth,
key_p256dh: key_p256dh,
key_auth: ensure_base64_urlsafe(key_auth),
key_p256dh: ensure_base64_urlsafe(key_p256dh),
data: alerts(params)
})
end
@ -63,4 +63,14 @@ def delete_if_exists(user, token) do
sub -> Repo.delete(sub)
end
end
# Some webpush clients (e.g. iOS Toot!) use an non urlsafe base64 as an encoding for the key.
# However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library we use
# requires the key to be properly encoded. So we just convert base64 to urlsafe base64.
defp ensure_base64_urlsafe(string) do
string
|> String.replace("+", "-")
|> String.replace("/", "_")
|> String.replace("=", "")
end
end

View File

@ -85,6 +85,15 @@ defmodule Pleroma.Web.Router do
plug(:accepts, ["html", "json"])
end
pipeline :mailbox_preview do
plug(:accepts, ["html"])
plug(:put_secure_browser_headers, %{
"content-security-policy" =>
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'"
})
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_api)
get("/password_reset/:token", UtilController, :show_password_reset)
@ -268,6 +277,7 @@ defmodule Pleroma.Web.Router do
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
post("/account/register", TwitterAPI.Controller, :register)
post("/account/password_reset", TwitterAPI.Controller, :password_reset)
get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
@ -434,6 +444,14 @@ defmodule Pleroma.Web.Router do
get("/:sig/:url/:filename", MediaProxyController, :remote)
end
if Mix.env() == :dev do
scope "/dev" do
pipe_through([:mailbox_preview])
forward("/mailbox", Plug.Swoosh.MailboxPreview, base_path: "/dev/mailbox")
end
end
scope "/", Fallback do
get("/registration/:token", RedirectController, :registration_page)
get("/*path", RedirectController, :redirector)

View File

@ -1 +1,2 @@
<h2>Password reset failed</h2>
<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>

View File

@ -1 +1,2 @@
<h2>Password changed!</h2>
<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>

View File

@ -156,14 +156,21 @@ def config(conn, _params) do
|> send_resp(200, response)
_ ->
vapid_public_key =
Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key)
vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
uploadlimit = %{
uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
}
data = %{
name: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(instance, :limit)),
uploadlimit: uploadlimit,
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
vapidPublicKey: vapid_public_key

View File

@ -167,6 +167,25 @@ def register_user(params) do
end
end
def password_reset(nickname_or_email) do
with true <- is_binary(nickname_or_email),
%User{local: true} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
user
|> Pleroma.UserEmail.password_reset_email(token_record.token)
|> Pleroma.Mailer.deliver()
else
false ->
{:error, "bad user identifier"}
%User{local: false} ->
{:error, "remote user"}
nil ->
{:error, "unknown user"}
end
end
def get_by_id_or_nickname(id_or_nickname) do
if !is_integer(id_or_nickname) && :error == Integer.parse(id_or_nickname) do
Repo.get_by(User, nickname: id_or_nickname)

View File

@ -1,5 +1,8 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView}
alias Pleroma.Web.CommonAPI
alias Pleroma.{Repo, Activity, Object, User, Notification}
@ -322,6 +325,14 @@ def register(conn, params) do
end
end
def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
json_response(conn, :no_content, "")
end
end
def update_avatar(%{assigns: %{user: user}} = conn, params) do
{:ok, object} = ActivityPub.upload(params, type: :avatar)
change = Changeset.change(user, %{avatar: object.data})

View File

@ -190,6 +190,11 @@ def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity}
text = "#{user.nickname} favorited a status."
favorited_status =
if liked_activity,
do: render("activity.json", Map.merge(opts, %{activity: liked_activity})),
else: nil
%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
@ -199,6 +204,7 @@ def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity}
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=Favourite",
"created_at" => created_at,
"favorited_status" => favorited_status,
"in_reply_to_status_id" => liked_activity_id,
"external_url" => activity.data["id"],
"activity_type" => "like"

View File

@ -75,7 +75,9 @@ defp deps do
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
{:cors_plug, "~> 1.5"},
{:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"}
{:web_push_encryption, "~> 0.2.1"},
{:swoosh, "~> 0.20"},
{:gen_smtp, "~> 0.13"}
]
end

View File

@ -20,6 +20,7 @@
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
@ -49,6 +50,7 @@
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.ef3dfe574a06c79d5c5ca88943ef4673.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f60b0c5412942809e9e3.js></script><script type=text/javascript src=/static/js/vendor.3be52ab73ddf60b35112.js></script><script type=text/javascript src=/static/js/app.c10f63ec367fc4f92eea.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.ea00efb3229c8591fcc5249f0241b986.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.e076977b8e6c6844fb00.js></script><script type=text/javascript src=/static/js/vendor.cc4190750f6ed4d697bd.js></script><script type=text/javascript src=/static/js/app.67f548ecb9e9fd6b25b0.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
@ -51,7 +50,7 @@
{
"uid": "09feb4465d9bd1364f4e301c9ddbaa92",
"css": "retweet",
"code": 59396,
"code": 59398,
"src": "fontawesome"
},
{
@ -66,12 +65,6 @@
"code": 61925,
"src": "fontawesome"
},
{
"uid": "1a5cfa186647e8c929c2b17b9fc4dac1",
"css": "plus-squared",
"code": 59398,
"src": "font-awesome"
},
{
"uid": "e99461abfef3923546da8d745372c995",
"css": "cog",
@ -191,6 +184,36 @@
"css": "brush",
"code": 59411,
"src": "iconic"
},
{
"uid": "ca90da02d2c6a3183f2458e4dc416285",
"css": "adjust",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "5e2ab018e3044337bcef5f7e94098ea1",
"css": "thumbs-up-alt",
"code": 61796,
"src": "fontawesome"
},
{
"uid": "c76b7947c957c9b78b11741173c8349b",
"css": "attention",
"code": 59412,
"src": "fontawesome"
},
{
"uid": "1a5cfa186647e8c929c2b17b9fc4dac1",
"css": "plus-squared",
"code": 61694,
"src": "fontawesome"
},
{
"uid": "44e04715aecbca7f266a17d5a7863c68",
"css": "plus",
"code": 59413,
"src": "fontawesome"
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -229,11 +229,11 @@ body {
}
@font-face {
font-family: 'fontello';
src: url('./font/fontello.eot?32487936');
src: url('./font/fontello.eot?32487936#iefix') format('embedded-opentype'),
url('./font/fontello.woff?32487936') format('woff'),
url('./font/fontello.ttf?32487936') format('truetype'),
url('./font/fontello.svg?32487936#fontello') format('svg');
src: url('./font/fontello.eot?15755415');
src: url('./font/fontello.eot?15755415#iefix') format('embedded-opentype'),
url('./font/fontello.woff?15755415') format('woff'),
url('./font/fontello.ttf?15755415') format('truetype'),
url('./font/fontello.svg?15755415#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@ -304,9 +304,9 @@ body {
<div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-star-empty">&#xe803;</i> <span class="i-name">icon-star-empty</span><span class="i-code">0xe803</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-retweet">&#xe804;</i> <span class="i-name">icon-retweet</span><span class="i-code">0xe804</span></div>
<div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-adjust">&#xe804;</i> <span class="i-name">icon-adjust</span><span class="i-code">0xe804</span></div>
<div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-eye-off">&#xe805;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe805</span></div>
<div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-plus-squared">&#xe806;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xe806</span></div>
<div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-retweet">&#xe806;</i> <span class="i-name">icon-retweet</span><span class="i-code">0xe806</span></div>
<div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-cog">&#xe807;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe807</span></div>
</div>
<div class="row">
@ -328,19 +328,25 @@ body {
<div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush">&#xe813;</i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xe814"><i class="demo-icon icon-attention">&#xe814;</i> <span class="i-name">icon-attention</span><span class="i-code">0xe814</span></div>
<div class="the-icons span3" title="Code: 0xe815"><i class="demo-icon icon-plus">&#xe815;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe815</span></div>
<div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
<div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
<div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div>
<div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
<div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
<div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
<div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div>
<div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
<div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt">&#xf164;</i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
<div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
<div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div>
</div>

View File

@ -14,11 +14,11 @@
<glyph glyph-name="star-empty" unicode="&#xe803;" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
<glyph glyph-name="retweet" unicode="&#xe804;" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
<glyph glyph-name="adjust" unicode="&#xe804;" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="eye-off" unicode="&#xe805;" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
<glyph glyph-name="plus-squared" unicode="&#xe806;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
<glyph glyph-name="retweet" unicode="&#xe806;" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
<glyph glyph-name="cog" unicode="&#xe807;" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
@ -46,6 +46,10 @@
<glyph glyph-name="brush" unicode="&#xe813;" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
<glyph glyph-name="attention" unicode="&#xe814;" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
<glyph glyph-name="plus" unicode="&#xe815;" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
<glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
<glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
@ -60,10 +64,14 @@
<glyph glyph-name="comment-empty" unicode="&#xf0e5;" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
<glyph glyph-name="plus-squared" unicode="&#xf0fe;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
<glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
<glyph glyph-name="lock-open-alt" unicode="&#xf13e;" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
<glyph glyph-name="thumbs-up-alt" unicode="&#xf164;" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
<glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="user-plus" unicode="&#xf234;" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,5 +5,11 @@
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
"mammal": [ "Mammal", "#272c37", "#444b5d", "#f8f8f8", "#9bacc8", "#7f3142", "#2bd850", "#2b90d9", "#ca8f04" ]
"mammal": [ "Mammal", "#272c37", "#444b5d", "#f8f8f8", "#9bacc8", "#7f3142", "#2bd850", "#2b90d9", "#ca8f04" ],
"redmond-xx": "/static/themes/redmond-xx.json",
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
"redmond-xxi": "/static/themes/redmond-xxi.json",
"breezy-dark": "/static/themes/breezy-dark.json",
"breezy-light": "/static/themes/breezy-light.json"
}

View File

@ -0,0 +1,139 @@
{
"_pleroma_theme_version": 2,
"name": "Breezy Dark (beta)",
"theme": {
"shadows": {
"panel": [
{
"x": "1",
"y": "2",
"blur": "6",
"spread": 0,
"color": "#000000",
"alpha": 0.6
}
],
"button": [
{
"x": 0,
"y": "0",
"blur": "0",
"spread": "1",
"color": "#ffffff",
"alpha": "0.15",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "1",
"spread": 0,
"color": "#000000",
"alpha": "0.3",
"inset": false
}
],
"panelHeader": [
{
"x": 0,
"y": "40",
"blur": "40",
"spread": "-40",
"inset": true,
"color": "#ffffff",
"alpha": "0.1"
}
],
"buttonHover": [
{
"x": 0,
"y": "0",
"blur": 0,
"spread": "1",
"color": "--link",
"alpha": "0.3",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "1",
"spread": 0,
"color": "#000000",
"alpha": "0.3",
"inset": false
}
],
"buttonPressed": [
{
"x": 0,
"y": 0,
"blur": "0",
"spread": "50",
"color": "--faint",
"alpha": 1,
"inset": true
},
{
"x": 0,
"y": "0",
"blur": 0,
"spread": "1",
"color": "#ffffff",
"alpha": 0.2,
"inset": true
},
{
"x": "1",
"y": "1",
"blur": 0,
"spread": 0,
"color": "#000000",
"alpha": "0.3",
"inset": false
}
],
"input": [
{
"x": 0,
"y": "0",
"blur": 0,
"spread": "1",
"color": "#FFFFFF",
"alpha": "0.2",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"panel": "0"
},
"colors": {
"bg": "#31363b",
"text": "#eff0f1",
"link": "#3daee9",
"fg": "#31363b",
"panel": "#31363b",
"input": "#232629",
"topBarLink": "#eff0f1",
"btn": "#31363b",
"border": "#4c545b",
"cRed": "#da4453",
"cBlue": "#3daee9",
"cGreen": "#27ae60",
"cOrange": "#f67400"
},
"radii": {
"btn": "2",
"input": "2",
"checkbox": "1",
"panel": "2",
"avatar": "2",
"avatarAlt": "2",
"tooltip": "2",
"attachment": "2"
}
}
}

View File

@ -0,0 +1,139 @@
{
"_pleroma_theme_version": 2,
"name": "Breezy Light (beta)",
"theme": {
"shadows": {
"panel": [
{
"x": "1",
"y": "2",
"blur": "6",
"spread": 0,
"color": "#000000",
"alpha": 0.6
}
],
"button": [
{
"x": 0,
"y": "0",
"blur": "0",
"spread": "1",
"color": "#000000",
"alpha": "0.3",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "1",
"spread": 0,
"color": "#000000",
"alpha": "0.3",
"inset": false
}
],
"panelHeader": [
{
"x": 0,
"y": "40",
"blur": "40",
"spread": "-40",
"inset": true,
"color": "#ffffff",
"alpha": "0.1"
}
],
"buttonHover": [
{
"x": 0,
"y": "0",
"blur": 0,
"spread": "1",
"color": "--link",
"alpha": "0.3",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "1",
"spread": 0,
"color": "#000000",
"alpha": "0.3",
"inset": false
}
],
"buttonPressed": [
{
"x": 0,
"y": 0,
"blur": "0",
"spread": "50",
"color": "--faint",
"alpha": 1,
"inset": true
},
{
"x": 0,
"y": "0",
"blur": 0,
"spread": "1",
"color": "#ffffff",
"alpha": 0.2,
"inset": true
},
{
"x": "1",
"y": "1",
"blur": 0,
"spread": 0,
"color": "#000000",
"alpha": "0.3",
"inset": false
}
],
"input": [
{
"x": 0,
"y": "0",
"blur": 0,
"spread": "1",
"color": "#000000",
"alpha": "0.2",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1"
},
"colors": {
"bg": "#eff0f1",
"text": "#232627",
"link": "#2980b9",
"fg": "#bcc2c7",
"panel": "#475057",
"panelText": "#fcfcfc",
"input": "#fcfcfc",
"topBar": "#475057",
"topBarLink": "#eff0f1",
"btn": "#eff0f1",
"cRed": "#da4453",
"cBlue": "#2980b9",
"cGreen": "#27ae60",
"cOrange": "#f67400"
},
"radii": {
"btn": "2",
"input": "2",
"checkbox": "1",
"panel": "2",
"avatar": "2",
"avatarAlt": "2",
"tooltip": "2",
"attachment": "2"
}
}
}

View File

@ -0,0 +1,297 @@
{
"_pleroma_theme_version": 2,
"name": "Redmond XX SE",
"theme": {
"shadows": {
"panel": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"panelHeader": [
{
"x": 0,
"y": 0,
"blur": 0,
"spread": "3",
"inset": true,
"color": "#c0c0c0",
"alpha": 1
},
{
"x": "-2200",
"y": 0,
"blur": "200",
"spread": "-2000",
"inset": true,
"color": "#1084d0",
"alpha": 1
}
],
"button": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonHover": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonPressed": [
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"input": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--input",
"alpha": "1",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"faint": "1"
},
"colors": {
"bg": "#c0c0c0",
"text": "#000000",
"link": "#0000ff",
"fg": "#c0c0c0",
"panel": "#000080",
"panelFaint": "#c0c0c0",
"input": "#ffffff",
"topBar": "#000080",
"topBarLink": "#ffffff",
"btn": "#c0c0c0",
"faint": "#3f3f3f",
"faintLink": "#404080",
"border": "#808080",
"cRed": "#FF0000",
"cBlue": "#008080",
"cGreen": "#008000",
"cOrange": "#808000"
},
"radii": {
"btn": "0",
"input": "0",
"checkbox": "0",
"panel": "0",
"avatar": "0",
"avatarAlt": "0",
"tooltip": "0",
"attachment": "0"
}
}
}

View File

@ -0,0 +1,288 @@
{
"_pleroma_theme_version": 2,
"name": "Redmond XX",
"theme": {
"shadows": {
"panel": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"panelHeader": [
{
"x": 0,
"y": 0,
"blur": 0,
"spread": "3",
"inset": true,
"color": "#c0c0c0",
"alpha": 1
}
],
"button": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonHover": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonPressed": [
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"input": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--input",
"alpha": "1",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"faint": "1"
},
"colors": {
"bg": "#c0c0c0",
"text": "#000000",
"link": "#0000ff",
"fg": "#c0c0c0",
"panel": "#000080",
"panelFaint": "#c0c0c0",
"input": "#ffffff",
"topBar": "#000080",
"topBarLink": "#ffffff",
"btn": "#c0c0c0",
"faint": "#3f3f3f",
"faintLink": "#404080",
"border": "#808080",
"cRed": "#FF0000",
"cBlue": "#008080",
"cGreen": "#008000",
"cOrange": "#808000"
},
"radii": {
"btn": "0",
"input": "0",
"checkbox": "0",
"panel": "0",
"avatar": "0",
"avatarAlt": "0",
"tooltip": "0",
"attachment": "0"
}
}
}

View File

@ -0,0 +1,270 @@
{
"_pleroma_theme_version": 2,
"name": "Redmond XXI",
"theme": {
"shadows": {
"panel": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"panelHeader": [
{
"x": 0,
"y": 0,
"blur": 0,
"spread": "3",
"inset": true,
"color": "#d6d6ce",
"alpha": 1
},
{
"x": "-2200",
"y": 0,
"blur": "200",
"spread": "-2000",
"inset": true,
"color": "#a5cef7",
"alpha": 1
}
],
"button": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonHover": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonPressed": [
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"input": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#d4d0c8",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--input",
"alpha": "1",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"faint": "1"
},
"colors": {
"bg": "#d6d6ce",
"text": "#000000",
"link": "#0000ff",
"fg": "#d6d6ce",
"panel": "#042967",
"panelFaint": "#FFFFFF",
"input": "#ffffff",
"topBar": "#042967",
"topBarLink": "#ffffff",
"btn": "#d6d6ce",
"faint": "#3f3f3f",
"faintLink": "#404080",
"border": "#808080",
"cRed": "#c42726",
"cBlue": "#6699cc",
"cGreen": "#669966",
"cOrange": "#cc6633"
},
"radii": {
"btn": "0",
"input": "0",
"checkbox": "0",
"panel": "0",
"avatar": "0",
"avatarAlt": "0",
"tooltip": "0",
"attachment": "0"
}
}
}

View File

@ -1 +0,0 @@
assets/sw.js

BIN
priv/static/sw.js Normal file

Binary file not shown.

BIN
priv/static/sw.js.map Normal file

Binary file not shown.

View File

@ -2,6 +2,7 @@ defmodule Pleroma.Web.CommonAPI.Test do
use Pleroma.DataCase
alias Pleroma.Web.CommonAPI
alias Pleroma.User
alias Pleroma.Activity
import Pleroma.Factory
@ -53,4 +54,42 @@ test "it filters out obviously bad tags when accepting a post as Markdown" do
assert content == "<p><b>2hu</b></p>alert('xss')"
end
end
describe "reactions" do
test "repeating a status" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
end
test "favoriting a status" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
end
test "retweeting a status twice returns an error" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
{:error, _} = CommonAPI.repeat(activity.id, user)
end
test "favoriting a status twice returns an error" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user)
{:error, _} = CommonAPI.favorite(activity.id, user)
end
end
end

View File

@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Comeonin.Pbkdf2
alias Ecto.Changeset
import Pleroma.Factory
@ -270,7 +271,7 @@ test "with credentials", %{conn: conn, user: current_user} do
since_id = List.last(activities).id
current_user =
Ecto.Changeset.change(current_user, following: [User.ap_followers(user)])
Changeset.change(current_user, following: [User.ap_followers(user)])
|> Repo.update!()
conn =
@ -832,6 +833,46 @@ test "it returns errors on a problem", %{conn: conn} do
end
end
describe "POST /api/account/password_reset, with valid parameters" do
setup %{conn: conn} do
user = insert(:user)
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
%{conn: conn, user: user}
end
test "it returns 204", %{conn: conn} do
assert json_response(conn, :no_content)
end
test "it creates a PasswordResetToken record for user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
assert token_record
end
test "it sends an email to user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
Swoosh.TestAssertions.assert_email_sent(
Pleroma.UserEmail.password_reset_email(user, token_record.token)
)
end
end
describe "POST /api/account/password_reset, with invalid parameters" do
setup [:valid_user]
test "it returns 500 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
assert json_response(conn, :internal_server_error)
end
test "it returns 500 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Changeset.change(user, local: false))
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
assert json_response(conn, :internal_server_error)
end
end
describe "GET /api/externalprofile/show" do
test "it returns the user", %{conn: conn} do
user = insert(:user)

View File

@ -112,6 +112,7 @@ test "a like activity" do
{:ok, like, _object} = CommonAPI.favorite(activity.id, other_user)
result = ActivityView.render("activity.json", activity: like)
activity = Pleroma.Activity.get_by_ap_id(activity.data["id"])
expected = %{
"activity_type" => "like",
@ -121,6 +122,7 @@ test "a like activity" do
"in_reply_to_status_id" => activity.id,
"is_local" => true,
"is_post_verb" => false,
"favorited_status" => ActivityView.render("activity.json", activity: activity),
"statusnet_html" => "shp favorited a status.",
"text" => "shp favorited a status.",
"uri" => "tag:#{like.data["id"]}:objectType=Favourite",
@ -148,6 +150,7 @@ test "a like activity for deleted post" do
"in_reply_to_status_id" => nil,
"is_local" => true,
"is_post_verb" => false,
"favorited_status" => nil,
"statusnet_html" => "shp favorited a status.",
"text" => "shp favorited a status.",
"uri" => "tag:#{like.data["id"]}:objectType=Favourite",