forked from AkkomaGang/akkoma
Merge branch 'develop' into issue/1790-updated-oban
This commit is contained in:
commit
3c0c1fd2ef
220 changed files with 3619 additions and 1855 deletions
|
@ -63,21 +63,19 @@ unit-testing:
|
|||
- mix ecto.migrate
|
||||
- mix coveralls --preload-modules
|
||||
|
||||
# Removed to fix CI issue. In this early state it wasn't adding much value anyway.
|
||||
# TODO Fix and reinstate federated testing
|
||||
# federated-testing:
|
||||
# stage: test
|
||||
# cache: *testing_cache_policy
|
||||
# services:
|
||||
# - name: minibikini/postgres-with-rum:12
|
||||
# alias: postgres
|
||||
# command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
# script:
|
||||
# - mix deps.get
|
||||
# - mix ecto.create
|
||||
# - mix ecto.migrate
|
||||
# - epmd -daemon
|
||||
# - mix test --trace --only federated
|
||||
federated-testing:
|
||||
stage: test
|
||||
cache: *testing_cache_policy
|
||||
services:
|
||||
- name: minibikini/postgres-with-rum:12
|
||||
alias: postgres
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
script:
|
||||
- mix deps.get
|
||||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- epmd -daemon
|
||||
- mix test --trace --only federated
|
||||
|
||||
unit-testing-rum:
|
||||
stage: test
|
||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -16,10 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
- **Breaking:** Pleroma API: The routes to update avatar, banner and background have been removed.
|
||||
- **Breaking:** Image description length is limited now.
|
||||
- **Breaking:** Emoji API: changed methods and renamed routes.
|
||||
- MastodonAPI: Allow removal of avatar, banner and background.
|
||||
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
|
||||
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
|
||||
- Mastodon API: On deletion, returns the original post text.
|
||||
- Mastodon API: Add `pleroma.unread_count` to the Marker entity.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
@ -35,6 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Added
|
||||
|
||||
- Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation.
|
||||
- Chats: Added support for federated chats. For details, see the docs.
|
||||
- ActivityPub: Added support for existing AP ids for instances migrated from Mastodon.
|
||||
- Instance: Add `background_image` to configuration and `/api/v1/instance`
|
||||
|
@ -51,6 +56,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Added `:reject_deletes` group to SimplePolicy
|
||||
- MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
|
||||
- Support pagination in emoji packs API (for packs and for files in pack)
|
||||
- Support for viewing instances favicons next to posts and accounts
|
||||
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
@ -58,8 +64,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Mastodon API: Extended `/api/v1/instance`.
|
||||
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||
- Mastodon API: Add support for filtering replies in public and home timelines
|
||||
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`
|
||||
- Mastodon API: Add support for filtering replies in public and home timelines.
|
||||
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`.
|
||||
- Mastodon API: Support irreversible property for filters.
|
||||
- Mastodon API: Add pleroma.favicon field to accounts.
|
||||
- Admin API: endpoints for create/update/delete OAuth Apps.
|
||||
- Admin API: endpoint for status view.
|
||||
- OTP: Add command to reload emoji packs
|
||||
|
@ -73,6 +81,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Resolving Peertube accounts with Webfinger
|
||||
- `blob:` urls not being allowed by connect-src CSP
|
||||
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
|
||||
- Rich Media Previews for Twitter links
|
||||
- Admin API: fix `GET /api/pleroma/admin/users/:nickname/credentials` returning 404 when getting the credentials of a remote user while `:instance, :limit_to_local_content` is set to `:unauthenticated`
|
||||
- Fix CSP policy generation to include remote Captcha services
|
||||
|
||||
## [Unreleased (patch)]
|
||||
|
||||
|
@ -214,7 +225,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Mastodon API: `pleroma.thread_muted` to the Status entity
|
||||
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
||||
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
||||
- Mastodon API: Add `pleroma.unread_count` to the Marker entity
|
||||
- Admin API: Render whole status in grouped reports
|
||||
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
||||
|
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.LoadTesting.Activities do
|
|||
@visibility ~w(public private direct unlisted)
|
||||
@types [
|
||||
:simple,
|
||||
:simple_filtered,
|
||||
:emoji,
|
||||
:mentions,
|
||||
:hell_thread,
|
||||
|
@ -242,6 +243,15 @@ defp insert_activity(:simple, visibility, group, users, _opts) do
|
|||
insert_local_activity(visibility, group, users, "Simple status")
|
||||
end
|
||||
|
||||
defp insert_activity(:simple_filtered, visibility, group, users, _opts)
|
||||
when group in @remote_groups do
|
||||
insert_remote_activity(visibility, group, users, "Remote status which must be filtered")
|
||||
end
|
||||
|
||||
defp insert_activity(:simple_filtered, visibility, group, users, _opts) do
|
||||
insert_local_activity(visibility, group, users, "Simple status which must be filtered")
|
||||
end
|
||||
|
||||
defp insert_activity(:emoji, visibility, group, users, _opts)
|
||||
when group in @remote_groups do
|
||||
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")
|
||||
|
|
|
@ -32,10 +32,22 @@ defp fetch_user(user) do
|
|||
)
|
||||
end
|
||||
|
||||
defp create_filter(user) do
|
||||
Pleroma.Filter.create(%Pleroma.Filter{
|
||||
user_id: user.id,
|
||||
phrase: "must be filtered",
|
||||
hide: true
|
||||
})
|
||||
end
|
||||
|
||||
defp delete_filter(filter), do: Repo.delete(filter)
|
||||
|
||||
defp fetch_timelines(user) do
|
||||
fetch_home_timeline(user)
|
||||
fetch_home_timeline_with_filter(user)
|
||||
fetch_direct_timeline(user)
|
||||
fetch_public_timeline(user)
|
||||
fetch_public_timeline_with_filter(user)
|
||||
fetch_public_timeline(user, :with_blocks)
|
||||
fetch_public_timeline(user, :local)
|
||||
fetch_public_timeline(user, :tag)
|
||||
|
@ -61,7 +73,7 @@ defp opts_for_home_timeline(user) do
|
|||
}
|
||||
end
|
||||
|
||||
defp fetch_home_timeline(user) do
|
||||
defp fetch_home_timeline(user, title_end \\ "") do
|
||||
opts = opts_for_home_timeline(user)
|
||||
|
||||
recipients = [user.ap_id | User.following(user)]
|
||||
|
@ -84,9 +96,11 @@ defp fetch_home_timeline(user) do
|
|||
|> Enum.reverse()
|
||||
|> List.last()
|
||||
|
||||
title = "home timeline " <> title_end
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"home timeline" => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
|
||||
title => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
|
||||
},
|
||||
inputs: %{
|
||||
"1 page" => opts,
|
||||
|
@ -108,6 +122,14 @@ defp fetch_home_timeline(user) do
|
|||
)
|
||||
end
|
||||
|
||||
defp fetch_home_timeline_with_filter(user) do
|
||||
{:ok, filter} = create_filter(user)
|
||||
|
||||
fetch_home_timeline(user, "with filters")
|
||||
|
||||
delete_filter(filter)
|
||||
end
|
||||
|
||||
defp opts_for_direct_timeline(user) do
|
||||
%{
|
||||
visibility: "direct",
|
||||
|
@ -210,6 +232,14 @@ defp fetch_public_timeline(user) do
|
|||
fetch_public_timeline(opts, "public timeline")
|
||||
end
|
||||
|
||||
defp fetch_public_timeline_with_filter(user) do
|
||||
{:ok, filter} = create_filter(user)
|
||||
opts = opts_for_public_timeline(user)
|
||||
|
||||
fetch_public_timeline(opts, "public timeline with filters")
|
||||
delete_filter(filter)
|
||||
end
|
||||
|
||||
defp fetch_public_timeline(user, :local) do
|
||||
opts = opts_for_public_timeline(user, :local)
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"dat",
|
||||
"dweb",
|
||||
"gopher",
|
||||
"hyper",
|
||||
"ipfs",
|
||||
"ipns",
|
||||
"irc",
|
||||
|
@ -188,6 +189,7 @@
|
|||
background_image: "/images/city.jpg",
|
||||
instance_thumbnail: "/instance/thumbnail.jpeg",
|
||||
limit: 5_000,
|
||||
description_limit: 5_000,
|
||||
chat_limit: 5_000,
|
||||
remote_limit: 100_000,
|
||||
upload_limit: 16_000_000,
|
||||
|
@ -436,8 +438,7 @@
|
|||
|
||||
config :pleroma, Pleroma.Web.Preload,
|
||||
providers: [
|
||||
Pleroma.Web.Preload.Providers.Instance,
|
||||
Pleroma.Web.Preload.Providers.StatusNet
|
||||
Pleroma.Web.Preload.Providers.Instance
|
||||
]
|
||||
|
||||
config :pleroma, :http_security,
|
||||
|
@ -704,6 +705,8 @@
|
|||
|
||||
config :ex_aws, http_client: Pleroma.HTTP.ExAws
|
||||
|
||||
config :pleroma, :instances_favicons, enabled: false
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -498,6 +498,7 @@
|
|||
"dat",
|
||||
"dweb",
|
||||
"gopher",
|
||||
"hyper",
|
||||
"ipfs",
|
||||
"ipns",
|
||||
"irc",
|
||||
|
@ -699,8 +700,9 @@
|
|||
key: :public,
|
||||
type: :boolean,
|
||||
description:
|
||||
"Makes the client API in authentificated mode-only except for user-profiles." <>
|
||||
" Useful for disabling the Local Timeline and The Whole Known Network."
|
||||
"Makes the client API in authenticated mode-only except for user-profiles." <>
|
||||
" Useful for disabling the Local Timeline and The Whole Known Network. " <>
|
||||
" Note: when setting to `false`, please also check `:restrict_unauthenticated` setting."
|
||||
},
|
||||
%{
|
||||
key: :quarantined_instances,
|
||||
|
@ -3439,5 +3441,18 @@
|
|||
suggestions: [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :instances_favicons,
|
||||
type: :group,
|
||||
description: "Control favicons for instances",
|
||||
children: [
|
||||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "Allow/disallow displaying and getting instances favicons"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -111,6 +111,8 @@
|
|||
|
||||
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
|
||||
|
||||
config :pleroma, :instances_favicons, enabled: true
|
||||
|
||||
if File.exists?("./config/test.secret.exs") do
|
||||
import_config "test.secret.exs"
|
||||
else
|
||||
|
|
|
@ -71,6 +71,8 @@ Has these additional fields under the `pleroma` object:
|
|||
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
||||
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
||||
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
|
||||
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||
|
||||
### Source
|
||||
|
||||
|
@ -182,9 +184,12 @@ Additional parameters can be added to the JSON body/Form data:
|
|||
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
|
||||
- `skip_thread_containment` - if true, skip filtering out broken threads
|
||||
- `allow_following_move` - if true, allows automatically follow moved following accounts
|
||||
- `pleroma_background_image` - sets the background image of the user.
|
||||
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
|
||||
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
|
||||
- `actor_type` - the type of this account.
|
||||
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
||||
|
||||
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
||||
|
||||
### Pleroma Settings Store
|
||||
|
||||
|
@ -220,6 +225,8 @@ Has theses additional parameters (which are the same as in Pleroma-API):
|
|||
`GET /api/v1/instance` has additional fields
|
||||
|
||||
- `max_toot_chars`: The maximum characters per post
|
||||
- `chat_limit`: The maximum characters per chat message
|
||||
- `description_limit`: The maximum characters per image description
|
||||
- `poll_limits`: The limits of polls
|
||||
- `upload_limit`: The maximum upload file size
|
||||
- `avatar_upload_limit`: The same for avatars
|
||||
|
|
|
@ -57,11 +57,11 @@ mix pleroma.user invites
|
|||
|
||||
## Revoke invite
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user revoke_invite <token_or_id>
|
||||
./bin/pleroma_ctl user revoke_invite <token>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user revoke_invite <token_or_id>
|
||||
mix pleroma.user revoke_invite <token>
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Updating your instance
|
||||
|
||||
You should **always check the release notes/changelog** in case there are config deprecations, special update special update steps, etc.
|
||||
You should **always check the [release notes/changelog](https://git.pleroma.social/pleroma/pleroma/-/releases)** in case there are config deprecations, special update steps, etc.
|
||||
|
||||
Besides that, doing the following is generally enough:
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `notify_email`: Email used for notifications.
|
||||
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
|
||||
* `limit`: Posts character limit (CW/Subject included in the counter).
|
||||
* `discription_limit`: The character limit for image descriptions.
|
||||
* `chat_limit`: Character limit of the instance chat messages.
|
||||
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
|
||||
* `upload_limit`: File size limit of uploads (except for avatar, background, banner).
|
||||
|
@ -36,7 +37,7 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
||||
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. See also: `restrict_unauthenticated`.
|
||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
|
||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
||||
|
@ -154,7 +155,7 @@ config :pleroma, :mrf_user_allowlist, %{
|
|||
* `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines
|
||||
* `:reject` rejects the message entirely
|
||||
|
||||
#### mrf_steal_emoji
|
||||
#### :mrf_steal_emoji
|
||||
* `hosts`: List of hosts to steal emojis from
|
||||
* `rejected_shortcodes`: Regex-list of shortcodes to reject
|
||||
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
|
||||
|
@ -970,11 +971,11 @@ config :pleroma, :database_config_whitelist, [
|
|||
|
||||
### :restrict_unauthenticated
|
||||
|
||||
Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
||||
Restrict access for unauthenticated users to timelines (public and federated), user profiles and statuses.
|
||||
|
||||
* `timelines`: public and federated timelines
|
||||
* `local`: public timeline
|
||||
* `federated`
|
||||
* `federated`: federated timeline (includes public timeline)
|
||||
* `profiles`: user profiles
|
||||
* `local`
|
||||
* `remote`
|
||||
|
@ -982,7 +983,14 @@ Restrict access for unauthenticated users to timelines (public and federate), us
|
|||
* `local`
|
||||
* `remote`
|
||||
|
||||
Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline).
|
||||
|
||||
## Pleroma.Web.ApiSpec.CastAndValidate
|
||||
|
||||
* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`.
|
||||
|
||||
## :instances_favicons
|
||||
|
||||
Control favicons for instances.
|
||||
|
||||
* `enabled`: Allow/disallow displaying and getting instances favicons
|
||||
|
|
|
@ -3,15 +3,48 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Pleroma do
|
||||
@apps [
|
||||
:restarter,
|
||||
:ecto,
|
||||
:ecto_sql,
|
||||
:postgrex,
|
||||
:db_connection,
|
||||
:cachex,
|
||||
:flake_id,
|
||||
:swoosh,
|
||||
:timex
|
||||
]
|
||||
@cachex_children ["object", "user"]
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
if Pleroma.Config.get(:env) != :test do
|
||||
Application.put_env(:logger, :console, level: :debug)
|
||||
end
|
||||
|
||||
{:ok, _} = Application.ensure_all_started(:pleroma)
|
||||
apps =
|
||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
|
||||
[:gun | @apps]
|
||||
else
|
||||
[:hackney | @apps]
|
||||
end
|
||||
|
||||
Enum.each(apps, &Application.ensure_all_started/1)
|
||||
|
||||
children = [
|
||||
Pleroma.Repo,
|
||||
{Pleroma.Config.TransferTask, false},
|
||||
Pleroma.Web.Endpoint
|
||||
]
|
||||
|
||||
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
|
||||
|
||||
Supervisor.start_link(children ++ cachex_children,
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Supervisor
|
||||
)
|
||||
|
||||
if Pleroma.Config.get(:env) not in [:test, :benchmark] do
|
||||
pleroma_rebooted?()
|
||||
|
|
|
@ -145,7 +145,7 @@ def run(["gen" | rest]) do
|
|||
options,
|
||||
:uploads_dir,
|
||||
"What directory should media uploads go in (when using the local uploader)?",
|
||||
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||
Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||
)
|
||||
|> Path.expand()
|
||||
|
||||
|
@ -154,7 +154,7 @@ def run(["gen" | rest]) do
|
|||
options,
|
||||
:static_dir,
|
||||
"What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
|
||||
Pleroma.Config.get([:instance, :static_dir])
|
||||
Config.get([:instance, :static_dir])
|
||||
)
|
||||
|> Path.expand()
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ def run(["tag", nickname | tags]) do
|
|||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
user = user |> User.tag(tags)
|
||||
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
|
||||
else
|
||||
_ ->
|
||||
shell_error("Could not change user tags for #{nickname}")
|
||||
|
@ -245,7 +245,7 @@ def run(["untag", nickname | tags]) do
|
|||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
user = user |> User.untag(tags)
|
||||
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
|
||||
else
|
||||
_ ->
|
||||
shell_error("Could not change user tags for #{nickname}")
|
||||
|
|
|
@ -35,7 +35,7 @@ def user_agent do
|
|||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
def start(_type, _args) do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Config.Holder.save_default()
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Config.DeprecationWarnings.warn()
|
||||
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||
|
@ -162,7 +162,8 @@ defp idempotency_expiration,
|
|||
defp seconds_valid_interval,
|
||||
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
|
||||
|
||||
defp build_cachex(type, opts),
|
||||
@spec build_cachex(String.t(), keyword()) :: map()
|
||||
def build_cachex(type, opts),
|
||||
do: %{
|
||||
id: String.to_atom("cachex_" <> type),
|
||||
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
|
||||
|
|
|
@ -12,6 +12,11 @@ defmodule Pleroma.Config.Loader do
|
|||
:swarm
|
||||
]
|
||||
|
||||
@reject_groups [
|
||||
:postgrex,
|
||||
:tesla
|
||||
]
|
||||
|
||||
if Code.ensure_loaded?(Config.Reader) do
|
||||
@reader Config.Reader
|
||||
|
||||
|
@ -47,7 +52,8 @@ defp filter(configs) do
|
|||
@spec filter_group(atom(), keyword()) :: keyword()
|
||||
def filter_group(group, configs) do
|
||||
Enum.reject(configs[group], fn {key, _v} ->
|
||||
key in @reject_keys or (group == :phoenix and key == :serve_endpoints) or group == :postgrex
|
||||
key in @reject_keys or group in @reject_groups or
|
||||
(group == :phoenix and key == :serve_endpoints)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,8 +31,8 @@ defmodule Pleroma.Config.TransferTask do
|
|||
{:pleroma, :gopher, [:enabled]}
|
||||
]
|
||||
|
||||
def start_link(_) do
|
||||
load_and_update_env()
|
||||
def start_link(restart_pleroma? \\ true) do
|
||||
load_and_update_env([], restart_pleroma?)
|
||||
if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
||||
:ignore
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
||||
defp instance_config, do: Pleroma.Config.get(:instance)
|
||||
defp instance_config, do: Config.get(:instance)
|
||||
defp instance_name, do: instance_config()[:name]
|
||||
|
||||
defp instance_notify_email do
|
||||
|
@ -72,6 +72,8 @@ def report(to, reporter, account, statuses, comment) do
|
|||
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
|
||||
#{comment_html}
|
||||
#{statuses_html}
|
||||
<p>
|
||||
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
|
||||
"""
|
||||
|
||||
new()
|
||||
|
|
|
@ -108,7 +108,7 @@ defp load_pack(pack_dir, emoji_groups) do
|
|||
if File.exists?(emoji_txt) do
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Pleroma.Config.get([:emoji, :pack_extensions])
|
||||
extensions = Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
|
||||
|
|
|
@ -34,10 +34,18 @@ def get(id, %{id: user_id} = _user) do
|
|||
Repo.one(query)
|
||||
end
|
||||
|
||||
def get_filters(%User{id: user_id} = _user) do
|
||||
def get_active(query) do
|
||||
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
|
||||
end
|
||||
|
||||
def get_irreversible(query) do
|
||||
from(f in query, where: f.hide)
|
||||
end
|
||||
|
||||
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
|
||||
query =
|
||||
from(
|
||||
f in Pleroma.Filter,
|
||||
f in query,
|
||||
where: f.user_id == ^user_id,
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
|
@ -95,4 +103,34 @@ def update(%Pleroma.Filter{} = filter, params) do
|
|||
|> validate_required([:phrase, :context])
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def compose_regex(user_or_filters, format \\ :postgres)
|
||||
|
||||
def compose_regex(%User{} = user, format) do
|
||||
__MODULE__
|
||||
|> get_active()
|
||||
|> get_irreversible()
|
||||
|> get_filters(user)
|
||||
|> compose_regex(format)
|
||||
end
|
||||
|
||||
def compose_regex([_ | _] = filters, format) do
|
||||
phrases =
|
||||
filters
|
||||
|> Enum.map(& &1.phrase)
|
||||
|> Enum.join("|")
|
||||
|
||||
case format do
|
||||
:postgres ->
|
||||
"\\y(#{phrases})\\y"
|
||||
|
||||
:re ->
|
||||
~r/\b#{phrases}\b/i
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def compose_regex(_, _), do: nil
|
||||
end
|
||||
|
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.Instances.Instance do
|
|||
schema "instances" do
|
||||
field(:host, :string)
|
||||
field(:unreachable_since, :naive_datetime_usec)
|
||||
field(:favicon, :string)
|
||||
field(:favicon_updated_at, :naive_datetime)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -25,7 +27,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:host, :unreachable_since])
|
||||
|> cast(params, [:host, :unreachable_since, :favicon, :favicon_updated_at])
|
||||
|> validate_required([:host])
|
||||
|> unique_constraint(:host)
|
||||
end
|
||||
|
@ -120,4 +122,48 @@ defp parse_datetime(datetime) when is_binary(datetime) do
|
|||
end
|
||||
|
||||
defp parse_datetime(datetime), do: datetime
|
||||
|
||||
def get_or_update_favicon(%URI{host: host} = instance_uri) do
|
||||
existing_record = Repo.get_by(Instance, %{host: host})
|
||||
now = NaiveDateTime.utc_now()
|
||||
|
||||
if existing_record && existing_record.favicon_updated_at &&
|
||||
NaiveDateTime.diff(now, existing_record.favicon_updated_at) < 86_400 do
|
||||
existing_record.favicon
|
||||
else
|
||||
favicon = scrape_favicon(instance_uri)
|
||||
|
||||
if existing_record do
|
||||
existing_record
|
||||
|> changeset(%{favicon: favicon, favicon_updated_at: now})
|
||||
|> Repo.update()
|
||||
else
|
||||
%Instance{}
|
||||
|> changeset(%{host: host, favicon: favicon, favicon_updated_at: now})
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
favicon
|
||||
end
|
||||
end
|
||||
|
||||
defp scrape_favicon(%URI{} = instance_uri) do
|
||||
try do
|
||||
with {:ok, %Tesla.Env{body: html}} <-
|
||||
Pleroma.HTTP.get(to_string(instance_uri), [{:Accept, "text/html"}]),
|
||||
favicon_rel <-
|
||||
html
|
||||
|> Floki.parse_document!()
|
||||
|> Floki.attribute("link[rel=icon]", "href")
|
||||
|> List.first(),
|
||||
favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(),
|
||||
true <- is_binary(favicon) do
|
||||
favicon
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -130,6 +130,7 @@ def for_user_query(user, opts \\ %{}) do
|
|||
|> preload([n, a, o], activity: {a, object: o})
|
||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||
|> exclude_blocked(user, exclude_blocked_opts)
|
||||
|> exclude_filtered(user)
|
||||
|> exclude_visibility(opts)
|
||||
end
|
||||
|
||||
|
@ -158,6 +159,20 @@ defp exclude_notification_muted(query, user, opts) do
|
|||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||
end
|
||||
|
||||
defp exclude_filtered(query, user) do
|
||||
case Pleroma.Filter.compose_regex(user) do
|
||||
nil ->
|
||||
query
|
||||
|
||||
regex ->
|
||||
from([_n, a, o] in query,
|
||||
where:
|
||||
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
||||
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@valid_visibilities ~w[direct unlisted public private]
|
||||
|
||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||
|
@ -337,6 +352,7 @@ def dismiss(%{id: user_id} = _user, id) do
|
|||
end
|
||||
end
|
||||
|
||||
@spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []}
|
||||
def create_notifications(activity, options \\ [])
|
||||
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
|
||||
|
@ -481,6 +497,10 @@ def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_i
|
|||
end
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
|
||||
[object_id]
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(activity) do
|
||||
[]
|
||||
|> Utils.maybe_notify_to_recipients(activity)
|
||||
|
@ -555,7 +575,8 @@ def skip?(%Activity{} = activity, %User{} = user) do
|
|||
:follows,
|
||||
:non_followers,
|
||||
:non_follows,
|
||||
:recently_followed
|
||||
:recently_followed,
|
||||
:filtered
|
||||
]
|
||||
|> Enum.find(&skip?(&1, activity, user))
|
||||
end
|
||||
|
@ -624,6 +645,26 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
|
|||
end)
|
||||
end
|
||||
|
||||
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
|
||||
|
||||
def skip?(:filtered, activity, user) do
|
||||
object = Object.normalize(activity)
|
||||
|
||||
cond do
|
||||
is_nil(object) ->
|
||||
false
|
||||
|
||||
object.data["actor"] == user.ap_id ->
|
||||
false
|
||||
|
||||
not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
|
||||
Regex.match?(regex, object.data["content"])
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def skip?(_, _, _), do: false
|
||||
|
||||
def for_user_and_activity(user, activity) do
|
||||
|
|
|
@ -69,10 +69,11 @@ defp csp_string do
|
|||
img_src = "img-src 'self' data: blob:"
|
||||
media_src = "media-src 'self'"
|
||||
|
||||
# Strict multimedia CSP enforcement only when MediaProxy is enabled
|
||||
{img_src, media_src} =
|
||||
if Config.get([:media_proxy, :enabled]) &&
|
||||
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
|
||||
sources = get_proxy_and_attachment_sources()
|
||||
sources = build_csp_multimedia_source_list()
|
||||
{[img_src, sources], [media_src, sources]}
|
||||
else
|
||||
{[img_src, " https:"], [media_src, " https:"]}
|
||||
|
@ -81,14 +82,14 @@ defp csp_string do
|
|||
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
|
||||
|
||||
connect_src =
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
if Config.get(:env) == :dev do
|
||||
[connect_src, " http://localhost:3035/"]
|
||||
else
|
||||
connect_src
|
||||
end
|
||||
|
||||
script_src =
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
if Config.get(:env) == :dev do
|
||||
"script-src 'self' 'unsafe-eval'"
|
||||
else
|
||||
"script-src 'self'"
|
||||
|
@ -107,29 +108,28 @@ defp csp_string do
|
|||
|> :erlang.iolist_to_binary()
|
||||
end
|
||||
|
||||
defp get_proxy_and_attachment_sources do
|
||||
defp build_csp_multimedia_source_list do
|
||||
media_proxy_whitelist =
|
||||
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
|
||||
add_source(acc, host)
|
||||
end)
|
||||
|
||||
media_proxy_base_url =
|
||||
if Config.get([:media_proxy, :base_url]),
|
||||
do: URI.parse(Config.get([:media_proxy, :base_url])).host
|
||||
media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
|
||||
|
||||
upload_base_url =
|
||||
if Config.get([Pleroma.Upload, :base_url]),
|
||||
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
|
||||
upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
|
||||
|
||||
s3_endpoint =
|
||||
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
|
||||
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
|
||||
s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
|
||||
|
||||
captcha_method = Config.get([Pleroma.Captcha, :method])
|
||||
|
||||
captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint]))
|
||||
|
||||
[]
|
||||
|> add_source(media_proxy_base_url)
|
||||
|> add_source(upload_base_url)
|
||||
|> add_source(s3_endpoint)
|
||||
|> add_source(media_proxy_whitelist)
|
||||
|> add_source(captcha_endpoint)
|
||||
end
|
||||
|
||||
defp add_source(iodata, nil), do: iodata
|
||||
|
@ -139,6 +139,16 @@ defp add_csp_param(csp_iodata, nil), do: csp_iodata
|
|||
|
||||
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
|
||||
|
||||
defp build_csp_param(nil), do: nil
|
||||
|
||||
defp build_csp_param(url) when is_binary(url) do
|
||||
%{host: host, scheme: scheme} = URI.parse(url)
|
||||
|
||||
if scheme do
|
||||
[scheme, "://", host]
|
||||
end
|
||||
end
|
||||
|
||||
def warn_if_disabled do
|
||||
unless Config.get([:http_security, :enabled]) do
|
||||
Logger.warn("
|
||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Plugs.StaticFEPlug do
|
|||
def init(options), do: options
|
||||
|
||||
def call(conn, _) do
|
||||
if enabled?() and accepts_html?(conn) do
|
||||
if enabled?() and requires_html?(conn) do
|
||||
conn
|
||||
|> StaticFEController.call(:show)
|
||||
|> halt()
|
||||
|
@ -20,10 +20,7 @@ def call(conn, _) do
|
|||
|
||||
defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
|
||||
|
||||
defp accepts_html?(conn) do
|
||||
case get_req_header(conn, "accept") do
|
||||
[accept | _] -> String.contains?(accept, "text/html")
|
||||
_ -> false
|
||||
end
|
||||
defp requires_html?(conn) do
|
||||
Phoenix.Controller.get_format(conn) == "html"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy do
|
||||
@range_headers ~w(range if-range)
|
||||
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
|
||||
~w(if-unmodified-since if-none-match if-range range)
|
||||
~w(if-unmodified-since if-none-match) ++ @range_headers
|
||||
@resp_cache_headers ~w(etag date last-modified)
|
||||
@keep_resp_headers @resp_cache_headers ++
|
||||
~w(content-type content-disposition content-encoding content-range) ++
|
||||
~w(accept-ranges vary)
|
||||
~w(content-length content-type content-disposition content-encoding) ++
|
||||
~w(content-range accept-ranges vary)
|
||||
@default_cache_control_header "public, max-age=1209600"
|
||||
@valid_resp_codes [200, 206, 304]
|
||||
@max_read_duration :timer.seconds(30)
|
||||
|
@ -170,6 +171,8 @@ defp request(method, url, headers, opts) do
|
|||
end
|
||||
|
||||
defp response(conn, client, url, status, headers, opts) do
|
||||
Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}")
|
||||
|
||||
result =
|
||||
conn
|
||||
|> put_resp_headers(build_resp_headers(headers, opts))
|
||||
|
@ -220,7 +223,9 @@ defp chunk_reply(conn, client, opts, sent_so_far, duration) do
|
|||
end
|
||||
end
|
||||
|
||||
defp head_response(conn, _url, code, headers, opts) do
|
||||
defp head_response(conn, url, code, headers, opts) do
|
||||
Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}")
|
||||
|
||||
conn
|
||||
|> put_resp_headers(build_resp_headers(headers, opts))
|
||||
|> send_resp(code, "")
|
||||
|
@ -262,9 +267,23 @@ defp build_req_headers(headers, opts) do
|
|||
headers
|
||||
|> downcase_headers()
|
||||
|> Enum.filter(fn {k, _} -> k in @keep_req_headers end)
|
||||
|> (fn headers ->
|
||||
headers = headers ++ Keyword.get(opts, :req_headers, [])
|
||||
|> build_req_range_or_encoding_header(opts)
|
||||
|> build_req_user_agent_header(opts)
|
||||
|> Keyword.merge(Keyword.get(opts, :req_headers, []))
|
||||
end
|
||||
|
||||
# Disable content-encoding if any @range_headers are requested (see #1823).
|
||||
defp build_req_range_or_encoding_header(headers, _opts) do
|
||||
range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end)
|
||||
|
||||
if range? && List.keymember?(headers, "accept-encoding", 0) do
|
||||
List.keydelete(headers, "accept-encoding", 0)
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp build_req_user_agent_header(headers, opts) do
|
||||
if Keyword.get(opts, :keep_user_agent, false) do
|
||||
List.keystore(
|
||||
headers,
|
||||
|
@ -275,7 +294,6 @@ defp build_req_headers(headers, opts) do
|
|||
else
|
||||
headers
|
||||
end
|
||||
end).()
|
||||
end
|
||||
|
||||
defp build_resp_headers(headers, opts) do
|
||||
|
@ -283,7 +301,7 @@ defp build_resp_headers(headers, opts) do
|
|||
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|
||||
|> build_resp_cache_headers(opts)
|
||||
|> build_resp_content_disposition_header(opts)
|
||||
|> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).()
|
||||
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
|
||||
end
|
||||
|
||||
defp build_resp_cache_headers(headers, _opts) do
|
||||
|
|
|
@ -63,6 +63,10 @@ def store(upload, opts \\ []) do
|
|||
with {:ok, upload} <- prepare_upload(upload, opts),
|
||||
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
||||
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
||||
description = Map.get(opts, :description) || upload.name,
|
||||
{_, true} <-
|
||||
{:description_limit,
|
||||
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
|
||||
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
|
||||
{:ok,
|
||||
%{
|
||||
|
@ -75,9 +79,12 @@ def store(upload, opts \\ []) do
|
|||
"href" => url_from_spec(upload, opts.base_url, url_spec)
|
||||
}
|
||||
],
|
||||
"name" => Map.get(opts, :description) || upload.name
|
||||
"name" => description
|
||||
}}
|
||||
else
|
||||
{:description_limit, _} ->
|
||||
{:error, :description_too_long}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error(
|
||||
"#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"
|
||||
|
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.User do
|
|||
field(:keys, :string)
|
||||
field(:public_key, :string)
|
||||
field(:ap_id, :string)
|
||||
field(:avatar, :map)
|
||||
field(:avatar, :map, default: %{})
|
||||
field(:local, :boolean, default: true)
|
||||
field(:follower_address, :string)
|
||||
field(:following_address, :string)
|
||||
|
@ -138,6 +138,7 @@ defmodule Pleroma.User do
|
|||
field(:also_known_as, {:array, :string}, default: [])
|
||||
field(:inbox, :string)
|
||||
field(:shared_inbox, :string)
|
||||
field(:accepts_chat_messages, :boolean, default: nil)
|
||||
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
|
@ -388,8 +389,8 @@ defp fix_follower_address(%{nickname: nickname} = params),
|
|||
defp fix_follower_address(params), do: params
|
||||
|
||||
def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
name =
|
||||
case params[:name] do
|
||||
|
@ -436,7 +437,8 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
|||
:discoverable,
|
||||
:invisible,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
:also_known_as,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
)
|
||||
|> validate_required([:name, :ap_id])
|
||||
|
@ -448,8 +450,8 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
|||
end
|
||||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
struct
|
||||
|> cast(
|
||||
|
@ -481,7 +483,8 @@ def update_changeset(struct, params \\ %{}) do
|
|||
:pleroma_settings_store,
|
||||
:discoverable,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
:also_known_as,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
)
|
||||
|> unique_constraint(:nickname)
|
||||
|
@ -539,15 +542,12 @@ defp put_emoji(changeset) do
|
|||
end
|
||||
|
||||
defp put_change_if_present(changeset, map_field, value_function) do
|
||||
if value = get_change(changeset, map_field) do
|
||||
with {:ok, new_value} <- value_function.(value) do
|
||||
with {:ok, value} <- fetch_change(changeset, map_field),
|
||||
{:ok, new_value} <- value_function.(value) do
|
||||
put_change(changeset, map_field, new_value)
|
||||
else
|
||||
_ -> changeset
|
||||
end
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp put_upload(value, type) do
|
||||
|
@ -621,12 +621,13 @@ def force_password_reset_async(user) do
|
|||
def force_password_reset(user), do: update_password_reset_pending(user, true)
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||
params = Map.put_new(params, :accepts_chat_messages, true)
|
||||
|
||||
need_confirmation? =
|
||||
if is_nil(opts[:need_confirmation]) do
|
||||
Pleroma.Config.get([:instance, :account_activation_required])
|
||||
Config.get([:instance, :account_activation_required])
|
||||
else
|
||||
opts[:need_confirmation]
|
||||
end
|
||||
|
@ -641,13 +642,14 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
:nickname,
|
||||
:password,
|
||||
:password_confirmation,
|
||||
:emoji
|
||||
:emoji,
|
||||
:accepts_chat_messages
|
||||
])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|
@ -662,7 +664,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
def maybe_validate_required_email(changeset, true), do: changeset
|
||||
|
||||
def maybe_validate_required_email(changeset, _) do
|
||||
if Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
if Config.get([:instance, :account_activation_required]) do
|
||||
validate_required(changeset, [:email])
|
||||
else
|
||||
changeset
|
||||
|
@ -682,7 +684,7 @@ defp put_following_and_follower_address(changeset) do
|
|||
end
|
||||
|
||||
defp autofollow_users(user) do
|
||||
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
||||
candidates = Config.get([:instance, :autofollowed_nicknames])
|
||||
|
||||
autofollowed_users =
|
||||
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|
||||
|
@ -709,7 +711,7 @@ def post_register_action(%User{} = user) do
|
|||
|
||||
def try_send_confirmation_email(%User{} = user) do
|
||||
if user.confirmation_pending &&
|
||||
Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
Config.get([:instance, :account_activation_required]) do
|
||||
user
|
||||
|> Pleroma.Emails.UserEmail.account_confirmation_email()
|
||||
|> Pleroma.Emails.Mailer.deliver_async()
|
||||
|
@ -766,7 +768,7 @@ def follow_all(follower, followeds) do
|
|||
defdelegate following(user), to: FollowingRelationship
|
||||
|
||||
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
cond do
|
||||
followed.deactivated ->
|
||||
|
@ -967,7 +969,7 @@ def get_cached_by_nickname(nickname) do
|
|||
end
|
||||
|
||||
def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
|
||||
restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
|
||||
restrict_to_local = Config.get([:instance, :limit_to_local_content])
|
||||
|
||||
cond do
|
||||
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
|
||||
|
@ -1163,7 +1165,7 @@ defp follow_information_changeset(user, params) do
|
|||
|
||||
@spec update_follower_count(User.t()) :: {:ok, User.t()}
|
||||
def update_follower_count(%User{} = user) do
|
||||
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
if user.local or !Config.get([:instance, :external_user_synchronization]) do
|
||||
follower_count = FollowingRelationship.follower_count(user)
|
||||
|
||||
user
|
||||
|
@ -1176,7 +1178,7 @@ def update_follower_count(%User{} = user) do
|
|||
|
||||
@spec update_following_count(User.t()) :: {:ok, User.t()}
|
||||
def update_following_count(%User{local: false} = user) do
|
||||
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
if Config.get([:instance, :external_user_synchronization]) do
|
||||
{:ok, maybe_fetch_follow_information(user)}
|
||||
else
|
||||
{:ok, user}
|
||||
|
@ -1263,7 +1265,7 @@ def unmute(%User{} = muter, %User{} = mutee) do
|
|||
end
|
||||
|
||||
def subscribe(%User{} = subscriber, %User{} = target) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
if blocks?(target, subscriber) and deny_follow_blocked do
|
||||
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
|
||||
|
@ -1546,7 +1548,7 @@ def perform(:follow_import, %User{} = follower, followed_identifiers)
|
|||
fn followed_identifier ->
|
||||
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
||||
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
||||
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
|
||||
followed
|
||||
else
|
||||
err ->
|
||||
|
@ -1654,7 +1656,7 @@ def html_filter_policy(%User{no_rich_text: true}) do
|
|||
Pleroma.HTML.Scrubber.TwitterText
|
||||
end
|
||||
|
||||
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
|
||||
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
|
||||
|
||||
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
|
@ -1836,7 +1838,7 @@ defp normalize_tags(tags) do
|
|||
end
|
||||
|
||||
defp local_nickname_regex do
|
||||
if Pleroma.Config.get([:instance, :extended_nickname_format]) do
|
||||
if Config.get([:instance, :extended_nickname_format]) do
|
||||
@extended_local_nickname_regex
|
||||
else
|
||||
@strict_local_nickname_regex
|
||||
|
@ -1964,8 +1966,8 @@ def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
|
|||
|
||||
def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
|
||||
# use instance-default
|
||||
config = Pleroma.Config.get([:assets, :mascots])
|
||||
default_mascot = Pleroma.Config.get([:assets, :default_mascot])
|
||||
config = Config.get([:assets, :mascots])
|
||||
default_mascot = Config.get([:assets, :default_mascot])
|
||||
mascot = Keyword.get(config, default_mascot)
|
||||
|
||||
%{
|
||||
|
@ -2060,7 +2062,7 @@ def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
|||
|
||||
def validate_fields(changeset, remote? \\ false) do
|
||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
||||
limit = Config.get([:instance, limit_name], 0)
|
||||
|
||||
changeset
|
||||
|> validate_length(:fields, max: limit)
|
||||
|
@ -2074,8 +2076,8 @@ def validate_fields(changeset, remote? \\ false) do
|
|||
end
|
||||
|
||||
defp valid_field?(%{"name" => name, "value" => value}) do
|
||||
name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
|
||||
value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
|
||||
name_limit = Config.get([:instance, :account_field_name_length], 255)
|
||||
value_limit = Config.get([:instance, :account_field_value_length], 255)
|
||||
|
||||
is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
|
||||
String.length(value) <= value_limit
|
||||
|
@ -2085,10 +2087,10 @@ defp valid_field?(_), do: false
|
|||
|
||||
defp truncate_field(%{"name" => name, "value" => value}) do
|
||||
{name, _chopped} =
|
||||
String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
|
||||
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
|
||||
|
||||
{value, _chopped} =
|
||||
String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
|
||||
String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
|
||||
|
||||
%{"name" => name, "value" => value}
|
||||
end
|
||||
|
@ -2143,7 +2145,7 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do
|
|||
|
||||
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
|
||||
if id not in user.pinned_activities do
|
||||
max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
|
||||
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
|
||||
params = %{pinned_activities: user.pinned_activities ++ [id]}
|
||||
|
||||
user
|
||||
|
|
|
@ -69,11 +69,15 @@ defp fts_search(query, query_string) do
|
|||
u in query,
|
||||
where:
|
||||
fragment(
|
||||
# The fragment must _exactly_ match `users_fts_index`, otherwise the index won't work
|
||||
"""
|
||||
(to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?)
|
||||
(
|
||||
setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
|
||||
setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')
|
||||
) @@ to_tsquery('simple', ?)
|
||||
""",
|
||||
u.name,
|
||||
u.nickname,
|
||||
u.name,
|
||||
^query_string
|
||||
)
|
||||
)
|
||||
|
@ -88,15 +92,23 @@ defp to_tsquery(query_string) do
|
|||
|> Enum.join(" | ")
|
||||
end
|
||||
|
||||
# Considers nickname match, localized nickname match, name match; preferences nickname match
|
||||
defp trigram_rank(query, query_string) do
|
||||
from(
|
||||
u in query,
|
||||
select_merge: %{
|
||||
search_rank:
|
||||
fragment(
|
||||
"similarity(?, trim(? || ' ' || coalesce(?, '')))",
|
||||
"""
|
||||
similarity(?, ?) +
|
||||
similarity(?, regexp_replace(?, '@.+', '')) +
|
||||
similarity(?, trim(coalesce(?, '')))
|
||||
""",
|
||||
^query_string,
|
||||
u.nickname,
|
||||
^query_string,
|
||||
u.nickname,
|
||||
^query_string,
|
||||
u.name
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Constants
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
|
@ -321,28 +322,6 @@ defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
|
||||
with {:ok, result} <-
|
||||
Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_follow(follower, followed, activity_id, local, opts) do
|
||||
skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
|
||||
data = make_follow_data(follower, followed, activity_id)
|
||||
|
||||
with {:ok, activity} <- insert(data, local),
|
||||
_ <- skip_notify_and_stream || notify_and_stream(activity),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, error} -> Repo.rollback(error)
|
||||
end
|
||||
end
|
||||
|
||||
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t()} | nil | {:error, any()}
|
||||
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||
|
@ -446,6 +425,7 @@ def fetch_activities_for_context_query(context, opts) do
|
|||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts[:user])
|
||||
|> restrict_filtered(opts)
|
||||
|> where(
|
||||
[activity],
|
||||
fragment(
|
||||
|
@ -961,6 +941,26 @@ defp restrict_instance(query, %{instance: instance}) do
|
|||
|
||||
defp restrict_instance(query, _), do: query
|
||||
|
||||
defp restrict_filtered(query, %{user: %User{} = user}) do
|
||||
case Filter.compose_regex(user) do
|
||||
nil ->
|
||||
query
|
||||
|
||||
regex ->
|
||||
from([activity, object] in query,
|
||||
where:
|
||||
fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
|
||||
activity.actor == ^user.ap_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
|
||||
restrict_filtered(query, %{user: user})
|
||||
end
|
||||
|
||||
defp restrict_filtered(query, _), do: query
|
||||
|
||||
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
|
||||
|
||||
defp exclude_poll_votes(query, _) do
|
||||
|
@ -1091,6 +1091,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(restrict_blocked_opts)
|
||||
|> restrict_muted(restrict_muted_opts)
|
||||
|> restrict_filtered(opts)
|
||||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
|> restrict_thread_visibility(opts, config)
|
||||
|
@ -1099,6 +1100,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||
|> restrict_instance(opts)
|
||||
|> restrict_announce_object_actor(opts)
|
||||
|> restrict_filtered(opts)
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> exclude_poll_votes(opts)
|
||||
|> exclude_chat_messages(opts)
|
||||
|
@ -1224,6 +1226,8 @@ defp object_to_user_data(data) do
|
|||
end)
|
||||
|
||||
locked = data["manuallyApprovesFollowers"] || false
|
||||
capabilities = data["capabilities"] || %{}
|
||||
accepts_chat_messages = capabilities["acceptsChatMessages"]
|
||||
data = Transmogrifier.maybe_fix_user_object(data)
|
||||
discoverable = data["discoverable"] || false
|
||||
invisible = data["invisible"] || false
|
||||
|
@ -1262,7 +1266,8 @@ defp object_to_user_data(data) do
|
|||
also_known_as: Map.get(data, "alsoKnownAs", []),
|
||||
public_key: public_key,
|
||||
inbox: data["inbox"],
|
||||
shared_inbox: shared_inbox
|
||||
shared_inbox: shared_inbox,
|
||||
accepts_chat_messages: accepts_chat_messages
|
||||
}
|
||||
|
||||
# nickname can be nil because of virtual actors
|
||||
|
|
|
@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
|
||||
require Pleroma.Constants
|
||||
|
||||
@spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||
def follow(follower, followed) do
|
||||
data = %{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"actor" => follower.ap_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed.ap_id,
|
||||
"to" => [followed.ap_id]
|
||||
}
|
||||
|
||||
{:ok, data, []}
|
||||
end
|
||||
|
||||
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
||||
def emoji_react(actor, object, emoji) do
|
||||
with {:ok, data, meta} <- object_action(actor, object) do
|
||||
|
|
|
@ -98,7 +98,7 @@ def filter(message), do: {:ok, message}
|
|||
@impl true
|
||||
def describe do
|
||||
mrf_object_age =
|
||||
Pleroma.Config.get(:mrf_object_age)
|
||||
Config.get(:mrf_object_age)
|
||||
|> Enum.into(%{})
|
||||
|
||||
{:ok, %{mrf_object_age: mrf_object_age}}
|
||||
|
|
|
@ -47,5 +47,5 @@ def filter(object), do: {:ok, object}
|
|||
|
||||
@impl true
|
||||
def describe,
|
||||
do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||
end
|
||||
|
|
|
@ -155,7 +155,7 @@ def filter(%{"type" => "Delete", "actor" => actor} = object) do
|
|||
%{host: actor_host} = URI.parse(actor)
|
||||
|
||||
reject_deletes =
|
||||
Pleroma.Config.get([:mrf_simple, :reject_deletes])
|
||||
Config.get([:mrf_simple, :reject_deletes])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
if MRF.subdomain_match?(reject_deletes, actor_host) do
|
||||
|
|
|
@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||
|
@ -25,6 +26,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
def validate(object, meta)
|
||||
|
||||
def validate(%{"type" => "Follow"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> FollowValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Block"} = block_activity, meta) do
|
||||
with {:ok, block_activity} <-
|
||||
block_activity
|
||||
|
|
|
@ -93,12 +93,14 @@ def validate_content_or_attachment(cng) do
|
|||
- If both users are in our system
|
||||
- If at least one of the users in this ChatMessage is a local user
|
||||
- If the recipient is not blocking the actor
|
||||
- If the recipient is explicitly not accepting chat messages
|
||||
"""
|
||||
def validate_local_concern(cng) do
|
||||
with actor_ap <- get_field(cng, :actor),
|
||||
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
|
||||
{_, %User{} = recipient} <-
|
||||
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
|
||||
{_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false},
|
||||
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
|
||||
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
|
||||
cng
|
||||
|
@ -107,6 +109,10 @@ def validate_local_concern(cng) do
|
|||
cng
|
||||
|> add_error(:actor, "actor is blocked by recipient")
|
||||
|
||||
{:not_accepting_chats?, true} ->
|
||||
cng
|
||||
|> add_error(:to, "recipient does not accept chat messages")
|
||||
|
||||
{:local?, false} ->
|
||||
cng
|
||||
|> add_error(:actor, "actor and recipient are both remote")
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:state, :string, default: "pending")
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||
|> validate_inclusion(:type, ["Follow"])
|
||||
|> validate_inclusion(:state, ~w{pending reject accept})
|
||||
|> validate_actor_presence()
|
||||
|> validate_actor_presence(field_name: :object)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> validate_data
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ def relay_ap_id do
|
|||
def follow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
|
||||
{:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do
|
||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
alias Pleroma.Activity.Ir.Topics
|
||||
alias Pleroma.Chat
|
||||
alias Pleroma.Chat.MessageReference
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
@ -21,6 +22,69 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
def handle(object, meta \\ [])
|
||||
|
||||
# Tasks this handle
|
||||
# - Follows if possible
|
||||
# - Sends a notification
|
||||
# - Generates accept or reject if appropriate
|
||||
def handle(
|
||||
%{
|
||||
data: %{
|
||||
"id" => follow_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed_user,
|
||||
"actor" => following_user
|
||||
}
|
||||
} = object,
|
||||
meta
|
||||
) do
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(following_user),
|
||||
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
|
||||
{_, {:ok, _}, _, _} <-
|
||||
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
|
||||
if followed.local && !followed.locked do
|
||||
Utils.update_follow_state_for_all(object, "accept")
|
||||
FollowingRelationship.update(follower, followed, :follow_accept)
|
||||
User.update_follower_count(followed)
|
||||
User.update_following_count(follower)
|
||||
|
||||
%{
|
||||
to: [following_user],
|
||||
actor: followed,
|
||||
object: follow_id,
|
||||
local: true
|
||||
}
|
||||
|> ActivityPub.accept()
|
||||
end
|
||||
else
|
||||
{:following, {:error, _}, follower, followed} ->
|
||||
Utils.update_follow_state_for_all(object, "reject")
|
||||
FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
if followed.local do
|
||||
%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_id,
|
||||
local: true
|
||||
}
|
||||
|> ActivityPub.reject()
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
{:ok, notifications} = Notification.create_notifications(object, do_send: false)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|> add_notifications(notifications)
|
||||
|
||||
updated_object = Activity.get_by_ap_id(follow_id)
|
||||
|
||||
{:ok, updated_object, meta}
|
||||
end
|
||||
|
||||
# Tasks this handles:
|
||||
# - Unfollow and block
|
||||
def handle(
|
||||
|
@ -209,14 +273,20 @@ def handle_object_creation(object) do
|
|||
{:ok, object}
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
|
||||
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
|
||||
{:ok, _} <- Utils.remove_like_from_object(object, liked_object),
|
||||
{:ok, _} <- Repo.delete(object) do
|
||||
:ok
|
||||
defp undo_like(nil, object), do: delete_object(object)
|
||||
|
||||
defp undo_like(%Object{} = liked_object, object) do
|
||||
with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do
|
||||
delete_object(object)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
|
||||
object.data["object"]
|
||||
|> Object.get_by_ap_id()
|
||||
|> undo_like(object)
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
|
||||
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
|
||||
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
|
||||
|
@ -246,6 +316,11 @@ def handle_undoing(
|
|||
|
||||
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
|
||||
|
||||
@spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
|
||||
defp delete_object(object) do
|
||||
with {:ok, _} <- Repo.delete(object), do: :ok
|
||||
end
|
||||
|
||||
defp send_notifications(meta) do
|
||||
Keyword.get(meta, :notifications, [])
|
||||
|> Enum.each(fn notification ->
|
||||
|
|
|
@ -233,8 +233,10 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
|||
is_map(url) && is_binary(url["href"]) -> url["href"]
|
||||
is_binary(data["url"]) -> data["url"]
|
||||
is_binary(data["href"]) -> data["href"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
if href do
|
||||
attachment_url =
|
||||
%{"href" => href}
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|
@ -244,7 +246,11 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
|||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("type", data["type"])
|
||||
|> Maps.put_if_present("name", data["name"])
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
Map.put(object, "attachment", attachments)
|
||||
end
|
||||
|
@ -263,12 +269,18 @@ def fix_url(%{"url" => url} = object) when is_map(url) do
|
|||
|
||||
def fix_url(%{"type" => object_type, "url" => url} = object)
|
||||
when object_type in ["Video", "Audio"] and is_list(url) do
|
||||
first_element = Enum.at(url, 0)
|
||||
attachment =
|
||||
Enum.find(url, fn x ->
|
||||
media_type = x["mediaType"] || x["mimeType"] || ""
|
||||
|
||||
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
|
||||
is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
|
||||
end)
|
||||
|
||||
link_element =
|
||||
Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", [first_element])
|
||||
|> Map.put("attachment", [attachment])
|
||||
|> Map.put("url", link_element["href"])
|
||||
end
|
||||
|
||||
|
@ -517,66 +529,6 @@ def handle_incoming(
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
|
||||
_options
|
||||
) do
|
||||
with %User{local: true} = followed <-
|
||||
User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
|
||||
{:ok, %User{} = follower} <-
|
||||
User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
|
||||
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||
{_, false} <- {:user_locked, User.locked?(followed)},
|
||||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
||||
{_, {:ok, _}} <-
|
||||
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
|
||||
{:ok, _relationship} <-
|
||||
FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
else
|
||||
{:user_blocked, true} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
{:follow, {:error, _}} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
{:user_locked, true} ->
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
|
||||
:noop
|
||||
end
|
||||
|
||||
ActivityPub.notify_and_stream(activity)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
|
||||
_options
|
||||
|
@ -684,7 +636,7 @@ def handle_incoming(
|
|||
%{"type" => type} = data,
|
||||
_options
|
||||
)
|
||||
when type in ~w{Update Block} do
|
||||
when type in ~w{Update Block Follow} do
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
|
|
|
@ -81,6 +81,15 @@ def render("user.json", %{user: user}) do
|
|||
|
||||
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
||||
|
||||
capabilities =
|
||||
if is_boolean(user.accepts_chat_messages) do
|
||||
%{
|
||||
"acceptsChatMessages" => user.accepts_chat_messages
|
||||
}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
%{
|
||||
"id" => user.ap_id,
|
||||
"type" => user.actor_type,
|
||||
|
@ -101,7 +110,8 @@ def render("user.json", %{user: user}) do
|
|||
"endpoints" => endpoints,
|
||||
"attachment" => fields,
|
||||
"tag" => emoji_tags,
|
||||
"discoverable" => user.discoverable
|
||||
"discoverable" => user.discoverable,
|
||||
"capabilities" => capabilities
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|
|
|
@ -206,8 +206,8 @@ def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
|
|||
end
|
||||
end
|
||||
|
||||
def user_show(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("show.json", %{user: user})
|
||||
|
@ -233,11 +233,11 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
|||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
end
|
||||
|
||||
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
|
||||
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
{_, page_size} = page_params(params)
|
||||
|
||||
activities =
|
||||
|
@ -526,7 +526,7 @@ def disable_mfa(conn, %{"nickname" => nickname}) do
|
|||
|
||||
@doc "Show a given user's credentials"
|
||||
def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("credentials.json", %{user: user, for: admin})
|
||||
|
|
|
@ -61,7 +61,7 @@ def update_credentials_operation do
|
|||
description: "Update the user's display and preferences.",
|
||||
operationId: "AccountController.update_credentials",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
requestBody: request_body("Parameters", update_creadentials_request(), required: true),
|
||||
requestBody: request_body("Parameters", update_credentials_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Account", "application/json", Account),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
|
@ -203,14 +203,23 @@ def follow_operation do
|
|||
security: [%{"oAuth" => ["follow", "write:follows"]}],
|
||||
description: "Follow the given account",
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
|
||||
Operation.parameter(
|
||||
:reblogs,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Receive this account's reblogs in home timeline? Defaults to true."
|
||||
)
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
|
||||
],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
reblogs: %Schema{
|
||||
type: :boolean,
|
||||
description: "Receive this account's reblogs in home timeline? Defaults to true.",
|
||||
default: true
|
||||
}
|
||||
}
|
||||
},
|
||||
required: false
|
||||
),
|
||||
responses: %{
|
||||
200 => Operation.response("Relationship", "application/json", AccountRelationship),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
|
@ -438,6 +447,7 @@ defp create_request do
|
|||
}
|
||||
end
|
||||
|
||||
# TODO: This is actually a token respone, but there's no oauth operation file yet.
|
||||
defp create_response do
|
||||
%Schema{
|
||||
title: "AccountCreateResponse",
|
||||
|
@ -446,19 +456,25 @@ defp create_response do
|
|||
properties: %{
|
||||
token_type: %Schema{type: :string},
|
||||
access_token: %Schema{type: :string},
|
||||
scope: %Schema{type: :array, items: %Schema{type: :string}},
|
||||
created_at: %Schema{type: :integer, format: :"date-time"}
|
||||
refresh_token: %Schema{type: :string},
|
||||
scope: %Schema{type: :string},
|
||||
created_at: %Schema{type: :integer, format: :"date-time"},
|
||||
me: %Schema{type: :string},
|
||||
expires_in: %Schema{type: :integer}
|
||||
},
|
||||
example: %{
|
||||
"token_type" => "Bearer",
|
||||
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
|
||||
"refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
|
||||
"created_at" => 1_585_918_714,
|
||||
"scope" => ["read", "write", "follow", "push"],
|
||||
"token_type" => "Bearer"
|
||||
"expires_in" => 600,
|
||||
"scope" => "read write follow push",
|
||||
"me" => "https://gensokyo.2hu/users/raymoo"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_creadentials_request do
|
||||
defp update_credentials_request do
|
||||
%Schema{
|
||||
title: "AccountUpdateCredentialsRequest",
|
||||
description: "POST body for creating an account",
|
||||
|
@ -492,6 +508,11 @@ defp update_creadentials_request do
|
|||
nullable: true,
|
||||
description: "Whether manual approval of follow requests is required."
|
||||
},
|
||||
accepts_chat_messages: %Schema{
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Whether the user accepts receiving chat messages."
|
||||
},
|
||||
fields_attributes: %Schema{
|
||||
nullable: true,
|
||||
oneOf: [
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
|
@ -40,48 +39,6 @@ def confirmation_resend_operation do
|
|||
}
|
||||
end
|
||||
|
||||
def update_avatar_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Set/clear user avatar image",
|
||||
operationId: "PleromaAPI.AccountController.update_avatar",
|
||||
requestBody:
|
||||
request_body("Parameters", update_avatar_or_background_request(), required: true),
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
responses: %{
|
||||
200 => update_response(),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_banner_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Set/clear user banner image",
|
||||
operationId: "PleromaAPI.AccountController.update_banner",
|
||||
requestBody: request_body("Parameters", update_banner_request(), required: true),
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
responses: %{
|
||||
200 => update_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_background_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Set/clear user background image",
|
||||
operationId: "PleromaAPI.AccountController.update_background",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", update_avatar_or_background_request(), required: true),
|
||||
responses: %{
|
||||
200 => update_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def favourites_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
|
@ -136,52 +93,4 @@ defp id_param do
|
|||
required: true
|
||||
)
|
||||
end
|
||||
|
||||
defp update_avatar_or_background_request do
|
||||
%Schema{
|
||||
title: "PleromaAccountUpdateAvatarOrBackgroundRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
img: %Schema{
|
||||
nullable: true,
|
||||
type: :string,
|
||||
format: :binary,
|
||||
description: "Image encoded using `multipart/form-data` or an empty string to clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_banner_request do
|
||||
%Schema{
|
||||
title: "PleromaAccountUpdateBannerRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
banner: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
format: :binary,
|
||||
description: "Image encoded using `multipart/form-data` or an empty string to clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_response do
|
||||
Operation.response("PleromaAccountUpdateResponse", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
url: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
nullable: true,
|
||||
description: "Image URL"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"url" =>
|
||||
"https://cofe.party/media/9d0add56-bcb6-4c0f-8225-cbbd0b6dd773/13eadb6972c9ccd3f4ffa3b8196f0e0d38b4d2f27594457c52e52946c054cd9a.gif"
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -102,6 +102,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
type: :object,
|
||||
description:
|
||||
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
|
||||
},
|
||||
accepts_chat_messages: %Schema{type: :boolean, nullable: true},
|
||||
favicon: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
nullable: true,
|
||||
description: "Favicon image of the user's instance"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -169,6 +176,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
"is_admin" => false,
|
||||
"is_moderator" => false,
|
||||
"skip_thread_containment" => false,
|
||||
"accepts_chat_messages" => true,
|
||||
"chat_token" =>
|
||||
"SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
|
||||
"unread_conversation_count" => 0,
|
||||
|
|
|
@ -101,12 +101,16 @@ def unblock(blocker, blocked) do
|
|||
def follow(follower, followed) do
|
||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||
|
||||
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
||||
with {:ok, follow_data, _} <- Builder.follow(follower, followed),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
|
||||
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
|
||||
if activity.data["state"] == "reject" do
|
||||
{:error, :rejected}
|
||||
else
|
||||
{:ok, follower, followed, activity}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
|
|
|
@ -143,7 +143,7 @@ def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data)
|
|||
|
||||
def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
|
||||
when is_list(options) do
|
||||
limits = Pleroma.Config.get([:instance, :poll_limits])
|
||||
limits = Config.get([:instance, :poll_limits])
|
||||
|
||||
with :ok <- validate_poll_expiration(expires_in, limits),
|
||||
:ok <- validate_poll_options_amount(options, limits),
|
||||
|
@ -502,7 +502,7 @@ def maybe_extract_mentions(_), do: []
|
|||
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
|
||||
|
||||
def make_report_content_html(comment) do
|
||||
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
|
||||
max_size = Config.get([:instance, :max_report_comment_size], 1000)
|
||||
|
||||
if String.length(comment) <= max_size do
|
||||
{:ok, format_input(comment, "text/plain")}
|
||||
|
@ -564,7 +564,7 @@ def validate_character_limit("" = _full_payload, [] = _attachments) do
|
|||
end
|
||||
|
||||
def validate_character_limit(full_payload, _attachments) do
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
limit = Config.get([:instance, :limit])
|
||||
length = String.length(full_payload)
|
||||
|
||||
if length <= limit do
|
||||
|
|
|
@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPIController
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
|
||||
|
@ -101,12 +102,7 @@ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
|
|||
:ok <- TwitterAPI.validate_captcha(app, params),
|
||||
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
|
||||
json(conn, %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
scope: app.scopes,
|
||||
created_at: Token.Utils.format_created_at(token)
|
||||
})
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
{:error, error} -> json_response(conn, :bad_request, %{error: error})
|
||||
end
|
||||
|
@ -148,6 +144,13 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
|||
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
# We use an empty string as a special value to reset
|
||||
# avatars, banners, backgrounds
|
||||
user_image_value = fn
|
||||
"" -> {:ok, nil}
|
||||
value -> {:ok, value}
|
||||
end
|
||||
|
||||
user_params =
|
||||
[
|
||||
:no_rich_text,
|
||||
|
@ -160,7 +163,8 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
|||
:show_role,
|
||||
:skip_thread_containment,
|
||||
:allow_following_move,
|
||||
:discoverable
|
||||
:discoverable,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
|> Enum.reduce(%{}, fn key, acc ->
|
||||
Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
|
||||
|
@ -168,9 +172,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
|||
|> Maps.put_if_present(:name, params[:display_name])
|
||||
|> Maps.put_if_present(:bio, params[:note])
|
||||
|> Maps.put_if_present(:raw_bio, params[:note])
|
||||
|> Maps.put_if_present(:avatar, params[:avatar])
|
||||
|> Maps.put_if_present(:banner, params[:header])
|
||||
|> Maps.put_if_present(:background, params[:pleroma_background_image])
|
||||
|> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
|
||||
|> Maps.put_if_present(:banner, params[:header], user_image_value)
|
||||
|> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
|
||||
|> Maps.put_if_present(
|
||||
:raw_fields,
|
||||
params[:fields_attributes],
|
||||
|
@ -346,7 +350,7 @@ def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
|
|||
{:error, "Can not follow yourself"}
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
|
||||
def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do
|
||||
with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
|
||||
render(conn, "relationship.json", user: follower, target: followed)
|
||||
else
|
||||
|
|
|
@ -201,15 +201,13 @@ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
|||
@doc "DELETE /api/v1/statuses/:id"
|
||||
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||
render <-
|
||||
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
try_render(conn, "show.json",
|
||||
activity: activity,
|
||||
for: user,
|
||||
with_direct_conversation_id: true,
|
||||
with_source: true
|
||||
),
|
||||
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
render
|
||||
)
|
||||
else
|
||||
_e -> {:error, :not_found}
|
||||
end
|
||||
|
|
|
@ -88,21 +88,20 @@ def direct(%{assigns: %{user: user}} = conn, params) do
|
|||
)
|
||||
end
|
||||
|
||||
defp restrict_unauthenticated?(true = _local_only) do
|
||||
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local])
|
||||
end
|
||||
|
||||
defp restrict_unauthenticated?(_) do
|
||||
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated])
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/public
|
||||
def public(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = params[:local]
|
||||
|
||||
cfg_key =
|
||||
if local_only do
|
||||
:local
|
||||
else
|
||||
:federated
|
||||
end
|
||||
|
||||
restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key])
|
||||
|
||||
if restrict? and is_nil(user) do
|
||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||
fail_on_bad_auth(conn)
|
||||
else
|
||||
activities =
|
||||
params
|
||||
|
@ -123,6 +122,10 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
end
|
||||
|
||||
defp fail_on_bad_auth(conn) do
|
||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||
end
|
||||
|
||||
defp hashtag_fetching(params, user, local_only) do
|
||||
tags =
|
||||
[params[:tag], params[:any]]
|
||||
|
@ -157,6 +160,10 @@ defp hashtag_fetching(params, user, local_only) do
|
|||
# GET /api/v1/timelines/tag/:tag
|
||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = params[:local]
|
||||
|
||||
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||
fail_on_bad_auth(conn)
|
||||
else
|
||||
activities = hashtag_fetching(params, user, local_only)
|
||||
|
||||
conn
|
||||
|
@ -167,6 +174,7 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
|||
as: :activity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/list/:list_id
|
||||
def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
|
||||
|
|
|
@ -204,6 +204,18 @@ defp do_render("show.json", %{user: user} = opts) do
|
|||
%{}
|
||||
end
|
||||
|
||||
favicon =
|
||||
if Pleroma.Config.get([:instances_favicons, :enabled]) do
|
||||
user
|
||||
|> Map.get(:ap_id, "")
|
||||
|> URI.parse()
|
||||
|> URI.merge("/")
|
||||
|> Pleroma.Instances.Instance.get_or_update_favicon()
|
||||
|> MediaProxy.url()
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(user.id),
|
||||
username: username_from_nickname(user.nickname),
|
||||
|
@ -245,7 +257,9 @@ defp do_render("show.json", %{user: user} = opts) do
|
|||
hide_favorites: user.hide_favorites,
|
||||
relationship: relationship,
|
||||
skip_thread_containment: user.skip_thread_containment,
|
||||
background_image: image_url(user.background) |> MediaProxy.url()
|
||||
background_image: image_url(user.background) |> MediaProxy.url(),
|
||||
accepts_chat_messages: user.accepts_chat_messages,
|
||||
favicon: favicon
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|
|
|
@ -34,6 +34,8 @@ def render("show.json", _) do
|
|||
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
||||
background_image: Keyword.get(instance, :background_image),
|
||||
chat_limit: Keyword.get(instance, :chat_limit),
|
||||
description_limit: Keyword.get(instance, :description_limit),
|
||||
pleroma: %{
|
||||
metadata: %{
|
||||
account_activation_required: Keyword.get(instance, :account_activation_required),
|
||||
|
|
|
@ -106,7 +106,7 @@ def filename(url_or_path) do
|
|||
|
||||
def build_url(sig_base64, url_base64, filename \\ nil) do
|
||||
[
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||
Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||
"proxy",
|
||||
sig_base64,
|
||||
url_base64,
|
||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
|
|||
alias Pleroma.Web.Auth.TOTPAuthenticator
|
||||
alias Pleroma.Web.OAuth.MFAView, as: View
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
plug(:fetch_session when action in [:show, :verify])
|
||||
|
@ -74,7 +75,7 @@ def challenge(conn, %{"mfa_token" => mfa_token} = params) do
|
|||
{:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
|
||||
{:ok, _} <- validates_challenge(user, params),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build(user, token))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
_error ->
|
||||
conn
|
||||
|
|
|
@ -5,4 +5,13 @@
|
|||
defmodule Pleroma.Web.OAuth.MFAView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
alias Pleroma.MFA
|
||||
|
||||
def render("mfa_response.json", %{token: token, user: user}) do
|
||||
%{
|
||||
error: "mfa_required",
|
||||
mfa_token: token.token,
|
||||
supported_challenge_types: MFA.supported_methods(user)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.MFAController
|
||||
alias Pleroma.Web.OAuth.MFAView
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
||||
|
@ -233,9 +235,7 @@ def token_exchange(
|
|||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
||||
{:ok, token} <- RefreshToken.grant(token) do
|
||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||
|
||||
json(conn, Token.Response.build(user, token, response_attrs))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
_error -> render_invalid_credentials_error(conn)
|
||||
end
|
||||
|
@ -247,9 +247,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
|
|||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||
|
||||
json(conn, Token.Response.build(user, token, response_attrs))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
error ->
|
||||
handle_token_exchange_error(conn, error)
|
||||
|
@ -267,7 +265,7 @@ def token_exchange(
|
|||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build(user, token))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
error ->
|
||||
handle_token_exchange_error(conn, error)
|
||||
|
@ -290,7 +288,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
|
|||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build_for_client_credentials(token))
|
||||
json(conn, OAuthView.render("token.json", %{token: token}))
|
||||
else
|
||||
_error ->
|
||||
handle_token_exchange_error(conn, :invalid_credentails)
|
||||
|
@ -548,7 +546,7 @@ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
|||
|
||||
defp build_and_response_mfa_token(user, auth) do
|
||||
with {:ok, token} <- MFA.Token.create_token(user, auth) do
|
||||
Token.Response.build_for_mfa_token(user, token)
|
||||
MFAView.render("mfa_response.json", %{token: token, user: user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,4 +5,26 @@
|
|||
defmodule Pleroma.Web.OAuth.OAuthView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
|
||||
alias Pleroma.Web.OAuth.Token.Utils
|
||||
|
||||
def render("token.json", %{token: token} = opts) do
|
||||
response = %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
created_at: Utils.format_created_at(token)
|
||||
}
|
||||
|
||||
if user = opts[:user] do
|
||||
response
|
||||
|> Map.put(:me, user.ap_id)
|
||||
else
|
||||
response
|
||||
end
|
||||
end
|
||||
|
||||
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
end
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.Token.Response do
|
||||
@moduledoc false
|
||||
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token.Utils
|
||||
|
||||
@doc false
|
||||
def build(%User{} = user, token, opts \\ %{}) do
|
||||
%{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|> Map.merge(opts)
|
||||
end
|
||||
|
||||
def build_for_client_credentials(token) do
|
||||
%{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
created_at: Utils.format_created_at(token),
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " ")
|
||||
}
|
||||
end
|
||||
|
||||
def build_for_mfa_token(user, mfa_token) do
|
||||
%{
|
||||
error: "mfa_required",
|
||||
mfa_token: mfa_token.token,
|
||||
supported_challenge_types: MFA.supported_methods(user)
|
||||
}
|
||||
end
|
||||
|
||||
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
end
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
import Pleroma.Web.ControllerHelper,
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
|
@ -35,17 +34,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:accounts"]}
|
||||
# Note: the following actions are not permission-secured in Mastodon:
|
||||
when action in [
|
||||
:update_avatar,
|
||||
:update_banner,
|
||||
:update_background
|
||||
]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
|
||||
|
@ -68,56 +56,6 @@ def confirmation_resend(conn, params) do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
|
||||
def update_avatar(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
|
||||
{:ok, _user} =
|
||||
user
|
||||
|> Changeset.change(%{avatar: nil})
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
|
||||
def update_avatar(%{assigns: %{user: user}, body_params: params} = conn, _params) do
|
||||
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
|
||||
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
||||
%{"url" => [%{"href" => href} | _]} = data
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
|
||||
def update_banner(%{assigns: %{user: user}, body_params: %{banner: ""}} = conn, _) do
|
||||
with {:ok, _user} <- User.update_banner(user, %{}) do
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
||||
def update_banner(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
with {:ok, object} <- ActivityPub.upload(%{img: params[:banner]}, type: :banner),
|
||||
{:ok, _user} <- User.update_banner(user, object.data) do
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_background"
|
||||
def update_background(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
|
||||
with {:ok, _user} <- User.update_background(user, %{}) do
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
||||
def update_background(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
with {:ok, object} <- ActivityPub.upload(params, type: :background),
|
||||
{:ok, _user} <- User.update_background(user, object.data) do
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/pleroma/accounts/:id/favourites"
|
||||
def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
|
||||
render_error(conn, :forbidden, "Can't get favorites")
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Preload.Providers.StatusNet do
|
||||
alias Pleroma.Web.Preload.Providers.Provider
|
||||
alias Pleroma.Web.TwitterAPI.UtilController
|
||||
|
||||
@behaviour Provider
|
||||
@config_url "/api/statusnet/config.json"
|
||||
|
||||
@impl Provider
|
||||
def generate_terms(_params) do
|
||||
%{}
|
||||
|> build_config_tag()
|
||||
end
|
||||
|
||||
defp build_config_tag(acc) do
|
||||
resp =
|
||||
Plug.Test.conn(:get, @config_url |> to_string())
|
||||
|> UtilController.config(nil)
|
||||
|
||||
Map.put(acc, @config_url, resp.resp_body)
|
||||
end
|
||||
end
|
|
@ -86,7 +86,10 @@ defp parse_url(url) do
|
|||
end
|
||||
|
||||
try do
|
||||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: opts)
|
||||
rich_media_agent = Pleroma.Application.user_agent() <> "; Bot"
|
||||
|
||||
{:ok, %Tesla.Env{body: html}} =
|
||||
Pleroma.HTTP.get(url, [{"user-agent", rich_media_agent}], adapter: opts)
|
||||
|
||||
html
|
||||
|> parse_html()
|
||||
|
|
|
@ -328,10 +328,6 @@ defmodule Pleroma.Web.Router do
|
|||
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
|
||||
post("/notifications/read", NotificationController, :mark_as_read)
|
||||
|
||||
patch("/accounts/update_avatar", AccountController, :update_avatar)
|
||||
patch("/accounts/update_banner", AccountController, :update_banner)
|
||||
patch("/accounts/update_background", AccountController, :update_background)
|
||||
|
||||
get("/mascot", MascotController, :show)
|
||||
put("/mascot", MascotController, :update)
|
||||
|
||||
|
@ -516,10 +512,6 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api", Pleroma.Web do
|
||||
pipe_through(:config)
|
||||
|
||||
get("/help/test", TwitterAPI.UtilController, :help_test)
|
||||
post("/help/test", TwitterAPI.UtilController, :help_test)
|
||||
get("/statusnet/config", TwitterAPI.UtilController, :config)
|
||||
get("/statusnet/version", TwitterAPI.UtilController, :version)
|
||||
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
|
||||
end
|
||||
|
||||
|
|
|
@ -104,7 +104,9 @@ def stream(topics, items) do
|
|||
:ok
|
||||
end
|
||||
|
||||
def filtered_by_user?(%User{} = user, %Activity{} = item) do
|
||||
def filtered_by_user?(user, item, streamed_type \\ :activity)
|
||||
|
||||
def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
|
||||
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
|
||||
User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
|
||||
|
||||
|
@ -116,7 +118,9 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) do
|
|||
true <-
|
||||
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
|
||||
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
|
||||
true <- !(item.data["type"] == "Announce" && parent.data["actor"] == user.ap_id),
|
||||
true <-
|
||||
!(streamed_type == :activity && item.data["type"] == "Announce" &&
|
||||
parent.data["actor"] == user.ap_id),
|
||||
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
||||
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
||||
%{host: item_host} <- URI.parse(item.actor),
|
||||
|
@ -131,8 +135,8 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) do
|
|||
end
|
||||
end
|
||||
|
||||
def filtered_by_user?(%User{} = user, %Notification{activity: activity}) do
|
||||
filtered_by_user?(user, activity)
|
||||
def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do
|
||||
filtered_by_user?(user, activity, :notification)
|
||||
end
|
||||
|
||||
defp do_stream("direct", item) do
|
||||
|
|
|
@ -13,9 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Notification
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.TwitterAPI.UtilView
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
|
||||
|
@ -42,12 +40,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
||||
|
||||
plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
|
||||
|
||||
def help_test(conn, _params) do
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
||||
avatar = User.avatar_url(user) do
|
||||
|
@ -89,80 +81,14 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_
|
|||
end
|
||||
end
|
||||
|
||||
def config(%{assigns: %{format: "xml"}} = conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
response = UtilView.status_net_config(instance)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, response)
|
||||
end
|
||||
|
||||
def config(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
|
||||
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: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
|
||||
private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
|
||||
vapidPublicKey: vapid_public_key,
|
||||
accountActivationRequired:
|
||||
bool_to_val(Keyword.get(instance, :account_activation_required, false)),
|
||||
invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
|
||||
safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
|
||||
}
|
||||
|
||||
managed_config = Keyword.get(instance, :managed_config)
|
||||
|
||||
data =
|
||||
if managed_config do
|
||||
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
|
||||
Map.put(data, "pleromafe", pleroma_fe)
|
||||
else
|
||||
data
|
||||
end
|
||||
|
||||
json(conn, %{site: data})
|
||||
end
|
||||
|
||||
defp bool_to_val(true), do: "1"
|
||||
defp bool_to_val(_), do: "0"
|
||||
defp bool_to_val(true, val, _), do: val
|
||||
defp bool_to_val(_, _, val), do: val
|
||||
|
||||
def frontend_configurations(conn, _params) do
|
||||
config =
|
||||
Pleroma.Config.get(:frontend_configurations, %{})
|
||||
Config.get(:frontend_configurations, %{})
|
||||
|> Enum.into(%{})
|
||||
|
||||
json(conn, config)
|
||||
end
|
||||
|
||||
def version(%{assigns: %{format: "xml"}} = conn, _params) do
|
||||
version = Pleroma.Application.named_version()
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, "<version>#{version}</version>")
|
||||
end
|
||||
|
||||
def version(conn, _params) do
|
||||
json(conn, Pleroma.Application.named_version())
|
||||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
emoji =
|
||||
Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -178,6 +178,7 @@ defp deps do
|
|||
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
|
||||
{:telemetry, "~> 0.3"},
|
||||
{:poolboy, "~> 1.5"},
|
||||
{:prometheus, "~> 4.6"},
|
||||
{:prometheus_ex, "~> 3.0"},
|
||||
{:prometheus_plugs, "~> 1.1"},
|
||||
{:prometheus_phoenix, "~> 1.3"},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -92,7 +92,7 @@
|
|||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
|
||||
"pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"},
|
||||
"prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"},
|
||||
"prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"},
|
||||
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
|
||||
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"},
|
||||
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
|
||||
|
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-06-19 14:33+0000\n"
|
||||
"PO-Revision-Date: 2020-06-19 20:38+0000\n"
|
||||
"PO-Revision-Date: 2020-07-09 14:40+0000\n"
|
||||
"Last-Translator: Ben Is <srsbzns@cock.li>\n"
|
||||
"Language-Team: Italian <https://translate.pleroma.social/projects/pleroma/"
|
||||
"pleroma/it/>\n"
|
||||
|
@ -29,258 +29,258 @@ msgstr "non può essere nullo"
|
|||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr ""
|
||||
msgstr "è stato già creato"
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr ""
|
||||
msgstr "non è valido"
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr ""
|
||||
msgstr "è in un formato invalido"
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr ""
|
||||
msgstr "ha una voce invalida"
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
msgstr "è vietato"
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
msgstr "non corrisponde alla verifica"
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "è ancora associato con questa voce"
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "sono ancora associati con questa voce"
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dovrebbe essere %{count} carattere"
|
||||
msgstr[1] "dovrebbero essere %{count} caratteri"
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dovrebbe avere %{count} voce"
|
||||
msgstr[1] "dovrebbe avere %{count} voci"
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dovrebbe contenere almeno %{count} carattere"
|
||||
msgstr[1] "dovrebbe contenere almeno %{count} caratteri"
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dovrebbe avere almeno %{count} voce"
|
||||
msgstr[1] "dovrebbe avere almeno %{count} voci"
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dovrebbe avere al massimo %{count} carattere"
|
||||
msgstr[1] "dovrebbe avere al massimo %{count} caratteri"
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dovrebbe avere al massimo %{count} voce"
|
||||
msgstr[1] "dovrebbe avere al massimo %{count} voci"
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr ""
|
||||
msgstr "dev'essere minore di %{number}"
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr ""
|
||||
msgstr "dev'essere maggiore di %{number}"
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "dev'essere minore o uguale a %{number}"
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "dev'essere maggiore o uguale a %{number}"
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "dev'essere uguale a %{number}"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:421
|
||||
#, elixir-format
|
||||
msgid "Account not found"
|
||||
msgstr ""
|
||||
msgstr "Profilo non trovato"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:249
|
||||
#, elixir-format
|
||||
msgid "Already voted"
|
||||
msgstr ""
|
||||
msgstr "Hai già votato"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:360
|
||||
#, elixir-format
|
||||
msgid "Bad request"
|
||||
msgstr ""
|
||||
msgstr "Richiesta invalida"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
||||
#, elixir-format
|
||||
msgid "Can't delete object"
|
||||
msgstr ""
|
||||
msgstr "Non puoi eliminare quest'oggetto"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
|
||||
#, elixir-format
|
||||
msgid "Can't delete this post"
|
||||
msgstr ""
|
||||
msgstr "Non puoi eliminare questo messaggio"
|
||||
|
||||
#: lib/pleroma/web/controller_helper.ex:95
|
||||
#: lib/pleroma/web/controller_helper.ex:101
|
||||
#, elixir-format
|
||||
msgid "Can't display this activity"
|
||||
msgstr ""
|
||||
msgstr "Non puoi vedere questo elemento"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
|
||||
#, elixir-format
|
||||
msgid "Can't find user"
|
||||
msgstr ""
|
||||
msgstr "Non trovo questo utente"
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
|
||||
#, elixir-format
|
||||
msgid "Can't get favorites"
|
||||
msgstr ""
|
||||
msgstr "Non posso ricevere i gradimenti"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
|
||||
#, elixir-format
|
||||
msgid "Can't like object"
|
||||
msgstr ""
|
||||
msgstr "Non posso gradire quest'oggetto"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:556
|
||||
#, elixir-format
|
||||
msgid "Cannot post an empty status without attachments"
|
||||
msgstr ""
|
||||
msgstr "Non puoi pubblicare un messaggio vuoto senza allegati"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:504
|
||||
#, elixir-format
|
||||
msgid "Comment must be up to %{max_size} characters"
|
||||
msgstr ""
|
||||
msgstr "I commenti posso al massimo consistere di %{max_size} caratteri"
|
||||
|
||||
#: lib/pleroma/config/config_db.ex:222
|
||||
#, elixir-format
|
||||
msgid "Config with params %{params} not found"
|
||||
msgstr ""
|
||||
msgstr "Configurazione con parametri %{max_size} non trovata"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:95
|
||||
#, elixir-format
|
||||
msgid "Could not delete"
|
||||
msgstr ""
|
||||
msgstr "Non eliminato"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:141
|
||||
#, elixir-format
|
||||
msgid "Could not favorite"
|
||||
msgstr ""
|
||||
msgstr "Non gradito"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:370
|
||||
#, elixir-format
|
||||
msgid "Could not pin"
|
||||
msgstr ""
|
||||
msgstr "Non intestato"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:112
|
||||
#, elixir-format
|
||||
msgid "Could not repeat"
|
||||
msgstr ""
|
||||
msgstr "Non ripetuto"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:188
|
||||
#, elixir-format
|
||||
msgid "Could not unfavorite"
|
||||
msgstr ""
|
||||
msgstr "Non sgradito"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:380
|
||||
#, elixir-format
|
||||
msgid "Could not unpin"
|
||||
msgstr ""
|
||||
msgstr "Non de-intestato"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:126
|
||||
#, elixir-format
|
||||
msgid "Could not unrepeat"
|
||||
msgstr ""
|
||||
msgstr "Non de-ripetuto"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:428
|
||||
#: lib/pleroma/web/common_api/common_api.ex:437
|
||||
#, elixir-format
|
||||
msgid "Could not update state"
|
||||
msgstr ""
|
||||
msgstr "Non aggiornato"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
|
||||
#, elixir-format
|
||||
msgid "Error."
|
||||
msgstr ""
|
||||
msgstr "Errore."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
|
||||
#, elixir-format
|
||||
msgid "Invalid CAPTCHA"
|
||||
msgstr ""
|
||||
msgstr "CAPTCHA invalido"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:569
|
||||
#, elixir-format
|
||||
msgid "Invalid credentials"
|
||||
msgstr ""
|
||||
msgstr "Credenziali invalide"
|
||||
|
||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
|
||||
#, elixir-format
|
||||
msgid "Invalid credentials."
|
||||
msgstr ""
|
||||
msgstr "Credenziali invalide."
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:265
|
||||
#, elixir-format
|
||||
msgid "Invalid indices"
|
||||
msgstr ""
|
||||
msgstr "Indici invalidi"
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
|
||||
#, elixir-format
|
||||
msgid "Invalid parameters"
|
||||
msgstr ""
|
||||
msgstr "Parametri invalidi"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:411
|
||||
#, elixir-format
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
msgstr "Parola d'ordine invalida."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
|
||||
#, elixir-format
|
||||
msgid "Invalid request"
|
||||
msgstr ""
|
||||
msgstr "Richiesta invalida"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
|
||||
#, elixir-format
|
||||
msgid "Kocaptcha service unavailable"
|
||||
msgstr ""
|
||||
msgstr "Servizio Kocaptcha non disponibile"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
|
||||
#, elixir-format
|
||||
msgid "Missing parameters"
|
||||
msgstr ""
|
||||
msgstr "Parametri mancanti"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:540
|
||||
#, elixir-format
|
||||
msgid "No such conversation"
|
||||
msgstr ""
|
||||
msgstr "Conversazione inesistente"
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
|
||||
#, elixir-format
|
||||
msgid "No such permission_group"
|
||||
msgstr ""
|
||||
msgstr "permission_group non esistente"
|
||||
|
||||
#: lib/pleroma/plugs/uploaded_media.ex:74
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
|
||||
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
|
||||
#, elixir-format
|
||||
msgid "Not found"
|
||||
msgstr ""
|
||||
msgstr "Non trovato"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:241
|
||||
#, elixir-format
|
||||
msgid "Poll's author can't vote"
|
||||
msgstr ""
|
||||
msgstr "L'autore del sondaggio non può votare"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
|
||||
|
@ -288,215 +288,215 @@ msgstr ""
|
|||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
||||
#, elixir-format
|
||||
msgid "Record not found"
|
||||
msgstr ""
|
||||
msgstr "Voce non trovata"
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
|
||||
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
|
||||
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
|
||||
#, elixir-format
|
||||
msgid "Something went wrong"
|
||||
msgstr ""
|
||||
msgstr "C'è stato un problema"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:107
|
||||
#, elixir-format
|
||||
msgid "The message visibility must be direct"
|
||||
msgstr ""
|
||||
msgstr "Il messaggio dev'essere privato"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:566
|
||||
#, elixir-format
|
||||
msgid "The status is over the character limit"
|
||||
msgstr ""
|
||||
msgstr "Il messaggio ha superato la lunghezza massima"
|
||||
|
||||
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
|
||||
#, elixir-format
|
||||
msgid "This resource requires authentication."
|
||||
msgstr ""
|
||||
msgstr "Accedi per leggere."
|
||||
|
||||
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
||||
#, elixir-format
|
||||
msgid "Throttled"
|
||||
msgstr ""
|
||||
msgstr "Strozzato"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:266
|
||||
#, elixir-format
|
||||
msgid "Too many choices"
|
||||
msgstr ""
|
||||
msgstr "Troppe alternative"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
|
||||
#, elixir-format
|
||||
msgid "Unhandled activity type"
|
||||
msgstr ""
|
||||
msgstr "Tipo di attività non gestibile"
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
|
||||
#, elixir-format
|
||||
msgid "You can't revoke your own admin status."
|
||||
msgstr ""
|
||||
msgstr "Non puoi divestirti da solo."
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:218
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:309
|
||||
#, elixir-format
|
||||
msgid "Your account is currently disabled"
|
||||
msgstr ""
|
||||
msgstr "Il tuo profilo è attualmente disabilitato"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:180
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:332
|
||||
#, elixir-format
|
||||
msgid "Your login is missing a confirmed e-mail address"
|
||||
msgstr ""
|
||||
msgstr "Devi aggiungere un indirizzo email valido"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
|
||||
#, elixir-format
|
||||
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
||||
msgstr ""
|
||||
msgstr "non puoi leggere i messaggi privati di %{nickname} come %{as_nickname}"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
|
||||
#, elixir-format
|
||||
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
||||
msgstr ""
|
||||
msgstr "non puoi aggiornare gli inviati di %{nickname} come %{as_nickname}"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:388
|
||||
#, elixir-format
|
||||
msgid "conversation is already muted"
|
||||
msgstr ""
|
||||
msgstr "la conversazione è già zittita"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
|
||||
#, elixir-format
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
msgstr "errore"
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
|
||||
#, elixir-format
|
||||
msgid "mascots can only be images"
|
||||
msgstr ""
|
||||
msgstr "le mascotte possono solo essere immagini"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
|
||||
#, elixir-format
|
||||
msgid "not found"
|
||||
msgstr ""
|
||||
msgstr "non trovato"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:395
|
||||
#, elixir-format
|
||||
msgid "Bad OAuth request."
|
||||
msgstr ""
|
||||
msgstr "Richiesta OAuth malformata."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
|
||||
#, elixir-format
|
||||
msgid "CAPTCHA already used"
|
||||
msgstr ""
|
||||
msgstr "CAPTCHA già utilizzato"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
|
||||
#, elixir-format
|
||||
msgid "CAPTCHA expired"
|
||||
msgstr ""
|
||||
msgstr "CAPTCHA scaduto"
|
||||
|
||||
#: lib/pleroma/plugs/uploaded_media.ex:55
|
||||
#, elixir-format
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
msgstr "Fallito"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:411
|
||||
#, elixir-format
|
||||
msgid "Failed to authenticate: %{message}."
|
||||
msgstr ""
|
||||
msgstr "Autenticazione fallita per: %{message}."
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:442
|
||||
#, elixir-format
|
||||
msgid "Failed to set up user account."
|
||||
msgstr ""
|
||||
msgstr "Profilo utente non creato."
|
||||
|
||||
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
|
||||
#, elixir-format
|
||||
msgid "Insufficient permissions: %{permissions}."
|
||||
msgstr ""
|
||||
msgstr "Permessi insufficienti: %{permissions}."
|
||||
|
||||
#: lib/pleroma/plugs/uploaded_media.ex:94
|
||||
#, elixir-format
|
||||
msgid "Internal Error"
|
||||
msgstr ""
|
||||
msgstr "Errore interno"
|
||||
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:22
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
||||
#, elixir-format
|
||||
msgid "Invalid Username/Password"
|
||||
msgstr ""
|
||||
msgstr "Nome utente/parola d'ordine invalidi"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
||||
#, elixir-format
|
||||
msgid "Invalid answer data"
|
||||
msgstr ""
|
||||
msgstr "Risposta malformata"
|
||||
|
||||
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
|
||||
#, elixir-format
|
||||
msgid "Nodeinfo schema version not handled"
|
||||
msgstr ""
|
||||
msgstr "Versione schema nodeinfo non compatibile"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:169
|
||||
#, elixir-format
|
||||
msgid "This action is outside the authorized scopes"
|
||||
msgstr ""
|
||||
msgstr "Quest'azione non è consentita in questa visibilità"
|
||||
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:14
|
||||
#, elixir-format
|
||||
msgid "Unknown error, please check the details and try again."
|
||||
msgstr ""
|
||||
msgstr "Errore sconosciuto, controlla i dettagli e riprova."
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:116
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:155
|
||||
#, elixir-format
|
||||
msgid "Unlisted redirect_uri."
|
||||
msgstr ""
|
||||
msgstr "redirect_uri nascosto."
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:391
|
||||
#, elixir-format
|
||||
msgid "Unsupported OAuth provider: %{provider}."
|
||||
msgstr ""
|
||||
msgstr "Gestore OAuth non supportato: %{provider}."
|
||||
|
||||
#: lib/pleroma/uploaders/uploader.ex:72
|
||||
#, elixir-format
|
||||
msgid "Uploader callback timeout"
|
||||
msgstr ""
|
||||
msgstr "Callback caricatmento scaduta"
|
||||
|
||||
#: lib/pleroma/web/uploader_controller.ex:23
|
||||
#, elixir-format
|
||||
msgid "bad request"
|
||||
msgstr ""
|
||||
msgstr "richiesta malformata"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
|
||||
#, elixir-format
|
||||
msgid "CAPTCHA Error"
|
||||
msgstr ""
|
||||
msgstr "Errore CAPTCHA"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:200
|
||||
#, elixir-format
|
||||
msgid "Could not add reaction emoji"
|
||||
msgstr ""
|
||||
msgstr "Reazione emoji non riuscita"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:211
|
||||
#, elixir-format
|
||||
msgid "Could not remove reaction emoji"
|
||||
msgstr ""
|
||||
msgstr "Rimozione reazione non riuscita"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
|
||||
#, elixir-format
|
||||
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
|
||||
msgstr ""
|
||||
msgstr "CAPTCHA invalido (Parametro mancante: %{name})"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
|
||||
#, elixir-format
|
||||
msgid "List not found"
|
||||
msgstr ""
|
||||
msgstr "Lista non trovata"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
|
||||
#, elixir-format
|
||||
msgid "Missing parameter: %{name}"
|
||||
msgstr ""
|
||||
msgstr "Parametro mancante: %{name}"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:207
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:322
|
||||
#, elixir-format
|
||||
msgid "Password reset is required"
|
||||
msgstr ""
|
||||
msgstr "Necessario reimpostare parola d'ordine"
|
||||
|
||||
#: lib/pleroma/tests/auth_test_controller.ex:9
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
|
||||
|
@ -528,53 +528,58 @@ msgstr ""
|
|||
#, elixir-format
|
||||
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||
msgstr ""
|
||||
"Sicurezza violata: il controllo autorizzazioni di OAuth non è stato svolto "
|
||||
"né saltato."
|
||||
|
||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
|
||||
#, elixir-format
|
||||
msgid "Two-factor authentication enabled, you must use a access token."
|
||||
msgstr ""
|
||||
"Autenticazione bifattoriale abilitata, devi utilizzare una chiave d'accesso."
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while adding file to pack."
|
||||
msgstr ""
|
||||
msgstr "Errore inaspettato durante l'aggiunta del file al pacchetto."
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while creating pack."
|
||||
msgstr ""
|
||||
msgstr "Errore inaspettato durante la creazione del pacchetto."
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while removing file from pack."
|
||||
msgstr ""
|
||||
msgstr "Errore inaspettato durante la rimozione del file dal pacchetto."
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while updating file in pack."
|
||||
msgstr ""
|
||||
msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto."
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while updating pack metadata."
|
||||
msgstr ""
|
||||
msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto."
|
||||
|
||||
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
|
||||
#, elixir-format
|
||||
msgid "User is not an admin or OAuth admin scope is not granted."
|
||||
msgstr ""
|
||||
"L'utente non è un amministratore o non ha ricevuto questa autorizzazione "
|
||||
"OAuth."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
||||
#, elixir-format
|
||||
msgid "Web push subscription is disabled on this Pleroma instance"
|
||||
msgstr ""
|
||||
msgstr "Gli aggiornamenti web push non sono disponibili in questa stanza"
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
|
||||
#, elixir-format
|
||||
msgid "You can't revoke your own admin/moderator status."
|
||||
msgstr ""
|
||||
msgstr "Non puoi divestire te stesso."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
|
||||
#, elixir-format
|
||||
msgid "authorization required for timeline view"
|
||||
msgstr ""
|
||||
msgstr "autorizzazione richiesta per vedere la sequenza"
|
||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-13 16:37+0000\n"
|
||||
"PO-Revision-Date: 2020-05-16 17:13+0000\n"
|
||||
"Last-Translator: Jędrzej Tomaszewski <jederow@hotmail.com>\n"
|
||||
"PO-Revision-Date: 2020-07-09 14:40+0000\n"
|
||||
"Last-Translator: Ben Is <srsbzns@cock.li>\n"
|
||||
"Language-Team: Polish <https://translate.pleroma.social/projects/pleroma/"
|
||||
"pleroma/pl/>\n"
|
||||
"Language: pl\n"
|
||||
|
@ -50,7 +50,7 @@ msgstr "jest zarezerwowany"
|
|||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
msgstr "nie pasuje do potwierdzenia"
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddChatAcceptanceToUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:users) do
|
||||
add(:accepts_chat_messages, :boolean, nullable: true)
|
||||
end
|
||||
|
||||
execute("update users set accepts_chat_messages = true where local = true")
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:users) do
|
||||
remove(:accepts_chat_messages)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Pleroma.Repo.Migrations.RemoveTeslaFromConfig do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("DELETE FROM config WHERE config.group = ':tesla'")
|
||||
end
|
||||
|
||||
def down do
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Pleroma.Repo.Migrations.InstancesAddFavicon do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:instances) do
|
||||
add(:favicon, :string)
|
||||
add(:favicon_updated_at, :naive_datetime)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
defmodule Pleroma.Repo.Migrations.DropUserTrigramIndex do
|
||||
@moduledoc "Drops unused trigram index on `users` (FTS index is being used instead)"
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
drop_if_exists(index(:users, [], name: :users_trigram_index))
|
||||
end
|
||||
|
||||
def down do
|
||||
create_if_not_exists(
|
||||
index(:users, ["(trim(nickname || ' ' || coalesce(name, ''))) gist_trgm_ops"],
|
||||
name: :users_trigram_index,
|
||||
using: :gist
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.18fea621d430000acc27.css rel=stylesheet><link href=/static/css/app.613cef07981cd95ccceb.css rel=stylesheet><link href=/static/fontello.1589385935077.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.561a1c605d1dfb0e6f74.js></script><script type=text/javascript src=/static/js/app.838ffa9aecf210c7d744.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.77b1644622e3bae24b6b.css rel=stylesheet><link href=/static/fontello.1594374054351.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.247dc52c7abe6a0dab87.js></script><script type=text/javascript src=/static/js/app.1e68e208590653dab5aa.js></script></body></html>
|
|
@ -13,6 +13,7 @@
|
|||
},
|
||||
"discoverable": "toot:discoverable",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"capabilities": "litepub:capabilities",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"schema": "http://schema.org#",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
|
|
|
@ -80,6 +80,7 @@ .display-name {
|
|||
/* keep emoji from being hilariously huge */
|
||||
.display-name img {
|
||||
max-height: 1em;
|
||||
max-width: 1em;
|
||||
}
|
||||
|
||||
.display-name .nickname {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
"logoMargin": ".1em",
|
||||
"logoMask": true,
|
||||
"minimalScopesMode": false,
|
||||
"noAttachmentLinks": false,
|
||||
"nsfwCensorImage": "",
|
||||
"postContentType": "text/plain",
|
||||
"redirectRootLogin": "/main/friends",
|
||||
|
@ -22,6 +21,7 @@
|
|||
"scopeCopy": true,
|
||||
"showFeaturesPanel": true,
|
||||
"showInstanceSpecificPanel": false,
|
||||
"sidebarRight": false,
|
||||
"subjectLineBehavior": "email",
|
||||
"theme": "pleroma-dark",
|
||||
"webPushNotifications": false
|
||||
|
|
9
priv/static/static/css/2.0778a6a864a1307a6c41.css
Normal file
9
priv/static/static/css/2.0778a6a864a1307a6c41.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.with-subscription-loading {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.with-subscription-loading .error {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=2.0778a6a864a1307a6c41.css.map*/
|
1
priv/static/static/css/2.0778a6a864a1307a6c41.css.map
Normal file
1
priv/static/static/css/2.0778a6a864a1307a6c41.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/2.0778a6a864a1307a6c41.css","sourcesContent":[".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""}
|
|
@ -1,11 +1,11 @@
|
|||
/*!
|
||||
* Cropper.js v1.5.6
|
||||
* Cropper.js v1.4.3
|
||||
* https://fengyuanchen.github.io/cropperjs
|
||||
*
|
||||
* Copyright 2015-present Chen Fengyuan
|
||||
* Released under the MIT license
|
||||
*
|
||||
* Date: 2019-10-04T04:33:44.164Z
|
||||
* Date: 2018-10-24T13:07:11.429Z
|
||||
*/
|
||||
|
||||
.cropper-container {
|
||||
|
@ -16,6 +16,7 @@ .cropper-container {
|
|||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
@ -55,14 +56,14 @@ .cropper-drag-box {
|
|||
|
||||
.cropper-modal {
|
||||
background-color: #000;
|
||||
opacity: 0.5;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.cropper-view-box {
|
||||
display: block;
|
||||
height: 100%;
|
||||
outline: 1px solid #39f;
|
||||
outline-color: rgba(51, 153, 255, 0.75);
|
||||
outline: 1px solid #39f;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -70,7 +71,7 @@ .cropper-view-box {
|
|||
.cropper-dashed {
|
||||
border: 0 dashed #eee;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
opacity: .5;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
@ -96,28 +97,28 @@ .cropper-center {
|
|||
display: block;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
opacity: 0.75;
|
||||
opacity: .75;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.cropper-center::before,
|
||||
.cropper-center::after {
|
||||
.cropper-center:before,
|
||||
.cropper-center:after {
|
||||
background-color: #eee;
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.cropper-center::before {
|
||||
.cropper-center:before {
|
||||
height: 1px;
|
||||
left: -3px;
|
||||
top: 0;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.cropper-center::after {
|
||||
.cropper-center:after {
|
||||
height: 7px;
|
||||
left: 0;
|
||||
top: -3px;
|
||||
|
@ -129,7 +130,7 @@ .cropper-line,
|
|||
.cropper-point {
|
||||
display: block;
|
||||
height: 100%;
|
||||
opacity: 0.1;
|
||||
opacity: .1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -175,7 +176,7 @@ .cropper-line.line-s {
|
|||
.cropper-point {
|
||||
background-color: #39f;
|
||||
height: 5px;
|
||||
opacity: 0.75;
|
||||
opacity: .75;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
|
@ -251,12 +252,12 @@ @media (min-width: 992px) {
|
|||
@media (min-width: 1200px) {
|
||||
.cropper-point.point-se {
|
||||
height: 5px;
|
||||
opacity: 0.75;
|
||||
opacity: .75;
|
||||
width: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.cropper-point.point-se::before {
|
||||
.cropper-point.point-se:before {
|
||||
background-color: #39f;
|
||||
bottom: -50%;
|
||||
content: ' ';
|
||||
|
@ -303,4 +304,4 @@ .cropper-disabled .cropper-point {
|
|||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=vendors~app.18fea621d430000acc27.css.map*/
|
||||
/*# sourceMappingURL=3.b2603a50868c68a1c192.css.map*/
|
1
priv/static/static/css/3.b2603a50868c68a1c192.css.map
Normal file
1
priv/static/static/css/3.b2603a50868c68a1c192.css.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,5 +0,0 @@
|
|||
.with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}
|
||||
.tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:"";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:"";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}
|
||||
.with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}
|
||||
|
||||
/*# sourceMappingURL=app.613cef07981cd95ccceb.css.map*/
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA,uBAAuB,aAAa,kBAAkB,qBAAqB,sBAAsB,qCAAqC,8BAA8B,e;ACApK,cAAc,oBAAoB,aAAa,0BAA0B,sBAAsB,wBAAwB,kBAAkB,cAAc,eAAe,gCAAgC,aAAa,wCAAwC,0BAA0B,aAAa,gBAAgB,oBAAoB,oBAAoB,aAAa,kBAAkB,WAAW,kBAAkB,gBAAgB,gBAAgB,sBAAsB,uDAAuD,cAAc,WAAW,kBAAkB,cAAc,wBAAwB,yBAAyB,wCAAwC,iCAAiC,YAAY,kBAAkB,oBAAoB,aAAa,kBAAkB,cAAc,sCAAsC,WAAW,cAAc,kBAAkB,4BAA4B,6BAA6B,gBAAgB,oBAAoB,oBAAoB,mBAAmB,cAAc,8BAA8B,yBAAyB,qCAAqC,mDAAmD,UAAU,yDAAyD,UAAU,6CAA6C,uBAAuB,UAAU,cAAc,oCAAoC,0CAA0C,gBAAgB,mBAAmB,gBAAgB,qDAAqD,WAAW,kBAAkB,OAAO,QAAQ,SAAS,UAAU,wBAAwB,yBAAyB,wC;ACAtlD,2BAA2B,aAAa,kBAAkB,kCAAkC,e","file":"static/css/app.613cef07981cd95ccceb.css","sourcesContent":[".with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}",".tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:\"\";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:\"\";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}",".with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}"],"sourceRoot":""}
|
246
priv/static/static/css/app.77b1644622e3bae24b6b.css
Normal file
246
priv/static/static/css/app.77b1644622e3bae24b6b.css
Normal file
|
@ -0,0 +1,246 @@
|
|||
.tab-switcher {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
.tab-switcher .tab-icon {
|
||||
font-size: 2em;
|
||||
display: block;
|
||||
}
|
||||
.tab-switcher.top-tabs {
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tab-switcher.top-tabs > .tabs {
|
||||
width: 100%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
padding-top: 5px;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {
|
||||
content: "";
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: #222;
|
||||
border-bottom-color: var(--border, #222);
|
||||
}
|
||||
.tab-switcher.top-tabs > .tabs .tab-wrapper {
|
||||
height: 28px;
|
||||
}
|
||||
.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: #222;
|
||||
border-bottom-color: var(--border, #222);
|
||||
}
|
||||
.tab-switcher.top-tabs > .tabs .tab {
|
||||
width: 100%;
|
||||
min-width: 1px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
padding-bottom: 99px;
|
||||
margin-bottom: -93px;
|
||||
}
|
||||
.tab-switcher.top-tabs .contents.scrollable-tabs {
|
||||
-ms-flex-preferred-size: 0;
|
||||
flex-basis: 0;
|
||||
}
|
||||
.tab-switcher.side-tabs {
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
@media all and (max-width: 800px) {
|
||||
.tab-switcher.side-tabs {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
.tab-switcher.side-tabs > .contents {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs {
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {
|
||||
-ms-flex-negative: 0;
|
||||
flex-shrink: 0;
|
||||
-ms-flex-preferred-size: 0.5em;
|
||||
flex-basis: 0.5em;
|
||||
content: "";
|
||||
border-right: 1px solid;
|
||||
border-right-color: #222;
|
||||
border-right-color: var(--border, #222);
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs::after {
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs::before {
|
||||
-ms-flex-positive: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs .tab-wrapper {
|
||||
min-width: 10em;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
@media all and (max-width: 800px) {
|
||||
.tab-switcher.side-tabs > .tabs .tab-wrapper {
|
||||
min-width: 1em;
|
||||
}
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-right: 1px solid;
|
||||
border-right-color: #222;
|
||||
border-right-color: var(--border, #222);
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs .tab-wrapper::before {
|
||||
-ms-flex: 0 0 6px;
|
||||
flex: 0 0 6px;
|
||||
content: "";
|
||||
border-right: 1px solid;
|
||||
border-right-color: #222;
|
||||
border-right-color: var(--border, #222);
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs .tab {
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
box-sizing: content-box;
|
||||
min-width: 10em;
|
||||
min-width: 1px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
padding-left: 1em;
|
||||
padding-right: calc(1em + 200px);
|
||||
margin-right: -200px;
|
||||
margin-left: 1em;
|
||||
}
|
||||
@media all and (max-width: 800px) {
|
||||
.tab-switcher.side-tabs > .tabs .tab {
|
||||
padding-left: 0.25em;
|
||||
padding-right: calc(.25em + 200px);
|
||||
margin-right: calc(.25em - 200px);
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
.tab-switcher.side-tabs > .tabs .tab .text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.tab-switcher .contents {
|
||||
-ms-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
min-height: 0px;
|
||||
}
|
||||
.tab-switcher .contents .hidden {
|
||||
display: none;
|
||||
}
|
||||
.tab-switcher .contents .full-height:not(.hidden) {
|
||||
height: 100%;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.tab-switcher .contents.scrollable-tabs {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tab-switcher .tab {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
padding: 6px 1em;
|
||||
background-color: #182230;
|
||||
background-color: var(--tab, #182230);
|
||||
}
|
||||
.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {
|
||||
color: #b9b9ba;
|
||||
color: var(--tabText, #b9b9ba);
|
||||
}
|
||||
.tab-switcher .tab:not(.active) {
|
||||
z-index: 4;
|
||||
}
|
||||
.tab-switcher .tab:not(.active):hover {
|
||||
z-index: 6;
|
||||
}
|
||||
.tab-switcher .tab.active {
|
||||
background: transparent;
|
||||
z-index: 5;
|
||||
color: #b9b9ba;
|
||||
color: var(--tabActiveText, #b9b9ba);
|
||||
}
|
||||
.tab-switcher .tab img {
|
||||
max-height: 26px;
|
||||
vertical-align: top;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.tab-switcher .tabs {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tab-switcher .tabs::after, .tab-switcher .tabs::before {
|
||||
display: block;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.tab-switcher .tab-wrapper {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.tab-switcher .tab-wrapper:not(.active)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 7;
|
||||
}
|
||||
.tab-switcher .mobile-label {
|
||||
padding-left: 0.3em;
|
||||
padding-bottom: 0.25em;
|
||||
margin-top: 0.5em;
|
||||
margin-left: 0.2em;
|
||||
margin-bottom: 0.25em;
|
||||
border-bottom: 1px solid var(--border, #222);
|
||||
}
|
||||
@media all and (min-width: 800px) {
|
||||
.tab-switcher .mobile-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.with-load-more-footer {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-top: 1px solid;
|
||||
border-top-color: #222;
|
||||
border-top-color: var(--border, #222);
|
||||
}
|
||||
.with-load-more-footer .error {
|
||||
font-size: 14px;
|
||||
}
|
||||
.with-load-more-footer a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=app.77b1644622e3bae24b6b.css.map*/
|
1
priv/static/static/css/app.77b1644622e3bae24b6b.css.map
Normal file
1
priv/static/static/css/app.77b1644622e3bae24b6b.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -80,8 +80,18 @@
|
|||
|
||||
<glyph glyph-name="user" unicode="" d="M714 76q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
|
||||
|
||||
<glyph glyph-name="download" unicode="" d="M714 107q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-39l-250-250q-10-11-25-11t-25 11l-250 250q-17 16-8 39 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
|
||||
|
||||
<glyph glyph-name="bookmark" unicode="" d="M650 786q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
|
||||
|
||||
<glyph glyph-name="ok" unicode="" d="M933 541q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="music" unicode="" d="M857 732v-625q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 33 58 18 54 6q58 0 107-22v300l-429-132v-396q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 34 58 17 54 6q58 0 107-21v539q0 17 10 32t28 20l464 142q7 3 16 3 22 0 38-16t15-38z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="doc" unicode="" d="M819 645q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="block" unicode="" d="M732 359q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-111-41-153q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="spin3" unicode="" 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="" 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" />
|
||||
|
@ -90,6 +100,10 @@
|
|||
|
||||
<glyph glyph-name="link-ext-alt" unicode="" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-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="bookmark-empty" unicode="" d="M643 714h-572v-693l237 227 49 47 50-47 236-227v693z m7 72q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
|
||||
|
||||
<glyph glyph-name="filter" unicode="" d="M783 692q9-22-8-39l-275-275v-414q0-23-22-33-7-3-14-3-15 0-25 11l-143 143q-10 11-10 25v271l-275 275q-18 17-8 39 9 22 33 22h714q23 0 33-22z" horiz-adv-x="785.7" />
|
||||
|
||||
<glyph glyph-name="menu" unicode="" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="mail-alt" unicode="" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" />
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
Binary file not shown.
BIN
priv/static/static/font/fontello.1594374054351.woff
Normal file
BIN
priv/static/static/font/fontello.1594374054351.woff
Normal file
Binary file not shown.
BIN
priv/static/static/font/fontello.1594374054351.woff2
Normal file
BIN
priv/static/static/font/fontello.1594374054351.woff2
Normal file
Binary file not shown.
152
priv/static/static/fontello.1594030805019.css
vendored
Normal file
152
priv/static/static/fontello.1594030805019.css
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
@font-face {
|
||||
font-family: "Icons";
|
||||
src: url("./font/fontello.1594030805019.eot");
|
||||
src: url("./font/fontello.1594030805019.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1594030805019.woff2") format("woff2"),
|
||||
url("./font/fontello.1594030805019.woff") format("woff"),
|
||||
url("./font/fontello.1594030805019.ttf") format("truetype"),
|
||||
url("./font/fontello.1594030805019.svg") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"]::before,
|
||||
[class*=" icon-"]::before {
|
||||
font-family: "Icons";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: .2em;
|
||||
text-align: center;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1em;
|
||||
margin-left: .2em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-spin4::before { content: "\e834"; }
|
||||
|
||||
.icon-cancel::before { content: "\e800"; }
|
||||
|
||||
.icon-upload::before { content: "\e801"; }
|
||||
|
||||
.icon-spin3::before { content: "\e832"; }
|
||||
|
||||
.icon-reply::before { content: "\f112"; }
|
||||
|
||||
.icon-star::before { content: "\e802"; }
|
||||
|
||||
.icon-star-empty::before { content: "\e803"; }
|
||||
|
||||
.icon-retweet::before { content: "\e804"; }
|
||||
|
||||
.icon-eye-off::before { content: "\e805"; }
|
||||
|
||||
.icon-binoculars::before { content: "\f1e5"; }
|
||||
|
||||
.icon-cog::before { content: "\e807"; }
|
||||
|
||||
.icon-user-plus::before { content: "\f234"; }
|
||||
|
||||
.icon-menu::before { content: "\f0c9"; }
|
||||
|
||||
.icon-logout::before { content: "\e808"; }
|
||||
|
||||
.icon-down-open::before { content: "\e809"; }
|
||||
|
||||
.icon-attach::before { content: "\e80a"; }
|
||||
|
||||
.icon-link-ext::before { content: "\f08e"; }
|
||||
|
||||
.icon-link-ext-alt::before { content: "\f08f"; }
|
||||
|
||||
.icon-picture::before { content: "\e80b"; }
|
||||
|
||||
.icon-video::before { content: "\e80c"; }
|
||||
|
||||
.icon-right-open::before { content: "\e80d"; }
|
||||
|
||||
.icon-left-open::before { content: "\e80e"; }
|
||||
|
||||
.icon-up-open::before { content: "\e80f"; }
|
||||
|
||||
.icon-comment-empty::before { content: "\f0e5"; }
|
||||
|
||||
.icon-mail-alt::before { content: "\f0e0"; }
|
||||
|
||||
.icon-lock::before { content: "\e811"; }
|
||||
|
||||
.icon-lock-open-alt::before { content: "\f13e"; }
|
||||
|
||||
.icon-globe::before { content: "\e812"; }
|
||||
|
||||
.icon-brush::before { content: "\e813"; }
|
||||
|
||||
.icon-search::before { content: "\e806"; }
|
||||
|
||||
.icon-adjust::before { content: "\e816"; }
|
||||
|
||||
.icon-thumbs-up-alt::before { content: "\f164"; }
|
||||
|
||||
.icon-attention::before { content: "\e814"; }
|
||||
|
||||
.icon-plus-squared::before { content: "\f0fe"; }
|
||||
|
||||
.icon-plus::before { content: "\e815"; }
|
||||
|
||||
.icon-edit::before { content: "\e817"; }
|
||||
|
||||
.icon-play-circled::before { content: "\f144"; }
|
||||
|
||||
.icon-pencil::before { content: "\e818"; }
|
||||
|
||||
.icon-chart-bar::before { content: "\e81b"; }
|
||||
|
||||
.icon-smile::before { content: "\f118"; }
|
||||
|
||||
.icon-bell-alt::before { content: "\f0f3"; }
|
||||
|
||||
.icon-wrench::before { content: "\e81a"; }
|
||||
|
||||
.icon-pin::before { content: "\e819"; }
|
||||
|
||||
.icon-ellipsis::before { content: "\f141"; }
|
||||
|
||||
.icon-bell-ringing-o::before { content: "\e810"; }
|
||||
|
||||
.icon-zoom-in::before { content: "\e81c"; }
|
||||
|
||||
.icon-gauge::before { content: "\f0e4"; }
|
||||
|
||||
.icon-users::before { content: "\e81d"; }
|
||||
|
||||
.icon-info-circled::before { content: "\e81f"; }
|
||||
|
||||
.icon-home-2::before { content: "\e821"; }
|
||||
|
||||
.icon-chat::before { content: "\e81e"; }
|
||||
|
||||
.icon-login::before { content: "\e820"; }
|
||||
|
||||
.icon-arrow-curved::before { content: "\e822"; }
|
||||
|
||||
.icon-link::before { content: "\e823"; }
|
||||
|
||||
.icon-share::before { content: "\f1e0"; }
|
||||
|
||||
.icon-user::before { content: "\e824"; }
|
||||
|
||||
.icon-ok::before { content: "\e827"; }
|
||||
|
||||
.icon-filter::before { content: "\f0b0"; }
|
||||
|
||||
.icon-download::before { content: "\e825"; }
|
||||
|
||||
.icon-bookmark::before { content: "\e826"; }
|
||||
|
||||
.icon-bookmark-empty::before { content: "\f097"; }
|
156
priv/static/static/fontello.1594134783339.css
vendored
Normal file
156
priv/static/static/fontello.1594134783339.css
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
@font-face {
|
||||
font-family: "Icons";
|
||||
src: url("./font/fontello.1594134783339.eot");
|
||||
src: url("./font/fontello.1594134783339.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1594134783339.woff2") format("woff2"),
|
||||
url("./font/fontello.1594134783339.woff") format("woff"),
|
||||
url("./font/fontello.1594134783339.ttf") format("truetype"),
|
||||
url("./font/fontello.1594134783339.svg") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"]::before,
|
||||
[class*=" icon-"]::before {
|
||||
font-family: "Icons";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: .2em;
|
||||
text-align: center;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1em;
|
||||
margin-left: .2em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-spin4::before { content: "\e834"; }
|
||||
|
||||
.icon-cancel::before { content: "\e800"; }
|
||||
|
||||
.icon-upload::before { content: "\e801"; }
|
||||
|
||||
.icon-spin3::before { content: "\e832"; }
|
||||
|
||||
.icon-reply::before { content: "\f112"; }
|
||||
|
||||
.icon-star::before { content: "\e802"; }
|
||||
|
||||
.icon-star-empty::before { content: "\e803"; }
|
||||
|
||||
.icon-retweet::before { content: "\e804"; }
|
||||
|
||||
.icon-eye-off::before { content: "\e805"; }
|
||||
|
||||
.icon-binoculars::before { content: "\f1e5"; }
|
||||
|
||||
.icon-cog::before { content: "\e807"; }
|
||||
|
||||
.icon-user-plus::before { content: "\f234"; }
|
||||
|
||||
.icon-menu::before { content: "\f0c9"; }
|
||||
|
||||
.icon-logout::before { content: "\e808"; }
|
||||
|
||||
.icon-down-open::before { content: "\e809"; }
|
||||
|
||||
.icon-attach::before { content: "\e80a"; }
|
||||
|
||||
.icon-link-ext::before { content: "\f08e"; }
|
||||
|
||||
.icon-link-ext-alt::before { content: "\f08f"; }
|
||||
|
||||
.icon-picture::before { content: "\e80b"; }
|
||||
|
||||
.icon-video::before { content: "\e80c"; }
|
||||
|
||||
.icon-right-open::before { content: "\e80d"; }
|
||||
|
||||
.icon-left-open::before { content: "\e80e"; }
|
||||
|
||||
.icon-up-open::before { content: "\e80f"; }
|
||||
|
||||
.icon-comment-empty::before { content: "\f0e5"; }
|
||||
|
||||
.icon-mail-alt::before { content: "\f0e0"; }
|
||||
|
||||
.icon-lock::before { content: "\e811"; }
|
||||
|
||||
.icon-lock-open-alt::before { content: "\f13e"; }
|
||||
|
||||
.icon-globe::before { content: "\e812"; }
|
||||
|
||||
.icon-brush::before { content: "\e813"; }
|
||||
|
||||
.icon-search::before { content: "\e806"; }
|
||||
|
||||
.icon-adjust::before { content: "\e816"; }
|
||||
|
||||
.icon-thumbs-up-alt::before { content: "\f164"; }
|
||||
|
||||
.icon-attention::before { content: "\e814"; }
|
||||
|
||||
.icon-plus-squared::before { content: "\f0fe"; }
|
||||
|
||||
.icon-plus::before { content: "\e815"; }
|
||||
|
||||
.icon-edit::before { content: "\e817"; }
|
||||
|
||||
.icon-play-circled::before { content: "\f144"; }
|
||||
|
||||
.icon-pencil::before { content: "\e818"; }
|
||||
|
||||
.icon-chart-bar::before { content: "\e81b"; }
|
||||
|
||||
.icon-smile::before { content: "\f118"; }
|
||||
|
||||
.icon-bell-alt::before { content: "\f0f3"; }
|
||||
|
||||
.icon-wrench::before { content: "\e81a"; }
|
||||
|
||||
.icon-pin::before { content: "\e819"; }
|
||||
|
||||
.icon-ellipsis::before { content: "\f141"; }
|
||||
|
||||
.icon-bell-ringing-o::before { content: "\e810"; }
|
||||
|
||||
.icon-zoom-in::before { content: "\e81c"; }
|
||||
|
||||
.icon-gauge::before { content: "\f0e4"; }
|
||||
|
||||
.icon-users::before { content: "\e81d"; }
|
||||
|
||||
.icon-info-circled::before { content: "\e81f"; }
|
||||
|
||||
.icon-home-2::before { content: "\e821"; }
|
||||
|
||||
.icon-chat::before { content: "\e81e"; }
|
||||
|
||||
.icon-login::before { content: "\e820"; }
|
||||
|
||||
.icon-arrow-curved::before { content: "\e822"; }
|
||||
|
||||
.icon-link::before { content: "\e823"; }
|
||||
|
||||
.icon-share::before { content: "\f1e0"; }
|
||||
|
||||
.icon-user::before { content: "\e824"; }
|
||||
|
||||
.icon-ok::before { content: "\e827"; }
|
||||
|
||||
.icon-filter::before { content: "\f0b0"; }
|
||||
|
||||
.icon-download::before { content: "\e825"; }
|
||||
|
||||
.icon-bookmark::before { content: "\e826"; }
|
||||
|
||||
.icon-bookmark-empty::before { content: "\f097"; }
|
||||
|
||||
.icon-music::before { content: "\e828"; }
|
||||
|
||||
.icon-doc::before { content: "\e829"; }
|
158
priv/static/static/fontello.1594374054351.css
vendored
Normal file
158
priv/static/static/fontello.1594374054351.css
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
@font-face {
|
||||
font-family: "Icons";
|
||||
src: url("./font/fontello.1594374054351.eot");
|
||||
src: url("./font/fontello.1594374054351.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1594374054351.woff2") format("woff2"),
|
||||
url("./font/fontello.1594374054351.woff") format("woff"),
|
||||
url("./font/fontello.1594374054351.ttf") format("truetype"),
|
||||
url("./font/fontello.1594374054351.svg") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"]::before,
|
||||
[class*=" icon-"]::before {
|
||||
font-family: "Icons";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: .2em;
|
||||
text-align: center;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1em;
|
||||
margin-left: .2em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-spin4::before { content: "\e834"; }
|
||||
|
||||
.icon-cancel::before { content: "\e800"; }
|
||||
|
||||
.icon-upload::before { content: "\e801"; }
|
||||
|
||||
.icon-spin3::before { content: "\e832"; }
|
||||
|
||||
.icon-reply::before { content: "\f112"; }
|
||||
|
||||
.icon-star::before { content: "\e802"; }
|
||||
|
||||
.icon-star-empty::before { content: "\e803"; }
|
||||
|
||||
.icon-retweet::before { content: "\e804"; }
|
||||
|
||||
.icon-eye-off::before { content: "\e805"; }
|
||||
|
||||
.icon-binoculars::before { content: "\f1e5"; }
|
||||
|
||||
.icon-cog::before { content: "\e807"; }
|
||||
|
||||
.icon-user-plus::before { content: "\f234"; }
|
||||
|
||||
.icon-menu::before { content: "\f0c9"; }
|
||||
|
||||
.icon-logout::before { content: "\e808"; }
|
||||
|
||||
.icon-down-open::before { content: "\e809"; }
|
||||
|
||||
.icon-attach::before { content: "\e80a"; }
|
||||
|
||||
.icon-link-ext::before { content: "\f08e"; }
|
||||
|
||||
.icon-link-ext-alt::before { content: "\f08f"; }
|
||||
|
||||
.icon-picture::before { content: "\e80b"; }
|
||||
|
||||
.icon-video::before { content: "\e80c"; }
|
||||
|
||||
.icon-right-open::before { content: "\e80d"; }
|
||||
|
||||
.icon-left-open::before { content: "\e80e"; }
|
||||
|
||||
.icon-up-open::before { content: "\e80f"; }
|
||||
|
||||
.icon-comment-empty::before { content: "\f0e5"; }
|
||||
|
||||
.icon-mail-alt::before { content: "\f0e0"; }
|
||||
|
||||
.icon-lock::before { content: "\e811"; }
|
||||
|
||||
.icon-lock-open-alt::before { content: "\f13e"; }
|
||||
|
||||
.icon-globe::before { content: "\e812"; }
|
||||
|
||||
.icon-brush::before { content: "\e813"; }
|
||||
|
||||
.icon-search::before { content: "\e806"; }
|
||||
|
||||
.icon-adjust::before { content: "\e816"; }
|
||||
|
||||
.icon-thumbs-up-alt::before { content: "\f164"; }
|
||||
|
||||
.icon-attention::before { content: "\e814"; }
|
||||
|
||||
.icon-plus-squared::before { content: "\f0fe"; }
|
||||
|
||||
.icon-plus::before { content: "\e815"; }
|
||||
|
||||
.icon-edit::before { content: "\e817"; }
|
||||
|
||||
.icon-play-circled::before { content: "\f144"; }
|
||||
|
||||
.icon-pencil::before { content: "\e818"; }
|
||||
|
||||
.icon-chart-bar::before { content: "\e81b"; }
|
||||
|
||||
.icon-smile::before { content: "\f118"; }
|
||||
|
||||
.icon-bell-alt::before { content: "\f0f3"; }
|
||||
|
||||
.icon-wrench::before { content: "\e81a"; }
|
||||
|
||||
.icon-pin::before { content: "\e819"; }
|
||||
|
||||
.icon-ellipsis::before { content: "\f141"; }
|
||||
|
||||
.icon-bell-ringing-o::before { content: "\e810"; }
|
||||
|
||||
.icon-zoom-in::before { content: "\e81c"; }
|
||||
|
||||
.icon-gauge::before { content: "\f0e4"; }
|
||||
|
||||
.icon-users::before { content: "\e81d"; }
|
||||
|
||||
.icon-info-circled::before { content: "\e81f"; }
|
||||
|
||||
.icon-home-2::before { content: "\e821"; }
|
||||
|
||||
.icon-chat::before { content: "\e81e"; }
|
||||
|
||||
.icon-login::before { content: "\e820"; }
|
||||
|
||||
.icon-arrow-curved::before { content: "\e822"; }
|
||||
|
||||
.icon-link::before { content: "\e823"; }
|
||||
|
||||
.icon-share::before { content: "\f1e0"; }
|
||||
|
||||
.icon-user::before { content: "\e824"; }
|
||||
|
||||
.icon-ok::before { content: "\e827"; }
|
||||
|
||||
.icon-filter::before { content: "\f0b0"; }
|
||||
|
||||
.icon-download::before { content: "\e825"; }
|
||||
|
||||
.icon-bookmark::before { content: "\e826"; }
|
||||
|
||||
.icon-bookmark-empty::before { content: "\f097"; }
|
||||
|
||||
.icon-music::before { content: "\e828"; }
|
||||
|
||||
.icon-doc::before { content: "\e829"; }
|
||||
|
||||
.icon-block::before { content: "\e82a"; }
|
|
@ -363,6 +363,48 @@
|
|||
"css": "ok",
|
||||
"code": 59431,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "4109c474ff99cad28fd5a2c38af2ec6f",
|
||||
"css": "filter",
|
||||
"code": 61616,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
|
||||
"css": "download",
|
||||
"code": 59429,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f04a5d24e9e659145b966739c4fde82a",
|
||||
"css": "bookmark",
|
||||
"code": 59430,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "2f5ef6f6b7aaebc56458ab4e865beff5",
|
||||
"css": "bookmark-empty",
|
||||
"code": 61591,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "9ea0a737ccc45d6c510dcbae56058849",
|
||||
"css": "music",
|
||||
"code": 59432,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
||||
"css": "doc",
|
||||
"code": 59433,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "98d9c83c1ee7c2c25af784b518c522c5",
|
||||
"css": "block",
|
||||
"code": 59434,
|
||||
"src": "fontawesome"
|
||||
}
|
||||
]
|
||||
}
|
BIN
priv/static/static/js/10.2823375ec309b971aaea.js
Normal file
BIN
priv/static/static/js/10.2823375ec309b971aaea.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/10.2823375ec309b971aaea.js.map
Normal file
BIN
priv/static/static/js/10.2823375ec309b971aaea.js.map
Normal file
Binary file not shown.
BIN
priv/static/static/js/11.2cb4b0f72a4654070a58.js
Normal file
BIN
priv/static/static/js/11.2cb4b0f72a4654070a58.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/11.2cb4b0f72a4654070a58.js.map
Normal file
BIN
priv/static/static/js/11.2cb4b0f72a4654070a58.js.map
Normal file
Binary file not shown.
BIN
priv/static/static/js/12.500b3e4676dd47599a58.js
Normal file
BIN
priv/static/static/js/12.500b3e4676dd47599a58.js
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue