diff --git a/.gitignore b/.gitignore index 8166e65e9..06107eaf1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ /db /deps /*.ez -/uploads /test/uploads /.elixir_ls /test/fixtures/test_tmp.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 02bb3fde5..67b9649e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,14 +16,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Configuration: `link_name` option - Configuration: `fetch_initial_posts` option - Configuration: `notify_email` option -- Pleroma API: User subscribtions +- Configuration: Media proxy `whitelist` option +- Pleroma API: User subscriptions +- Pleroma API: Healthcheck endpoint - Admin API: Endpoints for listing/revoking invite tokens - Admin API: Endpoints for making users follow/unfollow each other - Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/) - Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension) +- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension) - Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/) - ActivityPub C2S: OAuth endpoints - Metadata RelMe provider +- Emoji packs and emoji pack manager ### Changed - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer @@ -37,11 +41,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Configuration: Dedupe enabled by default - Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work. - Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change +- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats. - Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications` - Mastodon API: Add `languages` and `registrations` to `/api/v1/instance` - Mastodon API: Provide plaintext versions of cw/content in the Status entity -- Mastodon API: Add `pleroma.conversation_id` field to the Status entity -- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending` fields to the User entity +- Mastodon API: Add `pleroma.conversation_id`, `pleroma.in_reply_to_account_acct` fields to the Status entity +- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending`, `pleroma.hide_followers`, `pleroma.hide_follows`, `pleroma.hide_favorites` fields to the User entity +- Mastodon API: Add `pleroma.show_role`, `pleroma.no_rich_text` fields to the Source subentity +- Mastodon API: Add support for updating `no_rich_text`, `hide_followers`, `hide_follows`, `hide_favorites`, `show_role` in `PATCH /api/v1/update_credentials` - Mastodon API: Add `pleroma.is_seen` to the Notification entity - Mastodon API: Add `pleroma.local` to the Status entity - Mastodon API: Add `preview` parameter to `POST /api/v1/statuses` @@ -50,6 +57,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Remove attachment limit in the Status entity - Deps: Updated Cowboy to 2.6 - Deps: Updated Ecto to 3.0.7 +- Don't ship finmoji by default, they can be installed as an emoji pack +- Mastodon API: Added support max_id & since_id for bookmark timeline endpoints. ### Fixed - Followers counter not being updated when a follower is blocked @@ -64,13 +73,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Federation: Cope with missing or explicitly nulled address lists - Federation: Explicitly ensure activities addressed to `as:Public` become addressed to the followers collection - Federation: Better cope with actors which do not declare a followers collection and use `as:Public` with these semantics +- Federation: Follow requests from remote users who have been blocked will be automatically rejected if appropriate - MediaProxy: Parse name from content disposition headers even for non-whitelisted types - MediaProxy: S3 link encoding - Rich Media: Reject any data which cannot be explicitly encoded into JSON +- Pleroma API: Importing follows from Mastodon 2.8+ +- Twitter API: Exposing default scope, `no_rich_text` of the user to anyone +- Twitter API: Returning the `role` object in user entity despite `show_role = false` - Mastodon API: `/api/v1/favourites` serving only public activities - Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies - Mastodon API: Streaming API broadcasting wrong activity id - Mastodon API: 500 errors when requesting a card for a private conversation +- Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow` +- Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON +- Mastodon API: Exposing default scope of the user to anyone ## [0.9.9999] - 2019-04-05 ### Security diff --git a/COPYING b/COPYING index ceec519ae..eceb68efe 100644 --- a/COPYING +++ b/COPYING @@ -39,10 +39,3 @@ does not include the right to compile photos from Unsplash to replicate a similar or competing service. priv/static/images/city.jpg - ---- - -The files present under the priv/static/finmoji directory are copyright -Finland , and are distributed under the Creative -Commons Attribution-NonCommercial-NoDerivatives 4.0 International license, you -should have received a copy of the license file as CC-BY-NC-ND-4.0. diff --git a/config/config.exs b/config/config.exs index b1d506b59..eeaaedf48 100644 --- a/config/config.exs +++ b/config/config.exs @@ -100,9 +100,9 @@ shortcode_globs: ["/emoji/custom/**/*.png"], groups: [ # Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md` - Finmoji: "/finmoji/128px/*-128.png", - Custom: ["/emoji/*.png", "/emoji/custom/*.png"] - ] + Custom: ["/emoji/*.png", "/emoji/**/*.png"] + ], + default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" config :pleroma, :uri_schemes, valid_schemes: [ @@ -221,9 +221,9 @@ allowed_post_formats: [ "text/plain", "text/html", - "text/markdown" + "text/markdown", + "text/bbcode" ], - finmoji_enabled: true, mrf_transparency: true, autofollowed_nicknames: [], max_pinned_statuses: 1, @@ -231,7 +231,8 @@ welcome_user_nickname: nil, welcome_message: nil, max_report_comment_size: 1000, - safe_dm_mentions: false + safe_dm_mentions: false, + healthcheck: false config :pleroma, :markup, # XXX - unfortunately, inline images must be enabled by default right now, because @@ -326,7 +327,8 @@ follow_redirect: true, pool: :media ] - ] + ], + whitelist: [] config :pleroma, :chat, enabled: true diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index ed3fd9b67..1350ace43 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -20,6 +20,7 @@ Has these additional fields under the `pleroma` object: - `local`: true if the post was made on the local instance. - `conversation_id`: the ID of the conversation the status is associated with (if any) +- `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` @@ -37,9 +38,18 @@ Has these additional fields under the `pleroma` object: - `tags`: Lists an array of tags for the user - `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship -- `is_moderator`: boolean, true if user is a moderator -- `is_admin`: boolean, true if user is an admin +- `is_moderator`: boolean, nullable, true if user is a moderator +- `is_admin`: boolean, nullable, true if user is an admin - `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated +- `hide_followers`: boolean, true when the user has follower hiding enabled +- `hide_follows`: boolean, true when the user has follow hiding enabled + +### Source + +Has these additional fields under the `pleroma` object: + +- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown +- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API ## Account Search @@ -58,3 +68,15 @@ Has these additional fields under the `pleroma` object: Additional parameters can be added to the JSON body/Form data: - `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. +- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. + +## PATCH `/api/v1/update_credentials` + +Additional parameters can be added to the JSON body/Form data: + +- `no_rich_text` - if true, html tags are stripped from all statuses requested from the API +- `hide_followers` - if true, user's followers will be hidden +- `hide_follows` - if true, user's follows will be hidden +- `hide_favorites` - if true, user's favorites timeline will be hidden +- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API +- `default_scope` - the scope returned under `privacy` key in Source subentity diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index dbe250300..190846de9 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -77,7 +77,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * `token`: invite token required when the registrations aren't public. * Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}` * Example response: -``` +```json { "background_image": null, "cover_photo": "https://pleroma.soykaf.com/images/banner.png", @@ -187,6 +187,62 @@ See [Admin-API](Admin-API.md) } ``` +## `/api/v1/pleroma/accounts/:id/favourites` +### Returns favorites timeline of any user +* Method `GET` +* Authentication: not required +* Params: + * `id`: the id of the account for whom to return results + * `limit`: optional, the number of records to retrieve + * `since_id`: optional, returns results that are more recent than the specified id + * `max_id`: optional, returns results that are older than the specified id +* Response: JSON, returns a list of Mastodon Status entities on success, otherwise returns `{"error": "error_msg"}` +* Example response: +```json +[ + { + "account": { + "id": "9hptFmUF3ztxYh3Svg", + "url": "https://pleroma.example.org/users/nick2", + "username": "nick2", + ... + }, + "application": {"name": "Web", "website": null}, + "bookmarked": false, + "card": null, + "content": "This is :moominmamma: note 0", + "created_at": "2019-04-15T15:42:15.000Z", + "emojis": [], + "favourited": false, + "favourites_count": 1, + "id": "9hptFmVJ02khbzYJaS", + "in_reply_to_account_id": null, + "in_reply_to_id": null, + "language": null, + "media_attachments": [], + "mentions": [], + "muted": false, + "pinned": false, + "pleroma": { + "content": {"text/plain": "This is :moominmamma: note 0"}, + "conversation_id": 13679, + "local": true, + "spoiler_text": {"text/plain": "2hu"} + }, + "reblog": null, + "reblogged": false, + "reblogs_count": 0, + "replies_count": 0, + "sensitive": false, + "spoiler_text": "2hu", + "tags": [{"name": "2hu", "url": "/tag/2hu"}], + "uri": "https://pleroma.example.org/objects/198ed2a1-7912-4482-b559-244a0369e984", + "url": "https://pleroma.example.org/notice/9hptFmVJ02khbzYJaS", + "visibility": "public" + } +] +``` + ## `/api/pleroma/notification_settings` ### Updates user notification settings * Method `PUT` @@ -197,3 +253,20 @@ See [Admin-API](Admin-API.md) * `remote`: BOOLEAN field, receives notifications from people on remote instances * `local`: BOOLEAN field, receives notifications from people on the local instance * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` + +## `/api/pleroma/healthcheck` +### Healthcheck endpoint with additional system data. +* Method `GET` +* Authentication: not required +* Params: none +* Response: JSON, statuses (200 - healthy, 503 unhealthy). +* Example response: +```json +{ + "pool_size": 0, # database connection pool + "active": 0, # active processes + "idle": 0, # idle processes + "memory_used": 0.00, # Memory used + "healthy": true # Instance state +} +``` diff --git a/docs/config.md b/docs/config.md index 69d389382..cd0e145df 100644 --- a/docs/config.md +++ b/docs/config.md @@ -87,7 +87,6 @@ config :pleroma, Pleroma.Emails.Mailer, * `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 this config or in ``static/config.json`` * `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML) -* `finmoji_enabled`: Whenether to enable the finmojis in the custom emojis. * `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default. * `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values: @@ -104,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer, * `welcome_user_nickname`: The nickname of the local user that sends the welcome message. * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`) * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`) +* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``. ## :logger * `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack @@ -205,6 +205,7 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i * `enabled`: Enables proxying of remote media to the instance’s proxy * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`. +* `whitelist`: List of domains to bypass the mediaproxy ## :gopher * `enabled`: Enables the gopher interface @@ -499,3 +500,8 @@ config :ueberauth, Ueberauth, microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]} ] ``` + +## :emoji +* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` +* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]` +* `default_manifest`: Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays). diff --git a/docs/config/custom_emoji.md b/docs/config/custom_emoji.md index 5ce9865a2..f72c0edbc 100644 --- a/docs/config/custom_emoji.md +++ b/docs/config/custom_emoji.md @@ -1,15 +1,25 @@ # Custom Emoji +Before you add your own custom emoji, check if they are available in an existing pack. +See `Mix.Tasks.Pleroma.Emoji` for information about emoji packs. + To add custom emoji: -* Add the image file(s) to `priv/static/emoji/custom` -* In case of conflicts: add the desired shortcode with the path to `config/custom_emoji.txt`, comma-separated and one per line -* Force recompilation (``mix clean && mix compile``) +* Create the `STATIC-DIR/emoji/` directory if it doesn't exist + (`STATIC-DIR` is configurable, `instance/static/` by default) +* Create a directory with whatever name you want (custom is a good name to show the purpose of it). + This will create a local emoji pack. +* Put your `.png` emoji files in that directory. In case of conflicts, you can create an `emoji.txt` + file in that directory and specify a custom shortcode using the following format: + `shortcode, file-path, tag1, tag2, etc`. One emoji per line. Note that if you do so, + you'll have to list all other emojis in the pack too. +* Either restart pleroma or connect to the iex session pleroma's running and + run `Pleroma.Emoji.reload/0` in it. Example: -image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png` +image files (in `instance/static/emoji/custom`): `happy.png` and `sad.png` -content of `config/custom_emoji.txt`: +content of `emoji.txt`: ``` happy, /emoji/custom/happy.png, Tag1,Tag2 sad, /emoji/custom/sad.png, Tag1 @@ -18,6 +28,11 @@ foo, /emoji/custom/foo.png The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon. +Default file extentions and locations for emojis are set in `config.exs`. To use different locations or file-extentions, add the `shortcode_globs` to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. Note that not all fediverse-software will show emojis with other file extentions: +```elixir +config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png", "/emoji/custom/**/*.gif"] +``` + ## Emoji tags (groups) Default tags are set in `config.exs`. To set your own tags, copy the structure to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex new file mode 100644 index 000000000..646fb3b9d --- /dev/null +++ b/lib/healthcheck.ex @@ -0,0 +1,60 @@ +defmodule Pleroma.Healthcheck do + @moduledoc """ + Module collects metrics about app and assign healthy status. + """ + alias Pleroma.Healthcheck + alias Pleroma.Repo + + defstruct pool_size: 0, + active: 0, + idle: 0, + memory_used: 0, + healthy: true + + @type t :: %__MODULE__{ + pool_size: non_neg_integer(), + active: non_neg_integer(), + idle: non_neg_integer(), + memory_used: number(), + healthy: boolean() + } + + @spec system_info() :: t() + def system_info do + %Healthcheck{ + memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) + } + |> assign_db_info() + |> check_health() + end + + defp assign_db_info(healthcheck) do + database = Application.get_env(:pleroma, Repo)[:database] + + query = + "select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;" + + result = Repo.query!(query) + pool_size = Application.get_env(:pleroma, Repo)[:pool_size] + + db_info = + Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> + if state == "active" do + Map.put(states, :active, states.active + cnt) + else + Map.put(states, :idle, states.idle + cnt) + end + end) + |> Map.put(:pool_size, pool_size) + + Map.merge(healthcheck, db_info) + end + + @spec check_health(Healthcheck.t()) :: Healthcheck.t() + def check_health(%{pool_size: pool_size, active: active} = check) + when active >= pool_size do + %{check | healthy: false} + end + + def check_health(check), do: check +end diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex new file mode 100644 index 000000000..cced73226 --- /dev/null +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -0,0 +1,293 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Emoji do + use Mix.Task + + @shortdoc "Manages emoji packs" + @moduledoc """ + Manages emoji packs + + ## ls-packs + + mix pleroma.emoji ls-packs [OPTION...] + + Lists the emoji packs and metadata specified in the manifest. + + ### Options + + - `-m, --manifest PATH/URL` - path to a custom manifest, it can + either be an URL starting with `http`, in that case the + manifest will be fetched from that address, or a local path + + ## get-packs + + mix pleroma.emoji get-packs [OPTION...] PACKS + + Fetches, verifies and installs the specified PACKS from the + manifest into the `STATIC-DIR/emoji/PACK-NAME` + + ### Options + + - `-m, --manifest PATH/URL` - same as ls-packs + + ## gen-pack + + mix pleroma.emoji gen-pack PACK-URL + + Creates a new manifest entry and a file list from the specified + remote pack file. Currently, only .zip archives are recognized + as remote pack files and packs are therefore assumed to be zip + archives. This command is intended to run interactively and will + first ask you some basic questions about the pack, then download + the remote file and generate an SHA256 checksum for it, then + generate an emoji file list for you. + + The manifest entry will either be written to a newly created + `index.json` file or appended to the existing one, *replacing* + the old pack with the same name if it was in the file previously. + + The file list will be written to the file specified previously, + *replacing* that file. You _should_ check that the file list doesn't + contain anything you don't need in the pack, that is, anything that is + not an emoji (the whole pack is downloaded, but only emoji files + are extracted). + """ + + @default_manifest Pleroma.Config.get!([:emoji, :default_manifest]) + + def run(["ls-packs" | args]) do + Application.ensure_all_started(:hackney) + + {options, [], []} = parse_global_opts(args) + + manifest = + fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest) + + Enum.each(manifest, fn {name, info} -> + to_print = [ + {"Name", name}, + {"Homepage", info["homepage"]}, + {"Description", info["description"]}, + {"License", info["license"]}, + {"Source", info["src"]} + ] + + for {param, value} <- to_print do + IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value])) + end + + # A newline + IO.puts("") + end) + end + + def run(["get-packs" | args]) do + Application.ensure_all_started(:hackney) + + {options, pack_names, []} = parse_global_opts(args) + + manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest + + manifest = fetch_manifest(manifest_url) + + for pack_name <- pack_names do + if Map.has_key?(manifest, pack_name) do + pack = manifest[pack_name] + src_url = pack["src"] + + IO.puts( + IO.ANSI.format([ + "Downloading ", + :bright, + pack_name, + :normal, + " from ", + :underline, + src_url + ]) + ) + + binary_archive = Tesla.get!(src_url).body + archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() + + sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright] + + if archive_sha == String.upcase(pack["src_sha256"]) do + IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"])) + else + IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"])) + + raise "Bad SHA256 for #{pack_name}" + end + + # The url specified in files should be in the same directory + files_url = Path.join(Path.dirname(manifest_url), pack["files"]) + + IO.puts( + IO.ANSI.format([ + "Fetching the file list for ", + :bright, + pack_name, + :normal, + " from ", + :underline, + files_url + ]) + ) + + files = Tesla.get!(files_url).body |> Poison.decode!() + + IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name])) + + pack_path = + Path.join([ + Pleroma.Config.get!([:instance, :static_dir]), + "emoji", + pack_name + ]) + + files_to_unzip = + Enum.map( + files, + fn {_, f} -> to_charlist(f) end + ) + + {:ok, _} = + :zip.unzip(binary_archive, + cwd: pack_path, + file_list: files_to_unzip + ) + + IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name])) + + emoji_txt_str = + Enum.map( + files, + fn {shortcode, path} -> + emojo_path = Path.join("/emoji/#{pack_name}", path) + "#{shortcode}, #{emojo_path}" + end + ) + |> Enum.join("\n") + + File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str) + else + IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"])) + end + end + end + + def run(["gen-pack", src]) do + Application.ensure_all_started(:hackney) + + proposed_name = Path.basename(src) |> Path.rootname() + name = String.trim(IO.gets("Pack name [#{proposed_name}]: ")) + # If there's no name, use the default one + name = if String.length(name) > 0, do: name, else: proposed_name + + license = String.trim(IO.gets("License: ")) + homepage = String.trim(IO.gets("Homepage: ")) + description = String.trim(IO.gets("Description: ")) + + proposed_files_name = "#{name}.json" + files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: ")) + files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name + + default_exts = [".png", ".gif"] + default_exts_str = Enum.join(default_exts, " ") + + exts = + String.trim( + IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ") + ) + + exts = + if String.length(exts) > 0 do + String.split(exts, " ") + |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end) + else + default_exts + end + + IO.puts("Downloading the pack and generating SHA256") + + binary_archive = Tesla.get!(src).body + archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() + + IO.puts("SHA256 is #{archive_sha}") + + pack_json = %{ + name => %{ + license: license, + homepage: homepage, + description: description, + src: src, + src_sha256: archive_sha, + files: files_name + } + } + + tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}") + + {:ok, _} = + :zip.unzip( + binary_archive, + cwd: tmp_pack_dir + ) + + emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts) + + File.write!(files_name, Poison.encode!(emoji_map, pretty: true)) + + IO.puts(""" + + #{files_name} has been created and contains the list of all found emojis in the pack. + Please review the files in the remove those not needed. + """) + + if File.exists?("index.json") do + existing_data = File.read!("index.json") |> Poison.decode!() + + File.write!( + "index.json", + Poison.encode!( + Map.merge( + existing_data, + pack_json + ), + pretty: true + ) + ) + + IO.puts("index.json file has been update with the #{name} pack") + else + File.write!("index.json", Poison.encode!(pack_json, pretty: true)) + + IO.puts("index.json has been created with the #{name} pack") + end + end + + defp fetch_manifest(from) do + Poison.decode!( + if String.starts_with?(from, "http") do + Tesla.get!(from).body + else + File.read!(from) + end + ) + end + + defp parse_global_opts(args) do + OptionParser.parse( + args, + strict: [ + manifest: :string + ], + aliases: [ + m: :manifest + ] + ) + end +end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 441168df2..b396ff0de 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -162,7 +162,7 @@ def run(["new", nickname, email | rest]) do def run(["rm", nickname]) do Common.start_pleroma() - with %User{local: true} = user <- User.get_by_nickname(nickname) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do User.delete(user) Mix.shell().info("User #{nickname} deleted.") else @@ -174,7 +174,7 @@ def run(["rm", nickname]) do def run(["toggle_activated", nickname]) do Common.start_pleroma() - with %User{} = user <- User.get_by_nickname(nickname) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do {:ok, user} = User.deactivate(user, !user.info.deactivated) Mix.shell().info( @@ -189,7 +189,7 @@ def run(["toggle_activated", nickname]) do def run(["reset_password", nickname]) do Common.start_pleroma() - with %User{local: true} = user <- User.get_by_nickname(nickname), + with %User{local: true} = user <- User.get_cached_by_nickname(nickname), {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do Mix.shell().info("Generated password reset token for #{user.nickname}") @@ -211,14 +211,14 @@ def run(["reset_password", nickname]) do def run(["unsubscribe", nickname]) do Common.start_pleroma() - with %User{} = user <- User.get_by_nickname(nickname) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do Mix.shell().info("Deactivating #{user.nickname}") User.deactivate(user) {:ok, friends} = User.get_friends(user) Enum.each(friends, fn friend -> - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}") User.unfollow(user, friend) @@ -226,7 +226,7 @@ def run(["unsubscribe", nickname]) do :timer.sleep(500) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) if Enum.empty?(user.following) do Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}") @@ -250,7 +250,7 @@ def run(["set", nickname | rest]) do ] ) - with %User{local: true} = user <- User.get_by_nickname(nickname) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do user = case Keyword.get(options, :moderator) do nil -> user @@ -277,7 +277,7 @@ def run(["set", nickname | rest]) do def run(["tag", nickname | tags]) do Common.start_pleroma() - with %User{} = user <- User.get_by_nickname(nickname) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.tag(tags) Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") @@ -290,7 +290,7 @@ def run(["tag", nickname | tags]) do def run(["untag", nickname | tags]) do Common.start_pleroma() - with %User{} = user <- User.get_by_nickname(nickname) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.untag(tags) Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") @@ -379,7 +379,7 @@ def run(["revoke_invite", token]) do def run(["delete_activities", nickname]) do Common.start_pleroma() - with %User{local: true} = user <- User.get_by_nickname(nickname) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do User.delete_user_activities(user) Mix.shell().info("User #{nickname} statuses deleted.") else diff --git a/lib/pleroma/PasswordResetToken.ex b/lib/pleroma/PasswordResetToken.ex index 7afbc8751..f31ea5bc5 100644 --- a/lib/pleroma/PasswordResetToken.ex +++ b/lib/pleroma/PasswordResetToken.ex @@ -39,7 +39,7 @@ def used_changeset(struct) do def reset_password(token, data) do with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), - %User{} = user <- User.get_by_id(token.user_id), + %User{} = user <- User.get_cached_by_id(token.user_id), {:ok, _user} <- User.reset_password(user, data), {:ok, token} <- Repo.update(used_changeset(token)) do {:ok, token} diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex new file mode 100644 index 000000000..7f8fd43b6 --- /dev/null +++ b/lib/pleroma/bookmark.ex @@ -0,0 +1,60 @@ +defmodule Pleroma.Bookmark do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Activity + alias Pleroma.Bookmark + alias Pleroma.FlakeId + alias Pleroma.Repo + alias Pleroma.User + + @type t :: %__MODULE__{} + + schema "bookmarks" do + belongs_to(:user, User, type: FlakeId) + belongs_to(:activity, Activity, type: FlakeId) + + timestamps() + end + + @spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()} + def create(user_id, activity_id) do + attrs = %{ + user_id: user_id, + activity_id: activity_id + } + + %Bookmark{} + |> cast(attrs, [:user_id, :activity_id]) + |> validate_required([:user_id, :activity_id]) + |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) + |> Repo.insert() + end + + @spec for_user_query(FlakeId.t()) :: Ecto.Query.t() + def for_user_query(user_id) do + Bookmark + |> where(user_id: ^user_id) + |> join(:inner, [b], activity in assoc(b, :activity)) + |> preload([b, a], activity: a) + end + + def get(user_id, activity_id) do + Bookmark + |> where(user_id: ^user_id) + |> where(activity_id: ^activity_id) + |> Repo.one() + end + + @spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()} + def destroy(user_id, activity_id) do + from(b in Bookmark, + where: b.user_id == ^user_id, + where: b.activity_id == ^activity_id + ) + |> Repo.one() + |> Repo.delete() + end +end diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 87c7f2cec..6390cce4c 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Emoji do @moduledoc """ The emojis are loaded from: - * the built-in Finmojis (if enabled in configuration), + * emoji packs in INSTANCE-DIR/emoji * the files: `config/emoji.txt` and `config/custom_emoji.txt` * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder @@ -14,6 +14,8 @@ defmodule Pleroma.Emoji do """ use GenServer + require Logger + @type pattern :: Regex.t() | module() | String.t() @type patterns :: pattern() | [pattern()] @type group_patterns :: keyword(patterns()) @@ -79,95 +81,94 @@ def code_change(_old_vsn, state, _extra) do end defp load do - finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled) + emoji_dir_path = + Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + case File.ls(emoji_dir_path) do + {:error, :enoent} -> + # The custom emoji directory doesn't exist, + # don't do anything + nil + + {:error, e} -> + # There was some other error + Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}") + + {:ok, packs} -> + # Print the packs we've found + Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") + + emojis = + Enum.flat_map( + packs, + fn pack -> load_pack(Path.join(emoji_dir_path, pack)) end + ) + + true = :ets.insert(@ets, emojis) + end + + # Compat thing for old custom emoji handling & default emoji, + # it should run even if there are no emoji packs shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || [] emojis = - (load_finmoji(finmoji_enabled) ++ - load_from_file("config/emoji.txt") ++ + (load_from_file("config/emoji.txt") ++ load_from_file("config/custom_emoji.txt") ++ load_from_globs(shortcode_globs)) |> Enum.reject(fn value -> value == nil end) true = :ets.insert(@ets, emojis) + :ok end - @finmoji [ - "a_trusted_friend", - "alandislands", - "association", - "auroraborealis", - "baby_in_a_box", - "bear", - "black_gold", - "christmasparty", - "crosscountryskiing", - "cupofcoffee", - "education", - "fashionista_finns", - "finnishlove", - "flag", - "forest", - "four_seasons_of_bbq", - "girlpower", - "handshake", - "happiness", - "headbanger", - "icebreaker", - "iceman", - "joulutorttu", - "kaamos", - "kalsarikannit_f", - "kalsarikannit_m", - "karjalanpiirakka", - "kicksled", - "kokko", - "lavatanssit", - "losthopes_f", - "losthopes_m", - "mattinykanen", - "meanwhileinfinland", - "moominmamma", - "nordicfamily", - "out_of_office", - "peacemaker", - "perkele", - "pesapallo", - "polarbear", - "pusa_hispida_saimensis", - "reindeer", - "sami", - "sauna_f", - "sauna_m", - "sauna_whisk", - "sisu", - "stuck", - "suomimainittu", - "superfood", - "swan", - "the_cap", - "the_conductor", - "the_king", - "the_voice", - "theoriginalsanta", - "tomoffinland", - "torillatavataan", - "unbreakable", - "waiting", - "white_nights", - "woollysocks" - ] + defp load_pack(pack_dir) do + pack_name = Path.basename(pack_dir) - defp load_finmoji(true) do - Enum.map(@finmoji, fn finmoji -> - file_name = "/finmoji/128px/#{finmoji}-128.png" - group = match_extra(@groups, file_name) - {finmoji, file_name, to_string(group)} - end) + emoji_txt = Path.join(pack_dir, "emoji.txt") + + if File.exists?(emoji_txt) do + load_from_file(emoji_txt) + else + Logger.info( + "No emoji.txt found for pack \"#{pack_name}\", assuming all .png files are emoji" + ) + + make_shortcode_to_file_map(pack_dir, [".png"]) + |> Enum.map(fn {shortcode, rel_file} -> + filename = Path.join("/emoji/#{pack_name}", rel_file) + + {shortcode, filename, [to_string(match_extra(@groups, filename))]} + end) + end end - defp load_finmoji(_), do: [] + def make_shortcode_to_file_map(pack_dir, exts) do + find_all_emoji(pack_dir, exts) + |> Enum.map(&Path.relative_to(&1, pack_dir)) + |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end) + |> Enum.into(%{}) + end + + def find_all_emoji(dir, exts) do + Enum.reduce( + File.ls!(dir), + [], + fn f, acc -> + filepath = Path.join(dir, f) + + if File.dir?(filepath) do + acc ++ find_all_emoji(filepath, exts) + else + acc ++ [filepath] + end + end + ) + |> Enum.filter(fn f -> Path.extname(f) in exts end) + end defp load_from_file(file) do if File.exists?(file) do @@ -182,11 +183,11 @@ defp load_from_file_stream(stream) do |> Stream.map(&String.trim/1) |> Stream.map(fn line -> case String.split(line, ~r/,\s*/) do - [name, file, tags] -> - {name, file, tags} - [name, file] -> - {name, file, to_string(match_extra(@groups, file))} + {name, file, [to_string(match_extra(@groups, file))]} + + [name, file | tags] -> + {name, file, tags} _ -> nil @@ -209,7 +210,7 @@ defp load_from_globs(globs) do tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path))) shortcode = Path.basename(path, Path.extname(path)) external_path = Path.join("/", Path.relative_to(path, static_path)) - {shortcode, external_path, to_string(tag)} + {shortcode, external_path, [to_string(tag)]} end) end diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index 2ebc5d5f7..1d2e0785c 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -76,7 +76,7 @@ def render_activities(activities) do |> Enum.map(fn activity -> user = User.get_cached_by_ap_id(activity.data["actor"]) - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) like_count = object["like_count"] || 0 announcement_count = object["announcement_count"] || 0 diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 4b42d8c9b..cf6c0ee0a 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -106,7 +106,14 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do # links Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) - Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"]) + + Meta.allow_tag_with_this_attribute_values("a", "class", [ + "hashtag", + "u-url", + "mention", + "u-url mention", + "mention u-url" + ]) Meta.allow_tag_with_this_attribute_values("a", "rel", [ "tag", @@ -115,12 +122,15 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do "noreferrer" ]) + Meta.allow_tag_with_these_attributes("a", ["name", "title"]) + # paragraphs and linebreaks Meta.allow_tag_with_these_attributes("br", []) Meta.allow_tag_with_these_attributes("p", []) # microformats - Meta.allow_tag_with_these_attributes("span", ["class"]) + Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"]) + Meta.allow_tag_with_these_attributes("span", []) # allow inline images for custom emoji @allow_inline_images Keyword.get(@markup, :allow_inline_images) @@ -155,7 +165,14 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.strip_comments() Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) - Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"]) + + Meta.allow_tag_with_this_attribute_values("a", "class", [ + "hashtag", + "u-url", + "mention", + "u-url mention", + "mention u-url" + ]) Meta.allow_tag_with_this_attribute_values("a", "rel", [ "tag", @@ -164,6 +181,8 @@ defmodule Pleroma.HTML.Scrubber.Default do "noreferrer" ]) + Meta.allow_tag_with_these_attributes("a", ["name", "title"]) + Meta.allow_tag_with_these_attributes("abbr", ["title"]) Meta.allow_tag_with_these_attributes("b", []) @@ -177,11 +196,13 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes("ol", []) Meta.allow_tag_with_these_attributes("p", []) Meta.allow_tag_with_these_attributes("pre", []) - Meta.allow_tag_with_these_attributes("span", ["class"]) Meta.allow_tag_with_these_attributes("strong", []) Meta.allow_tag_with_these_attributes("u", []) Meta.allow_tag_with_these_attributes("ul", []) + Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"]) + Meta.allow_tag_with_these_attributes("span", []) + @allow_inline_images Keyword.get(@markup, :allow_inline_images) if @allow_inline_images do diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index 110be8355..a5b1cad68 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -80,7 +80,7 @@ def get_lists_from_activity(%Activity{actor: ap_id}) do # Get lists to which the account belongs. def get_lists_account_belongs(%User{} = owner, account_id) do - user = User.get_by_id(account_id) + user = User.get_cached_by_id(account_id) query = from( diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index d79f0f563..bf45be961 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -220,7 +220,7 @@ def skip?( def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do actor = activity.data["actor"] - followed = User.get_by_ap_id(actor) + followed = User.get_cached_by_ap_id(actor) User.following?(user, followed) end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 138e7866f..8d4bcc95e 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -39,7 +39,7 @@ def fetch_object_from_id(id) do Logger.info("Couldn't get object via AP, trying out OStatus fetching...") case OStatus.fetch_activity_from_url(id) do - {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"], false)} + {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)} e -> e end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 2509d2366..4417a12dd 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -10,6 +10,7 @@ defmodule Pleroma.User do alias Comeonin.Pbkdf2 alias Pleroma.Activity + alias Pleroma.Bookmark alias Pleroma.Formatter alias Pleroma.Notification alias Pleroma.Object @@ -53,9 +54,9 @@ defmodule Pleroma.User do field(:search_rank, :float, virtual: true) field(:search_type, :integer, virtual: true) field(:tags, {:array, :string}, default: []) - field(:bookmarks, {:array, :string}, default: []) field(:last_refreshed_at, :naive_datetime_usec) field(:last_digest_emailed_at, :naive_datetime) + has_many(:bookmarks, Bookmark) has_many(:notifications, Notification) has_many(:registrations, Registration) embeds_one(:info, Pleroma.User.Info) @@ -270,6 +271,7 @@ defp autofollow_users(user) do def register(%Ecto.Changeset{} = changeset) do with {:ok, user} <- Repo.insert(changeset), {:ok, user} <- autofollow_users(user), + {:ok, user} <- set_cache(user), {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user), {:ok, _} <- try_send_confirmation_email(user) do {:ok, user} @@ -454,10 +456,13 @@ def get_by_guessed_nickname(ap_id) do name = List.last(String.split(ap_id, "/")) nickname = "#{name}@#{domain}" - get_by_nickname(nickname) + get_cached_by_nickname(nickname) end - def set_cache(user) do + def set_cache({:ok, user}), do: set_cache(user) + def set_cache({:error, err}), do: {:error, err} + + def set_cache(%User{} = user) do Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) Cachex.put(:user_cache, "nickname:#{user.nickname}", user) Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) @@ -545,6 +550,7 @@ def get_or_fetch_by_nickname(nickname) do with [_nick, _domain] <- String.split(nickname, "@"), {:ok, user} <- fetch_by_nickname(nickname) do if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do + # TODO turn into job {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user]) end @@ -1003,7 +1009,7 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do # helper to handle the block given only an actor's AP id def block(blocker, %{ap_id: ap_id}) do - block(blocker, User.get_by_ap_id(ap_id)) + block(blocker, get_cached_by_ap_id(ap_id)) end def unblock(blocker, %{ap_id: ap_id}) do @@ -1033,7 +1039,7 @@ def blocks?(user, %{ap_id: ap_id}) do end def subscribed_to?(user, %{ap_id: ap_id}) do - with %User{} = target <- User.get_by_ap_id(ap_id) do + with %User{} = target <- get_cached_by_ap_id(ap_id) do Enum.member?(target.info.subscribers, user.ap_id) end end @@ -1208,7 +1214,7 @@ def fetch_by_ap_id(ap_id) do end def get_or_fetch_by_ap_id(ap_id) do - user = get_by_ap_id(ap_id) + user = get_cached_by_ap_id(ap_id) if !is_nil(user) and !User.needs_update?(user) do user @@ -1231,7 +1237,7 @@ def get_or_fetch_by_ap_id(ap_id) do def get_or_create_instance_user do relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay" - if user = get_by_ap_id(relay_uri) do + if user = get_cached_by_ap_id(relay_uri) do user else changes = @@ -1278,13 +1284,11 @@ defp blank?(""), do: nil defp blank?(n), do: n def insert_or_update_user(data) do - data = - data - |> Map.put(:name, blank?(data[:name]) || data[:nickname]) - - cs = User.remote_user_creation(data) - - Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname) + data + |> Map.put(:name, blank?(data[:name]) || data[:nickname]) + |> remote_user_creation() + |> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname) + |> set_cache() end def ap_enabled?(%User{local: true}), do: true @@ -1300,8 +1304,8 @@ def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname) # this is because we have synchronous follow APIs and need to simulate them # with an async handshake def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do - with %User{} = a <- User.get_by_id(a.id), - %User{} = b <- User.get_by_id(b.id) do + with %User{} = a <- User.get_cached_by_id(a.id), + %User{} = b <- User.get_cached_by_id(b.id) do {:ok, a, b} else _e -> @@ -1311,8 +1315,8 @@ def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do def wait_and_refresh(timeout, %User{} = a, %User{} = b) do with :ok <- :timer.sleep(timeout), - %User{} = a <- User.get_by_id(a.id), - %User{} = b <- User.get_by_id(b.id) do + %User{} = a <- User.get_cached_by_id(a.id), + %User{} = b <- User.get_cached_by_id(b.id) do {:ok, a, b} else _e -> @@ -1351,7 +1355,7 @@ def tag(user_identifiers, tags) when is_list(user_identifiers) do end def tag(nickname, tags) when is_binary(nickname), - do: tag(User.get_by_nickname(nickname), tags) + do: tag(get_by_nickname(nickname), tags) def tag(%User{} = user, tags), do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags))) @@ -1363,7 +1367,7 @@ def untag(user_identifiers, tags) when is_list(user_identifiers) do end def untag(nickname, tags) when is_binary(nickname), - do: untag(User.get_by_nickname(nickname), tags) + do: untag(get_by_nickname(nickname), tags) def untag(%User{} = user, tags), do: update_tags(user, (user.tags || []) -- normalize_tags(tags)) @@ -1377,22 +1381,6 @@ defp update_tags(%User{} = user, new_tags) do updated_user end - def bookmark(%User{} = user, status_id) do - bookmarks = Enum.uniq(user.bookmarks ++ [status_id]) - update_bookmarks(user, bookmarks) - end - - def unbookmark(%User{} = user, status_id) do - bookmarks = Enum.uniq(user.bookmarks -- [status_id]) - update_bookmarks(user, bookmarks) - end - - def update_bookmarks(%User{} = user, bookmarks) do - user - |> change(%{bookmarks: bookmarks}) - |> update_and_set_cache - end - defp normalize_tags(tags) do [tags] |> List.flatten() diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index d827293b8..2d360d650 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -40,6 +40,7 @@ defmodule Pleroma.User.Info do field(:salmon, :string, default: nil) field(:hide_followers, :boolean, default: false) field(:hide_follows, :boolean, default: false) + field(:hide_favorites, :boolean, default: true) field(:pinned_activities, {:array, :string}, default: []) field(:flavour, :string, default: nil) field(:email_notifications, :map, default: %{"digest" => false}) @@ -229,6 +230,7 @@ def profile_update(info, params) do :banner, :hide_follows, :hide_followers, + :hide_favorites, :background, :show_role ]) @@ -252,14 +254,6 @@ def confirmation_changeset(info, params) do cast(info, params, [:confirmation_pending, :confirmation_token]) end - def mastodon_profile_update(info, params) do - info - |> cast(params, [ - :locked, - :banner - ]) - end - def mastodon_settings_update(info, settings) do params = %{settings: settings} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e77b2b72d..604ffae7b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -168,7 +168,7 @@ def stream_out(activity) do public = "https://www.w3.org/ns/activitystreams#Public" if activity.data["type"] in ["Create", "Announce", "Delete"] do - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) Pleroma.Web.Streamer.stream("user", activity) Pleroma.Web.Streamer.stream("list", activity) @@ -197,7 +197,7 @@ def stream_out(activity) do if !Enum.member?(activity.data["cc"] || [], public) && !Enum.member?( activity.data["to"], - User.get_by_ap_id(activity.data["actor"]).follower_address + User.get_cached_by_ap_id(activity.data["actor"]).follower_address ), do: Pleroma.Web.Streamer.stream("direct", activity) end @@ -889,7 +889,7 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do end def make_user_from_ap_id(ap_id) do - if _user = User.get_by_ap_id(ap_id) do + if _user = User.get_cached_by_ap_id(ap_id) do Transmogrifier.upgrade_user_from_ap_id(ap_id) else with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a80aa52c6..b1e859d7c 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -438,20 +438,46 @@ def handle_incoming( with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), %User{} = follower <- User.get_or_fetch_by_ap_id(follower), {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do - if not User.locked?(followed) do + with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), + {:user_blocked, false} <- + {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, + {:user_locked, false} <- {:user_locked, User.locked?(followed)}, + {:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do ActivityPub.accept(%{ to: [follower.ap_id], actor: followed, object: data, local: true }) + else + {:user_blocked, true} -> + {:ok, _} = Utils.update_follow_state(activity, "reject") - User.follow(follower, followed) + ActivityPub.reject(%{ + to: [follower.ap_id], + actor: followed, + object: data, + local: true + }) + + {:follow, {:error, _}} -> + {:ok, _} = Utils.update_follow_state(activity, "reject") + + ActivityPub.reject(%{ + to: [follower.ap_id], + actor: followed, + object: data, + local: true + }) + + {:user_locked, true} -> + :noop end {:ok, activity} else - _e -> :error + _e -> + :error end end @@ -537,7 +563,7 @@ def handle_incoming( data ) when object_type in ["Person", "Application", "Service", "Organization"] do - with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do + with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) banner = new_user_data[:info]["banner"] @@ -964,7 +990,7 @@ def perform(:user_upgrade, user) do end def upgrade_user_from_ap_id(ap_id) do - with %User{local: false} = user <- User.get_by_ap_id(ap_id), + with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), already_ap <- User.ap_enabled?(user), {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index c436715d5..711f233a6 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do action_fallback(:errors) def user_delete(conn, %{"nickname" => nickname}) do - User.get_by_nickname(nickname) + User.get_cached_by_nickname(nickname) |> User.delete() conn @@ -27,8 +27,8 @@ def user_delete(conn, %{"nickname" => nickname}) do end def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do - with %User{} = follower <- User.get_by_nickname(follower_nick), - %User{} = followed <- User.get_by_nickname(followed_nick) do + with %User{} = follower <- User.get_cached_by_nickname(follower_nick), + %User{} = followed <- User.get_cached_by_nickname(followed_nick) do User.follow(follower, followed) end @@ -37,8 +37,8 @@ def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick end def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do - with %User{} = follower <- User.get_by_nickname(follower_nick), - %User{} = followed <- User.get_by_nickname(followed_nick) do + with %User{} = follower <- User.get_cached_by_nickname(follower_nick), + %User{} = followed <- User.get_cached_by_nickname(followed_nick) do User.unfollow(follower, followed) end @@ -67,7 +67,7 @@ def user_create( end def user_show(conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_by_nickname(nickname) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> json(AccountView.render("show.json", %{user: user})) else @@ -76,7 +76,7 @@ def user_show(conn, %{"nickname" => nickname}) do end def user_toggle_activation(conn, %{"nickname" => nickname}) do - user = User.get_by_nickname(nickname) + user = User.get_cached_by_nickname(nickname) {:ok, updated_user} = User.deactivate(user, !user.info.deactivated) @@ -131,7 +131,7 @@ defp maybe_parse_filters(filters) do def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) when permission_group in ["moderator", "admin"] do - user = User.get_by_nickname(nickname) + user = User.get_cached_by_nickname(nickname) info = %{} @@ -156,7 +156,7 @@ def right_add(conn, _) do end def right_get(conn, %{"nickname" => nickname}) do - user = User.get_by_nickname(nickname) + user = User.get_cached_by_nickname(nickname) conn |> json(%{ @@ -178,7 +178,7 @@ def right_delete( |> put_status(403) |> json(%{error: "You can't revoke your own admin status."}) else - user = User.get_by_nickname(nickname) + user = User.get_cached_by_nickname(nickname) info = %{} @@ -204,7 +204,7 @@ def right_delete(conn, _) do def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do with {:ok, status} <- Ecto.Type.cast(:boolean, status), - %User{} = user <- User.get_by_nickname(nickname), + %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, _} <- User.deactivate(user, !status), do: json_response(conn, :no_content, "") end @@ -277,7 +277,7 @@ def revoke_invite(conn, %{"token" => token}) do @doc "Get a password reset token (base64 string) for given nickname" def get_password_reset(conn, %{"nickname" => nickname}) do - (%User{local: true} = user) = User.get_by_nickname(nickname) + (%User{local: true} = user) = User.get_cached_by_nickname(nickname) {:ok, token} = Pleroma.PasswordResetToken.create_token(user) conn diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index 6503979a1..8e2759e3b 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Web.UserSocket do def connect(%{"token" => token}, socket) do with true <- Pleroma.Config.get([:chat, :enabled]), {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600), - %User{} = user <- Pleroma.User.get_by_id(user_id) do + %User{} = user <- Pleroma.User.get_cached_by_id(user_id) do {:ok, assign(socket, :user_name, user.nickname)} else _e -> :error diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 6458a3449..ecd183110 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity + alias Pleroma.Bookmark alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.ThreadMute @@ -282,9 +283,18 @@ def thread_muted?(user, activity) do end end + def bookmarked?(user, activity) do + with %Bookmark{} <- Bookmark.get(user.id, activity.id) do + true + else + _ -> + false + end + end + def report(user, data) do with {:account_id, %{"account_id" => account_id}} <- {:account_id, data}, - {:account, %User{} = account} <- {:account, User.get_by_id(account_id)}, + {:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)}, {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]), {:ok, statuses} <- get_report_statuses(account, data), {:ok, activity} <- diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 25f498fcb..1dfe50b40 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -182,6 +182,18 @@ def format_input(text, "text/plain", options) do end).() end + @doc """ + Formatting text as BBCode. + """ + def format_input(text, "text/bbcode", options) do + text + |> String.replace(~r/\r/, "") + |> Formatter.html_escape("text/plain") + |> BBCode.to_html() + |> (fn {:ok, html} -> html end).() + |> Formatter.linkify(options) + end + @doc """ Formatting text to html. """ @@ -226,7 +238,7 @@ def make_note_data( } if in_reply_to do - in_reply_to_object = Object.normalize(in_reply_to.data["object"]) + in_reply_to_object = Object.normalize(in_reply_to) object |> Map.put("inReplyTo", in_reply_to_object.data["id"]) @@ -284,7 +296,7 @@ defp shortname(name) do end def confirm_current_password(user, password) do - with %User{local: true} = db_user <- User.get_by_id(user.id), + with %User{local: true} = db_user <- User.get_cached_by_id(user.id), true <- Pbkdf2.checkpw(password, db_user.password_hash) do {:ok, db_user} else diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 1b4deb6dc..29e178ba9 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -186,7 +186,7 @@ def perform(type, _) do end def ap_enabled_actor(id) do - user = User.get_by_ap_id(id) + user = User.get_cached_by_ap_id(id) if User.ap_enabled?(user) do {:ok, user} diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 382f07e6b..3a3ec7c2a 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -7,6 +7,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do alias Pleroma.Pagination alias Pleroma.ScheduledActivity alias Pleroma.User + alias Pleroma.Web.CommonAPI + + def follow(follower, followed, params \\ %{}) do + options = cast_params(params) + reblogs = options[:reblogs] + + result = + if not User.following?(follower, followed) do + CommonAPI.follow(follower, followed) + else + {:ok, follower, followed, nil} + end + + with {:ok, follower, followed, _} <- result do + reblogs + |> case do + false -> CommonAPI.hide_reblogs(follower, followed) + _ -> CommonAPI.show_reblogs(follower, followed) + end + |> case do + {:ok, follower} -> {:ok, follower} + _ -> {:ok, follower} + end + end + end def get_followers(user, params \\ %{}) do user @@ -37,7 +62,8 @@ def get_scheduled_activities(user, params \\ %{}) do defp cast_params(params) do param_types = %{ - exclude_types: {:array, :string} + exclude_types: {:array, :string}, + reblogs: :boolean } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 3916d7c41..811a45c79 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller alias Ecto.Changeset alias Pleroma.Activity + alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.Filter alias Pleroma.Notification @@ -35,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token - import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] + alias Pleroma.Web.ControllerHelper import Ecto.Query require Logger @@ -46,7 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do action_fallback(:errors) def create_app(conn, params) do - scopes = oauth_scopes(params, ["read"]) + scopes = ControllerHelper.oauth_scopes(params, ["read"]) app_attrs = params @@ -96,8 +97,13 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do end) info_params = - %{} - |> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end) + [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role] + |> Enum.reduce(%{}, fn key, acc -> + add_if_present(acc, params, to_string(key), key, fn value -> + {:ok, ControllerHelper.truthy_param?(value)} + end) + end) + |> add_if_present(params, "default_scope", :default_scope) |> add_if_present(params, "header", :banner, fn value -> with %Plug.Upload{} <- value, {:ok, object} <- ActivityPub.upload(value, type: :banner) do @@ -107,7 +113,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do end end) - info_cng = User.Info.mastodon_profile_update(user.info, info_params) + info_cng = User.Info.profile_update(user.info, info_params) with changeset <- User.update_changeset(user, user_params), changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), @@ -190,7 +196,7 @@ defp mastodonized_emoji do "static_url" => url, "visible_in_picker" => true, "url" => url, - "tags" => String.split(tags, ",") + "tags" => tags } end) end @@ -279,6 +285,8 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do |> ActivityPub.contain_timeline(user) |> Enum.reverse() + user = Repo.preload(user, bookmarks: :activity) + conn |> add_link_headers(:home_timeline, activities) |> put_view(StatusView) @@ -297,6 +305,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do |> ActivityPub.fetch_public_activities() |> Enum.reverse() + user = Repo.preload(user, bookmarks: :activity) + conn |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only}) |> put_view(StatusView) @@ -304,7 +314,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do end def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- User.get_by_id(params["id"]) do + with %User{} = user <- User.get_cached_by_id(params["id"]), + reading_user <- Repo.preload(reading_user, :bookmarks) do activities = ActivityPub.fetch_user_activities(user, reading_user, params) conn @@ -331,6 +342,8 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) + user = Repo.preload(user, bookmarks: :activity) + conn |> add_link_headers(:dm_timeline, activities) |> put_view(StatusView) @@ -338,8 +351,10 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do end def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), + with %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do + user = Repo.preload(user, bookmarks: :activity) + conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user}) @@ -487,7 +502,10 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do end def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do + with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), + %Activity{} = announce <- Activity.normalize(announce.data) do + user = Repo.preload(user, bookmarks: :activity) + conn |> put_view(StatusView) |> try_render("status.json", %{activity: announce, for: user, as: :activity}) @@ -496,7 +514,9 @@ def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do + user = Repo.preload(user, bookmarks: :activity) + conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -544,10 +564,11 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), - %Object{} = object <- Object.normalize(activity), - %User{} = user <- User.get_by_nickname(user.nickname), + %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, user} <- User.bookmark(user, object.data["id"]) do + {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do + user = Repo.preload(user, bookmarks: :activity) + conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -556,10 +577,11 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), - %Object{} = object <- Object.normalize(activity), - %User{} = user <- User.get_by_nickname(user.nickname), + %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, user} <- User.unbookmark(user, object.data["id"]) do + {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do + user = Repo.preload(user, bookmarks: :activity) + conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -749,7 +771,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do end def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do - with %User{} = user <- User.get_by_id(id), + with %User{} = user <- User.get_cached_by_id(id), followers <- MastodonAPI.get_followers(user, params) do followers = cond do @@ -766,7 +788,7 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do end def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do - with %User{} = user <- User.get_by_id(id), + with %User{} = user <- User.get_cached_by_id(id), followers <- MastodonAPI.get_friends(user, params) do followers = cond do @@ -791,7 +813,7 @@ def follow_requests(%{assigns: %{user: followed}} = conn, _params) do end def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do - with %User{} = follower <- User.get_by_id(id), + with %User{} = follower <- User.get_cached_by_id(id), {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do conn |> put_view(AccountView) @@ -805,7 +827,7 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id} end def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do - with %User{} = follower <- User.get_by_id(id), + with %User{} = follower <- User.get_cached_by_id(id), {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do conn |> put_view(AccountView) @@ -821,8 +843,7 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)}, {_, true} <- {:followed, follower.id != followed.id}, - false <- User.following?(follower, followed), - {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do + {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do conn |> put_view(AccountView) |> render("relationship.json", %{user: follower, target: followed}) @@ -830,19 +851,6 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do {:followed, _} -> {:error, :not_found} - true -> - followed = User.get_cached_by_id(id) - - {:ok, follower} = - case conn.params["reblogs"] do - true -> CommonAPI.show_reblogs(follower, followed) - false -> CommonAPI.hide_reblogs(follower, followed) - end - - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: follower, target: followed}) - {:error, message} -> conn |> put_resp_content_type("application/json") @@ -885,7 +893,7 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do end def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do - with %User{} = muted <- User.get_by_id(id), + with %User{} = muted <- User.get_cached_by_id(id), {:ok, muter} <- User.mute(muter, muted) do conn |> put_view(AccountView) @@ -899,7 +907,7 @@ def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do end def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do - with %User{} = muted <- User.get_by_id(id), + with %User{} = muted <- User.get_cached_by_id(id), {:ok, muter} <- User.unmute(muter, muted) do conn |> put_view(AccountView) @@ -920,7 +928,7 @@ def mutes(%{assigns: %{user: user}} = conn, _) do end def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do - with %User{} = blocked <- User.get_by_id(id), + with %User{} = blocked <- User.get_cached_by_id(id), {:ok, blocker} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do conn @@ -935,7 +943,7 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do end def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do - with %User{} = blocked <- User.get_by_id(id), + with %User{} = blocked <- User.get_cached_by_id(id), {:ok, blocker} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do conn @@ -1094,21 +1102,65 @@ def favourites(%{assigns: %{user: user}} = conn, params) do ActivityPub.fetch_activities([], params) |> Enum.reverse() + user = Repo.preload(user, bookmarks: :activity) + conn |> add_link_headers(:favourites, activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end - def bookmarks(%{assigns: %{user: user}} = conn, _) do - user = User.get_by_id(user.id) + def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do + with %User{} = user <- User.get_by_id(id), + false <- user.info.hide_favorites do + params = + params + |> Map.put("type", "Create") + |> Map.put("favorited_by", user.ap_id) + |> Map.put("blocking_user", for_user) + + recipients = + if for_user do + ["https://www.w3.org/ns/activitystreams#Public"] ++ + [for_user.ap_id | for_user.following] + else + ["https://www.w3.org/ns/activitystreams#Public"] + end + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + conn + |> add_link_headers(:favourites, activities) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: for_user, as: :activity}) + else + nil -> + {:error, :not_found} + + true -> + conn + |> put_status(403) + |> json(%{error: "Can't get favorites"}) + end + end + + def bookmarks(%{assigns: %{user: user}} = conn, params) do + user = User.get_cached_by_id(user.id) + user = Repo.preload(user, bookmarks: :activity) + + bookmarks = + Bookmark.for_user_query(user.id) + |> Pagination.fetch_paginated(params) activities = - user.bookmarks - |> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end) - |> Enum.reverse() + bookmarks + |> Enum.map(fn b -> b.activity end) conn + |> add_link_headers(:bookmarks, bookmarks) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -1158,7 +1210,7 @@ def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts |> Enum.each(fn account_id -> with %Pleroma.List{} = list <- Pleroma.List.get(id, user), - %User{} = followed <- User.get_by_id(account_id) do + %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.follow(list, followed) end end) @@ -1170,7 +1222,7 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id accounts |> Enum.each(fn account_id -> with %Pleroma.List{} = list <- Pleroma.List.get(id, user), - %User{} = followed <- Pleroma.User.get_by_id(account_id) do + %User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do Pleroma.List.unfollow(list, followed) end end) @@ -1214,6 +1266,8 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) |> ActivityPub.fetch_activities_bounded(following, params) |> Enum.reverse() + user = Repo.preload(user, bookmarks: :activity) + conn |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) @@ -1463,7 +1517,7 @@ def logout(conn, _) do def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do Logger.debug("Unimplemented, returning unmodified relationship") - with %User{} = target <- User.get_by_id(id) do + with %User{} = target <- User.get_cached_by_id(id) do conn |> put_view(AccountView) |> render("relationship.json", %{user: user, target: target}) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index af56c4149..779b9a382 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -68,7 +68,7 @@ def render("relationships.json", %{user: user, targets: targets}) do defp do_render("account.json", %{user: user} = opts) do image = User.avatar_url(user) |> MediaProxy.url() header = User.banner_url(user) |> MediaProxy.url() - user_info = User.user_info(user) + user_info = User.get_cached_user_info(user) bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"] emojis = @@ -113,21 +113,23 @@ defp do_render("account.json", %{user: user} = opts) do bot: bot, source: %{ note: "", - privacy: user_info.default_scope, - sensitive: false + sensitive: false, + pleroma: %{} }, # Pleroma extension - pleroma: - %{ - confirmation_pending: user_info.confirmation_pending, - tags: user.tags, - is_moderator: user.info.is_moderator, - is_admin: user.info.is_admin, - relationship: relationship - } - |> with_notification_settings(user, opts[:for]) + pleroma: %{ + confirmation_pending: user_info.confirmation_pending, + tags: user.tags, + hide_followers: user.info.hide_followers, + hide_follows: user.info.hide_follows, + hide_favorites: user.info.hide_favorites, + relationship: relationship + } } + |> maybe_put_role(user, opts[:for]) + |> maybe_put_settings(user, opts[:for], user_info) + |> maybe_put_notification_settings(user, opts[:for]) end defp username_from_nickname(string) when is_binary(string) do @@ -136,9 +138,37 @@ defp username_from_nickname(string) when is_binary(string) do defp username_from_nickname(_), do: nil - defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do - Map.put(data, :notification_settings, user.info.notification_settings) + defp maybe_put_settings( + data, + %User{id: user_id} = user, + %User{id: user_id}, + user_info + ) do + data + |> Kernel.put_in([:source, :privacy], user_info.default_scope) + |> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role) + |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text) end - defp with_notification_settings(data, _, _), do: data + defp maybe_put_settings(data, _, _, _), do: data + + defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do + data + |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin) + |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator) + end + + defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do + data + |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin) + |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator) + end + + defp maybe_put_role(data, _, _), do: data + + defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do + Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings) + end + + defp maybe_put_notification_settings(data, _, _), do: data end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index f8961eb6c..62d064d71 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -31,7 +31,7 @@ defp get_replied_to_activities(activities) do |> Activity.create_by_object_ap_id() |> Repo.all() |> Enum.reduce(%{}, fn activity, acc -> - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) Map.put(acc, object.data["id"], activity) end) end @@ -83,6 +83,11 @@ def render( reblogged_activity = Activity.get_create_by_object_ap_id(object) reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity)) + activity_object = Object.normalize(activity) + favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) + + bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity) + mentions = activity.recipients |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end) @@ -103,8 +108,8 @@ def render( replies_count: 0, favourites_count: 0, reblogged: reblogged?(reblogged_activity, opts[:for]), - favourited: false, - bookmarked: false, + favourited: present?(favorited), + bookmarked: present?(bookmarked), muted: false, pinned: pinned?(activity, user), sensitive: false, @@ -144,7 +149,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = opts[:for] && object.data["id"] in opts[:for].bookmarks + bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity) attachment_data = object.data["attachment"] || [] attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) @@ -234,6 +239,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity pleroma: %{ local: activity.local, conversation_id: get_context_id(activity), + in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary_plaintext} } @@ -312,7 +318,7 @@ def render("attachment.json", %{attachment: attachment}) do end def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) with nil <- replied_to_activities[object.data["inReplyTo"]] do # If user didn't participate in the thread diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 1b3721e2b..abfa26754 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -90,7 +90,7 @@ defp allow_request(stream, nil) when stream in @anonymous_streams do # Authenticated streams. defp allow_request(stream, {"access_token", access_token}) when stream in @streams do with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), - user = %User{} <- User.get_by_id(user_id) do + user = %User{} <- User.get_cached_by_id(user_id) do {:ok, user} else _ -> {:error, 403} diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 3bd2affe9..5762e767b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -13,32 +13,44 @@ def url("/" <> _ = url), do: url def url(url) do config = Application.get_env(:pleroma, :media_proxy, []) + domain = URI.parse(url).host - if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) do - url - else - secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] - - # Must preserve `%2F` for compatibility with S3 - # https://git.pleroma.social/pleroma/pleroma/issues/580 - replacement = get_replacement(url, ":2F:") - - # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. - base64 = + cond do + !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) -> url - |> String.replace("%2F", replacement) - |> URI.decode() - |> URI.encode() - |> String.replace(replacement, "%2F") - |> Base.url_encode64(@base64_opts) - sig = :crypto.hmac(:sha, secret, base64) - sig64 = sig |> Base.url_encode64(@base64_opts) + Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> + String.equivalent?(domain, pattern) + end) -> + url - build_url(sig64, base64, filename(url)) + true -> + encode_url(url) end end + def encode_url(url) do + secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] + + # Must preserve `%2F` for compatibility with S3 + # https://git.pleroma.social/pleroma/pleroma/issues/580 + replacement = get_replacement(url, ":2F:") + + # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. + base64 = + url + |> String.replace("%2F", replacement) + |> URI.decode() + |> URI.encode() + |> String.replace(replacement, "%2F") + |> Base.url_encode64(@base64_opts) + + sig = :crypto.hmac(:sha, secret, base64) + sig64 = sig |> Base.url_encode64(@base64_opts) + + build_url(sig64, base64, filename(url)) + end + def decode_url(sig, url) do secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] sig = Base.url_decode64!(sig, @base64_opts) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 9874bac23..688eaca11 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -23,6 +23,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do action_fallback(Pleroma.Web.OAuth.FallbackController) + # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg + def authorize(conn, %{"authorization" => _} = params) do + {auth_attrs, params} = Map.pop(params, "authorization") + authorize(conn, Map.merge(params, auth_attrs)) + end + def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do if ControllerHelper.truthy_param?(params["force_login"]) do do_authorize(conn, params) @@ -44,21 +50,20 @@ def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do def authorize(conn, params), do: do_authorize(conn, params) - defp do_authorize(conn, %{"authorization" => auth_attrs}), do: do_authorize(conn, auth_attrs) - - defp do_authorize(conn, auth_attrs) do - app = Repo.get_by(App, client_id: auth_attrs["client_id"]) + defp do_authorize(conn, params) do + app = Repo.get_by(App, client_id: params["client_id"]) available_scopes = (app && app.scopes) || [] - scopes = oauth_scopes(auth_attrs, nil) || available_scopes + scopes = oauth_scopes(params, nil) || available_scopes + # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template render(conn, Authenticator.auth_template(), %{ - response_type: auth_attrs["response_type"], - client_id: auth_attrs["client_id"], + response_type: params["response_type"], + client_id: params["client_id"], available_scopes: available_scopes, scopes: scopes, - redirect_uri: auth_attrs["redirect_uri"], - state: auth_attrs["state"], - params: auth_attrs + redirect_uri: params["redirect_uri"], + state: params["state"], + params: params }) end @@ -138,7 +143,7 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do fixed_token = fix_padding(params["code"]), %Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id), - %User{} = user <- User.get_by_id(auth.user_id), + %User{} = user <- User.get_cached_by_id(auth.user_id), {:ok, token} <- Token.exchange_token(app, auth), {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do response = %{ diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 2b5ad9b94..399140003 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do def exchange_token(app, auth) do with {:ok, auth} <- Authorization.use_token(auth), true <- auth.app_id == app.id do - create_token(app, User.get_by_id(auth.user_id), auth.scopes) + create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes) end end diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index b11a2b5ce..166691a09 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -84,7 +84,7 @@ def to_simple_form(activity, user, with_author \\ false) def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do h = fn str -> [to_charlist(str)] end - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) updated_at = object.data["published"] inserted_at = object.data["published"] diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 9a34d7ad5..4744c6d83 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -294,7 +294,7 @@ def make_user(uri, update \\ false) do } with false <- update, - %User{} = user <- User.get_by_ap_id(data.ap_id) do + %User{} = user <- User.get_cached_by_ap_id(data.ap_id) do {:ok, user} else _e -> User.insert_or_update_user(data) diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 2233480c5..35d3ff07c 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -21,8 +21,10 @@ defmodule Pleroma.Web.Push.Impl do @doc "Performs sending notifications for user subscriptions" @spec perform(Notification.t()) :: list(any) | :error def perform( - %{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} = - notif + %{ + activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, + user_id: user_id + } = notif ) when activity_type in @types do actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) @@ -30,13 +32,14 @@ def perform( type = Activity.mastodon_notification_type(notif.activity) gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key) avatar_url = User.avatar_url(actor) + object = Object.normalize(activity) for subscription <- fetch_subsriptions(user_id), get_in(subscription.data, ["alerts", type]) do %{ title: format_title(notif), access_token: subscription.token.token, - body: format_body(notif, actor), + body: format_body(notif, actor, object), notification_id: notif.id, notification_type: type, icon: avatar_url, @@ -95,25 +98,25 @@ def build_sub(subscription) do end def format_body( - %{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}}, - actor + %{activity: %{data: %{"type" => "Create"}}}, + actor, + %{data: %{"content" => content}} ) do "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" end def format_body( - %{activity: %{data: %{"type" => "Announce", "object" => activity_id}}}, - actor + %{activity: %{data: %{"type" => "Announce"}}}, + actor, + %{data: %{"content" => content}} ) do - %Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id) - %Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id) - "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}" end def format_body( %{activity: %{data: %{"type" => type}}}, - actor + actor, + _object ) when type in ["Follow", "Like"] do case type do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 09e51e602..9b833bc48 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -135,6 +135,7 @@ defmodule Pleroma.Web.Router do post("/password_reset", UtilController, :password_reset) get("/emoji", UtilController, :emoji) get("/captcha", UtilController, :captcha) + get("/healthcheck", UtilController, :healthcheck) end scope "/api/pleroma", Pleroma.Web do @@ -394,6 +395,8 @@ defmodule Pleroma.Web.Router do get("/accounts/:id", MastodonAPIController, :user) get("/search", MastodonAPIController, :search) + + get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites) end end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index a82109f92..72eaf2084 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -81,7 +81,7 @@ def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do _ -> Pleroma.List.get_lists_from_activity(item) |> Enum.filter(fn list -> - owner = User.get_by_id(list.user_id) + owner = User.get_cached_by_id(list.user_id) Visibility.visible_for_user?(item, owner) end) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 9441984c7..1122e6c5d 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def show_password_reset(conn, %{"token" => token}) do with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), - %User{} = user <- User.get_by_id(token.user_id) do + %User{} = user <- User.get_cached_by_id(token.user_id) do render(conn, "password_reset.html", %{ token: token, user: user @@ -113,13 +113,13 @@ defp is_status?(acct) do def do_remote_follow(conn, %{ "authorization" => %{"name" => username, "password" => password, "id" => id} }) do - followee = User.get_by_id(id) + followee = User.get_cached_by_id(id) avatar = User.avatar_url(followee) name = followee.nickname with %User{} = user <- User.get_cached_by_nickname(username), true <- Pbkdf2.checkpw(password, user.password_hash), - %User{} = _followed <- User.get_by_id(id), + %User{} = _followed <- User.get_cached_by_id(id), {:ok, follower} <- User.follow(user, followee), {:ok, _activity} <- ActivityPub.follow(follower, followee) do conn @@ -141,7 +141,7 @@ def do_remote_follow(conn, %{ end def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do - with %User{} = followee <- User.get_by_id(id), + with %User{} = followee <- User.get_cached_by_id(id), {:ok, follower} <- User.follow(user, followee), {:ok, _activity} <- ActivityPub.follow(follower, followee) do conn @@ -286,7 +286,7 @@ def emoji(conn, _params) do emoji = Emoji.get_all() |> Enum.map(fn {short_code, path, tags} -> - {short_code, %{image_url: path, tags: String.split(tags, ",")}} + {short_code, %{image_url: path, tags: tags}} end) |> Enum.into(%{}) @@ -363,4 +363,22 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do def captcha(conn, _params) do json(conn, Pleroma.Captcha.new()) end + + def healthcheck(conn, _params) do + info = + if Pleroma.Config.get([:instance, :healthcheck]) do + Pleroma.Healthcheck.system_info() + else + %{} + end + + conn = + if info[:healthy] do + conn + else + Plug.Conn.put_status(conn, :service_unavailable) + end + + json(conn, info) + end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 8e44dbeb8..adeac6f3c 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -240,7 +240,7 @@ def get_user(user \\ nil, params) do end %{"screen_name" => nickname} -> - case User.get_by_nickname(nickname) do + case User.get_cached_by_nickname(nickname) do nil -> {:error, "No user with such screen_name"} target -> {:ok, target} end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index a7ec9949c..79ed9dad2 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -434,7 +434,7 @@ def password_reset(conn, params) do end def confirm_email(conn, %{"user_id" => uid, "token" => token}) do - with %User{} = user <- User.get_by_id(uid), + with %User{} = user <- User.get_cached_by_id(uid), true <- user.local, true <- user.info.confirmation_pending, true <- user.info.confirmation_token == token, @@ -587,7 +587,7 @@ def friend_requests(conn, params) do def approve_friend_request(conn, %{"user_id" => uid} = _params) do with followed <- conn.assigns[:user], - %User{} = follower <- User.get_by_id(uid), + %User{} = follower <- User.get_cached_by_id(uid), {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do conn |> put_view(UserView) @@ -599,7 +599,7 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do def deny_friend_request(conn, %{"user_id" => uid} = _params) do with followed <- conn.assigns[:user], - %User{} = follower <- User.get_by_id(uid), + %User{} = follower <- User.get_cached_by_id(uid), {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do conn |> put_view(UserView) @@ -632,7 +632,7 @@ def raw_empty_array(conn, _params) do defp build_info_cng(user, params) do info_params = - ["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"] + ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"] |> Enum.reduce(%{}, fn key, res -> if value = params[key] do Map.put(res, key, value == "true") diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 0791ed760..ea015b8f0 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -74,58 +74,49 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) - data = %{ - "created_at" => user.inserted_at |> Utils.format_naive_asctime(), - "description" => HTML.strip_tags((user.bio || "") |> String.replace("
", "\n")), - "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)), - "favourites_count" => 0, - "followers_count" => user_info[:follower_count], - "following" => following, - "follows_you" => follows_you, - "statusnet_blocking" => statusnet_blocking, - "friends_count" => user_info[:following_count], - "id" => user.id, - "name" => user.name || user.nickname, - "name_html" => - if(user.name, - do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), - else: user.nickname - ), - "profile_image_url" => image, - "profile_image_url_https" => image, - "profile_image_url_profile_size" => image, - "profile_image_url_original" => image, - "rights" => %{ - "delete_others_notice" => !!user.info.is_moderator, - "admin" => !!user.info.is_admin - }, - "screen_name" => user.nickname, - "statuses_count" => user_info[:note_count], - "statusnet_profile_url" => user.ap_id, - "cover_photo" => User.banner_url(user) |> MediaProxy.url(), - "background_image" => image_url(user.info.background) |> MediaProxy.url(), - "is_local" => user.local, - "locked" => user.info.locked, - "default_scope" => user.info.default_scope, - "no_rich_text" => user.info.no_rich_text, - "hide_followers" => user.info.hide_followers, - "hide_follows" => user.info.hide_follows, - "fields" => fields, - - # Pleroma extension - "pleroma" => - %{ - "confirmation_pending" => user_info.confirmation_pending, - "tags" => user.tags - } - |> maybe_with_activation_status(user, for_user) - } - data = - if(user.info.is_admin || user.info.is_moderator, - do: maybe_with_role(data, user, for_user), - else: data - ) + %{ + "created_at" => user.inserted_at |> Utils.format_naive_asctime(), + "description" => HTML.strip_tags((user.bio || "") |> String.replace("
", "\n")), + "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)), + "favourites_count" => 0, + "followers_count" => user_info[:follower_count], + "following" => following, + "follows_you" => follows_you, + "statusnet_blocking" => statusnet_blocking, + "friends_count" => user_info[:following_count], + "id" => user.id, + "name" => user.name || user.nickname, + "name_html" => + if(user.name, + do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), + else: user.nickname + ), + "profile_image_url" => image, + "profile_image_url_https" => image, + "profile_image_url_profile_size" => image, + "profile_image_url_original" => image, + "screen_name" => user.nickname, + "statuses_count" => user_info[:note_count], + "statusnet_profile_url" => user.ap_id, + "cover_photo" => User.banner_url(user) |> MediaProxy.url(), + "background_image" => image_url(user.info.background) |> MediaProxy.url(), + "is_local" => user.local, + "locked" => user.info.locked, + "hide_followers" => user.info.hide_followers, + "hide_follows" => user.info.hide_follows, + "fields" => fields, + + # Pleroma extension + "pleroma" => + %{ + "confirmation_pending" => user_info.confirmation_pending, + "tags" => user.tags + } + |> maybe_with_activation_status(user, for_user) + } + |> maybe_with_user_settings(user, for_user) + |> maybe_with_role(user, for_user) if assigns[:token] do Map.put(data, "token", token_string(assigns[:token])) @@ -141,15 +132,35 @@ defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do defp maybe_with_activation_status(data, _, _), do: data defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do - Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role}) + Map.merge(data, %{ + "role" => role(user), + "show_role" => user.info.show_role, + "rights" => %{ + "delete_others_notice" => !!user.info.is_moderator, + "admin" => !!user.info.is_admin + } + }) end defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do - Map.merge(data, %{"role" => role(user)}) + Map.merge(data, %{ + "role" => role(user), + "rights" => %{ + "delete_others_notice" => !!user.info.is_moderator, + "admin" => !!user.info.is_admin + } + }) end defp maybe_with_role(data, _, _), do: data + defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do + data + |> Kernel.put_in(["default_scope"], info.default_scope) + |> Kernel.put_in(["no_rich_text"], info.no_rich_text) + end + + defp maybe_with_user_settings(data, _, _), do: data defp role(%User{info: %{:is_admin => true}}), do: "admin" defp role(%User{info: %{:is_moderator => true}}), do: "moderator" defp role(_), do: "member" diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 32c3455f5..a3b0bf999 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -37,7 +37,7 @@ def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do regex = ~r/(acct:)?(?\w+)@#{host}/ with %{"username" => username} <- Regex.named_captures(regex, resource), - %User{} = user <- User.get_by_nickname(username) do + %User{} = user <- User.get_cached_by_nickname(username) do {:ok, represent_user(user, fmt)} else _e -> diff --git a/mix.exs b/mix.exs index 2cdfb1392..bc2e55225 100644 --- a/mix.exs +++ b/mix.exs @@ -84,6 +84,7 @@ defp deps do {:ex_aws, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"}, {:earmark, "~> 1.3"}, + {:bbcode, "~> 0.1"}, {:ex_machina, "~> 2.3", only: :test}, {:credo, "~> 0.9.3", only: [:dev, :test]}, {:mock, "~> 0.3.1", only: :test}, diff --git a/mix.lock b/mix.lock index 73aed012f..e70d45b88 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,7 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "90613b4bae875a3610c275b7056b61ffdd53210d", [ref: "90613b4bae875a3610c275b7056b61ffdd53210d"]}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, + "bbcode": {:hex, :bbcode, "0.1.0", "400e618b640b635261611d7fb7f79d104917fc5b084aae371ab6b08477cb035b", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"}, "calendar": {:hex, :calendar, "0.17.5", "0ff5b09a60b9677683aa2a6fee948558660501c74a289103ea099806bc41a352", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/priv/repo/migrations/20190413082658_create_bookmarks.exs b/priv/repo/migrations/20190413082658_create_bookmarks.exs new file mode 100644 index 000000000..38b108158 --- /dev/null +++ b/priv/repo/migrations/20190413082658_create_bookmarks.exs @@ -0,0 +1,14 @@ +defmodule Pleroma.Repo.Migrations.CreateBookmarks do + use Ecto.Migration + + def change do + create table(:bookmarks) do + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) + add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all)) + + timestamps() + end + + create(unique_index(:bookmarks, [:user_id, :activity_id])) + end +end diff --git a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs new file mode 100644 index 000000000..134b7c6f7 --- /dev/null +++ b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs @@ -0,0 +1,29 @@ +defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Activity + alias Pleroma.Bookmark + alias Pleroma.User + alias Pleroma.Repo + + def change do + query = + from(u in User, + where: u.local == true, + where: fragment("array_length(bookmarks, 1)") > 0, + select: %{id: u.id, bookmarks: fragment("bookmarks")} + ) + + Repo.stream(query) + |> Enum.each(fn %{id: user_id, bookmarks: bookmarks} -> + Enum.each(bookmarks, fn ap_id -> + activity = Activity.get_create_by_object_ap_id(ap_id) + unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id) + end) + end) + + alter table(:users) do + remove(:bookmarks) + end + end +end diff --git a/priv/static/finmoji/1000px/a_trusted_friend.png b/priv/static/finmoji/1000px/a_trusted_friend.png deleted file mode 100644 index 5658d589c..000000000 Binary files a/priv/static/finmoji/1000px/a_trusted_friend.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/alandislands.png b/priv/static/finmoji/1000px/alandislands.png deleted file mode 100644 index 094dd3284..000000000 Binary files a/priv/static/finmoji/1000px/alandislands.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/association.png b/priv/static/finmoji/1000px/association.png deleted file mode 100644 index dad3b8864..000000000 Binary files a/priv/static/finmoji/1000px/association.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/auroraborealis.png b/priv/static/finmoji/1000px/auroraborealis.png deleted file mode 100644 index 5875dc2c4..000000000 Binary files a/priv/static/finmoji/1000px/auroraborealis.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/baby_in_a_box.png b/priv/static/finmoji/1000px/baby_in_a_box.png deleted file mode 100644 index 9479aaebb..000000000 Binary files a/priv/static/finmoji/1000px/baby_in_a_box.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/bear.png b/priv/static/finmoji/1000px/bear.png deleted file mode 100644 index 5d9fbb320..000000000 Binary files a/priv/static/finmoji/1000px/bear.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/black_gold.png b/priv/static/finmoji/1000px/black_gold.png deleted file mode 100644 index 707e949ec..000000000 Binary files a/priv/static/finmoji/1000px/black_gold.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/christmasparty.png b/priv/static/finmoji/1000px/christmasparty.png deleted file mode 100644 index 785decb8d..000000000 Binary files a/priv/static/finmoji/1000px/christmasparty.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/crosscountryskiing.png b/priv/static/finmoji/1000px/crosscountryskiing.png deleted file mode 100644 index 2a9bddf41..000000000 Binary files a/priv/static/finmoji/1000px/crosscountryskiing.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/cupofcoffee.png b/priv/static/finmoji/1000px/cupofcoffee.png deleted file mode 100644 index a12cc867c..000000000 Binary files a/priv/static/finmoji/1000px/cupofcoffee.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/education.png b/priv/static/finmoji/1000px/education.png deleted file mode 100644 index af9feee59..000000000 Binary files a/priv/static/finmoji/1000px/education.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/fashionista_finns.png b/priv/static/finmoji/1000px/fashionista_finns.png deleted file mode 100644 index d1140250d..000000000 Binary files a/priv/static/finmoji/1000px/fashionista_finns.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/finnishlove.png b/priv/static/finmoji/1000px/finnishlove.png deleted file mode 100644 index 00148202f..000000000 Binary files a/priv/static/finmoji/1000px/finnishlove.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/flag.png b/priv/static/finmoji/1000px/flag.png deleted file mode 100644 index e709449d7..000000000 Binary files a/priv/static/finmoji/1000px/flag.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/forest.png b/priv/static/finmoji/1000px/forest.png deleted file mode 100644 index b2d64ea37..000000000 Binary files a/priv/static/finmoji/1000px/forest.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/four_seasons_of_bbq.png b/priv/static/finmoji/1000px/four_seasons_of_bbq.png deleted file mode 100644 index 42f4a7fb7..000000000 Binary files a/priv/static/finmoji/1000px/four_seasons_of_bbq.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/girlpower.png b/priv/static/finmoji/1000px/girlpower.png deleted file mode 100644 index 7674f2e26..000000000 Binary files a/priv/static/finmoji/1000px/girlpower.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/handshake.png b/priv/static/finmoji/1000px/handshake.png deleted file mode 100644 index d9857d699..000000000 Binary files a/priv/static/finmoji/1000px/handshake.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/happiness.png b/priv/static/finmoji/1000px/happiness.png deleted file mode 100644 index fbfc34fe4..000000000 Binary files a/priv/static/finmoji/1000px/happiness.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/headbanger.png b/priv/static/finmoji/1000px/headbanger.png deleted file mode 100644 index d9c2f6247..000000000 Binary files a/priv/static/finmoji/1000px/headbanger.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/icebreaker.png b/priv/static/finmoji/1000px/icebreaker.png deleted file mode 100644 index aedce3dca..000000000 Binary files a/priv/static/finmoji/1000px/icebreaker.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/iceman.png b/priv/static/finmoji/1000px/iceman.png deleted file mode 100644 index c172e60d5..000000000 Binary files a/priv/static/finmoji/1000px/iceman.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/joulutorttu.png b/priv/static/finmoji/1000px/joulutorttu.png deleted file mode 100644 index d7b5a7e53..000000000 Binary files a/priv/static/finmoji/1000px/joulutorttu.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/kaamos.png b/priv/static/finmoji/1000px/kaamos.png deleted file mode 100644 index 139b21953..000000000 Binary files a/priv/static/finmoji/1000px/kaamos.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/kalsarikannit_f.png b/priv/static/finmoji/1000px/kalsarikannit_f.png deleted file mode 100644 index 064c86160..000000000 Binary files a/priv/static/finmoji/1000px/kalsarikannit_f.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/kalsarikannit_m.png b/priv/static/finmoji/1000px/kalsarikannit_m.png deleted file mode 100644 index e08bd27af..000000000 Binary files a/priv/static/finmoji/1000px/kalsarikannit_m.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/karjalanpiirakka.png b/priv/static/finmoji/1000px/karjalanpiirakka.png deleted file mode 100644 index dbf647df5..000000000 Binary files a/priv/static/finmoji/1000px/karjalanpiirakka.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/kicksled.png b/priv/static/finmoji/1000px/kicksled.png deleted file mode 100644 index 305a56f77..000000000 Binary files a/priv/static/finmoji/1000px/kicksled.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/kokko.png b/priv/static/finmoji/1000px/kokko.png deleted file mode 100644 index 0a5472c9a..000000000 Binary files a/priv/static/finmoji/1000px/kokko.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/lavatanssit.png b/priv/static/finmoji/1000px/lavatanssit.png deleted file mode 100644 index a1f0a69dd..000000000 Binary files a/priv/static/finmoji/1000px/lavatanssit.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/losthopes_f.png b/priv/static/finmoji/1000px/losthopes_f.png deleted file mode 100644 index a847df3c5..000000000 Binary files a/priv/static/finmoji/1000px/losthopes_f.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/losthopes_m.png b/priv/static/finmoji/1000px/losthopes_m.png deleted file mode 100644 index 93c83b995..000000000 Binary files a/priv/static/finmoji/1000px/losthopes_m.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/mattinykanen.png b/priv/static/finmoji/1000px/mattinykanen.png deleted file mode 100644 index 2d9c9d38f..000000000 Binary files a/priv/static/finmoji/1000px/mattinykanen.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/meanwhileinfinland.png b/priv/static/finmoji/1000px/meanwhileinfinland.png deleted file mode 100644 index 794db1eed..000000000 Binary files a/priv/static/finmoji/1000px/meanwhileinfinland.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/moominmamma.png b/priv/static/finmoji/1000px/moominmamma.png deleted file mode 100644 index d34b1b98b..000000000 Binary files a/priv/static/finmoji/1000px/moominmamma.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/nordicfamily.png b/priv/static/finmoji/1000px/nordicfamily.png deleted file mode 100644 index 21292eaff..000000000 Binary files a/priv/static/finmoji/1000px/nordicfamily.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/out_of_office.png b/priv/static/finmoji/1000px/out_of_office.png deleted file mode 100644 index b72d6dbd5..000000000 Binary files a/priv/static/finmoji/1000px/out_of_office.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/peacemaker.png b/priv/static/finmoji/1000px/peacemaker.png deleted file mode 100644 index 48a51fa6f..000000000 Binary files a/priv/static/finmoji/1000px/peacemaker.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/perkele.png b/priv/static/finmoji/1000px/perkele.png deleted file mode 100644 index 16a68d053..000000000 Binary files a/priv/static/finmoji/1000px/perkele.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/pesapallo.png b/priv/static/finmoji/1000px/pesapallo.png deleted file mode 100644 index 2f35c8e02..000000000 Binary files a/priv/static/finmoji/1000px/pesapallo.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/polarbear.png b/priv/static/finmoji/1000px/polarbear.png deleted file mode 100644 index ce6c65e8b..000000000 Binary files a/priv/static/finmoji/1000px/polarbear.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/pusa_hispida_saimensis.png b/priv/static/finmoji/1000px/pusa_hispida_saimensis.png deleted file mode 100644 index 35ec8caed..000000000 Binary files a/priv/static/finmoji/1000px/pusa_hispida_saimensis.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/reindeer.png b/priv/static/finmoji/1000px/reindeer.png deleted file mode 100644 index e60f0f0a4..000000000 Binary files a/priv/static/finmoji/1000px/reindeer.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/sami.png b/priv/static/finmoji/1000px/sami.png deleted file mode 100644 index e4703dfd2..000000000 Binary files a/priv/static/finmoji/1000px/sami.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/sauna_f.png b/priv/static/finmoji/1000px/sauna_f.png deleted file mode 100644 index 9a4ba8629..000000000 Binary files a/priv/static/finmoji/1000px/sauna_f.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/sauna_m.png b/priv/static/finmoji/1000px/sauna_m.png deleted file mode 100644 index 4bdd33f7b..000000000 Binary files a/priv/static/finmoji/1000px/sauna_m.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/sauna_whisk.png b/priv/static/finmoji/1000px/sauna_whisk.png deleted file mode 100644 index c16928065..000000000 Binary files a/priv/static/finmoji/1000px/sauna_whisk.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/sisu.png b/priv/static/finmoji/1000px/sisu.png deleted file mode 100644 index 238453bb5..000000000 Binary files a/priv/static/finmoji/1000px/sisu.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/stuck.png b/priv/static/finmoji/1000px/stuck.png deleted file mode 100644 index 4180e3ecd..000000000 Binary files a/priv/static/finmoji/1000px/stuck.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/suomimainittu.png b/priv/static/finmoji/1000px/suomimainittu.png deleted file mode 100644 index af46347f5..000000000 Binary files a/priv/static/finmoji/1000px/suomimainittu.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/superfood.png b/priv/static/finmoji/1000px/superfood.png deleted file mode 100644 index 8fa033c18..000000000 Binary files a/priv/static/finmoji/1000px/superfood.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/swan.png b/priv/static/finmoji/1000px/swan.png deleted file mode 100644 index 5363f861d..000000000 Binary files a/priv/static/finmoji/1000px/swan.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/the_cap.png b/priv/static/finmoji/1000px/the_cap.png deleted file mode 100644 index 7f547dc0e..000000000 Binary files a/priv/static/finmoji/1000px/the_cap.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/the_conductor.png b/priv/static/finmoji/1000px/the_conductor.png deleted file mode 100644 index ed5ca7f1f..000000000 Binary files a/priv/static/finmoji/1000px/the_conductor.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/the_king.png b/priv/static/finmoji/1000px/the_king.png deleted file mode 100644 index 8c3a5c66d..000000000 Binary files a/priv/static/finmoji/1000px/the_king.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/the_voice.png b/priv/static/finmoji/1000px/the_voice.png deleted file mode 100644 index 9bfc87b3a..000000000 Binary files a/priv/static/finmoji/1000px/the_voice.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/theoriginalsanta.png b/priv/static/finmoji/1000px/theoriginalsanta.png deleted file mode 100644 index b8dc1ef47..000000000 Binary files a/priv/static/finmoji/1000px/theoriginalsanta.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/tomoffinland.png b/priv/static/finmoji/1000px/tomoffinland.png deleted file mode 100644 index 97da05a64..000000000 Binary files a/priv/static/finmoji/1000px/tomoffinland.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/torillatavataan.png b/priv/static/finmoji/1000px/torillatavataan.png deleted file mode 100644 index ff7a81eda..000000000 Binary files a/priv/static/finmoji/1000px/torillatavataan.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/unbreakable.png b/priv/static/finmoji/1000px/unbreakable.png deleted file mode 100644 index 1778fc115..000000000 Binary files a/priv/static/finmoji/1000px/unbreakable.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/waiting.png b/priv/static/finmoji/1000px/waiting.png deleted file mode 100644 index 2aa9afa70..000000000 Binary files a/priv/static/finmoji/1000px/waiting.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/white_nights.png b/priv/static/finmoji/1000px/white_nights.png deleted file mode 100644 index 8e9cd3fc8..000000000 Binary files a/priv/static/finmoji/1000px/white_nights.png and /dev/null differ diff --git a/priv/static/finmoji/1000px/woollysocks.png b/priv/static/finmoji/1000px/woollysocks.png deleted file mode 100644 index 5ee4e6de1..000000000 Binary files a/priv/static/finmoji/1000px/woollysocks.png and /dev/null differ diff --git a/priv/static/finmoji/128px/a_trusted_friend-128.png b/priv/static/finmoji/128px/a_trusted_friend-128.png deleted file mode 100644 index 16d596bda..000000000 Binary files a/priv/static/finmoji/128px/a_trusted_friend-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/alandislands-128.png b/priv/static/finmoji/128px/alandislands-128.png deleted file mode 100644 index 13cdf6e76..000000000 Binary files a/priv/static/finmoji/128px/alandislands-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/association-128.png b/priv/static/finmoji/128px/association-128.png deleted file mode 100644 index 5b388d781..000000000 Binary files a/priv/static/finmoji/128px/association-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/auroraborealis-128.png b/priv/static/finmoji/128px/auroraborealis-128.png deleted file mode 100644 index 7e2af77b9..000000000 Binary files a/priv/static/finmoji/128px/auroraborealis-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/baby_in_a_box-128.png b/priv/static/finmoji/128px/baby_in_a_box-128.png deleted file mode 100644 index 9c495e24a..000000000 Binary files a/priv/static/finmoji/128px/baby_in_a_box-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/bear-128.png b/priv/static/finmoji/128px/bear-128.png deleted file mode 100644 index 8bb101bf4..000000000 Binary files a/priv/static/finmoji/128px/bear-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/black_gold-128.png b/priv/static/finmoji/128px/black_gold-128.png deleted file mode 100644 index 1833edab4..000000000 Binary files a/priv/static/finmoji/128px/black_gold-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/christmasparty-128.png b/priv/static/finmoji/128px/christmasparty-128.png deleted file mode 100644 index 98216830c..000000000 Binary files a/priv/static/finmoji/128px/christmasparty-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/crosscountryskiing-128.png b/priv/static/finmoji/128px/crosscountryskiing-128.png deleted file mode 100644 index 67553f398..000000000 Binary files a/priv/static/finmoji/128px/crosscountryskiing-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/cupofcoffee-128.png b/priv/static/finmoji/128px/cupofcoffee-128.png deleted file mode 100644 index 20064f218..000000000 Binary files a/priv/static/finmoji/128px/cupofcoffee-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/education-128.png b/priv/static/finmoji/128px/education-128.png deleted file mode 100644 index c98083bdd..000000000 Binary files a/priv/static/finmoji/128px/education-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/fashionista_finns-128.png b/priv/static/finmoji/128px/fashionista_finns-128.png deleted file mode 100644 index 4248825e0..000000000 Binary files a/priv/static/finmoji/128px/fashionista_finns-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/finnishlove-128.png b/priv/static/finmoji/128px/finnishlove-128.png deleted file mode 100644 index 5d4f9476c..000000000 Binary files a/priv/static/finmoji/128px/finnishlove-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/flag-128.png b/priv/static/finmoji/128px/flag-128.png deleted file mode 100644 index 0087cc589..000000000 Binary files a/priv/static/finmoji/128px/flag-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/forest-128.png b/priv/static/finmoji/128px/forest-128.png deleted file mode 100644 index 142e60b94..000000000 Binary files a/priv/static/finmoji/128px/forest-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/four_seasons_of_bbq-128.png b/priv/static/finmoji/128px/four_seasons_of_bbq-128.png deleted file mode 100644 index bb7fe1f51..000000000 Binary files a/priv/static/finmoji/128px/four_seasons_of_bbq-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/girlpower-128.png b/priv/static/finmoji/128px/girlpower-128.png deleted file mode 100644 index bc76a51c5..000000000 Binary files a/priv/static/finmoji/128px/girlpower-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/handshake-128.png b/priv/static/finmoji/128px/handshake-128.png deleted file mode 100644 index 4ebf196ab..000000000 Binary files a/priv/static/finmoji/128px/handshake-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/happiness-128.png b/priv/static/finmoji/128px/happiness-128.png deleted file mode 100644 index e28f99a26..000000000 Binary files a/priv/static/finmoji/128px/happiness-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/headbanger-128.png b/priv/static/finmoji/128px/headbanger-128.png deleted file mode 100644 index 0de620efe..000000000 Binary files a/priv/static/finmoji/128px/headbanger-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/icebreaker-128.png b/priv/static/finmoji/128px/icebreaker-128.png deleted file mode 100644 index 7fb36a4a3..000000000 Binary files a/priv/static/finmoji/128px/icebreaker-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/iceman-128.png b/priv/static/finmoji/128px/iceman-128.png deleted file mode 100644 index eb814e6aa..000000000 Binary files a/priv/static/finmoji/128px/iceman-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/joulutorttu-128.png b/priv/static/finmoji/128px/joulutorttu-128.png deleted file mode 100644 index 50448e333..000000000 Binary files a/priv/static/finmoji/128px/joulutorttu-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/kaamos-128.png b/priv/static/finmoji/128px/kaamos-128.png deleted file mode 100644 index 8b2df03ef..000000000 Binary files a/priv/static/finmoji/128px/kaamos-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/kalsarikannit_f-128.png b/priv/static/finmoji/128px/kalsarikannit_f-128.png deleted file mode 100644 index bcd94141a..000000000 Binary files a/priv/static/finmoji/128px/kalsarikannit_f-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/kalsarikannit_m-128.png b/priv/static/finmoji/128px/kalsarikannit_m-128.png deleted file mode 100644 index c6938e677..000000000 Binary files a/priv/static/finmoji/128px/kalsarikannit_m-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/karjalanpiirakka-128.png b/priv/static/finmoji/128px/karjalanpiirakka-128.png deleted file mode 100644 index a82a902db..000000000 Binary files a/priv/static/finmoji/128px/karjalanpiirakka-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/kicksled-128.png b/priv/static/finmoji/128px/kicksled-128.png deleted file mode 100644 index ff42462db..000000000 Binary files a/priv/static/finmoji/128px/kicksled-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/kokko-128.png b/priv/static/finmoji/128px/kokko-128.png deleted file mode 100644 index e0b6e07fa..000000000 Binary files a/priv/static/finmoji/128px/kokko-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/lavatanssit-128.png b/priv/static/finmoji/128px/lavatanssit-128.png deleted file mode 100644 index f89dc358c..000000000 Binary files a/priv/static/finmoji/128px/lavatanssit-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/losthopes_f-128.png b/priv/static/finmoji/128px/losthopes_f-128.png deleted file mode 100644 index 60f0949c0..000000000 Binary files a/priv/static/finmoji/128px/losthopes_f-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/losthopes_m-128.png b/priv/static/finmoji/128px/losthopes_m-128.png deleted file mode 100644 index 9ae6f9e2f..000000000 Binary files a/priv/static/finmoji/128px/losthopes_m-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/mattinykanen-128.png b/priv/static/finmoji/128px/mattinykanen-128.png deleted file mode 100644 index 0e81271ca..000000000 Binary files a/priv/static/finmoji/128px/mattinykanen-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/meanwhileinfinland-128.png b/priv/static/finmoji/128px/meanwhileinfinland-128.png deleted file mode 100644 index 5a9710a3b..000000000 Binary files a/priv/static/finmoji/128px/meanwhileinfinland-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/moominmamma-128.png b/priv/static/finmoji/128px/moominmamma-128.png deleted file mode 100644 index ae37bb94a..000000000 Binary files a/priv/static/finmoji/128px/moominmamma-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/nordicfamily-128.png b/priv/static/finmoji/128px/nordicfamily-128.png deleted file mode 100644 index cff41b228..000000000 Binary files a/priv/static/finmoji/128px/nordicfamily-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/out_of_office-128.png b/priv/static/finmoji/128px/out_of_office-128.png deleted file mode 100644 index 45cd1c2f5..000000000 Binary files a/priv/static/finmoji/128px/out_of_office-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/peacemaker-128.png b/priv/static/finmoji/128px/peacemaker-128.png deleted file mode 100644 index c4e9bd447..000000000 Binary files a/priv/static/finmoji/128px/peacemaker-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/perkele-128.png b/priv/static/finmoji/128px/perkele-128.png deleted file mode 100644 index e89e5bf32..000000000 Binary files a/priv/static/finmoji/128px/perkele-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/pesapallo-128.png b/priv/static/finmoji/128px/pesapallo-128.png deleted file mode 100644 index 5e06bec50..000000000 Binary files a/priv/static/finmoji/128px/pesapallo-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/polarbear-128.png b/priv/static/finmoji/128px/polarbear-128.png deleted file mode 100644 index fd3c3ec30..000000000 Binary files a/priv/static/finmoji/128px/polarbear-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/pusa_hispida_saimensis-128.png b/priv/static/finmoji/128px/pusa_hispida_saimensis-128.png deleted file mode 100644 index 60620be5d..000000000 Binary files a/priv/static/finmoji/128px/pusa_hispida_saimensis-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/reindeer-128.png b/priv/static/finmoji/128px/reindeer-128.png deleted file mode 100644 index 8cdd05f27..000000000 Binary files a/priv/static/finmoji/128px/reindeer-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/sami-128.png b/priv/static/finmoji/128px/sami-128.png deleted file mode 100644 index e9e9f41a7..000000000 Binary files a/priv/static/finmoji/128px/sami-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/sauna_f-128.png b/priv/static/finmoji/128px/sauna_f-128.png deleted file mode 100644 index 474f126ff..000000000 Binary files a/priv/static/finmoji/128px/sauna_f-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/sauna_m-128.png b/priv/static/finmoji/128px/sauna_m-128.png deleted file mode 100644 index f7f563a9b..000000000 Binary files a/priv/static/finmoji/128px/sauna_m-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/sauna_whisk-128.png b/priv/static/finmoji/128px/sauna_whisk-128.png deleted file mode 100644 index 80ebb55e4..000000000 Binary files a/priv/static/finmoji/128px/sauna_whisk-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/sisu-128.png b/priv/static/finmoji/128px/sisu-128.png deleted file mode 100644 index 7b9330654..000000000 Binary files a/priv/static/finmoji/128px/sisu-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/stuck-128.png b/priv/static/finmoji/128px/stuck-128.png deleted file mode 100644 index c14bc555d..000000000 Binary files a/priv/static/finmoji/128px/stuck-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/suomimainittu-128.png b/priv/static/finmoji/128px/suomimainittu-128.png deleted file mode 100644 index 8d35b9be1..000000000 Binary files a/priv/static/finmoji/128px/suomimainittu-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/superfood-128.png b/priv/static/finmoji/128px/superfood-128.png deleted file mode 100644 index 2e9d924cc..000000000 Binary files a/priv/static/finmoji/128px/superfood-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/swan-128.png b/priv/static/finmoji/128px/swan-128.png deleted file mode 100644 index d1711c70b..000000000 Binary files a/priv/static/finmoji/128px/swan-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/the_cap-128.png b/priv/static/finmoji/128px/the_cap-128.png deleted file mode 100644 index 10d83c22e..000000000 Binary files a/priv/static/finmoji/128px/the_cap-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/the_conductor-128.png b/priv/static/finmoji/128px/the_conductor-128.png deleted file mode 100644 index 0da7c42e8..000000000 Binary files a/priv/static/finmoji/128px/the_conductor-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/the_king-128.png b/priv/static/finmoji/128px/the_king-128.png deleted file mode 100644 index 07dd27ad7..000000000 Binary files a/priv/static/finmoji/128px/the_king-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/the_voice-128.png b/priv/static/finmoji/128px/the_voice-128.png deleted file mode 100644 index bb436f95b..000000000 Binary files a/priv/static/finmoji/128px/the_voice-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/theoriginalsanta-128.png b/priv/static/finmoji/128px/theoriginalsanta-128.png deleted file mode 100644 index 082d58c28..000000000 Binary files a/priv/static/finmoji/128px/theoriginalsanta-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/tomoffinland-128.png b/priv/static/finmoji/128px/tomoffinland-128.png deleted file mode 100644 index 29c68bcba..000000000 Binary files a/priv/static/finmoji/128px/tomoffinland-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/torillatavataan-128.png b/priv/static/finmoji/128px/torillatavataan-128.png deleted file mode 100644 index da7b502b4..000000000 Binary files a/priv/static/finmoji/128px/torillatavataan-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/unbreakable-128.png b/priv/static/finmoji/128px/unbreakable-128.png deleted file mode 100644 index eb825e14f..000000000 Binary files a/priv/static/finmoji/128px/unbreakable-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/waiting-128.png b/priv/static/finmoji/128px/waiting-128.png deleted file mode 100644 index 10b9167f2..000000000 Binary files a/priv/static/finmoji/128px/waiting-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/white_nights-128.png b/priv/static/finmoji/128px/white_nights-128.png deleted file mode 100644 index 8eacd11f0..000000000 Binary files a/priv/static/finmoji/128px/white_nights-128.png and /dev/null differ diff --git a/priv/static/finmoji/128px/woollysocks-128.png b/priv/static/finmoji/128px/woollysocks-128.png deleted file mode 100644 index 856af5b2e..000000000 Binary files a/priv/static/finmoji/128px/woollysocks-128.png and /dev/null differ diff --git a/priv/static/finmoji/LICENSE b/priv/static/finmoji/LICENSE deleted file mode 100644 index e3a607aa3..000000000 --- a/priv/static/finmoji/LICENSE +++ /dev/null @@ -1 +0,0 @@ -these are under CC-BY-ND, see https://finland.fi/emoji/ diff --git a/priv/static/index.html b/priv/static/index.html index 3114acffe..1dcedeec8 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/config.json b/priv/static/static/config.json index 533a5b087..04cbb97b5 100644 --- a/priv/static/static/config.json +++ b/priv/static/static/config.json @@ -8,7 +8,6 @@ "redirectRootLogin": "/main/friends", "chatDisabled": false, "showInstanceSpecificPanel": false, - "scopeOptionsEnabled": false, "formattingOptionsEnabled": false, "collapseMessageWithSubject": false, "scopeCopy": true, @@ -21,5 +20,6 @@ "webPushNotifications": false, "noAttachmentLinks": false, "nsfwCensorImage": "", - "showFeaturesPanel": true + "showFeaturesPanel": true, + "minimalScopesMode": false } diff --git a/priv/static/static/css/app.a81578273cb4c57163939ab70c80eb06.css b/priv/static/static/css/app.a81578273cb4c57163939ab70c80eb06.css new file mode 100644 index 000000000..bf3c12d78 Binary files /dev/null and b/priv/static/static/css/app.a81578273cb4c57163939ab70c80eb06.css differ diff --git a/priv/static/static/css/app.a81578273cb4c57163939ab70c80eb06.css.map b/priv/static/static/css/app.a81578273cb4c57163939ab70c80eb06.css.map new file mode 100644 index 000000000..e4bc2dbe1 --- /dev/null +++ b/priv/static/static/css/app.a81578273cb4c57163939ab70c80eb06.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///webpack:///src/components/timeline/timeline.vue","webpack:///webpack:///src/components/status/status.vue","webpack:///webpack:///src/components/attachment/attachment.vue","webpack:///webpack:///src/components/still-image/still-image.vue","webpack:///webpack:///src/components/favorite_button/favorite_button.vue","webpack:///webpack:///src/components/retweet_button/retweet_button.vue","webpack:///webpack:///src/components/delete_button/delete_button.vue","webpack:///webpack:///src/components/post_status_form/post_status_form.vue","webpack:///webpack:///src/components/media_upload/media_upload.vue","webpack:///webpack:///src/components/emoji-input/emoji-input.vue","webpack:///webpack:///src/components/user_card/user_card.vue","webpack:///webpack:///src/components/user_avatar/user_avatar.vue","webpack:///webpack:///src/components/remote_follow/remote_follow.vue","webpack:///webpack:///src/components/moderation_tools/moderation_tools.vue","webpack:///webpack:///src/components/dialog_modal/dialog_modal.vue","webpack:///webpack:///~/vue-popperjs/src/component/popper.js.vue","webpack:///webpack:///src/components/gallery/gallery.vue","webpack:///webpack:///src/components/link-preview/link-preview.vue","webpack:///webpack:///src/components/conversation/conversation.vue","webpack:///webpack:///src/components/user_profile/user_profile.vue","webpack:///webpack:///src/components/follow_card/follow_card.vue","webpack:///webpack:///src/components/basic_user_card/basic_user_card.vue","webpack:///webpack:///src/components/list/list.vue","webpack:///webpack:///src/hocs/with_load_more/src/hocs/with_load_more/with_load_more.scss","webpack:///webpack:///src/components/settings/settings.vue","webpack:///webpack:///src/components/tab_switcher/src/components/tab_switcher/tab_switcher.scss","webpack:///webpack:///src/components/style_switcher/style_switcher.scss","webpack:///webpack:///src/components/color_input/color_input.vue","webpack:///webpack:///src/components/shadow_control/shadow_control.vue","webpack:///webpack:///src/components/font_control/font_control.vue","webpack:///webpack:///src/components/contrast_ratio/contrast_ratio.vue","webpack:///webpack:///src/components/export_import/export_import.vue","webpack:///webpack:///src/components/registration/registration.vue","webpack:///webpack:///src/components/user_settings/user_settings.vue","webpack:///webpack:///src/components/image_cropper/image_cropper.vue","webpack:///webpack:///~/cropperjs/dist/cropper.css","webpack:///webpack:///src/components/block_card/block_card.vue","webpack:///webpack:///src/components/mute_card/mute_card.vue","webpack:///webpack:///src/components/selectable_list/selectable_list.vue","webpack:///webpack:///src/components/checkbox/checkbox.vue","webpack:///webpack:///src/components/autosuggest/autosuggest.vue","webpack:///webpack:///src/hocs/with_subscription/src/hocs/with_subscription/with_subscription.scss","webpack:///webpack:///src/components/follow_request_card/follow_request_card.vue","webpack:///webpack:///src/components/user_search/user_search.vue","webpack:///webpack:///src/components/notifications/notifications.scss","webpack:///webpack:///src/components/login_form/login_form.vue","webpack:///webpack:///src/components/chat_panel/chat_panel.vue","webpack:///webpack:///src/components/features_panel/features_panel.vue","webpack:///webpack:///src/components/terms_of_service_panel/terms_of_service_panel.vue","webpack:///webpack:///src/App.scss","webpack:///webpack:///src/components/nav_panel/nav_panel.vue","webpack:///webpack:///src/components/user_finder/user_finder.vue","webpack:///webpack:///src/components/who_to_follow_panel/who_to_follow_panel.vue","webpack:///webpack:///src/components/media_modal/media_modal.vue","webpack:///webpack:///src/components/side_drawer/side_drawer.vue","webpack:///webpack:///src/components/mobile_post_status_modal/mobile_post_status_modal.vue","webpack:///webpack:///src/components/mobile_nav/mobile_nav.vue"],"names":[],"mappings":"AACA,yBAAyB,SAAS,CAElC,yBAAyB,kBAAkB,gBAAgB,gBAAgB,qBAAuB,mBAAmB,gCAAiC,aAAa,UAAU,yBAAyB,qCAAsC,CCF5O,aAAa,WAAW,OAAO,WAAW,CAE1C,0BAA8D,kBAAkB,mCAAgC,CAEhH,0BAA0B,kBAAkB,cAAc,CAE1D,gBAAgB,kBAAkB,cAAc,oBAAoB,aAAa,yBAAyB,mCAAoC,kBAAkB,oCAAqE,kBAAkB,uCAAwC,sCAAuC,8BAA8B,iBAAkB,iBAAkB,UAAU,CAElZ,wBAAwB,WAAW,OAAO,SAAS,cAAc,CAEjE,wBAAwB,cAAc,eAAe,YAAY,kBAAkB,iBAAiB,kBAAkB,CAEtH,0BAA0B,aAAa,CAEvC,YAAY,kBAAkB,CAE9B,WAAW,qBAAqB,iBAAiB,aAAa,yBAAyB,qBAAqB,sBAAsB,oBAAsB,YAAY,kBAAkB,gCAAiC,oBAAoB,+BAAgC,CAE3Q,mBAAmB,yBAAyB,uCAAwC,CAEpF,qBAAqB,wBAAwB,yBAAyB,CAEtE,uBAAuB,WAAW,OAAO,SAAS,CAElD,4BAA4B,mBAAmB,CAE/C,sBAAsB,mBAAmB,eAAe,gBAAgB,oBAAoB,cAAc,cAAc,eAAgB,CAExI,0BAA0B,WAAW,YAAY,sBAAsB,kBAAkB,CAEzF,0BAA0B,UAAU,sBAAsB,6BAA6B,gBAAgB,kBAAmB,CAE1H,4BAA4B,qBAAqB,oBAAoB,CAErE,gCAAgC,mBAAmB,CAEnD,4CAA4C,UAAU,oBAAoB,aAAa,sBAAsB,8BAA8B,gBAAgB,CAE3J,mEAAmE,oBAAoB,aAAa,WAAW,CAE/G,uDAAuD,oBAAoB,cAAc,kBAAmB,gBAAgB,sBAAsB,CAElJ,0DAA0D,gBAAgB,kBAAmB,mBAAmB,gBAAgB,uBAAuB,iBAAiB,UAAU,CAElL,yCAAyC,oBAAoB,aAAa,oBAAoB,aAAa,CAE3G,mCAAmC,iBAAkB,CAErD,6CAA6C,4BAA4B,uBAAuB,eAAe,iBAAiB,eAAe,oBAAoB,aAAa,mBAAmB,eAAe,uBAAuB,mBAAmB,CAE5P,+CAA+C,eAAe,uBAAuB,gBAAgB,kBAAkB,CAEvH,oDAAoD,oBAAoB,aAAa,YAAY,kBAAmB,gBAAgB,cAAc,CAElJ,gEAAgE,oBAAoB,CAIpF,0EAAoC,oBAAoB,YAAY,CAEpE,yCAAyC,gBAAgB,uBAAuB,oBAAsB,CAEtG,6CAA6C,gBAAiB,CAE9D,mCAAmC,iBAAiB,eAAe,oBAAoB,aAAa,mBAAmB,cAAc,CAErI,qCAAqC,iBAAkB,CAEvD,sCAAsC,WAAW,CAEjD,wBAAwB,kBAAkB,aAAa,kBAAkB,iBAAiB,CAE1F,8BAA8B,qBAAqB,qBAAqB,kBAAkB,YAAY,iBAAiB,WAAW,kBAAkB,kBAAkB,2DAAgE,oEAA0E,CAEhT,sCAAsC,2DAAgE,yEAA+E,CAErL,uDAAuD,WAAW,kBAAkB,qBAAqB,oBAAoB,CAE7H,2BAA2B,uCAAwC,iBAAiB,CAEpF,gEAAgE,eAAe,iBAAiB,sBAAsB,kBAAkB,CAExI,4EAA4E,WAAW,WAAW,CAElG,sCAAsC,uBAAyB,iBAAiB,CAEhF,+BAA+B,aAAa,CAE5C,6JAA6J,yCAA0C,CAEvM,6BAA6B,cAAgB,CAE7C,wCAAwC,QAAc,CAEtD,8BAA8B,gBAAgB,kBAAkB,cAAc,CAE9E,8BAA8B,gBAAgB,YAAc,CAE5D,8BAA8B,cAAc,cAAc,CAE1D,8BAA8B,cAAc,CAE5C,yBAAyB,mBAAoB,QAAQ,CAErD,6CAA6C,mBAAmB,0CAA2C,iBAAiB,WAAW,WAAW,CAElJ,qCAAqC,cAAc,iBAAiB,oBAAoB,aAAa,0BAA0B,qBAAqB,mBAAmB,cAAc,CAErL,gDAAgD,gBAAiB,gBAAgB,sBAAsB,CAEvG,oDAAoD,WAAW,YAAY,sBAAsB,kBAAkB,CAEnH,uCAAuC,cAAe,CAEtD,uCAAuC,eAAe,gBAAgB,uBAAuB,kBAAkB,CAE/G,eAAe,uBAAwB,qBAAqB,CAE5D,kBACA,GAAK,SAAS,CAEd,GAAG,SAAS,CACX,CAED,WAAW,WAAW,CAEtB,qBAAqB,uBAAuB,CAE5C,gBAAgB,WAAW,oBAAoB,aAAa,gBAAgB,CAE5E,oDAAoD,cAAc,WAAW,MAAM,CAEnF,kBAA4D,cAAc,CAE1E,gDAFkB,cAAc,0BAA4B,CAI5D,sCAAsC,YAAY,CAElD,mCAAmC,kBAAkB,CAErD,QAAQ,oBAAoB,aAAa,aAAa,CAEtD,mBAAmB,aAAa,CAEhC,gCAAgC,kBAAkB,CAElD,OAAO,kBAAoB,CAE3B,cAAc,gBAAgB,CAE9B,kBAAkB,gBAAgB,CAElC,SAAS,cAAc,gBAAgB,CAEvC,YAAY,WAAW,OAAO,cAAc,CAE5C,YAAY,WAAW,MAAM,CAE7B,gCAAgC,4BAA4B,kEAAoE,kBAAkB,CAElJ,yBACA,6CAA6C,gBAAgB,CAE7D,QAAQ,cAAc,CAEtB,4BAA4B,WAAW,WAAW,CAElD,2CAA2C,WAAW,WAAW,CAChE,CCxKD,aAAa,oBAAoB,aAAa,mBAAmB,cAAc,CAE/E,gDAAgD,kBAAkB,cAAc,iBAAiB,eAAe,oBAAoB,YAAY,CAEhJ,sDAAsD,cAAc,CAEpE,0BAA0B,iBAAiB,iBAAiB,CAE5D,+BAA+B,cAAc,CAE7C,uCAAuC,eAAe,CAEtD,yBAAyB,kBAAkB,gBAAiB,0BAA0B,sBAAsB,cAAkD,mBAAmB,2CAA4C,kBAAkB,oCAAiC,eAAe,CAE/R,2CAA2C,iBAAiB,YAAY,CAExE,2CAA2C,YAAY,CAEvD,4CAA4C,aAAa,oBAAoB,WAAW,CAExF,4CAA4C,aAAa,oBAAoB,YAAY,CAEzF,2CAA2C,gBAAgB,kBAAkB,CAE7E,wBAAwB,6BAA6B,eAAe,CAEpE,mBAAmB,aAAa,CAEhC,8BAA8B,oBAAoB,aAAa,eAAe,CAE9E,oBAAoB,UAAU,CAE9B,wBAAwB,kBAAkB,eAAe,qBAAqB,sBAAsB,0BAA6B,kCAAmC,CAEpK,+BAAgC,QAAQ,CAExC,kBAAkB,4BAA4B,eAAe,WAAW,oBAAoB,YAAY,CAExG,oBAAoB,kBAAkB,QAAQ,mBAAmB,YAAY,YAAY,6BAAiC,gBAAiB,UAAU,cAAc,kBAAkB,sCAAuC,CAE5N,mBAAmB,SAAS,CAE5B,mBAAmB,UAAU,CAE7B,8BAA8B,cAAc,iBAAiB,cAAc,CAE3E,qBAAqB,kBAAkB,kBAAkB,cAAc,WAAW,kBAAkB,oBAAoB,YAAY,CAEpI,yBAAyB,UAAU,CAEnC,4BAA4B,WAAW,MAAM,CAE7C,gCAAgC,SAAW,kBAAkB,YAAY,gBAAgB,CAEzF,2BAA2B,WAAW,OAAO,WAAW,oBAAoB,CAE5E,8BAA8B,eAAe,QAAU,CAEvD,+BAA+B,WAAW,WAAW,CAErD,sCAAsC,YAAY,CAElD,qCAAqC,iBAAiB,WAAW,WAAW,CAE5E,mCAAmC,4BAA4B,CChE/D,aAAa,kBAAkB,cAAc,gBAAgB,WAAW,WAAW,CAEnF,0BAA0B,YAAY,CAEtC,iBAAiB,WAAW,YAAY,kBAAkB,CAE1D,6DAA8D,iBAAiB,CAE/E,gCAAgC,kBAAkB,CAElD,6BAA8B,cAAc,kBAAkB,iBAAiB,eAAe,QAAQ,SAAS,6BAAiC,WAAW,cAAc,gBAAgB,kBAAkB,uCAAwC,SAAS,CAE5P,oBAAoB,kBAAkB,MAAM,SAAS,OAAO,QAAQ,WAAW,YAAY,kBAAkB,CCZ7G,YAAY,eAAe,sBAAuB,CAIlD,6CAA2B,aAAa,2BAA4B,CCJpE,WAAW,eAAe,sBAAuB,CAIjD,yCAAwB,cAAc,2BAA4B,CCJlE,4BAA4B,cAAc,CAE1C,wCAAwC,UAAU,qBAAsB,CCFxE,sBAAsB,SAAW,CAEjC,yBAAyB,oBAAoB,aAAa,sBAAsB,kBAAkB,CAElG,uBAAuB,YAAY,WAAW,YAAY,mBAAmB,yCAA0C,CAEvH,mCAAmC,oBAAoB,aAAa,sBAAsB,8BAA8B,+BAA+B,0BAA0B,CAEjL,mDAAmD,oBAAoB,aAAa,aAAc,WAAW,CAE7G,iEAAiE,UAAU,CAE3E,uDAAuD,aAAc,cAAe,oBAAoB,YAAY,CAEpH,uCAAuC,iBAAiB,CAExD,qEAAqE,kBAAkB,cAAc,eAAe,eAAe,kBAAkB,kBAAkB,CAEvK,+FAA+F,qBAAqB,gBAAgB,SAAS,iBAAiB,iBAAiB,yCAA0C,yBAAyB,oCAAqC,4BAA4B,4BAA4B,CAE/U,mDAAmD,cAAe,CAElE,2EAA2E,SAAS,kBAAkB,kBAAkB,cAAc,sBAAsB,oCAAqC,iBAAiB,CAElN,uFAAuF,gBAAgB,kBAAkB,aAAa,CAEtI,+EAA+E,cAAc,gBAAgB,gBAAgB,YAAY,CAEzI,uDAAuD,kBAAkB,YAAY,YAAY,6BAAiC,mBAAmB,2CAA4C,eAAgB,CAMjN,mCAAmC,oBAAoB,aAAa,0BAA0B,sBAAsB,YAAa,CAEjI,iDAAiD,oBAAoB,aAAa,0BAA0B,sBAAsB,uBAA0B,gBAAgB,CAI5K,oJAFqE,iBAAiB,YAAY,gBAAgB,8BAAkC,cAAc,CAGjK,+EAD4K,sBAAsB,CAEnM,2FAA2F,eAAe,CAE1G,mCAAmC,cAAc,CAEjD,uDAAuD,kBAAkB,CAEzE,mDAAmD,eAAe,SAAS,CChD3E,cACI,eACA,WACI,MAAQ,CAEhB,aACI,cAAgB,CCNpB,2BAA2B,UAAU,CCArC,WAAW,sBAAsB,eAAe,CAEhD,0BAA0B,eAAe,kBAAkB,gBAAgB,uBAAuB,0BAA0B,sBAAsB,uBAAuB,mBAAmB,CAE5L,uBAAuB,qBAAqB,2DAAgE,oEAA0E,CAEtL,aAAa,eAAe,CAE5B,eAAe,iBAAiB,CAEhC,mBAAmB,mBAAmB,sBAAsB,eAAe,gBAAgB,CAE3F,yBAAyB,WAAW,WAAW,CAE/C,qBAAqB,4BAA4B,+CAAgD,6BAA6B,+CAAgD,CAE9K,mBAAmB,mBAAmB,qCAAsC,CAE5E,oBAAwD,kBAAkB,mCAAgC,CAE1G,WAAW,cAAc,+BAAgC,cAAc,CAEvE,sBAAsB,mBAAmB,oBAAoB,aAAa,eAAe,CAEzF,8BAA8B,kBAAkB,cAAc,WAAW,YAAY,qCAAwC,+BAA+B,gBAAgB,CAE5K,yCAAyC,YAAY,CAErD,sCAAsC,kBAAkB,CAExD,yBAAyB,cAAc,+BAAgC,UAAU,CAEjF,iCAAiC,cAAc,iBAAkB,gBAAgB,uBAAuB,mBAAmB,iBAAiB,WAAW,SAAS,CAEhK,qCAAqC,WAAW,YAAY,sBAAsB,kBAAkB,CAEpG,2CAA2C,oBAAoB,YAAY,CAE3E,sBAAsB,uBAAuB,gBAAgB,kBAAkB,cAAc,iBAAiB,cAAc,CAE5H,0BAA0B,mBAAmB,YAAY,WAAW,qBAAqB,CAEzF,6BAA6B,cAAc,+BAAgC,qBAAqB,kBAAkB,eAAe,mBAAoB,WAAW,oBAAoB,YAAY,CAEhM,uCAAuC,cAAc,kBAAkB,cAAc,gBAAgB,eAAgB,cAAc,yBAA0B,CAE7J,qCAAqC,cAAc,kBAAkB,cAAc,uBAAuB,eAAe,CAEzH,oCAAoC,0BAA0B,cAAc,6BAA8B,yBAAyB,mCAAoC,CAEvK,sBAAsB,oBAAoB,oBAAoB,aAAa,wBAAwB,qBAAqB,eAAe,iBAAiB,mBAAmB,cAAc,CAEzL,iCAAiC,kBAAkB,cAAc,SAAS,oBAAoB,eAAe,CAE7G,mCAAmC,kBAAkB,cAAc,oBAAoB,aAAa,mBAAmB,eAAe,mBAAmB,0BAA0B,gBAAgB,CAEnM,oDAAoD,iBAAiB,kBAAkB,aAAa,CAEpG,iHAAiH,cAAc,iBAAiB,kBAAkB,aAAa,CAE/K,8DAA8D,gBAAgB,CAE9E,sDAAsD,WAAW,kBAAkB,aAAa,CAEhG,2NAA2N,YAAY,mBAAmB,kBAAkB,mBAAmB,CAE/R,8BAA8B,oBAAoB,aAAa,uBAAuB,mBAAmB,sBAAsB,8BAA8B,mBAAmB,CAEhL,kCAAkC,iBAAiB,WAAW,mBAAmB,mBAAmB,kBAAkB,CAItH,0EAAsC,gBAAgB,eAAe,CAErE,qCAAqC,WAAW,YAAY,QAAQ,CAEpE,6CAA6C,sBAAuB,SAAS,CAE7E,uCAAuC,uCAA0C,+BAAgC,CAEjH,aAAa,oBAAoB,aAAa,iBAAiB,qBAA6B,kBAAkB,sBAAsB,8BAA8B,cAAc,+BAAgC,mBAAmB,cAAc,CAEjP,YAAY,kBAAkB,cAAc,eAAsB,aAAa,CAE/E,eAAe,cAAc,mBAAmB,gBAAiB,CAEjE,cAAc,oBAAoB,CCtFlC,oBAAoB,WAAW,YAAY,qCAAqC,kBAAkB,qCAAsC,CAExI,wBAAwB,WAAW,WAAW,CAE9C,kCAAkC,0CAA0C,sCAAsC,CAElH,oCAAqC,YAAY,CAEjD,mCAAmC,WAAW,YAAY,mBAAmB,yCAA0C,CCRvH,eAAe,eAAe,CAE9B,8BAA8B,WAAW,eAAe,CCFxD,gBAAgB,SAAS,CAEzB,+BAA+B,QAAQ,SAAS,mBAAmB,kBAAkB,UAAU,CAE/F,kCAAoC,iBAAiB,CAErD,iDAAmD,uBAA2B,6CAAyD,uDAAoE,YAAY,qBAAqB,aAAa,eAAe,CAExQ,qCAAuC,cAAc,CAErD,oDAAsD,uBAA2B,6CAAyD,uDAAoE,SAAS,qBAAqB,aAAa,eAAe,CAExQ,oCAAsC,eAAe,CAErD,mDAAqD,2BAA2B,yDAAyD,mEAAoE,UAAU,oBAAoB,cAAc,cAAc,CAEvQ,mCAAqC,gBAAgB,CAErD,kDAAoD,2BAA2B,yDAAyD,mEAAoE,WAAW,oBAAoB,cAAc,cAAc,CAEvQ,eAAe,cAAc,gBAAgB,eAAe,gBAAgB,gBAAgB,gBAAgB,WAAW,sCAAuC,8BAA8B,YAAY,kBAAkB,mCAAoC,yBAAyB,kCAAmC,CAE1T,iCAAiC,SAAS,eAAe,gBAAgB,0BAA0B,uCAAwC,CAE3I,8BAA8B,iBAAiB,iBAAiB,cAAc,cAAc,kCAAoC,WAAW,gBAAgB,mBAAmB,mBAAmB,YAAY,gBAAkB,6BAA6B,gBAAgB,WAAW,WAAW,CAElS,oCAAoC,yBAAyB,oCAAqC,eAAe,CAEjH,eAAe,YAAY,eAAe,eAAe,gBAAgB,gBAAgB,iBAAiB,kBAAkB,gBAAkB,yBAAyB,sCAAuC,8BAAmC,6BAA6B,CAE9Q,2CAA4C,eAAW,CC9BvD,qBAAsB,SAAS,YAAyC,OAAsB,QAAc,6BAA8B,UAAU,CAEpJ,yCAF2C,cAAc,eAAsB,eAAuB,KAAM,CAG3G,oBADyB,SAAS,gBAAgB,eAAe,iBAAgC,2BAA2B,YAAyC,yBAAyB,kCAAmC,CAElO,0CAA0C,aAAkB,kBAAkB,gBAAgB,mBAAmB,uBAAuB,yBAAyB,qCAAsC,CAEvM,iDAAiD,eAAe,CAEhE,0CAA0C,SAAS,aAAkB,yBAAyB,wCAAyC,kBAAkB,CAEzJ,yCAAyC,SAAS,aAAkB,yBAAyB,wCAAyC,6BAA6B,uCAAwC,kBAAkB,wBAAwB,CAErP,gDAAgD,WAAW,iBAAiB,CCZ5E,QACE,WACA,yBACA,cACA,kBACA,YACA,qBACA,kBACA,kBACA,eACA,gBACA,yBACA,eACA,4BAAsC,CAExC,uBACE,QACA,SACA,mBACA,kBACA,UAAY,CAEd,0BACE,iBAAmB,CAErB,yCACE,uBACA,6CACA,YACA,qBACA,aACA,eAAiB,CAEnB,6BACE,cAAgB,CAElB,4CACE,uBACA,6CACA,SACA,qBACA,aACA,eAAiB,CAEnB,4BACE,eAAiB,CAEnB,2CACE,2BACA,yDACA,UACA,oBACA,cACA,cAAgB,CAElB,2BACE,gBAAkB,CAEpB,0CACE,2BACA,yDACA,WACA,oBACA,cACA,cAAgB,CChElB,aAAa,aAAa,WAAW,oBAAoB,aAAa,uBAAuB,mBAAmB,qBAAqB,iBAAiB,2BAA2B,sBAAsB,oBAAoB,YAAY,eAAgB,CAEvP,mDAAmD,kBAAmB,oBAAoB,YAAY,YAAY,sBAAsB,aAAa,CAErJ,yEAAyE,QAAQ,CAEjF,+BAA+B,WAAW,WAAW,CAErD,8BAA8B,WAAW,CAEzC,4DAA4D,kBAAkB,CAE9E,wDAAwD,gBAAgB,CCZxE,mBAAmB,oBAAoB,aAAa,uBAAuB,mBAAmB,eAAe,gBAAgB,gBAAiB,cAAc,0BAA+D,mBAAmB,2CAA4C,kBAAkB,mCAAgC,CAE5U,+BAA+B,oBAAoB,cAAc,YAAY,aAAa,CAE1F,mCAAmC,WAAW,YAAY,iBAAiB,mBAAmB,0CAA2C,CAEzI,gCAAgC,UAAU,CAE1C,iCAAiC,gBAAgB,YAAa,oBAAoB,aAAa,0BAA0B,qBAAqB,CAE9I,8BAA8B,cAAc,CAE5C,qCAAqC,gBAAmB,gBAAgB,uBAAuB,sBAAsB,kBAAkB,gCAAgC,CCZvK,qCAAqC,iBAAiB,wBAAwB,0BAA0B,gCAAiC,eAAe,CCAxJ,cAAc,WAAW,OAAO,8BAA8B,gBAAgB,CAE9E,oCAAiH,sBAAsB,mBAAmB,WAAW,CAErK,oEAFoC,oBAAoB,aAAa,qBAAqB,sBAAuB,CAIjH,wFAAwF,WAAW,MAAM,CAEzG,iDAAiD,YAAY,gBAAgB,CAE7E,sFAAsF,YAAY,CAElG,sCAAsC,oBAAoB,aAAa,qBAAqB,uBAAuB,sBAAsB,mBAAmB,WAAW,CCZvK,+BAA+B,oBAAoB,cAAc,oBAAoB,aAAa,uBAAuB,mBAAmB,sBAAsB,8BAA8B,mBAAmB,eAAe,iBAAiB,CAEnP,2BAA2B,gBAAiB,iBAAiB,UAAU,CCFvE,iBAAiB,oBAAoB,aAAa,aAAa,SAAS,SAAS,gBAAiB,CAElG,mCAAmC,iBAAkB,gBAAgB,WAAW,OAAO,WAAW,CAElG,+BAA+B,mBAAmB,YAAY,WAAW,qBAAqB,CAE9F,iCAAiC,qBAAqB,eAAe,gBAAgB,mBAAmB,sBAAsB,CAE9H,kCAAkC,WAAW,OAAO,gBAAiB,CCRrE,4BAA4B,wBAAwB,yBAAyB,sCAAuC,CAEpH,oBAAoB,kBAAkB,YAAY,CCAlD,uBAEI,aACA,kBACA,qBACA,sBACA,mCAAqB,CANzB,8BASM,cAAgB,CCXtB,cAAc,0CAA2C,qBAAqB,oBAAoB,CAElG,kBAAkB,kBAAkB,CAEpC,6BAA6B,eAAe,CAE5C,yBAAyB,mBAAmB,iBAAiB,iBAAiB,CAE9E,qBAAqB,cAAc,CAEnC,uBAAuB,WAAW,eAAe,YAAY,CAE7D,wDAAwD,sBAAuB,SAAS,CAExF,mBAAmB,gBAAgB,eAAe,aAAa,CAE/D,4BAA4B,aAAa,CAEzC,iBAAiB,oBAAoB,YAAY,CAEjD,8BAA8B,SAAS,iBAAiB,CAExD,2BAA2B,qBAAqB,gBAAgB,CAEhE,iCAAiC,kBAAmB,CAEpD,mDAAmD,eAAgB,CCzBnE,gCAGM,YAAc,CAHpB,oBAOI,aACA,kBACA,WACA,kBACA,gBACA,gBACA,qBAAuB,CAb3B,qDAgBM,cACA,WACA,cACA,wBACA,yBACA,sCAAwB,CArB9B,iCAyBM,YACA,kBACA,aACA,aAAe,CA5BrB,sCA+BQ,WACA,cACA,kBACA,4BACA,6BACA,gBACA,oBACA,oBACA,kBAAoB,CAvC5B,mDA0CU,SAAW,CA1CrB,yDA6CY,SAAW,CA7CvB,6CAkDU,uBACA,SAAW,CAnDrB,oDAyDU,WACA,kBACA,OACA,QACA,SACA,UACA,wBACA,yBACA,sCAAwB,CClElC,iCAAiC,gBAAgB,CAEjD,+BAA+B,oBAAoB,aAAa,wBAAwB,qBAAqB,iBAAiB,CAE9H,sCAAsC,WAAW,MAAM,CAEvD,2IAA2I,UAAU,CAErJ,2EAA2E,cAAc,SAAS,WAAW,MAAM,CAEnH,mGAAmG,YAAY,eAAe,YAAY,cAAc,YAAY,4BAA4B,2BAA2B,kBAAkB,CAE7O,qGAAqG,aAAa,CAElH,mGAAmG,WAAW,OAAO,aAAa,CAElI,qHAAqH,YAAY,CAEjI,mJAAmJ,0BAA0B,qBAAqB,CAElM,8BAA8B,aAAa,CAE3C,iCAAiC,mBAAmB,cAAc,CAElE,sKAAsK,oBAAoB,YAAY,CAEtM,mEAAmE,0BAA0B,qBAAqB,CAElH,iCAAiC,mBAAmB,eAAe,sBAAsB,6BAA6B,CAEtH,oCAAoC,SAAS,CAE7C,yKAAyK,gBAAgB,CAEzL,4BAA4B,oBAAoB,aAAa,sBAAsB,8BAA8B,wBAAwB,qBAAqB,WAAW,gBAAgB,iBAAiB,CAE1M,iCAAiC,cAAc,gBAAgB,YAAY,aAAa,CAExF,8BAA8B,WAAW,OAAO,SAAS,iBAAiB,CAE1E,2CAA2C,WAAW,OAAO,gBAAgB,CAE7E,mDAAmD,gBAAgB,kBAAkB,CAErF,8DAA8D,oBAAoB,aAAa,qBAAqB,uBAAuB,wBAAwB,qBAAqB,mBAAmB,cAAc,CAEzN,4KAA4K,kBAAkB,CAE9L,4FAA4F,oBAAoB,YAAY,CAE5H,kFAAkF,gBAAgB,CAElG,mCAAmC,mBAAmB,eAAe,gBAAgB,qBAAqB,sBAAsB,CAEhI,gDAAgD,mBAAmB,aAAa,CAEhF,mCAAmC,sBAAsB,yBAAyB,kBAAkB,gCAAiC,kBAAkB,YAAY,wCAAwC,sBAAsB,2BAA2B,CAE5P,gDAAgD,4BAA4B,oBAAoB,YAAY,CAE5G,yDAAyD,WAAW,MAAM,CAE1E,4DAA4D,mBAAmB,CAE/E,gEAAgE,gBAAgB,oBAAoB,YAAY,CAEhH,kEAAkE,gBAAgB,CAElF,sDAAsD,eAAe,oBAAoB,aAAa,sBAAsB,kBAAkB,CAE9I,wGAAwG,2HAA2I,WAAY,uBAAuB,kBAAkB,gBAAgB,CAExT,sDAAsD,gBAAgB,YAAY,iBAAiB,eAAe,eAAe,gBAAgB,iBAAiB,mBAAmB,yCAA0C,CAE/N,kDAAkD,gBAAgB,YAAY,WAAW,YAAY,eAAe,gBAAgB,CAEpI,mDAAmD,oBAAoB,aAAa,wBAAwB,oBAAoB,CAEhI,6DAA6D,2BAA2B,oBAAoB,wBAAwB,qBAAqB,iBAAiB,WAAW,MAAM,CAE3L,qDAAqD,WAAW,wBAAwB,kBAAkB,+BAAgC,CAE1I,8PAA8P,gBAAgB,kBAAkB,CAEhS,gEAAgE,uBAAuB,cAAc,iBAAiB,CAEtH,sEAAsE,WAAW,MAAM,CAEvF,+CAA+C,cAAc,cAAc,cAAc,eAAe,CAExG,iCAAiC,qBAAqB,sBAAsB,CAE5E,yDAAyD,eAAe,mBAAmB,oBAAoB,aAAa,0BAA0B,sBAAsB,iBAAiB,UAAU,CAEvM,mEAAmE,aAAa,CAEhF,6GAA+G,gBAAgB,CAE/H,kJAAkJ,oBAAoB,aAAa,wBAAwB,oBAAoB,CAE/N,6BAA6B,6BAA6B,eAAe,CAEzE,iEAAiE,SAAS,gBAAgB,uBAAuB,uCAA0C,4BAA4B,2BAA2B,kBAAkB,CAEpO,iGAAiG,eAAe,CAEhH,iCAAiC,cAEA,cAAc,WAAW,MAAM,CAEhE,iCAAiC,cAAc,CAE/C,uCAAuC,YAAY,CAEnD,qBAAqB,kBAAkB,kBAAkB,CClHzD,gCAAgC,cAAc,WAAW,MAAM,CCA/D,gBAAgB,oBAAoB,aAAa,mBAAmB,eAAe,qBAAqB,uBAAuB,iBAAiB,CAEhJ,wEAAwE,kBAAkB,CAE1F,0CAA0C,WAAW,OAAO,oBAAoB,aAAa,mBAAmB,cAAc,CAE9H,6DAA6D,UAAU,aAAa,CAEpF,sHAAsH,oBAAoB,aAAa,WAAW,MAAM,CAExK,gKAAgK,UAAU,CAE1K,2DAA2D,qBAAqB,sBAAsB,CAEtG,6HAA6H,SAAS,WAAW,UAAU,CAE3J,2DAA2D,0BAA0B,sBAAsB,mBAAmB,oBAAoB,CAElJ,iEAAiE,UAAU,WAAW,CAEtF,6EAA6E,yBAAyB,uBAAuB,CAE7H,0DAA0D,WAAW,OAAO,sBAAyB,oBAAoB,aAAa,sBAAsB,mBAAmB,qBAAqB,uBAAuB,2MAA2N,0BAA0B,kDAAqD,kBAAkB,oCAAqC,CAE5jB,yEAAyE,UAAU,WAAW,yBAAyB,mCAAoC,mBAAmB,qCAAsC,CAEpN,8BAA8B,WAAW,OAAO,eAAe,CAE/D,0CAA0C,uBAAuB,mBAAmB,CAEpF,iGAAiG,cAAc,gBAAgB,CAE/H,+CAA+C,eAAe,aAAa,CAE3E,kDAAkD,WAAW,MAAM,CAEnE,yDAAyD,4BAA4B,2BAA2B,eAAkB,CCpClI,gCAAgC,cAAc,CAE9C,6BAA6B,0BAA0B,4BAA4B,CAEnF,kCAAkC,yBAAyB,2BAA2B,CCJtF,gBAAgB,oBAAoB,aAAa,kBAAkB,yBAAyB,gBAAgB,iBAAiB,CAE7H,uBAAuB,gBAAgB,CAEvC,wBAAwB,qBAAqB,iBAAiB,CCJ9D,yBAAyB,oBAAoB,aAAa,mBAAmB,eAAe,wBAAwB,qBAAqB,qBAAqB,sBAAsB,CCApL,mBAAmB,oBAAoB,aAAa,0BAA0B,sBAAsB,WAAY,CAEhH,8BAA8B,oBAAoB,aAAa,uBAAuB,kBAAkB,CAExG,qCAAqC,iBAAiB,aAAa,WAAY,CAE/E,gCAAgC,gBAAiB,aAAa,SAAS,oBAAoB,aAAa,0BAA0B,qBAAqB,CAEvJ,4BAA4B,gBAAgB,CAE5C,+BAA+B,oBAAoB,aAAa,0BAA0B,sBAAsB,eAA0B,iBAAiB,iBAAiB,CAE5K,sCAAsC,0BAA0B,uBAAuB,qCAAqC,CAE5H,mDAAmD,cAAc,yBAA0B,CAE3F,+BAA+B,iBAAkB,eAAe,CAEhE,oCAAoC,cAAc,CAElD,kCAAkC,gBAAgB,kBAAkB,YAAY,CAEhF,4CAA6C,kBAAY,CAEzD,iCAAiC,iBAAiB,eAAe,CAEjE,4BAA4B,gBAAgB,kBAAmB,CAE/D,wBAAwB,gBAAiB,WAAW,CAEpD,0BAA0B,iBAAiB,CAE3C,yBACA,8BAA8B,kCAAkC,6BAA6B,CAC5F,CClCD,mBAAmB,QAAQ,CAE3B,+BAA+B,YAAY,WAAW,CAEtD,sBAAsB,cAAc,CAEpC,yBAAyB,gBAAgB,YAAa,CAEtD,4BAA4B,UAAU,CAEtC,kBAAkB,cAAc,CAEhC,8BAA8B,cAAc,YAAY,aAAa,kBAAkB,qCAAsC,CAE7H,4BAA4B,UAAU,CAEtC,+BAA+B,eAAe,CAE9C,qCAAqC,gBAAgB,CAErD,iCAAiC,WAAW,CAE5C,2BAA2B,iBAAiB,cAAc,eAAe,CAEzE,kCAAkC,UAAU,CCxB5C,yBAAyB,YAAY,CAErC,+BAA+B,iBAAiB,CAEhD,mCAAmC,cAAc,cAAc,CAE/D,+BAA+B,eAAe,CAE9C,sCAAsC,cAAc,CCTpD;;;;;;;;GAUA,mBACE,cACA,YACA,cACA,kBACA,sBACA,kBACA,yBACA,sBACA,qBACA,gBAAkB,CAGpB,uBACE,cACA,YACA,uBACA,0BACA,yBACA,uBACA,sBACA,UAAY,CAGd,qFAKE,SACA,OACA,kBACA,QACA,KAAO,CAGT,kCAEE,eAAiB,CAGnB,kBACE,sBACA,SAAW,CAGb,eACE,sBACA,UAAY,CAGd,kBACE,cACA,YACA,mCACA,uBACA,gBACA,UAAY,CAGd,gBACE,qBACA,cACA,WACA,iBAAmB,CAGrB,yBACE,wBACA,qBACA,iBACA,OACA,cACA,UAAY,CAGd,yBACE,sBACA,uBACA,YACA,eACA,MACA,eAAsB,CAGxB,gBACE,cACA,SACA,SACA,YACA,kBACA,QACA,OAAS,CAGX,6CAEE,sBACA,YACA,cACA,iBAAmB,CAGrB,uBACE,WACA,UACA,MACA,SAAW,CAGb,sBACE,WACA,OACA,SACA,SAAW,CAGb,2CAGE,cACA,YACA,WACA,kBACA,UAAY,CAGd,cACE,sBACA,OACA,KAAO,CAGT,cACE,qBAAuB,CAGzB,qBACE,iBACA,WACA,MACA,SAAW,CAGb,qBACE,iBACA,WACA,OACA,QAAU,CAGZ,qBACE,iBACA,UACA,MACA,SAAW,CAGb,qBACE,YACA,iBACA,WACA,MAAQ,CAGV,eACE,sBACA,WACA,YACA,SAAW,CAGb,uBACE,iBACA,gBACA,WACA,OAAS,CAGX,uBACE,iBACA,SACA,iBACA,QAAU,CAGZ,uBACE,iBACA,UACA,gBACA,OAAS,CAGX,uBACE,YACA,gBACA,SACA,gBAAkB,CAGpB,wBACE,mBACA,WACA,QAAU,CAGZ,wBACE,mBACA,UACA,QAAU,CAGZ,wBACE,YACA,mBACA,SAAW,CAGb,wBACE,YACA,mBACA,YACA,UACA,WACA,UAAY,CAGd,yBACE,wBACE,YACA,UAAY,CACb,CAGH,yBACE,wBACE,YACA,UAAY,CACb,CAGH,0BACE,wBACE,WACA,YACA,SAAW,CACZ,CAGH,+BACE,sBACA,YACA,YACA,cACA,YACA,UACA,kBACA,WACA,UAAY,CAGd,mBACE,SAAW,CAGb,YACE,8QAAgR,CAGlR,cACE,cACA,SACA,kBACA,OAAS,CAGX,gBACE,sBAAyB,CAG3B,cACE,WAAa,CAGf,cACE,gBAAkB,CAGpB,qIAIE,kBAAoB,CC7StB,8BAA8B,gBAAiB,gBAAgB,CAE/D,qCAAqC,UAAU,CCF/C,6BAA6B,gBAAiB,gBAAgB,CAE9D,oCAAoC,UAAU,CCF9C,4BAA4B,oBAAoB,aAAa,sBAAsB,kBAAkB,CAErG,qCAAqC,yBAAyB,uCAAwC,CAEtG,wBAAwB,oBAAoB,aAAa,sBAAsB,mBAAmB,eAAgB,wBAAwB,yBAAyB,sCAAuC,CAE1M,gCAAgC,WAAW,MAAM,CAEjD,kCAAkC,eAAe,cAAc,SAAS,CCRxE,UAAU,kBAAkB,qBAAqB,mBAAmB,gBAAgB,CAEpF,2BAA4B,kBAAkB,OAAO,MAAM,cAAc,gBAAY,qBAAuB,YAAY,aAAa,kBAAkB,wCAAyC,8BAAmC,8BAA8B,yBAAyB,sCAAuC,mBAAmB,kBAAkB,kBAAkB,gBAAgB,kBAAkB,gBAAgB,qBAAqB,CAE/b,+BAA+B,YAAY,CAE3C,kEAAmE,cAAc,yBAA0B,CAE3G,wEAAyE,gBAAY,cAAc,yBAA0B,CAE7H,mEAAoE,UAAU,CAE9E,eAAe,gBAAgB,CCZ/B,aAAa,iBAAiB,CAE9B,mBAAmB,cAAc,UAAU,CAE3C,qBAAqB,kBAAkB,OAAO,SAAS,QAAQ,iBAAiB,yBAAyB,wCAA6E,kBAAkB,oCAAiC,kBAAkB,qCAAsC,yBAAyB,0BAA0B,sCAAuC,8BAA8B,gBAAgB,SAAS,CCLlb,2BAEI,aACA,iBAAmB,CAHvB,kCAMM,cAAgB,CCLtB,uCAAuC,oBAAoB,aAAa,uBAAuB,mBAAmB,mBAAmB,cAAc,CAEnJ,8CAA8C,gBAAiB,kBAAmB,aAAa,SAAS,eAAe,aAAa,CAEpI,yDAAyD,cAAc,CCJvE,6BAA6B,YAAa,oBAAoB,aAAa,qBAAqB,sBAAsB,CAEtH,4CAA4C,gBAAiB,CAE7D,cAAc,WAAW,CCJzB,eAAe,mBAAmB,CAElC,+BAA+B,cAAc,yBAA0B,CAEvE,6BAA6B,iBAAiB,CAE9C,mDAAmD,kBAAkB,MAAM,QAAQ,OAAO,SAAS,mBAAmB,CAEtH,0DAA0D,0FAA6F,CAEvJ,cAAc,sBAAsB,oBAAoB,aAAa,wBAAwB,kBAAkB,+BAAgC,CAE/I,4CAA4C,YAAY,CAExD,yCAAyC,kBAAkB,CAE3D,2BAA2B,oBAAoB,aAAa,WAAW,OAAO,qBAAqB,iBAAiB,aAAc,WAAW,CAE7I,6CAA6C,WAAW,WAAW,CAEnE,sCAAsC,SAAS,CAE/C,8CAA8C,gBAAiB,0BAA4B,sCAAyC,CAEpI,gDAAgD,sBAAsB,CAEtE,kDAAkD,QAAQ,CAE1D,2BAA2B,cAAe,CAE1C,yBAAyB,WAAW,MAAM,CAE1C,mBAAmB,kBAAkB,CAErC,kCAAkC,WAAW,OAAO,kBAAmB,WAAW,CAElF,oCAAoC,YAAc,qBAAqB,iBAAiB,kBAAkB,gBAAgB,WAAW,iBAAiB,WAAW,oBAAoB,aAAa,qBAAqB,iBAAiB,sBAAsB,6BAA6B,CAE3R,qDAAqD,WAAW,OAAO,gBAAgB,sBAAsB,CAE7G,8CAA8C,mBAAmB,eAAe,uBAAuB,kBAAkB,CAEzH,kDAAkD,WAAW,YAAY,sBAAsB,kBAAkB,CAEjH,6CAA6C,iBAAiB,CAE9D,sDAAsD,cAAc,2BAA4B,CAIhG,4GAAoD,cAAc,0BAA2B,CAE7F,mDAAgE,aAAa,2BAA4B,CAEzG,oDAAoD,SAAS,gBAAgB,CAE7E,uCAAuC,qBAAqB,gBAAiB,UAAU,cAAc,gBAAgB,CAErH,6CAA6C,mBAAmB,CAEhE,sCAAsC,SAAS,aAAa,kBAAmB,CC5D/E,iBAAiB,gBAAgB,UAAU,CAE3C,sBAAsB,aAAa,QAAQ,CAE3C,0BAA0B,eAAiB,oBAAoB,aAAa,uBAAuB,mBAAmB,sBAAsB,mBAAmB,sBAAsB,6BAA6B,CAElN,cAAc,kBAAkB,0BAA0B,uBAAwB,qCAAqC,CCNvH,eAAe,eAAe,QAAU,SAAW,aAAa,cAAc,CAE9E,cAAc,cAAc,CAE5B,kCAAkC,cAAc,yBAA0B,CAE1E,aAAa,gBAAgB,kBAAkB,eAAe,CAE9D,uBAAuB,WAAW,CAElC,cAAc,oBAAoB,aAAa,iBAAmB,CAElE,iBAAiB,YAAY,WAAW,kBAAkB,sCAAuC,kBAAmB,gBAAiB,CAErI,YAAY,oBAAoB,YAAY,CAE5C,qBAAqB,WAAW,OAAO,YAAa,iBAAiB,WAAW,CAEhF,mBAAmB,oBAAoB,aAAa,sBAAsB,6BAA6B,CClBvG,mBAAmB,gBAAgB,CCAnC,aAAa,UAAU,CCAvB,KAAK,iBAAiB,eAAe,eAAe,CAEpD,gBAAgB,eAAe,WAAW,YAAY,WAAW,sBAAsB,4BAA4B,yBAAyB,CAE5I,EAAE,yBAAyB,sBAAsB,qBAAqB,gBAAgB,CAEtF,GAAG,QAAQ,CAEX,SAAS,sBAAsB,iBAAiB,YAAY,iBAAiB,gBAAgB,iCAAkC,yBAAyB,wBAAwB,CAEhL,aAAa,iBAAiB,CAE9B,KAAK,uBAAuB,4CAA6C,eAAe,SAAS,cAAc,0BAA2B,gBAAgB,iBAAiB,CAE3K,EAAE,qBAAqB,cAAc,yBAA0B,CAE/D,OAAO,yBAAyB,sBAAsB,qBAAqB,iBAA6D,yBAAyB,oCAAqC,YAAY,kBAAkB,mCAAoC,eAAe,6FAAmH,+BAA+B,eAAe,uBAAuB,2CAA4C,CAE3f,8BAF4F,cAAc,4BAA8B,CAIxI,yBAAyB,WAAW,CAEpC,aAAa,sCAA6C,mCAAmC,CAE7F,cAAc,2GAAoI,qCAAqC,CAEvL,gBAAgB,mBAAmB,UAAW,CAE9C,eAAe,0BAA4B,uCAA0C,yBAAyB,kCAAmC,CAEjJ,cAAc,cAAc,yCAA0C,oCAAqC,qDAAuD,CAElK,aAAa,SAAS,CAEtB,uBAAuB,YAAY,kBAAkB,qCAAsC,mGAAyH,8BAA8B,yBAAyB,sCAAuC,cAAc,+BAAgC,uBAAuB,wCAAyC,eAAe,iBAAiB,sBAAsB,qBAAqB,kBAAkB,YAAY,iBAAiB,qBAAqB,iBAAiB,YAAY,CAE5kB,kIAAkI,mBAAmB,UAAW,CAEhK,uEAAuE,kBAAkB,MAAM,SAAS,UAAU,YAAY,cAAc,0BAA2B,iBAAiB,UAAU,mBAAmB,CAErN,4CAA4C,wBAAwB,qBAAqB,gBAAgB,uBAAuB,YAAY,cAAc,sCAAwC,SAAS,qBAAqB,uBAAuB,wCAAyC,eAAe,WAAW,UAAU,YAAY,gBAAgB,CAEhW,2DAA2D,gBAAgB,YAAY,SAAS,gBAAgB,WAAW,MAAM,CAEjI,+HAA+H,YAAY,CAE3I,6PAAmQ,cAAc,yBAA0B,CAE3S,ipBAAupB,UAAU,CAEjqB,6MAAmN,qBAAqB,gBAAY,qBAAuB,YAAY,aAAa,kBAAkB,wCAAyC,8BAAmC,8BAA8B,kBAAkB,yBAAyB,sCAAuC,mBAAmB,kBAAkB,kBAAkB,gBAAsC,kBAAkB,gBAAgB,qBAAqB,CAEtoB,OAAO,cAAc,0BAA2B,yBAAyB,kCAAmC,CAE5G,gBAAgB,WAAW,sBAAuB,CAElD,WAA4C,mBAAmB,eAAe,SAAS,cAAqB,CAE5G,iBAFW,oBAAoB,YAAa,CAG3C,MADK,WAAW,OAAO,iBAAiB,YAAY,gBAAiD,mBAAmB,cAAc,CAEvI,gBAAgB,gBAAiB,CAEjC,YAAY,kBAAkB,wBAAwB,CAEtD,WAAW,WAAW,MAAM,CAE5B,SAAS,UAAU,WAAW,sBAAsB,mBAAmB,eAAe,WAAW,CAEjG,eAAe,oBAAoB,aAA6D,uBAAuB,oBAAoB,qBAAqB,uBAAuB,kBAAkB,cAAc,WAAW,mBAAmB,oCAAoC,uBAAyB,CAElT,oCAFgD,kBAAkB,MAAM,SAAS,OAAO,OAAQ,CAG/F,qBADoB,8BAA8B,sBAAsB,6BAA6B,qBAAqB,0BAA0B,kBAAkB,yBAAyB,0CAA4C,CAE5O,mBAAmB,YAAY,mBAAmB,cAAc,WAAW,MAAM,CAEjF,oBAAoB,YAAY,sBAAsB,kBAAkB,mBAAmB,oBAAoB,aAAa,sBAAsB,mBAAmB,8BAA8B,iBAAiB,WAAW,CAE/N,8CAA8C,cAAc,+BAAgC,CAE5F,YAAY,WAAW,MAAM,CAE7B,gBAAgB,sBAAuB,eAAe,CAEtD,kBAAkB,SAAS,cAAe,CAE1C,OAAO,oBAAoB,aAAa,kBAAkB,0BAA0B,sBAAsB,YAAa,yBAAyB,kCAAmC,CAEnL,oBAAqB,mBAAmB,qCAAsC,CAE9E,aAAc,WAAW,kBAAkB,MAAM,SAAS,OAAO,QAAQ,oBAAoB,sCAAuC,6BAA6B,CAEjK,yBAA0B,6BAAqB,cAAc,WAAW,iBAAiB,CAEzF,eAAe,oBAAoB,aAAa,4BAA4B,kEAAoE,sBAAsB,aAAkB,gBAAgB,iBAAiB,uBAAuB,yBAAyB,sCAAuC,wBAAwB,qBAAqB,mCAAmC,CAEhY,sBAAsB,kBAAkB,cAAc,eAAe,CAErE,sBAAsB,6BAA6B,0BAA4B,2CAA8C,CAE7H,sBAAsB,mBAAmB,uBAAuB,iBAAiB,CAEjF,sBAAsB,oBAAoB,aAAa,CAEvD,4CAA4C,iBAAiB,aAAa,sBAAsB,SAAS,kBAAkB,cAAc,4BAA4B,2BAA2B,kBAAkB,CAElN,iBAAiB,cAAc,8BAA+B,CAE9D,oBAAoB,mBAAmB,qCAAsC,CAE7E,cAAc,4BAA4B,iEAAmE,CAE7G,qBAAqB,0BAA4B,2CAA8C,CAE/F,gBAAgB,cAAc,8BAA+B,CAE7D,cAAc,iBAAiB,YAAY,QAAQ,CAEnD,aAAa,WAAa,CAE1B,IAAI,UAAU,CAEd,IAAI,aAAa,wBAAwB,yBAAyB,uCAAwC,0BAA4B,uCAA0C,kCAAuC,8BAA8B,CAErP,iBAAiB,cAAc,eAAe,sCAAuC,wBAA0B,mCAAmC,CAElJ,mBAAmB,YAAY,CAE/B,wBAAwB,UAAU,aAAa,CAE/C,sCAAsC,sBAAsB,CAE5D,+BAA+B,SAAS,CAExC,MAAM,4BAA4B,eAAe,oBAAoB,YAAY,oBAAoB,aAAa,CAElH,gBAAgB,WAAW,OAAO,4BAA4B,cAAc,CAE5E,gBAAgB,WAAW,OAAO,8BAA8B,iBAAiB,WAAW,CAE5F,cAAc,YAAY,CAE1B,yBACA,KAAK,iBAAiB,CAEtB,iBAAiB,YAAY,CAE7B,gBAAgB,gBAAgB,iBAAiB,YAAY,eAAe,gBAAgB,CAE5F,kCAAkC,YAAY,YAAY,iBAAiB,mBAAmB,kBAAkB,iBAAiB,CAEjI,yBAAyB,WAAW,CAEpC,gBAAgB,gBAAgB,oBAAoB,cAAc,oBAAoB,WAAW,CAChG,CAED,OAAO,qBAAqB,mBAAmB,eAAe,eAAe,gBAAgB,gBAAgB,eAAe,iBAAiB,kBAAkB,sBAAsB,mBAAmB,SAAS,CAEjN,0BAA0B,qBAAqB,8CAA+C,WAAY,uCAAwC,CAElJ,OAAO,aAAc,cAAe,kBAAkB,uCAAwC,gBAAgB,gBAAgB,CAE9H,aAAa,oCAAqC,sDAAwD,cAAc,mCAAoC,CAE5J,4BAA4B,cAAc,wCAAyC,CAInF,mBAAY,0BAA4B,sCAAyC,CAEjF,kBAAkB,yBAAyB,CAE3C,yBACA,MAAM,mBAAoB,CACzB,CAED,YAAY,gBAAgB,CAE5B,iBAAiB,gBAAgB,YAAY,cAAc,CAE3D,2BAA2B,cAAc,8BAA+B,CAExE,qBAAqB,eAAe,CAEpC,mBAAmB,aAAa,qCAAuC,kDAAqD,kBAAkB,oCAAqC,CAEnL,mCACA,GAAK,4BAA4B,CAEjC,GAAG,+BAAgC,CAClC,CAED,YAAY,aAAa,eAAe,MAAM,OAAO,QAAQ,SAAS,oBAAoB,aAAa,qBAAqB,uBAAuB,sBAAsB,mBAAmB,cAAc,uBAAwB,gCAAiC,sCAAsC,CAEzS,aAAa,eAAe,CAE5B,sBACA,GAAG,uBAAuB,CAE1B,IAAI,6BAA8B,CAElC,IAAI,8BAA+B,CAEnC,IAAI,6BAA8B,CAElC,IAAI,8BAA+B,CAEnC,IAAI,6BAA8B,CAElC,IAAI,8BAA+B,CAEnC,GAAK,uBAAuB,CAC3B,CAED,yBACA,eAAe,YAAY,CAE3B,gBAAgB,oBAAoB,YAAY,CAEhD,WAAW,SAAS,CAEpB,OAAO,aAAsB,CAE7B,aAAa,cAAc,iBAAkB,CAC5C,CAED,YAAY,iBAAiB,CAE7B,yBACA,YAAY,YAAY,CACvB,CAED,cAAc,qBAAqB,cAAgB,UAAU,CAE7D,iBAAiB,eAAe,CAEhC,oBAAoB,iBAAiB,CAErC,yBAAyB,cAAuB,kBAAkB,uCAAwC,kBAAkB,UAAU,sCAAuC,8BAA8B,cAAc,mBAAmB,6BAA8B,cAAc,8BAA+B,CAEvT,mBAAmB,eAAe,kBAAgC,uCAAwC,oBAAoB,YAAY,CAE1I,uBAAuB,WAAW,YAAY,kBAAkB,CAEhE,wBAAwB,iBAAiB,oBAAsB,CAE/D,yBAAyB,iBAAiB,0BAA4B,sCAAyC,CAE/G,+BAA+B,yBAAyB,uCAAwC,CCtPhG,kBAAkB,gBAAgB,6BAA6B,CAE/D,cAAc,gBAAgB,SAAS,SAAS,CAEhD,sBAAsB,iBAAiB,yBAAyB,iDAAoD,CAEpH,cAAc,wBAAwB,kBAAkB,gCAAiC,SAAS,CAElG,4BAA4B,6BAA6B,gDAAiD,4BAA4B,8CAA+C,CAErL,2BAA2B,gCAAgC,mDAAoD,+BAA+B,iDAAkD,CAEhM,yBAAyB,WAAW,CAEpC,aAAa,cAAc,kBAAoB,CAI/C,mDAFmB,yBAAyB,uCAAwC,CAGnF,gCAD+B,kBAAmB,CAEnD,sCAAsC,yBAAyB,CCpB/D,uBAAuB,eAAe,2BAA2B,oBAAoB,wBAAwB,qBAAqB,uBAAuB,CAEzJ,gFAAgF,WAAW,CAE3F,0CAA0C,yCAAyC,CAEnF,sCAAsC,iBAAiB,iBAAiB,CCNxE,iBAAiB,qBAAqB,CAEtC,mBAAmB,WAAW,WAAW,CAEzC,eAAe,iBAA4B,SAAW,iBAAiB,mBAAmB,gBAAgB,sBAAsB,CCJhI,iDAAiD,WAAY,CAE7D,8GAA8G,aAAa,eAAe,CAE1I,uDAAuD,SAAS,CAEhE,aAAa,cAAc,eAAe,sCAAyC,CAEnF,yBAAyB,kBAAkB,cAAc,QAAQ,iBAAiB,WAAW,aAAa,SAAS,UAAU,UAAU,gBAAgB,gBAAgB,wBAAwB,qBAAqB,gBAAgB,iBAAiB,eAAe,iDAAsD,CAE1T,qCAAqC,kBAAkB,SAAS,YAAY,WAAW,eAAe,iBAAiB,WAAW,kBAAkB,+BAAgC,CAEpL,+BAA+B,MAAM,CAErC,2CAA2C,QAAQ,CAEnD,+BAA+B,OAAO,CAEtC,2CAA2C,SAAS,CClBpD,uBAAuB,eAAe,aAAa,MAAM,OAAO,WAAW,YAAY,oBAAoB,aAAa,uBAAuB,oBAAoB,uBAAuB,6BAA6B,CAEvN,4BAA4B,sBAAuB,CAEnD,8BAA8B,sBAAuB,0BAA0B,CAE/E,oBAAoB,MAAM,OAAO,YAAY,aAAa,eAAe,WAAW,gBAAiB,qCAAqC,+BAAgC,CAE1K,2BAA2B,4BAA4B,CAEvD,2BAA2B,kBAAkB,aAAa,CAE1D,aAAa,kBAAkB,kDAAsD,gBAAiB,8BAA8B,oBAAoB,sBAAsB,UAAU,eAAe,iBAAiB,aAAa,sCAAuC,8BAA8B,yBAAyB,kCAAmC,CAEtW,0BAA0B,oBAAoB,aAAa,sBAAsB,mBAAmB,aAAc,CAElH,8BAA8B,cAAc,UAAU,YAAY,kBAAmB,CAErF,+BAA+B,gBAAgB,uBAAuB,kBAAkB,CAExF,kCAAkC,iBAAiB,UAAU,CAE7D,oBAAoB,0BAA0B,CAE9C,qBAAqB,uBAAuB,0BAA0B,sBAAsB,uBAAuB,oBAAoB,oBAAoB,aAAa,UAAU,QAAQ,CAE1L,gBAAgB,gBAAgB,SAAS,UAAU,wBAAwB,kBAAkB,gCAAiC,aAAc,CAE5I,2BAA2B,QAAQ,CAEnC,gBAAgB,SAAS,CAEzB,kBAAkB,cAAc,kBAAoB,CAEpD,wBAAwB,yBAAyB,uCAAwC,CClCzF,sBAAsB,gBAAgB,aAAa,CAEnD,uBAAuB,oBAAoB,cAAc,iBAAmB,UAAU,CAEtF,mBAAmB,UAAU,WAAW,mBAAmB,eAAe,aAAa,YAAY,yBAAyB,oCAAqC,oBAAoB,aAAa,qBAAqB,uBAAuB,sBAAsB,mBAAmB,6DAAmE,WAAW,0BAA2B,iDAAqD,CAErb,0BAA0B,0BAA0B,CAEpD,qBAAqB,gBAAgB,cAAc,yBAA0B,CAE7E,yBACA,mBAAmB,YAAY,CAC9B,CCZD,kBAAkB,WAAW,oBAAoB,aAAa,sBAAsB,kBAAkB,CAEtG,mBAAmB,oBAAoB,aAAa,qBAAqB,uBAAuB,WAAW,kBAAkB,cAAc,CAE3I,WAAW,mBAAmB,WAAW,UAAU,kBAAkB,qBAAqB,oBAAoB,gBAAgB,gBAAgB,qBAAqB,6CAA8C,CAEjN,6BAA6B,WAAW,aAAa,kBAAkB,eAAe,MAAM,OAAO,sCAAuC,8BAA8B,8BAA8B,yBAA0B,uBAAuB,CAEvP,oCAAoC,0BAA0B,CAE9D,6BAA6B,oBAAoB,aAAa,sBAAsB,mBAAmB,sBAAsB,8BAA8B,UAAU,WAAW,YAAY,iBAAiB,kBAAkB,wBAAwB,yBAAyB,uCAAwC,kCAAuC,8BAA8B,CAE7X,oCAAoC,gBAAgB,gBAAiB,CAErE,sBAAsB,gBAAgB,YAAY,0BAA0B,kBAAkB,kBAAkB,cAAc,0BAA2B,yBAAyB,kCAAmC,CAErN,qCAAqC,UAAU,gBAAgB,eAAe,CAE9E,4CAA4C,gBAAgB,SAAS,eAAe,CAEpF,kDAAkD,eAAe,CAEjE,2DAA2D,gBAAgB,eAAe","file":"static/css/app.a81578273cb4c57163939ab70c80eb06.css","sourcesContent":["\n.timeline .loadmore-text{opacity:1\n}\n.new-status-notification{position:relative;margin-top:-1px;font-size:1.1em;border-width:1px 0 0 0;border-style:solid;border-color:var(--border, #222);padding:10px;z-index:1;background-color:#182230;background-color:var(--panel, #182230)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/timeline/timeline.vue","\n.status-body{-ms-flex:1;flex:1;min-width:0\n}\n.status-preview.status-el{border-style:solid;border-width:1px;border-color:#222;border-color:var(--border, #222)\n}\n.status-preview-container{position:relative;max-width:100%\n}\n.status-preview{position:absolute;max-width:95%;display:-ms-flexbox;display:flex;background-color:#121a24;background-color:var(--bg, #121a24);border-color:#222;border-color:var(--border, #222);border-style:solid;border-width:1px;border-radius:5px;border-radius:var(--tooltipRadius, 5px);box-shadow:2px 2px 3px rgba(0,0,0,0.5);box-shadow:var(--popupShadow);margin-top:0.25em;margin-left:0.5em;z-index:50\n}\n.status-preview .status{-ms-flex:1;flex:1;border:0;min-width:15em\n}\n.status-preview-loading{display:block;min-width:15em;padding:1em;text-align:center;border-width:1px;border-style:solid\n}\n.status-preview-loading i{font-size:2em\n}\n.media-left{margin-right:.75em\n}\n.status-el{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;border-left-width:0px;min-width:0;border-color:#222;border-color:var(--border, #222);border-left:4px red;border-left:4px var(--cRed, red)\n}\n.status-el_focused{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.timeline .status-el{border-bottom-width:1px;border-bottom-style:solid\n}\n.status-el .media-body{-ms-flex:1;flex:1;padding:0\n}\n.status-el .status-usercard{margin-bottom:.75em\n}\n.status-el .user-name{white-space:nowrap;font-size:14px;overflow:hidden;-ms-flex-negative:0;flex-shrink:0;max-width:85%;font-weight:bold\n}\n.status-el .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.status-el .media-heading{padding:0;vertical-align:bottom;-ms-flex-preferred-size:100%;flex-basis:100%;margin-bottom:0.5em\n}\n.status-el .media-heading a{display:inline-block;word-break:break-all\n}\n.status-el .media-heading small{font-weight:lighter\n}\n.status-el .media-heading .heading-name-row{padding:0;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;line-height:18px\n}\n.status-el .media-heading .heading-name-row .name-and-account-name{display:-ms-flexbox;display:flex;min-width:0\n}\n.status-el .media-heading .heading-name-row .user-name{-ms-flex-negative:1;flex-shrink:1;margin-right:0.4em;overflow:hidden;text-overflow:ellipsis\n}\n.status-el .media-heading .heading-name-row .account-name{min-width:1.6em;margin-right:0.4em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-ms-flex:1 1 0px;flex:1 1 0\n}\n.status-el .media-heading .heading-right{display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0\n}\n.status-el .media-heading .timeago{margin-right:0.2em\n}\n.status-el .media-heading .heading-reply-row{-ms-flex-line-pack:baseline;align-content:baseline;font-size:12px;line-height:18px;max-width:100%;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch\n}\n.status-el .media-heading .heading-reply-row a{max-width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap\n}\n.status-el .media-heading .reply-to-and-accountname{display:-ms-flexbox;display:flex;height:18px;margin-right:0.5em;overflow:hidden;max-width:100%\n}\n.status-el .media-heading .reply-to-and-accountname .icon-reply{transform:scaleX(-1)\n}\n.status-el .media-heading .reply-info{display:-ms-flexbox;display:flex\n}\n.status-el .media-heading .reply-to{display:-ms-flexbox;display:flex\n}\n.status-el .media-heading .reply-to-text{overflow:hidden;text-overflow:ellipsis;margin:0 0.4em 0 0.2em\n}\n.status-el .media-heading .replies-separator{margin-left:0.4em\n}\n.status-el .media-heading .replies{line-height:18px;font-size:12px;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.status-el .media-heading .replies>*{margin-right:0.4em\n}\n.status-el .media-heading .reply-link{height:17px\n}\n.status-el .tall-status{position:relative;height:220px;overflow-x:hidden;overflow-y:hidden\n}\n.status-el .tall-status-hider{display:inline-block;word-break:break-all;position:absolute;height:70px;margin-top:150px;width:100%;text-align:center;line-height:110px;background:linear-gradient(to bottom, transparent, #121a24 80%);background:linear-gradient(to bottom, transparent, var(--bg, #121a24) 80%)\n}\n.status-el .tall-status-hider_focused{background:linear-gradient(to bottom, transparent, #151e2a 80%);background:linear-gradient(to bottom, transparent, var(--lightBg, #151e2a) 80%)\n}\n.status-el .status-unhider,.status-el .cw-status-hider{width:100%;text-align:center;display:inline-block;word-break:break-all\n}\n.status-el .status-content{font-family:var(--postFont, sans-serif);line-height:1.4em\n}\n.status-el .status-content img,.status-el .status-content video{max-width:100%;max-height:400px;vertical-align:middle;object-fit:contain\n}\n.status-el .status-content img.emoji,.status-el .status-content video.emoji{width:32px;height:32px\n}\n.status-el .status-content blockquote{margin:0.2em 0 0.2em 2em;font-style:italic\n}\n.status-el .status-content pre{overflow:auto\n}\n.status-el .status-content code,.status-el .status-content samp,.status-el .status-content kbd,.status-el .status-content var,.status-el .status-content pre{font-family:var(--postCodeFont, monospace)\n}\n.status-el .status-content p{margin:0 0 1em 0\n}\n.status-el .status-content p:last-child{margin:0 0 0 0\n}\n.status-el .status-content h1{font-size:1.1em;line-height:1.2em;margin:1.4em 0\n}\n.status-el .status-content h2{font-size:1.1em;margin:1.0em 0\n}\n.status-el .status-content h3{font-size:1em;margin:1.2em 0\n}\n.status-el .status-content h4{margin:1.1em 0\n}\n.status-el .retweet-info{padding:0.4em .75em;margin:0\n}\n.status-el .retweet-info .avatar.still-image{border-radius:10px;border-radius:var(--avatarAltRadius, 10px);margin-left:28px;width:20px;height:20px\n}\n.status-el .retweet-info .media-body{font-size:1em;line-height:22px;display:-ms-flexbox;display:flex;-ms-flex-line-pack:center;align-content:center;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.status-el .retweet-info .media-body .user-name{font-weight:bold;overflow:hidden;text-overflow:ellipsis\n}\n.status-el .retweet-info .media-body .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.status-el .retweet-info .media-body i{padding:0 0.2em\n}\n.status-el .retweet-info .media-body a{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\n}\n.status-fadein{animation-duration:0.4s;animation-name:fadein\n}\n@keyframes fadein{\nfrom{opacity:0\n}\nto{opacity:1\n}\n}\n.greentext{color:green\n}\n.status-conversation{border-left-style:solid\n}\n.status-actions{width:100%;display:-ms-flexbox;display:flex;margin-top:.75em\n}\n.status-actions div,.status-actions favorite-button{max-width:4em;-ms-flex:1;flex:1\n}\n.icon-reply:hover{color:#0095ff;color:var(--cBlue, #0095ff);cursor:pointer\n}\n.icon-reply.icon-reply-active{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.status:hover .animated.avatar canvas{display:none\n}\n.status:hover .animated.avatar img{visibility:visible\n}\n.status{display:-ms-flexbox;display:flex;padding:.75em\n}\n.status.is-retweet{padding-top:0\n}\n.status-conversation:last-child{border-bottom:none\n}\n.muted{padding:0.25em 0.5em\n}\n.muted button{margin-left:auto\n}\n.muted .muteWords{margin-left:10px\n}\na.unmute{display:block;margin-left:auto\n}\n.reply-left{-ms-flex:0;flex:0;min-width:48px\n}\n.reply-body{-ms-flex:1;flex:1\n}\n.timeline>.status-el:last-child{border-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius, 10px) var(--panelRadius, 10px);border-bottom:none\n}\n@media all and (max-width: 800px){\n.status-el .retweet-info .avatar.still-image{margin-left:20px\n}\n.status{max-width:100%\n}\n.status .avatar.still-image{width:40px;height:40px\n}\n.status .avatar.still-image.avatar-compact{width:32px;height:32px\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/status/status.vue","\n.attachments{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.attachments .attachment.media-upload-container{-ms-flex:0 0 auto;flex:0 0 auto;max-height:200px;max-width:100%;display:-ms-flexbox;display:flex\n}\n.attachments .attachment.media-upload-container video{max-width:100%\n}\n.attachments .placeholder{margin-right:8px;margin-bottom:4px\n}\n.attachments .nsfw-placeholder{cursor:pointer\n}\n.attachments .nsfw-placeholder.loading{cursor:progress\n}\n.attachments .attachment{position:relative;margin-top:0.5em;-ms-flex-item-align:start;align-self:flex-start;line-height:0;border-style:solid;border-width:1px;border-radius:10px;border-radius:var(--attachmentRadius, 10px);border-color:#222;border-color:var(--border, #222);overflow:hidden\n}\n.attachments .non-gallery.attachment.video{-ms-flex:1 0 40%;flex:1 0 40%\n}\n.attachments .non-gallery.attachment .nsfw{height:260px\n}\n.attachments .non-gallery.attachment .small{height:120px;-ms-flex-positive:0;flex-grow:0\n}\n.attachments .non-gallery.attachment .video{height:260px;display:-ms-flexbox;display:flex\n}\n.attachments .non-gallery.attachment video{max-height:100%;object-fit:contain\n}\n.attachments .fullwidth{-ms-flex-preferred-size:100%;flex-basis:100%\n}\n.attachments.video{line-height:0\n}\n.attachments .video-container{display:-ms-flexbox;display:flex;max-height:100%\n}\n.attachments .video{width:100%\n}\n.attachments .play-icon{position:absolute;font-size:64px;top:calc(50% - 32px);left:calc(50% - 32px);color:rgba(255,255,255,0.75);text-shadow:0 0 2px rgba(0,0,0,0.4)\n}\n.attachments .play-icon::before{margin:0\n}\n.attachments.html{-ms-flex-preferred-size:90%;flex-basis:90%;width:100%;display:-ms-flexbox;display:flex\n}\n.attachments .hider{position:absolute;right:0;white-space:nowrap;margin:10px;padding:5px;background:rgba(230,230,230,0.6);font-weight:bold;z-index:4;line-height:1;border-radius:5px;border-radius:var(--tooltipRadius, 5px)\n}\n.attachments video{z-index:0\n}\n.attachments audio{width:100%\n}\n.attachments img.media-upload{line-height:0;max-height:200px;max-width:100%\n}\n.attachments .oembed{line-height:1.2em;-ms-flex:1 0 100%;flex:1 0 100%;width:100%;margin-right:15px;display:-ms-flexbox;display:flex\n}\n.attachments .oembed img{width:100%\n}\n.attachments .oembed .image{-ms-flex:1;flex:1\n}\n.attachments .oembed .image img{border:0px;border-radius:5px;height:100%;object-fit:cover\n}\n.attachments .oembed .text{-ms-flex:2;flex:2;margin:8px;word-break:break-all\n}\n.attachments .oembed .text h1{font-size:14px;margin:0px\n}\n.attachments .image-attachment{width:100%;height:100%\n}\n.attachments .image-attachment.hidden{display:none\n}\n.attachments .image-attachment .nsfw{object-fit:cover;width:100%;height:100%\n}\n.attachments .image-attachment img{image-orientation:from-image\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/attachment/attachment.vue","\n.still-image{position:relative;line-height:0;overflow:hidden;width:100%;height:100%\n}\n.still-image:hover canvas{display:none\n}\n.still-image img{width:100%;height:100%;object-fit:contain\n}\n.still-image.animated:hover::before,.still-image.animated img{visibility:hidden\n}\n.still-image.animated:hover img{visibility:visible\n}\n.still-image.animated::before{content:'gif';position:absolute;line-height:10px;font-size:10px;top:5px;left:5px;background:rgba(127,127,127,0.5);color:#FFF;display:block;padding:2px 4px;border-radius:5px;border-radius:var(--tooltipRadius, 5px);z-index:2\n}\n.still-image canvas{position:absolute;top:0;bottom:0;left:0;right:0;width:100%;height:100%;object-fit:contain\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/still-image/still-image.vue","\n.fav-active{cursor:pointer;animation-duration:0.6s\n}\n.fav-active:hover{color:orange;color:var(--cOrange, orange)\n}\n.favorite-button.icon-star{color:orange;color:var(--cOrange, orange)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/favorite_button/favorite_button.vue","\n.rt-active{cursor:pointer;animation-duration:0.6s\n}\n.rt-active:hover{color:#0fa00f;color:var(--cGreen, #0fa00f)\n}\n.icon-retweet.retweeted{color:#0fa00f;color:var(--cGreen, #0fa00f)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/retweet_button/retweet_button.vue","\n.icon-cancel,.delete-status{cursor:pointer\n}\n.icon-cancel:hover,.delete-status:hover{color:red;color:var(--cRed, red)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/delete_button/delete_button.vue","\n.tribute-container ul{padding:0px\n}\n.tribute-container ul li{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center\n}\n.tribute-container img{padding:3px;width:16px;height:16px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.post-status-form .visibility-tray{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-direction:row-reverse;flex-direction:row-reverse\n}\n.post-status-form .form-bottom,.login .form-bottom{display:-ms-flexbox;display:flex;padding:0.5em;height:32px\n}\n.post-status-form .form-bottom button,.login .form-bottom button{width:10em\n}\n.post-status-form .form-bottom p,.login .form-bottom p{margin:0.35em;padding:0.35em;display:-ms-flexbox;display:flex\n}\n.post-status-form .error,.login .error{text-align:center\n}\n.post-status-form .media-upload-wrapper,.login .media-upload-wrapper{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;min-width:50px;margin-right:.2em;margin-bottom:.5em\n}\n.post-status-form .media-upload-wrapper .icon-cancel,.login .media-upload-wrapper .icon-cancel{display:inline-block;position:static;margin:0;padding-bottom:0;margin-left:10px;margin-left:var(--attachmentRadius, 10px);background-color:#182230;background-color:var(--btn, #182230);border-bottom-left-radius:0;border-bottom-right-radius:0\n}\n.post-status-form .attachments,.login .attachments{padding:0 0.5em\n}\n.post-status-form .attachments .attachment,.login .attachments .attachment{margin:0;position:relative;-ms-flex:0 0 auto;flex:0 0 auto;border:1px solid #222;border:1px solid var(--border, #222);text-align:center\n}\n.post-status-form .attachments .attachment audio,.login .attachments .attachment audio{min-width:300px;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.post-status-form .attachments .attachment a,.login .attachments .attachment a{display:block;text-align:left;line-height:1.2;padding:.5em\n}\n.post-status-form .attachments i,.login .attachments i{position:absolute;margin:10px;padding:5px;background:rgba(230,230,230,0.6);border-radius:10px;border-radius:var(--attachmentRadius, 10px);font-weight:bold\n}\n.post-status-form .btn,.login .btn{cursor:pointer\n}\n.post-status-form .btn[disabled],.login .btn[disabled]{cursor:not-allowed\n}\n.post-status-form form,.login form{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:0.6em\n}\n.post-status-form .form-group,.login .form-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:0.3em 0.5em 0.6em;line-height:24px\n}\n.post-status-form form textarea.form-cw,.login form textarea.form-cw{line-height:16px;resize:none;overflow:hidden;transition:min-height 200ms 100ms;min-height:1px\n}\n.post-status-form form textarea.form-control,.login form textarea.form-control{line-height:16px;resize:none;overflow:hidden;transition:min-height 200ms 100ms;min-height:1px;box-sizing:content-box\n}\n.post-status-form form textarea.form-control:focus,.login form textarea.form-control:focus{min-height:48px\n}\n.post-status-form .btn,.login .btn{cursor:pointer\n}\n.post-status-form .btn[disabled],.login .btn[disabled]{cursor:not-allowed\n}\n.post-status-form .icon-cancel,.login .icon-cancel{cursor:pointer;z-index:4\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/post_status_form/post_status_form.vue","\n.media-upload {\n font-size: 26px;\n -ms-flex: 1;\n flex: 1;\n}\n.icon-upload {\n cursor: pointer;\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/media_upload/media_upload.vue","\n.emoji-input .form-control{width:100%\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/emoji-input/emoji-input.vue","\n.user-card{background-size:cover;overflow:hidden\n}\n.user-card .panel-heading{padding:.5em 0;text-align:center;box-shadow:none;background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\n}\n.user-card .panel-body{word-wrap:break-word;background:linear-gradient(to bottom, transparent, #121a24 80%);background:linear-gradient(to bottom, transparent, var(--bg, #121a24) 80%)\n}\n.user-card p{margin-bottom:0\n}\n.user-card-bio{text-align:center\n}\n.user-card-bio img{object-fit:contain;vertical-align:middle;max-width:100%;max-height:400px\n}\n.user-card-bio img.emoji{width:32px;height:32px\n}\n.user-card-rounded-t{border-top-left-radius:10px;border-top-left-radius:var(--panelRadius, 10px);border-top-right-radius:10px;border-top-right-radius:var(--panelRadius, 10px)\n}\n.user-card-rounded{border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.user-card-bordered{border-width:1px;border-style:solid;border-color:#222;border-color:var(--border, #222)\n}\n.user-info{color:#b9b9ba;color:var(--lightText, #b9b9ba);padding:0 26px\n}\n.user-info .container{padding:16px 0 6px;display:-ms-flexbox;display:flex;max-height:56px\n}\n.user-info .container .avatar{-ms-flex:1 0 100%;flex:1 0 100%;width:56px;height:56px;box-shadow:0px 1px 8px rgba(0,0,0,0.75);box-shadow:var(--avatarShadow);object-fit:cover\n}\n.user-info:hover .animated.avatar canvas{display:none\n}\n.user-info:hover .animated.avatar img{visibility:visible\n}\n.user-info .usersettings{color:#b9b9ba;color:var(--lightText, #b9b9ba);opacity:.8\n}\n.user-info .name-and-screen-name{display:block;margin-left:0.6em;text-align:left;text-overflow:ellipsis;white-space:nowrap;-ms-flex:1 1 0px;flex:1 1 0;z-index:1\n}\n.user-info .name-and-screen-name img{width:26px;height:26px;vertical-align:middle;object-fit:contain\n}\n.user-info .name-and-screen-name .top-line{display:-ms-flexbox;display:flex\n}\n.user-info .user-name{text-overflow:ellipsis;overflow:hidden;-ms-flex:1 1 auto;flex:1 1 auto;margin-right:1em;font-size:15px\n}\n.user-info .user-name img{object-fit:contain;height:16px;width:16px;vertical-align:middle\n}\n.user-info .user-screen-name{color:#b9b9ba;color:var(--lightText, #b9b9ba);display:inline-block;font-weight:light;font-size:15px;padding-right:0.1em;width:100%;display:-ms-flexbox;display:flex\n}\n.user-info .user-screen-name .dailyAvg{min-width:1px;-ms-flex:0 0 auto;flex:0 0 auto;margin-left:1em;font-size:0.7em;color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.user-info .user-screen-name .handle{min-width:1px;-ms-flex:0 1 auto;flex:0 1 auto;text-overflow:ellipsis;overflow:hidden\n}\n.user-info .user-screen-name .staff{text-transform:capitalize;color:#b9b9ba;color:var(--btnText, #b9b9ba);background-color:#182230;background-color:var(--btn, #182230)\n}\n.user-info .user-meta{margin-bottom:.15em;display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline;font-size:14px;line-height:22px;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.user-info .user-meta .following{-ms-flex:1 0 auto;flex:1 0 auto;margin:0;margin-bottom:.25em;text-align:left\n}\n.user-info .user-meta .highlighter{-ms-flex:0 1 auto;flex:0 1 auto;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-.5em;-ms-flex-item-align:start;align-self:start\n}\n.user-info .user-meta .highlighter .userHighlightCl{padding:2px 10px;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.user-info .user-meta .highlighter .userHighlightSel,.user-info .user-meta .highlighter .userHighlightSel.select{padding-top:0;padding-bottom:0;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.user-info .user-meta .highlighter .userHighlightSel.select i{line-height:22px\n}\n.user-info .user-meta .highlighter .userHighlightText{width:70px;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.user-info .user-meta .highlighter .userHighlightCl,.user-info .user-meta .highlighter .userHighlightText,.user-info .user-meta .highlighter .userHighlightSel,.user-info .user-meta .highlighter .userHighlightSel.select{height:22px;vertical-align:top;margin-right:.5em;margin-bottom:.25em\n}\n.user-info .user-interactions{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-pack:justify;justify-content:space-between;margin-right:-.75em\n}\n.user-info .user-interactions div{-ms-flex:1 0 0px;flex:1 0 0;margin-right:.75em;margin-bottom:.6em;white-space:nowrap\n}\n.user-info .user-interactions .mute{max-width:220px;min-height:28px\n}\n.user-info .user-interactions .follow{max-width:220px;min-height:28px\n}\n.user-info .user-interactions button{width:100%;height:100%;margin:0\n}\n.user-info .user-interactions .remote-button{height:28px !important;width:92%\n}\n.user-info .user-interactions .pressed{border-bottom-color:rgba(255,255,255,0.2);border-top-color:rgba(0,0,0,0.2)\n}\n.user-counts{display:-ms-flexbox;display:flex;line-height:16px;padding:.5em 1.5em 0em 1.5em;text-align:center;-ms-flex-pack:justify;justify-content:space-between;color:#b9b9ba;color:var(--lightText, #b9b9ba);-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.user-count{-ms-flex:1 0 auto;flex:1 0 auto;padding:.5em 0 .5em 0;margin:0 .5em\n}\n.user-count h5{font-size:1em;font-weight:bolder;margin:0 0 0.25em\n}\n.user-count a{text-decoration:none\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_card/user_card.vue","\n.avatar.still-image{width:48px;height:48px;box-shadow:var(--avatarStatusShadow);border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.avatar.still-image img{width:100%;height:100%\n}\n.avatar.still-image.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.avatar.still-image.animated::before{display:none\n}\n.avatar.still-image.avatar-compact{width:32px;height:32px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_avatar/user_avatar.vue","\n.remote-follow{max-width:220px\n}\n.remote-follow .remote-button{width:100%;min-height:28px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/remote_follow/remote_follow.vue","\n.popper-wrapper{z-index:8\n}\n.popper-wrapper .popper__arrow{width:0;height:0;border-style:solid;position:absolute;margin:5px\n}\n.popper-wrapper[x-placement^=\"top\"]{margin-bottom:5px\n}\n.popper-wrapper[x-placement^=\"top\"] .popper__arrow{border-width:5px 5px 0 5px;border-color:#121a24 transparent transparent transparent;border-color:var(--bg, #121a24) transparent transparent transparent;bottom:-5px;left:calc(50% - 5px);margin-top:0;margin-bottom:0\n}\n.popper-wrapper[x-placement^=\"bottom\"]{margin-top:5px\n}\n.popper-wrapper[x-placement^=\"bottom\"] .popper__arrow{border-width:0 5px 5px 5px;border-color:transparent transparent #121a24 transparent;border-color:transparent transparent var(--bg, #121a24) transparent;top:-5px;left:calc(50% - 5px);margin-top:0;margin-bottom:0\n}\n.popper-wrapper[x-placement^=\"right\"]{margin-left:5px\n}\n.popper-wrapper[x-placement^=\"right\"] .popper__arrow{border-width:5px 5px 5px 0;border-color:transparent #121a24 transparent transparent;border-color:transparent var(--bg, #121a24) transparent transparent;left:-5px;top:calc(50% - 5px);margin-left:0;margin-right:0\n}\n.popper-wrapper[x-placement^=\"left\"]{margin-right:5px\n}\n.popper-wrapper[x-placement^=\"left\"] .popper__arrow{border-width:5px 0 5px 5px;border-color:transparent transparent transparent #121a24;border-color:transparent transparent transparent var(--bg, #121a24);right:-5px;top:calc(50% - 5px);margin-left:0;margin-right:0\n}\n.dropdown-menu{display:block;padding:.5rem 0;font-size:1rem;text-align:left;list-style:none;max-width:100vw;z-index:10;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow);border:none;border-radius:4px;border-radius:var(--btnRadius, 4px);background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.dropdown-menu .dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #222;border-top:1px solid var(--border, #222)\n}\n.dropdown-menu .dropdown-item{line-height:21px;margin-right:5px;overflow:auto;display:block;padding:.25rem 1.0rem .25rem 1.5rem;clear:both;font-weight:400;text-align:inherit;white-space:normal;border:none;border-radius:0px;background-color:transparent;box-shadow:none;width:100%;height:100%\n}\n.dropdown-menu .dropdown-item:hover{background-color:#182230;background-color:var(--btn, #182230);box-shadow:none\n}\n.menu-checkbox{float:right;min-width:22px;max-width:22px;min-height:22px;max-height:22px;line-height:22px;text-align:center;border-radius:0px;background-color:#182230;background-color:var(--input, #182230);box-shadow:0px 0px 2px black inset;box-shadow:var(--inputShadow)\n}\n.menu-checkbox.menu-checkbox-checked::after{content:'✔'\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/moderation_tools/moderation_tools.vue","\n.dark-overlay::before{bottom:0;content:\" \";display:block;cursor:default;left:0;position:fixed;right:0;top:0;background:rgba(27,31,35,0.5);z-index:99\n}\n.dialog-modal.panel{top:0;left:50%;max-height:80vh;max-width:90vw;margin:15vh auto;position:fixed;transform:translateX(-50%);z-index:999;cursor:default;display:block;background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.dialog-modal.panel .dialog-modal-heading{padding:.5em .5em;margin-right:auto;margin-bottom:0;white-space:nowrap;color:var(--panelText);background-color:#182230;background-color:var(--panel, #182230)\n}\n.dialog-modal.panel .dialog-modal-heading .title{margin-bottom:0\n}\n.dialog-modal.panel .dialog-modal-content{margin:0;padding:1rem 1rem;background-color:#151e2a;background-color:var(--lightBg, #151e2a);white-space:normal\n}\n.dialog-modal.panel .dialog-modal-footer{margin:0;padding:.5em .5em;background-color:#151e2a;background-color:var(--lightBg, #151e2a);border-top:1px solid #121a24;border-top:1px solid var(--bg, #121a24);-ms-flex-pack:end;justify-content:flex-end\n}\n.dialog-modal.panel .dialog-modal-footer button{width:auto;margin-left:.5rem\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/dialog_modal/dialog_modal.vue","\n.popper {\n width: auto;\n background-color: #fafafa;\n color: #212121;\n text-align: center;\n padding: 2px;\n display: inline-block;\n border-radius: 3px;\n position: absolute;\n font-size: 14px;\n font-weight: normal;\n border: 1px #ebebeb solid;\n z-index: 200000;\n box-shadow: rgb(58, 58, 58) 0 0 6px 0;\n}\n.popper .popper__arrow {\n width: 0;\n height: 0;\n border-style: solid;\n position: absolute;\n margin: 5px;\n}\n.popper[x-placement^=\"top\"] {\n margin-bottom: 5px;\n}\n.popper[x-placement^=\"top\"] .popper__arrow {\n border-width: 5px 5px 0 5px;\n border-color: #fafafa transparent transparent transparent;\n bottom: -5px;\n left: calc(50% - 5px);\n margin-top: 0;\n margin-bottom: 0;\n}\n.popper[x-placement^=\"bottom\"] {\n margin-top: 5px;\n}\n.popper[x-placement^=\"bottom\"] .popper__arrow {\n border-width: 0 5px 5px 5px;\n border-color: transparent transparent #fafafa transparent;\n top: -5px;\n left: calc(50% - 5px);\n margin-top: 0;\n margin-bottom: 0;\n}\n.popper[x-placement^=\"right\"] {\n margin-left: 5px;\n}\n.popper[x-placement^=\"right\"] .popper__arrow {\n border-width: 5px 5px 5px 0;\n border-color: transparent #fafafa transparent transparent;\n left: -5px;\n top: calc(50% - 5px);\n margin-left: 0;\n margin-right: 0;\n}\n.popper[x-placement^=\"left\"] {\n margin-right: 5px;\n}\n.popper[x-placement^=\"left\"] .popper__arrow {\n border-width: 5px 0 5px 5px;\n border-color: transparent transparent transparent #fafafa;\n right: -5px;\n top: calc(50% - 5px);\n margin-left: 0;\n margin-right: 0;\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///~/vue-popperjs/src/component/popper.js.vue","\n.gallery-row{height:200px;width:100%;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-line-pack:stretch;align-content:stretch;-ms-flex-positive:1;flex-grow:1;margin-top:0.5em\n}\n.gallery-row .attachments,.gallery-row .attachment{margin:0 0.5em 0 0;-ms-flex-positive:1;flex-grow:1;height:100%;box-sizing:border-box;min-width:2em\n}\n.gallery-row .attachments:last-child,.gallery-row .attachment:last-child{margin:0\n}\n.gallery-row .image-attachment{width:100%;height:100%\n}\n.gallery-row .video-container{height:100%\n}\n.gallery-row.contain-fit img,.gallery-row.contain-fit video{object-fit:contain\n}\n.gallery-row.cover-fit img,.gallery-row.cover-fit video{object-fit:cover\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/gallery/gallery.vue","\n.link-preview-card{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;cursor:pointer;overflow:hidden;margin-top:0.5em;color:#b9b9ba;color:var(--text, #b9b9ba);border-style:solid;border-width:1px;border-radius:10px;border-radius:var(--attachmentRadius, 10px);border-color:#222;border-color:var(--border, #222)\n}\n.link-preview-card .card-image{-ms-flex-negative:0;flex-shrink:0;width:120px;max-width:25%\n}\n.link-preview-card .card-image img{width:100%;height:100%;object-fit:cover;border-radius:10px;border-radius:var(--attachmentRadius, 10px)\n}\n.link-preview-card .small-image{width:80px\n}\n.link-preview-card .card-content{max-height:100%;margin:0.5em;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column\n}\n.link-preview-card .card-host{font-size:12px\n}\n.link-preview-card .card-description{margin:0.5em 0 0 0;overflow:hidden;text-overflow:ellipsis;word-break:break-word;line-height:1.2em;max-height:calc(1.2em * 3 - 1px)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/link-preview/link-preview.vue","\n.timeline .panel-disabled .status-el{border-left:none;border-bottom-width:1px;border-bottom-style:solid;border-color:var(--border, #222);border-radius:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/conversation/conversation.vue","\n.user-profile{-ms-flex:2;flex:2;-ms-flex-preferred-size:500px;flex-basis:500px\n}\n.user-profile .userlist-placeholder{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:middle;align-items:middle;padding:2em\n}\n.user-profile .timeline-heading{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center\n}\n.user-profile .timeline-heading .loadmore-button,.user-profile .timeline-heading .alert{-ms-flex:1;flex:1\n}\n.user-profile .timeline-heading .loadmore-button{height:28px;margin:10px .6em\n}\n.user-profile .timeline-heading .title,.user-profile .timeline-heading .loadmore-text{display:none\n}\n.user-profile-placeholder .panel-body{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:middle;align-items:middle;padding:7em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_profile/user_profile.vue","\n.follow-card-content-container{-ms-flex-negative:0;flex-shrink:0;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:1.5em\n}\n.follow-card-follow-button{margin-top:0.5em;margin-left:auto;width:10em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/follow_card/follow_card.vue","\n.basic-user-card{display:-ms-flexbox;display:flex;-ms-flex:1 0;flex:1 0;margin:0;padding:0.6em 1em\n}\n.basic-user-card-collapsed-content{margin-left:0.7em;text-align:left;-ms-flex:1;flex:1;min-width:0\n}\n.basic-user-card-user-name img{object-fit:contain;height:16px;width:16px;vertical-align:middle\n}\n.basic-user-card-user-name-value{display:inline-block;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis\n}\n.basic-user-card-expanded-content{-ms-flex:1;flex:1;margin-left:0.7em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/basic_user_card/basic_user_card.vue","\n.list-item:not(:last-child){border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)\n}\n.list-empty-content{text-align:center;padding:10px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/list/list.vue","\n@import '../../_variables.scss';\n\n.with-load-more {\n &-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: $fallback--border;\n border-top-color: var(--border, $fallback--border);\n\n .error {\n font-size: 14px;\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/hocs/with_load_more/src/hocs/with_load_more/with_load_more.scss","\n.setting-item{border-bottom:2px solid var(--fg, #182230);margin:1em 1em 1.4em;padding-bottom:1.4em\n}\n.setting-item>div{margin-bottom:.5em\n}\n.setting-item>div:last-child{margin-bottom:0\n}\n.setting-item:last-child{border-bottom:none;padding-bottom:0;margin-bottom:1em\n}\n.setting-item select{min-width:10em\n}\n.setting-item textarea{width:100%;max-width:100%;height:100px\n}\n.setting-item .unavailable,.setting-item .unavailable i{color:var(--cRed, red);color:red\n}\n.setting-item .btn{min-height:28px;min-width:10em;padding:0 2em\n}\n.setting-item .number-input{max-width:6em\n}\n.select-multiple{display:-ms-flexbox;display:flex\n}\n.select-multiple .option-list{margin:0;padding-left:.5em\n}\n.setting-list,.option-list{list-style-type:none;padding-left:2em\n}\n.setting-list li,.option-list li{margin-bottom:0.5em\n}\n.setting-list .suboptions,.option-list .suboptions{margin-top:0.3em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/settings/settings.vue","@import '../../_variables.scss';\n\n.tab-switcher {\n .contents {\n .hidden {\n display: none;\n }\n }\n .tabs {\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n\n &::after, &::before {\n display: block;\n content: '';\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n }\n\n .tab-wrapper {\n height: 28px;\n position: relative;\n display: flex;\n flex: 0 0 auto;\n\n .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: 6px - 99px;\n white-space: nowrap;\n\n &:not(.active) {\n z-index: 4;\n\n &:hover {\n z-index: 6;\n }\n }\n\n &.active {\n background: transparent;\n z-index: 5;\n }\n }\n\n &:not(.active) {\n &::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n }\n }\n }\n\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/tab_switcher/src/components/tab_switcher/tab_switcher.scss","\n.style-switcher .preset-switcher{margin-right:1em\n}\n.style-switcher .style-control{display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline;margin-bottom:5px\n}\n.style-switcher .style-control .label{-ms-flex:1;flex:1\n}\n.style-switcher .style-control.disabled input:not(.exclude-disabled),.style-switcher .style-control.disabled select:not(.exclude-disabled){opacity:.5\n}\n.style-switcher .style-control input,.style-switcher .style-control select{min-width:3em;margin:0;-ms-flex:0;flex:0\n}\n.style-switcher .style-control input[type=color],.style-switcher .style-control select[type=color]{padding:1px;cursor:pointer;height:29px;min-width:2em;border:none;-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch\n}\n.style-switcher .style-control input[type=number],.style-switcher .style-control select[type=number]{min-width:5em\n}\n.style-switcher .style-control input[type=range],.style-switcher .style-control select[type=range]{-ms-flex:1;flex:1;min-width:3em\n}\n.style-switcher .style-control input[type=checkbox]+label,.style-switcher .style-control select[type=checkbox]+label{margin:6px 0\n}\n.style-switcher .style-control input:not([type=number]):not([type=text]),.style-switcher .style-control select:not([type=number]):not([type=text]){-ms-flex-item-align:start;align-self:flex-start\n}\n.style-switcher .tab-switcher{margin:0 -1em\n}\n.style-switcher .reset-container{-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.style-switcher .fonts-container,.style-switcher .reset-container,.style-switcher .apply-container,.style-switcher .radius-container,.style-switcher .color-container{display:-ms-flexbox;display:flex\n}\n.style-switcher .fonts-container,.style-switcher .radius-container{-ms-flex-direction:column;flex-direction:column\n}\n.style-switcher .color-container{-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between\n}\n.style-switcher .color-container>h4{width:99%\n}\n.style-switcher .fonts-container,.style-switcher .color-container,.style-switcher .shadow-container,.style-switcher .radius-container,.style-switcher .presets-container{margin:1em 1em 0\n}\n.style-switcher .tab-header{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:baseline;align-items:baseline;width:100%;min-height:30px;margin-bottom:1em\n}\n.style-switcher .tab-header .btn{min-width:1px;-ms-flex:0 auto;flex:0 auto;padding:0 1em\n}\n.style-switcher .tab-header p{-ms-flex:1;flex:1;margin:0;margin-right:.5em\n}\n.style-switcher .shadow-selector .override{-ms-flex:1;flex:1;margin-left:.5em\n}\n.style-switcher .shadow-selector .select-container{margin-top:-4px;margin-bottom:-3px\n}\n.style-switcher .save-load,.style-switcher .save-load-options{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:baseline;align-items:baseline;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.style-switcher .save-load .presets,.style-switcher .save-load .import-export,.style-switcher .save-load-options .presets,.style-switcher .save-load-options .import-export{margin-bottom:.5em\n}\n.style-switcher .save-load .import-export,.style-switcher .save-load-options .import-export{display:-ms-flexbox;display:flex\n}\n.style-switcher .save-load .override,.style-switcher .save-load-options .override{margin-left:.5em\n}\n.style-switcher .save-load-options{-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:.5em;-ms-flex-pack:center;justify-content:center\n}\n.style-switcher .save-load-options .keep-option{margin:0 .5em .5em;min-width:25%\n}\n.style-switcher .preview-container{border-top:1px dashed;border-bottom:1px dashed;border-color:#222;border-color:var(--border, #222);margin:1em -1em 0;padding:1em;background:var(--body-background-image);background-size:cover;background-position:50% 50%\n}\n.style-switcher .preview-container .dummy .post{font-family:var(--postFont);display:-ms-flexbox;display:flex\n}\n.style-switcher .preview-container .dummy .post .content{-ms-flex:1;flex:1\n}\n.style-switcher .preview-container .dummy .post .content h4{margin-bottom:.25em\n}\n.style-switcher .preview-container .dummy .post .content .icons{margin-top:.5em;display:-ms-flexbox;display:flex\n}\n.style-switcher .preview-container .dummy .post .content .icons i{margin-right:1em\n}\n.style-switcher .preview-container .dummy .after-post{margin-top:1em;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center\n}\n.style-switcher .preview-container .dummy .avatar,.style-switcher .preview-container .dummy .avatar-alt{background:linear-gradient(135deg, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);color:black;font-family:sans-serif;text-align:center;margin-right:1em\n}\n.style-switcher .preview-container .dummy .avatar-alt{-ms-flex:0 auto;flex:0 auto;margin-left:28px;font-size:12px;min-width:20px;min-height:20px;line-height:20px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.style-switcher .preview-container .dummy .avatar{-ms-flex:0 auto;flex:0 auto;width:48px;height:48px;font-size:14px;line-height:48px\n}\n.style-switcher .preview-container .dummy .actions{display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline\n}\n.style-switcher .preview-container .dummy .actions .checkbox{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:baseline;align-items:baseline;margin-right:1em;-ms-flex:1;flex:1\n}\n.style-switcher .preview-container .dummy .separator{margin:1em;border-bottom:1px solid;border-color:#222;border-color:var(--border, #222)\n}\n.style-switcher .preview-container .dummy .panel-heading .badge,.style-switcher .preview-container .dummy .panel-heading .alert,.style-switcher .preview-container .dummy .panel-heading .btn,.style-switcher .preview-container .dummy .panel-heading .faint{margin-left:1em;white-space:nowrap\n}\n.style-switcher .preview-container .dummy .panel-heading .faint{text-overflow:ellipsis;min-width:2em;overflow-x:hidden\n}\n.style-switcher .preview-container .dummy .panel-heading .flex-spacer{-ms-flex:1;flex:1\n}\n.style-switcher .preview-container .dummy .btn{margin-left:0;padding:0 1em;min-width:3em;min-height:30px\n}\n.style-switcher .apply-container{-ms-flex-pack:center;justify-content:center\n}\n.style-switcher .radius-item,.style-switcher .color-item{min-width:20em;margin:5px 6px 0 0;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex:1 1 0px;flex:1 1 0\n}\n.style-switcher .radius-item.wide,.style-switcher .color-item.wide{min-width:60%\n}\n.style-switcher .radius-item:not(.wide):nth-child(2n+1),.style-switcher .color-item:not(.wide):nth-child(2n+1){margin-right:7px\n}\n.style-switcher .radius-item .color,.style-switcher .radius-item .opacity,.style-switcher .color-item .color,.style-switcher .color-item .opacity{display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline\n}\n.style-switcher .radius-item{-ms-flex-preferred-size:auto;flex-basis:auto\n}\n.style-switcher .theme-radius-rn,.style-switcher .theme-color-cl{border:0;box-shadow:none;background:transparent;color:var(--faint, rgba(185,185,186,0.5));-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch\n}\n.style-switcher .theme-color-cl,.style-switcher .theme-radius-in,.style-switcher .theme-color-in{margin-left:4px\n}\n.style-switcher .theme-radius-in{min-width:1em\n}\n.style-switcher .theme-radius-in{max-width:7em;-ms-flex:1;flex:1\n}\n.style-switcher .theme-radius-lb{max-width:50em\n}\n.style-switcher .theme-preview-content{padding:20px\n}\n.style-switcher .btn{margin-left:.25em;margin-right:.25em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/style_switcher/style_switcher.scss","\n.color-control input.text-input{max-width:7em;-ms-flex:1;flex:1\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/color_input/color_input.vue","\n.shadow-control{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:center;justify-content:center;margin-bottom:1em\n}\n.shadow-control .shadow-preview-container,.shadow-control .shadow-tweak{margin:5px 6px 0 0\n}\n.shadow-control .shadow-preview-container{-ms-flex:0;flex:0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.shadow-control .shadow-preview-container input[type=number]{width:5em;min-width:2em\n}\n.shadow-control .shadow-preview-container .x-shift-control,.shadow-control .shadow-preview-container .y-shift-control{display:-ms-flexbox;display:flex;-ms-flex:0;flex:0\n}\n.shadow-control .shadow-preview-container .x-shift-control[disabled=disabled] *,.shadow-control .shadow-preview-container .y-shift-control[disabled=disabled] *{opacity:.5\n}\n.shadow-control .shadow-preview-container .x-shift-control{-ms-flex-align:start;align-items:flex-start\n}\n.shadow-control .shadow-preview-container .x-shift-control .wrap,.shadow-control .shadow-preview-container input[type=range]{margin:0;width:15em;height:2em\n}\n.shadow-control .shadow-preview-container .y-shift-control{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:end;align-items:flex-end\n}\n.shadow-control .shadow-preview-container .y-shift-control .wrap{width:2em;height:15em\n}\n.shadow-control .shadow-preview-container .y-shift-control input[type=range]{transform-origin:1em 1em;transform:rotate(90deg)\n}\n.shadow-control .shadow-preview-container .preview-window{-ms-flex:1;flex:1;background-color:#999999;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;background-image:linear-gradient(45deg, #666 25%, transparent 25%),linear-gradient(-45deg, #666 25%, transparent 25%),linear-gradient(45deg, transparent 75%, #666 75%),linear-gradient(-45deg, transparent 75%, #666 75%);background-size:20px 20px;background-position:0 0, 0 10px, 10px -10px, -10px 0;border-radius:4px;border-radius:var(--inputRadius, 4px)\n}\n.shadow-control .shadow-preview-container .preview-window .preview-block{width:33%;height:33%;background-color:#121a24;background-color:var(--bg, #121a24);border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.shadow-control .shadow-tweak{-ms-flex:1;flex:1;min-width:280px\n}\n.shadow-control .shadow-tweak .id-control{-ms-flex-align:stretch;align-items:stretch\n}\n.shadow-control .shadow-tweak .id-control .select,.shadow-control .shadow-tweak .id-control .btn{min-width:1px;margin-right:5px\n}\n.shadow-control .shadow-tweak .id-control .btn{padding:0 .4em;margin:0 .1em\n}\n.shadow-control .shadow-tweak .id-control .select{-ms-flex:1;flex:1\n}\n.shadow-control .shadow-tweak .id-control .select select{-ms-flex-item-align:initial;-ms-grid-row-align:initial;align-self:initial\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/shadow_control/shadow_control.vue","\n.font-control input.custom-font{min-width:10em\n}\n.font-control.custom .select{border-top-right-radius:0;border-bottom-right-radius:0\n}\n.font-control.custom .custom-font{border-top-left-radius:0;border-bottom-left-radius:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/font_control/font_control.vue","\n.contrast-ratio{display:-ms-flexbox;display:flex;-ms-flex-pack:end;justify-content:flex-end;margin-top:-4px;margin-bottom:5px\n}\n.contrast-ratio .label{margin-right:1em\n}\n.contrast-ratio .rating{display:inline-block;text-align:center\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/contrast_ratio/contrast_ratio.vue","\n.import-export-container{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:baseline;align-items:baseline;-ms-flex-pack:center;justify-content:center\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/export_import/export_import.vue","\n.registration-form{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;margin:0.6em\n}\n.registration-form .container{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row\n}\n.registration-form .terms-of-service{-ms-flex:0 1 50%;flex:0 1 50%;margin:0.8em\n}\n.registration-form .text-fields{margin-top:0.6em;-ms-flex:1 0;flex:1 0;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column\n}\n.registration-form textarea{min-height:100px\n}\n.registration-form .form-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:0.3em 0.0em 0.3em;line-height:24px;margin-bottom:1em\n}\n.registration-form .form-group--error{animation-name:shakeError;animation-duration:.6s;animation-timing-function:ease-in-out\n}\n.registration-form .form-group--error .form--label{color:#f04124;color:var(--cRed, #f04124)\n}\n.registration-form .form-error{margin-top:-0.7em;text-align:left\n}\n.registration-form .form-error span{font-size:12px\n}\n.registration-form .form-error ul{list-style:none;padding:0 0 0 5px;margin-top:0\n}\n.registration-form .form-error ul li::before{content:\"• \"\n}\n.registration-form form textarea{line-height:16px;resize:vertical\n}\n.registration-form .captcha{max-width:350px;margin-bottom:0.4em\n}\n.registration-form .btn{margin-top:0.6em;height:28px\n}\n.registration-form .error{text-align:center\n}\n@media all and (max-width: 800px){\n.registration-form .container{-ms-flex-direction:column-reverse;flex-direction:column-reverse\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/registration/registration.vue","\n.profile-edit .bio{margin:0\n}\n.profile-edit input[type=file]{padding:5px;height:auto\n}\n.profile-edit .banner{max-width:100%\n}\n.profile-edit .uploading{font-size:1.5em;margin:0.25em\n}\n.profile-edit .name-changer{width:100%\n}\n.profile-edit .bg{max-width:100%\n}\n.profile-edit .current-avatar{display:block;width:150px;height:150px;border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.profile-edit .oauth-tokens{width:100%\n}\n.profile-edit .oauth-tokens th{text-align:left\n}\n.profile-edit .oauth-tokens .actions{text-align:right\n}\n.profile-edit-usersearch-wrapper{padding:1em\n}\n.profile-edit-bulk-actions{text-align:right;padding:0 1em;min-height:28px\n}\n.profile-edit-bulk-actions button{width:10em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_settings/user_settings.vue","\n.image-cropper-img-input{display:none\n}\n.image-cropper-image-container{position:relative\n}\n.image-cropper-image-container img{display:block;max-width:100%\n}\n.image-cropper-buttons-wrapper{margin-top:10px\n}\n.image-cropper-buttons-wrapper button{margin-top:5px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/image_cropper/image_cropper.vue","/*!\n * Cropper.js v1.4.3\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2018-10-24T13:07:11.429Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: .5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline-color: rgba(51, 153, 255, 0.75);\n outline: 1px solid #39f;\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: .5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: .75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center:before,\n.cropper-center:after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center:before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center:after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: .1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: .75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: .75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se:before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///~/cropperjs/dist/cropper.css","\n.block-card-content-container{margin-top:0.5em;text-align:right\n}\n.block-card-content-container button{width:10em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/block_card/block_card.vue","\n.mute-card-content-container{margin-top:0.5em;text-align:right\n}\n.mute-card-content-container button{width:10em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/mute_card/mute_card.vue","\n.selectable-list-item-inner{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center\n}\n.selectable-list-item-selected-inner{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.selectable-list-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:0.6em 0;border-bottom:2px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)\n}\n.selectable-list-header-actions{-ms-flex:1;flex:1\n}\n.selectable-list-checkbox-wrapper{padding:0 10px;-ms-flex:none;flex:none\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/selectable_list/selectable_list.vue","\n.checkbox{position:relative;display:inline-block;padding-left:1.2em;min-height:1.2em\n}\n.checkbox-indicator::before{position:absolute;left:0;top:0;display:block;content:'✔';transition:color 200ms;width:1.1em;height:1.1em;border-radius:2px;border-radius:var(--checkboxRadius, 2px);box-shadow:0px 0px 2px black inset;box-shadow:var(--inputShadow);background-color:#182230;background-color:var(--input, #182230);vertical-align:top;text-align:center;line-height:1.1em;font-size:1.1em;color:transparent;overflow:hidden;box-sizing:border-box\n}\n.checkbox input[type=checkbox]{display:none\n}\n.checkbox input[type=checkbox]:checked+.checkbox-indicator::before{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.checkbox input[type=checkbox]:indeterminate+.checkbox-indicator::before{content:'–';color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.checkbox input[type=checkbox]:disabled+.checkbox-indicator::before{opacity:.5\n}\n.checkbox>span{margin-left:.5em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/checkbox/checkbox.vue","\n.autosuggest{position:relative\n}\n.autosuggest-input{display:block;width:100%\n}\n.autosuggest-results{position:absolute;left:0;top:100%;right:0;max-height:400px;background-color:#151e2a;background-color:var(--lightBg, #151e2a);border-style:solid;border-width:1px;border-color:#222;border-color:var(--border, #222);border-radius:4px;border-radius:var(--inputRadius, 4px);border-top-left-radius:0;border-top-right-radius:0;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow);overflow-y:auto;z-index:1\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/autosuggest/autosuggest.vue",".with-subscription {\n &-loading {\n padding: 10px;\n text-align: center;\n\n .error {\n font-size: 14px;\n }\n }\n}\n\n\n// WEBPACK FOOTER //\n// webpack:///src/hocs/with_subscription/src/hocs/with_subscription/with_subscription.scss","\n.follow-request-card-content-container{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.follow-request-card-content-container button{margin-top:0.5em;margin-right:0.5em;-ms-flex:1 1;flex:1 1;max-width:12em;min-width:8em\n}\n.follow-request-card-content-container button:last-child{margin-right:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/follow_request_card/follow_request_card.vue","\n.user-search-input-container{margin:0.5em;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center\n}\n.user-search-input-container .search-button{margin-left:0.5em\n}\n.loading-icon{padding:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_search/user_search.vue","\n.notifications{padding-bottom:15em\n}\n.notifications .loadmore-error{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.notifications .notification{position:relative\n}\n.notifications .notification .notification-overlay{position:absolute;top:0;right:0;left:0;bottom:0;pointer-events:none\n}\n.notifications .notification.unseen .notification-overlay{background-image:linear-gradient(135deg, var(--badgeNotification, red) 4px, transparent 10px)\n}\n.notification{box-sizing:border-box;display:-ms-flexbox;display:flex;border-bottom:1px solid;border-color:#222;border-color:var(--border, #222)\n}\n.notification:hover .animated.avatar canvas{display:none\n}\n.notification:hover .animated.avatar img{visibility:visible\n}\n.notification .non-mention{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:0.6em;min-width:0\n}\n.notification .non-mention .avatar-container{width:32px;height:32px\n}\n.notification .non-mention .status-el{padding:0\n}\n.notification .non-mention .status-el .status{padding:0.25em 0;color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.notification .non-mention .status-el .status a{color:var(--faintLink)\n}\n.notification .non-mention .status-el .media-body{margin:0\n}\n.notification .follow-text{padding:0.5em 0\n}\n.notification .status-el{-ms-flex:1;flex:1\n}\n.notification time{white-space:nowrap\n}\n.notification .notification-right{-ms-flex:1;flex:1;padding-left:0.8em;min-width:0\n}\n.notification .notification-details{min-width:0px;word-wrap:break-word;line-height:18px;position:relative;overflow:hidden;width:100%;-ms-flex:1 1 0px;flex:1 1 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:justify;justify-content:space-between\n}\n.notification .notification-details .name-and-action{-ms-flex:1;flex:1;overflow:hidden;text-overflow:ellipsis\n}\n.notification .notification-details .username{font-weight:bolder;max-width:100%;text-overflow:ellipsis;white-space:nowrap\n}\n.notification .notification-details .username img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.notification .notification-details .timeago{margin-right:.2em\n}\n.notification .notification-details .icon-retweet.lit{color:#0fa00f;color:var(--cGreen, #0fa00f)\n}\n.notification .notification-details .icon-user-plus.lit{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.notification .notification-details .icon-reply.lit{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.notification .notification-details .icon-star.lit{color:orange;color:orange;color:var(--cOrange, orange)\n}\n.notification .notification-details .status-content{margin:0;max-height:300px\n}\n.notification .notification-details h1{word-break:break-all;margin:0 0 0.3em;padding:0;font-size:1em;line-height:20px\n}\n.notification .notification-details h1 small{font-weight:lighter\n}\n.notification .notification-details p{margin:0;margin-top:0;margin-bottom:0.3em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/notifications/notifications.scss","\n.login-form .btn{min-height:28px;width:10em\n}\n.login-form .register{-ms-flex:1 1;flex:1 1\n}\n.login-form .login-bottom{margin-top:1.0em;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between\n}\n.login .error{text-align:center;animation-name:shakeError;animation-duration:0.4s;animation-timing-function:ease-in-out\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/login_form/login_form.vue","\n.floating-chat{position:fixed;right:0px;bottom:0px;z-index:1000;max-width:25em\n}\n.chat-heading{cursor:pointer\n}\n.chat-heading .icon-comment-empty{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.chat-window{overflow-y:auto;overflow-x:hidden;max-height:20em\n}\n.chat-window-container{height:100%\n}\n.chat-message{display:-ms-flexbox;display:flex;padding:0.2em 0.5em\n}\n.chat-avatar img{height:24px;width:24px;border-radius:4px;border-radius:var(--avatarRadius, 4px);margin-right:0.5em;margin-top:0.25em\n}\n.chat-input{display:-ms-flexbox;display:flex\n}\n.chat-input textarea{-ms-flex:1;flex:1;margin:0.6em;min-height:3.5em;resize:none\n}\n.chat-panel .title{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/chat_panel/chat_panel.vue","\n.features-panel li{line-height:24px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/features_panel/features_panel.vue","\n.tos-content{margin:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/terms_of_service_panel/terms_of_service_panel.vue","\n#app{min-height:100vh;max-width:100%;overflow:hidden\n}\n.app-bg-wrapper{position:fixed;z-index:-1;height:100%;width:100%;background-size:cover;background-repeat:no-repeat;background-position:0 50%\n}\ni{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none\n}\nh4{margin:0\n}\n#content{box-sizing:border-box;padding-top:60px;margin:auto;min-height:100vh;max-width:980px;background-color:rgba(0,0,0,0.15);-ms-flex-line-pack:start;align-content:flex-start\n}\n.text-center{text-align:center\n}\nbody{font-family:sans-serif;font-family:var(--interfaceFont, sans-serif);font-size:14px;margin:0;color:#b9b9ba;color:var(--text, #b9b9ba);max-width:100vw;overflow-x:hidden\n}\na{text-decoration:none;color:#d8a070;color:var(--link, #d8a070)\n}\nbutton{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#b9b9ba;color:var(--btnText, #b9b9ba);background-color:#182230;background-color:var(--btn, #182230);border:none;border-radius:4px;border-radius:var(--btnRadius, 4px);cursor:pointer;box-shadow:0px 0px 2px 0px #000,0px 1px 0px 0px rgba(255,255,255,0.2) inset,0px -1px 0px 0px rgba(0,0,0,0.2) inset;box-shadow:var(--buttonShadow);font-size:14px;font-family:sans-serif;font-family:var(--interfaceFont, sans-serif)\n}\nbutton i[class*=icon-]{color:#b9b9ba;color:var(--btnText, #b9b9ba)\n}\nbutton::-moz-focus-inner{border:none\n}\nbutton:hover{box-shadow:0px 0px 4px rgba(255,255,255,0.3);box-shadow:var(--buttonHoverShadow)\n}\nbutton:active{box-shadow:0px 0px 4px 0px rgba(255,255,255,0.3),0px 1px 0px 0px rgba(0,0,0,0.2) inset,0px -1px 0px 0px rgba(255,255,255,0.2) inset;box-shadow:var(--buttonPressedShadow)\n}\nbutton:disabled{cursor:not-allowed;opacity:0.5\n}\nbutton.pressed{color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5));background-color:#121a24;background-color:var(--bg, #121a24)\n}\nbutton.danger{color:#b9b9ba;color:var(--alertErrorPanelText, #b9b9ba);background-color:rgba(211,16,20,0.5);background-color:var(--alertError, rgba(211,16,20,0.5))\n}\nlabel.select{padding:0\n}\ninput,textarea,.select{border:none;border-radius:4px;border-radius:var(--inputRadius, 4px);box-shadow:0px 1px 0px 0px rgba(0,0,0,0.2) inset,0px -1px 0px 0px rgba(255,255,255,0.2) inset,0px 0px 2px 0px #000 inset;box-shadow:var(--inputShadow);background-color:#182230;background-color:var(--input, #182230);color:#b9b9ba;color:var(--inputText, #b9b9ba);font-family:sans-serif;font-family:var(--inputFont, sans-serif);font-size:14px;padding:8px .5em;box-sizing:border-box;display:inline-block;position:relative;height:28px;line-height:16px;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none\n}\ninput:disabled,input[disabled=disabled],textarea:disabled,textarea[disabled=disabled],.select:disabled,.select[disabled=disabled]{cursor:not-allowed;opacity:0.5\n}\ninput .icon-down-open,textarea .icon-down-open,.select .icon-down-open{position:absolute;top:0;bottom:0;right:5px;height:100%;color:#b9b9ba;color:var(--text, #b9b9ba);line-height:28px;z-index:0;pointer-events:none\n}\ninput select,textarea select,.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:none;color:#b9b9ba;color:var(--inputText, --text, #b9b9ba);margin:0;padding:0 2em 0 .2em;font-family:sans-serif;font-family:var(--inputFont, sans-serif);font-size:14px;width:100%;z-index:1;height:28px;line-height:16px\n}\ninput[type=range],textarea[type=range],.select[type=range]{background:none;border:none;margin:0;box-shadow:none;-ms-flex:1;flex:1\n}\ninput[type=radio],input[type=checkbox],textarea[type=radio],textarea[type=checkbox],.select[type=radio],.select[type=checkbox]{display:none\n}\ninput[type=radio]:checked+label::before,input[type=checkbox]:checked+label::before,textarea[type=radio]:checked+label::before,textarea[type=checkbox]:checked+label::before,.select[type=radio]:checked+label::before,.select[type=checkbox]:checked+label::before{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\ninput[type=radio]:disabled,input[type=radio]:disabled+label,input[type=radio]:disabled+label::before,input[type=checkbox]:disabled,input[type=checkbox]:disabled+label,input[type=checkbox]:disabled+label::before,textarea[type=radio]:disabled,textarea[type=radio]:disabled+label,textarea[type=radio]:disabled+label::before,textarea[type=checkbox]:disabled,textarea[type=checkbox]:disabled+label,textarea[type=checkbox]:disabled+label::before,.select[type=radio]:disabled,.select[type=radio]:disabled+label,.select[type=radio]:disabled+label::before,.select[type=checkbox]:disabled,.select[type=checkbox]:disabled+label,.select[type=checkbox]:disabled+label::before{opacity:.5\n}\ninput[type=radio]+label::before,input[type=checkbox]+label::before,textarea[type=radio]+label::before,textarea[type=checkbox]+label::before,.select[type=radio]+label::before,.select[type=checkbox]+label::before{display:inline-block;content:'✔';transition:color 200ms;width:1.1em;height:1.1em;border-radius:2px;border-radius:var(--checkboxRadius, 2px);box-shadow:0px 0px 2px black inset;box-shadow:var(--inputShadow);margin-right:.5em;background-color:#182230;background-color:var(--input, #182230);vertical-align:top;text-align:center;line-height:1.1em;font-size:1.1em;box-sizing:border-box;color:transparent;overflow:hidden;box-sizing:border-box\n}\noption{color:#b9b9ba;color:var(--text, #b9b9ba);background-color:#121a24;background-color:var(--bg, #121a24)\n}\ni[class*=icon-]{color:#666;color:var(--icon, #666)\n}\n.container{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0;padding:0 10px 0 10px\n}\n.item{-ms-flex:1;flex:1;line-height:50px;height:50px;overflow:hidden;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.item .nav-icon{margin-left:0.4em\n}\n.item.right{-ms-flex-pack:end;justify-content:flex-end\n}\n.auto-size{-ms-flex:1;flex:1\n}\n.nav-bar{padding:0;width:100%;-ms-flex-align:center;align-items:center;position:fixed;height:50px\n}\n.nav-bar .logo{display:-ms-flexbox;display:flex;position:absolute;top:0;bottom:0;left:0;right:0;-ms-flex-align:stretch;align-items:stretch;-ms-flex-pack:center;justify-content:center;-ms-flex:0 0 auto;flex:0 0 auto;z-index:-1;transition:opacity;transition-timing-function:ease-out;transition-duration:100ms\n}\n.nav-bar .logo .mask{-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-size:contain;mask-size:contain;background-color:#182230;background-color:var(--topBarText, #182230);position:absolute;top:0;bottom:0;left:0;right:0\n}\n.nav-bar .logo img{height:100%;object-fit:contain;display:block;-ms-flex:0;flex:0\n}\n.nav-bar .inner-nav{margin:auto;box-sizing:border-box;padding-left:10px;padding-right:10px;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-preferred-size:970px;flex-basis:970px;height:50px\n}\n.nav-bar .inner-nav a,.nav-bar .inner-nav a i{color:#d8a070;color:var(--topBarLink, #d8a070)\n}\nmain-router{-ms-flex:1;flex:1\n}\n.status.compact{color:rgba(0,0,0,0.42);font-weight:300\n}\n.status.compact p{margin:0;font-size:0.8em\n}\n.panel{display:-ms-flexbox;display:flex;position:relative;-ms-flex-direction:column;flex-direction:column;margin:0.5em;background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.panel::after,.panel{border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.panel::after{content:'';position:absolute;top:0;bottom:0;left:0;right:0;pointer-events:none;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow)\n}\n.panel-body:empty::before{content:\"¯\\\\_(ツ)_/¯\";display:block;margin:1em;text-align:center\n}\n.panel-heading{display:-ms-flexbox;display:flex;border-radius:10px 10px 0 0;border-radius:var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;background-size:cover;padding:.6em .6em;text-align:left;line-height:28px;color:var(--panelText);background-color:#182230;background-color:var(--panel, #182230);-ms-flex-align:baseline;align-items:baseline;box-shadow:var(--panelHeaderShadow)\n}\n.panel-heading .title{-ms-flex:1 0 auto;flex:1 0 auto;font-size:1.3em\n}\n.panel-heading .faint{background-color:transparent;color:rgba(185,185,186,0.5);color:var(--panelFaint, rgba(185,185,186,0.5))\n}\n.panel-heading .alert{white-space:nowrap;text-overflow:ellipsis;overflow-x:hidden\n}\n.panel-heading button{-ms-flex-negative:0;flex-shrink:0\n}\n.panel-heading button,.panel-heading .alert{line-height:21px;min-height:0;box-sizing:border-box;margin:0;margin-left:.25em;min-width:1px;-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch\n}\n.panel-heading a{color:#d8a070;color:var(--panelLink, #d8a070)\n}\n.panel-heading.stub{border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.panel-footer{border-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius, 10px) var(--panelRadius, 10px)\n}\n.panel-footer .faint{color:rgba(185,185,186,0.5);color:var(--panelFaint, rgba(185,185,186,0.5))\n}\n.panel-footer a{color:#d8a070;color:var(--panelLink, #d8a070)\n}\n.panel-body>p{line-height:18px;padding:1em;margin:0\n}\n.container>*{min-width:0px\n}\n.fa{color:grey\n}\nnav{z-index:1000;color:var(--topBarText);background-color:#182230;background-color:var(--topBar, #182230);color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5));box-shadow:0px 0px 4px rgba(0,0,0,0.6);box-shadow:var(--topBarShadow)\n}\nnav .back-button{display:block;max-width:99px;transition-property:opacity, max-width;transition-duration:300ms;transition-timing-function:ease-out\n}\nnav .back-button i{margin:0 1em\n}\nnav .back-button.hidden{opacity:0;max-width:5px\n}\n.fade-enter-active,.fade-leave-active{transition:opacity .2s\n}\n.fade-enter,.fade-leave-active{opacity:0\n}\n.main{-ms-flex-preferred-size:50%;flex-basis:50%;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:1;flex-shrink:1\n}\n.sidebar-bounds{-ms-flex:0;flex:0;-ms-flex-preferred-size:35%;flex-basis:35%\n}\n.sidebar-flexer{-ms-flex:1;flex:1;-ms-flex-preferred-size:345px;flex-basis:345px;width:365px\n}\n.mobile-shown{display:none\n}\n@media all and (min-width: 800px){\nbody{overflow-y:scroll\n}\nnav .back-button{display:none\n}\n.sidebar-bounds{overflow:hidden;max-height:100vh;width:345px;position:fixed;margin-top:-10px\n}\n.sidebar-bounds .sidebar-scroller{height:96vh;width:365px;padding-top:10px;padding-right:50px;overflow-x:hidden;overflow-y:scroll\n}\n.sidebar-bounds .sidebar{width:345px\n}\n.sidebar-flexer{max-height:96vh;-ms-flex-negative:0;flex-shrink:0;-ms-flex-positive:0;flex-grow:0\n}\n}\n.badge{display:inline-block;border-radius:99px;min-width:22px;max-width:22px;min-height:22px;max-height:22px;font-size:15px;line-height:22px;text-align:center;vertical-align:middle;white-space:nowrap;padding:0\n}\n.badge.badge-notification{background-color:red;background-color:var(--badgeNotification, red);color:white;color:var(--badgeNotificationText, #fff)\n}\n.alert{margin:0.35em;padding:0.25em;border-radius:5px;border-radius:var(--tooltipRadius, 5px);min-height:28px;line-height:28px\n}\n.alert.error{background-color:rgba(211,16,20,0.5);background-color:var(--alertError, rgba(211,16,20,0.5));color:#b9b9ba;color:var(--alertErrorText, #b9b9ba)\n}\n.panel-heading .alert.error{color:#b9b9ba;color:var(--alertErrorPanelText, #b9b9ba)\n}\n.faint{color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.faint-link{color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.faint-link:hover{text-decoration:underline\n}\n@media all and (min-width: 800px){\n.logo{opacity:1 !important\n}\n}\n.item.right{text-align:right\n}\n.visibility-tray{font-size:1.2em;padding:3px;cursor:pointer\n}\n.visibility-tray .selected{color:#b9b9ba;color:var(--lightText, #b9b9ba)\n}\n.visibility-tray div{padding-top:5px\n}\n.visibility-notice{padding:.5em;border:1px solid rgba(185,185,186,0.5);border:1px solid var(--faint, rgba(185,185,186,0.5));border-radius:4px;border-radius:var(--inputRadius, 4px)\n}\n@keyframes modal-background-fadein{\nfrom{background-color:transparent\n}\nto{background-color:rgba(0,0,0,0.5)\n}\n}\n.modal-view{z-index:1000;position:fixed;top:0;left:0;right:0;bottom:0;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;overflow:auto;animation-duration:0.2s;background-color:rgba(0,0,0,0.5);animation-name:modal-background-fadein\n}\n.button-icon{font-size:1.2em\n}\n@keyframes shakeError{\n0%{transform:translateX(0)\n}\n15%{transform:translateX(0.375rem)\n}\n30%{transform:translateX(-0.375rem)\n}\n45%{transform:translateX(0.375rem)\n}\n60%{transform:translateX(-0.375rem)\n}\n75%{transform:translateX(0.375rem)\n}\n90%{transform:translateX(-0.375rem)\n}\n100%{transform:translateX(0)\n}\n}\n@media all and (max-width: 800px){\n.mobile-hidden{display:none\n}\n.panel-switcher{display:-ms-flexbox;display:flex\n}\n.container{padding:0\n}\n.panel{margin:0.5em 0 0.5em 0\n}\n.menu-button{display:block;margin-right:0.8em\n}\n}\n.login-hint{text-align:center\n}\n@media all and (min-width: 801px){\n.login-hint{display:none\n}\n}\n.login-hint a{display:inline-block;padding:1em 0px;width:100%\n}\n.btn.btn-default{min-height:28px\n}\n.autocomplete-panel{position:relative\n}\n.autocomplete-panel-body{margin:0 0.5em 0 0.5em;border-radius:5px;border-radius:var(--tooltipRadius, 5px);position:absolute;z-index:1;box-shadow:1px 2px 4px rgba(0,0,0,0.5);box-shadow:var(--popupShadow);min-width:75%;background:#121a24;background:var(--bg, #121a24);color:#b9b9ba;color:var(--lightText, #b9b9ba)\n}\n.autocomplete-item{cursor:pointer;padding:0.2em 0.4em 0.2em 0.4em;border-bottom:1px solid rgba(0,0,0,0.4);display:-ms-flexbox;display:flex\n}\n.autocomplete-item img{width:24px;height:24px;object-fit:contain\n}\n.autocomplete-item span{line-height:24px;margin:0 0.1em 0 0.2em\n}\n.autocomplete-item small{margin-left:.5em;color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.autocomplete-item.highlighted{background-color:#182230;background-color:var(--lightBg, #182230)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/App.scss","\n.nav-panel .panel{overflow:hidden;box-shadow:var(--panelShadow)\n}\n.nav-panel ul{list-style:none;margin:0;padding:0\n}\n.follow-request-count{margin:-6px 10px;background-color:#121a24;background-color:var(--input, rgba(185,185,186,0.5))\n}\n.nav-panel li{border-bottom:1px solid;border-color:#222;border-color:var(--border, #222);padding:0\n}\n.nav-panel li:first-child a{border-top-right-radius:10px;border-top-right-radius:var(--panelRadius, 10px);border-top-left-radius:10px;border-top-left-radius:var(--panelRadius, 10px)\n}\n.nav-panel li:last-child a{border-bottom-right-radius:10px;border-bottom-right-radius:var(--panelRadius, 10px);border-bottom-left-radius:10px;border-bottom-left-radius:var(--panelRadius, 10px)\n}\n.nav-panel li:last-child{border:none\n}\n.nav-panel a{display:block;padding:0.8em 0.85em\n}\n.nav-panel a:hover{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.nav-panel a.router-link-active{font-weight:bolder;background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.nav-panel a.router-link-active:hover{text-decoration:underline\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/nav_panel/nav_panel.vue","\n.user-finder-container{max-width:100%;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:baseline;align-items:baseline;vertical-align:baseline\n}\n.user-finder-container .user-finder-input,.user-finder-container .search-button{height:29px\n}\n.user-finder-container .user-finder-input{max-width:calc(100% - 30px - 30px - 20px)\n}\n.user-finder-container .search-button{margin-left:.5em;margin-right:.5em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_finder/user_finder.vue","\n.who-to-follow *{vertical-align:middle\n}\n.who-to-follow img{width:32px;height:32px\n}\n.who-to-follow{padding:0.5em 1em 0.5em 1em;margin:0px;line-height:40px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/who_to_follow_panel/who_to_follow_panel.vue","\n.media-modal-view:hover .modal-view-button-arrow{opacity:0.75\n}\n.media-modal-view:hover .modal-view-button-arrow:focus,.media-modal-view:hover .modal-view-button-arrow:hover{outline:none;box-shadow:none\n}\n.media-modal-view:hover .modal-view-button-arrow:hover{opacity:1\n}\n.modal-image{max-width:90%;max-height:90%;box-shadow:0px 5px 15px 0 rgba(0,0,0,0.5)\n}\n.modal-view-button-arrow{position:absolute;display:block;top:50%;margin-top:-50px;width:70px;height:100px;border:0;padding:0;opacity:0;box-shadow:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:visible;cursor:pointer;transition:opacity 333ms cubic-bezier(0.4, 0, 0.22, 1)\n}\n.modal-view-button-arrow .arrow-icon{position:absolute;top:35px;height:30px;width:32px;font-size:14px;line-height:30px;color:#FFF;text-align:center;background-color:rgba(0,0,0,0.3)\n}\n.modal-view-button-arrow--prev{left:0\n}\n.modal-view-button-arrow--prev .arrow-icon{left:6px\n}\n.modal-view-button-arrow--next{right:0\n}\n.modal-view-button-arrow--next .arrow-icon{right:6px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/media_modal/media_modal.vue","\n.side-drawer-container{position:fixed;z-index:1000;top:0;left:0;width:100%;height:100%;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;align-items:stretch;transition-duration:0s;transition-property:transform\n}\n.side-drawer-container-open{transform:translate(0%)\n}\n.side-drawer-container-closed{transition-delay:0.35s;transform:translate(-100%)\n}\n.side-drawer-darken{top:0;left:0;width:100vw;height:100vh;position:fixed;z-index:-1;transition:0.35s;transition-property:background-color;background-color:rgba(0,0,0,0.5)\n}\n.side-drawer-darken-closed{background-color:transparent\n}\n.side-drawer-click-outside{-ms-flex:1 1 100%;flex:1 1 100%\n}\n.side-drawer{overflow-x:hidden;transition-timing-function:cubic-bezier(0, 1, 0.5, 1);transition:0.35s;transition-property:transform;margin:0 0 0 -100px;padding:0 0 1em 100px;width:80%;max-width:20em;-ms-flex:0 0 80%;flex:0 0 80%;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow);background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.side-drawer-logo-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:0.85em\n}\n.side-drawer-logo-wrapper img{-ms-flex:none;flex:none;height:50px;margin-right:0.85em\n}\n.side-drawer-logo-wrapper span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap\n}\n.side-drawer-click-outside-closed{-ms-flex:0 0 0px;flex:0 0 0\n}\n.side-drawer-closed{transform:translate(-100%)\n}\n.side-drawer-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch;display:-ms-flexbox;display:flex;padding:0;margin:0\n}\n.side-drawer ul{list-style:none;margin:0;padding:0;border-bottom:1px solid;border-color:#222;border-color:var(--border, #222);margin:0.2em 0\n}\n.side-drawer ul:last-child{border:0\n}\n.side-drawer li{padding:0\n}\n.side-drawer li a{display:block;padding:0.5em 0.85em\n}\n.side-drawer li a:hover{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/side_drawer/side_drawer.vue","\n.post-form-modal-view{max-height:100%;display:block\n}\n.post-form-modal-panel{-ms-flex-negative:0;flex-shrink:0;margin:25% 0 4em 0;width:100%\n}\n.new-status-button{width:5em;height:5em;border-radius:100%;position:fixed;bottom:1.5em;right:1.5em;background-color:#182230;background-color:var(--btn, #182230);display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;box-shadow:0px 2px 2px rgba(0,0,0,0.3),0px 4px 6px rgba(0,0,0,0.3);z-index:10;transition:0.35s transform;transition-timing-function:cubic-bezier(0, 1, 0.5, 1)\n}\n.new-status-button.hidden{transform:translateY(150%)\n}\n.new-status-button i{font-size:1.5em;color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n@media all and (min-width: 801px){\n.new-status-button{display:none\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/mobile_post_status_modal/mobile_post_status_modal.vue","\n.mobile-inner-nav{width:100%;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center\n}\n.mobile-nav-button{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;width:50px;position:relative;cursor:pointer\n}\n.alert-dot{border-radius:100%;height:8px;width:8px;position:absolute;left:calc(50% - 4px);top:calc(50% - 4px);margin-left:6px;margin-top:-6px;background-color:red;background-color:var(--badgeNotification, red)\n}\n.mobile-notifications-drawer{width:100%;height:100vh;overflow-x:hidden;position:fixed;top:0;left:0;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow);transition-property:transform;transition-duration:0.25s;transform:translateX(0)\n}\n.mobile-notifications-drawer.closed{transform:translateX(100%)\n}\n.mobile-notifications-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;z-index:1;width:100%;height:50px;line-height:50px;position:absolute;color:var(--topBarText);background-color:#182230;background-color:var(--topBar, #182230);box-shadow:0px 0px 4px rgba(0,0,0,0.6);box-shadow:var(--topBarShadow)\n}\n.mobile-notifications-header .title{font-size:1.3em;margin-left:0.6em\n}\n.mobile-notifications{margin-top:50px;width:100vw;height:calc(100vh - 50px);overflow-x:hidden;overflow-y:scroll;color:#b9b9ba;color:var(--text, #b9b9ba);background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.mobile-notifications .notifications{padding:0;border-radius:0;box-shadow:none\n}\n.mobile-notifications .notifications .panel{border-radius:0;margin:0;box-shadow:none\n}\n.mobile-notifications .notifications .panel:after{border-radius:0\n}\n.mobile-notifications .notifications .panel .panel-heading{border-radius:0;box-shadow:none\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/mobile_nav/mobile_nav.vue"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.ea66966b753e709d7ce58f910a2c003e.css b/priv/static/static/css/app.ea66966b753e709d7ce58f910a2c003e.css deleted file mode 100644 index 7cd3bda40..000000000 Binary files a/priv/static/static/css/app.ea66966b753e709d7ce58f910a2c003e.css and /dev/null differ diff --git a/priv/static/static/css/app.ea66966b753e709d7ce58f910a2c003e.css.map b/priv/static/static/css/app.ea66966b753e709d7ce58f910a2c003e.css.map deleted file mode 100644 index 94e03d028..000000000 --- a/priv/static/static/css/app.ea66966b753e709d7ce58f910a2c003e.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///webpack:///src/components/timeline/timeline.vue","webpack:///webpack:///src/components/status/status.vue","webpack:///webpack:///src/components/attachment/attachment.vue","webpack:///webpack:///src/components/still-image/still-image.vue","webpack:///webpack:///src/components/favorite_button/favorite_button.vue","webpack:///webpack:///src/components/retweet_button/retweet_button.vue","webpack:///webpack:///src/components/delete_button/delete_button.vue","webpack:///webpack:///src/components/post_status_form/post_status_form.vue","webpack:///webpack:///src/components/media_upload/media_upload.vue","webpack:///webpack:///src/components/user_card/user_card.vue","webpack:///webpack:///src/components/user_avatar/user_avatar.vue","webpack:///webpack:///src/components/gallery/gallery.vue","webpack:///webpack:///src/components/link-preview/link-preview.vue","webpack:///webpack:///src/components/status_or_conversation/status_or_conversation.vue","webpack:///webpack:///src/components/user_profile/user_profile.vue","webpack:///webpack:///src/components/follow_card/follow_card.vue","webpack:///webpack:///src/components/basic_user_card/basic_user_card.vue","webpack:///webpack:///src/hocs/with_load_more/src/hocs/with_load_more/with_load_more.scss","webpack:///webpack:///src/hocs/with_list/src/hocs/with_list/with_list.scss","webpack:///webpack:///src/components/settings/settings.vue","webpack:///webpack:///src/components/tab_switcher/src/components/tab_switcher/tab_switcher.scss","webpack:///webpack:///src/components/style_switcher/style_switcher.scss","webpack:///webpack:///src/components/color_input/color_input.vue","webpack:///webpack:///src/components/shadow_control/shadow_control.vue","webpack:///webpack:///src/components/font_control/font_control.vue","webpack:///webpack:///src/components/contrast_ratio/contrast_ratio.vue","webpack:///webpack:///src/components/export_import/export_import.vue","webpack:///webpack:///src/components/registration/registration.vue","webpack:///webpack:///src/components/user_settings/user_settings.vue","webpack:///webpack:///src/components/image_cropper/image_cropper.vue","webpack:///webpack:///~/cropperjs/dist/cropper.css","webpack:///webpack:///src/components/block_card/block_card.vue","webpack:///webpack:///src/hocs/with_subscription/src/hocs/with_subscription/with_subscription.scss","webpack:///webpack:///src/components/follow_request_card/follow_request_card.vue","webpack:///webpack:///src/components/user_search/user_search.vue","webpack:///webpack:///src/components/notifications/notifications.scss","webpack:///webpack:///src/components/login_form/login_form.vue","webpack:///webpack:///src/components/chat_panel/chat_panel.vue","webpack:///webpack:///src/components/features_panel/features_panel.vue","webpack:///webpack:///src/components/terms_of_service_panel/terms_of_service_panel.vue","webpack:///webpack:///src/App.scss","webpack:///webpack:///src/components/nav_panel/nav_panel.vue","webpack:///webpack:///src/components/user_finder/user_finder.vue","webpack:///webpack:///src/components/who_to_follow_panel/who_to_follow_panel.vue","webpack:///webpack:///src/components/media_modal/media_modal.vue","webpack:///webpack:///src/components/side_drawer/side_drawer.vue","webpack:///webpack:///src/components/mobile_post_status_modal/mobile_post_status_modal.vue"],"names":[],"mappings":"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","file":"static/css/app.ea66966b753e709d7ce58f910a2c003e.css","sourcesContent":["\n.timeline .loadmore-text{opacity:1\n}\n.new-status-notification{position:relative;margin-top:-1px;font-size:1.1em;border-width:1px 0 0 0;border-style:solid;border-color:var(--border, #222);padding:10px;z-index:1;background-color:#182230;background-color:var(--panel, #182230)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/timeline/timeline.vue","\n.status-body{-ms-flex:1;flex:1;min-width:0\n}\n.status-preview.status-el{border-style:solid;border-width:1px;border-color:#222;border-color:var(--border, #222)\n}\n.status-preview-container{position:relative;max-width:100%\n}\n.status-preview{position:absolute;max-width:95%;display:-ms-flexbox;display:flex;background-color:#121a24;background-color:var(--bg, #121a24);border-color:#222;border-color:var(--border, #222);border-style:solid;border-width:1px;border-radius:5px;border-radius:var(--tooltipRadius, 5px);box-shadow:2px 2px 3px rgba(0,0,0,0.5);box-shadow:var(--popupShadow);margin-top:0.25em;margin-left:0.5em;z-index:50\n}\n.status-preview .status{-ms-flex:1;flex:1;border:0;min-width:15em\n}\n.status-preview-loading{display:block;min-width:15em;padding:1em;text-align:center;border-width:1px;border-style:solid\n}\n.status-preview-loading i{font-size:2em\n}\n.media-left{margin-right:.75em\n}\n.status-el{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;border-left-width:0px;min-width:0;border-color:#222;border-color:var(--border, #222);border-left:4px red;border-left:4px var(--cRed, red)\n}\n.status-el_focused{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.timeline .status-el{border-bottom-width:1px;border-bottom-style:solid\n}\n.status-el .media-body{-ms-flex:1;flex:1;padding:0\n}\n.status-el .status-usercard{margin-bottom:.75em\n}\n.status-el .user-name{white-space:nowrap;font-size:14px;overflow:hidden;-ms-flex-negative:0;flex-shrink:0;max-width:85%;font-weight:bold\n}\n.status-el .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.status-el .media-heading{padding:0;vertical-align:bottom;-ms-flex-preferred-size:100%;flex-basis:100%;margin-bottom:0.5em\n}\n.status-el .media-heading a{display:inline-block;word-break:break-all\n}\n.status-el .media-heading small{font-weight:lighter\n}\n.status-el .media-heading .heading-name-row{padding:0;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;line-height:18px\n}\n.status-el .media-heading .heading-name-row .name-and-account-name{display:-ms-flexbox;display:flex;min-width:0\n}\n.status-el .media-heading .heading-name-row .user-name{-ms-flex-negative:1;flex-shrink:1;margin-right:0.4em;overflow:hidden;text-overflow:ellipsis\n}\n.status-el .media-heading .heading-name-row .account-name{min-width:1.6em;margin-right:0.4em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-ms-flex:1 1 0px;flex:1 1 0\n}\n.status-el .media-heading .heading-right{display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0\n}\n.status-el .media-heading .timeago{margin-right:0.2em\n}\n.status-el .media-heading .heading-reply-row{-ms-flex-line-pack:baseline;align-content:baseline;font-size:12px;line-height:18px;max-width:100%;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch\n}\n.status-el .media-heading .heading-reply-row a{max-width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap\n}\n.status-el .media-heading .reply-to-and-accountname{display:-ms-flexbox;display:flex;height:18px;margin-right:0.5em;overflow:hidden;max-width:100%\n}\n.status-el .media-heading .reply-to-and-accountname .icon-reply{transform:scaleX(-1)\n}\n.status-el .media-heading .reply-info{display:-ms-flexbox;display:flex\n}\n.status-el .media-heading .reply-to{display:-ms-flexbox;display:flex\n}\n.status-el .media-heading .reply-to-text{overflow:hidden;text-overflow:ellipsis;margin:0 0.4em 0 0.2em\n}\n.status-el .media-heading .replies-separator{margin-left:0.4em\n}\n.status-el .media-heading .replies{line-height:18px;font-size:12px;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.status-el .media-heading .replies>*{margin-right:0.4em\n}\n.status-el .media-heading .reply-link{height:17px\n}\n.status-el .tall-status{position:relative;height:220px;overflow-x:hidden;overflow-y:hidden\n}\n.status-el .tall-status-hider{display:inline-block;word-break:break-all;position:absolute;height:70px;margin-top:150px;width:100%;text-align:center;line-height:110px;background:linear-gradient(to bottom, transparent, #121a24 80%);background:linear-gradient(to bottom, transparent, var(--bg, #121a24) 80%)\n}\n.status-el .tall-status-hider_focused{background:linear-gradient(to bottom, transparent, #151e2a 80%);background:linear-gradient(to bottom, transparent, var(--lightBg, #151e2a) 80%)\n}\n.status-el .status-unhider,.status-el .cw-status-hider{width:100%;text-align:center;display:inline-block;word-break:break-all\n}\n.status-el .status-content{font-family:var(--postFont, sans-serif);line-height:1.4em\n}\n.status-el .status-content img,.status-el .status-content video{max-width:100%;max-height:400px;vertical-align:middle;object-fit:contain\n}\n.status-el .status-content img.emoji,.status-el .status-content video.emoji{width:32px;height:32px\n}\n.status-el .status-content blockquote{margin:0.2em 0 0.2em 2em;font-style:italic\n}\n.status-el .status-content pre{overflow:auto\n}\n.status-el .status-content code,.status-el .status-content samp,.status-el .status-content kbd,.status-el .status-content var,.status-el .status-content pre{font-family:var(--postCodeFont, monospace)\n}\n.status-el .status-content p{margin:0 0 1em 0\n}\n.status-el .status-content p:last-child{margin:0 0 0 0\n}\n.status-el .status-content h1{font-size:1.1em;line-height:1.2em;margin:1.4em 0\n}\n.status-el .status-content h2{font-size:1.1em;margin:1.0em 0\n}\n.status-el .status-content h3{font-size:1em;margin:1.2em 0\n}\n.status-el .status-content h4{margin:1.1em 0\n}\n.status-el .retweet-info{padding:0.4em .75em;margin:0\n}\n.status-el .retweet-info .avatar.still-image{border-radius:10px;border-radius:var(--avatarAltRadius, 10px);margin-left:28px;width:20px;height:20px\n}\n.status-el .retweet-info .media-body{font-size:1em;line-height:22px;display:-ms-flexbox;display:flex;-ms-flex-line-pack:center;align-content:center;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.status-el .retweet-info .media-body .user-name{font-weight:bold;overflow:hidden;text-overflow:ellipsis\n}\n.status-el .retweet-info .media-body .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.status-el .retweet-info .media-body i{padding:0 0.2em\n}\n.status-el .retweet-info .media-body a{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\n}\n.status-fadein{animation-duration:0.4s;animation-name:fadein\n}\n@keyframes fadein{\nfrom{opacity:0\n}\nto{opacity:1\n}\n}\n.greentext{color:green\n}\n.status-conversation{border-left-style:solid\n}\n.status-actions{width:100%;display:-ms-flexbox;display:flex;margin-top:.75em\n}\n.status-actions div,.status-actions favorite-button{max-width:4em;-ms-flex:1;flex:1\n}\n.icon-reply:hover{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.icon-reply.icon-reply-active{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.status:hover .animated.avatar canvas{display:none\n}\n.status:hover .animated.avatar img{visibility:visible\n}\n.status{display:-ms-flexbox;display:flex;padding:.75em\n}\n.status.is-retweet{padding-top:0\n}\n.status-conversation:last-child{border-bottom:none\n}\n.muted{padding:0.25em 0.5em\n}\n.muted button{margin-left:auto\n}\n.muted .muteWords{margin-left:10px\n}\na.unmute{display:block;margin-left:auto\n}\n.reply-left{-ms-flex:0;flex:0;min-width:48px\n}\n.reply-body{-ms-flex:1;flex:1\n}\n.timeline>.status-el:last-child{border-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius, 10px) var(--panelRadius, 10px);border-bottom:none\n}\n@media all and (max-width: 800px){\n.status-el .retweet-info .avatar.still-image{margin-left:20px\n}\n.status{max-width:100%\n}\n.status .avatar.still-image{width:40px;height:40px\n}\n.status .avatar.still-image.avatar-compact{width:32px;height:32px\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/status/status.vue","\n.attachments{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.attachments .attachment.media-upload-container{-ms-flex:0 0 auto;flex:0 0 auto;max-height:200px;max-width:100%;display:-ms-flexbox;display:flex\n}\n.attachments .attachment.media-upload-container video{max-width:100%\n}\n.attachments .placeholder{margin-right:8px;margin-bottom:4px\n}\n.attachments .nsfw-placeholder{cursor:pointer\n}\n.attachments .nsfw-placeholder.loading{cursor:progress\n}\n.attachments .attachment{position:relative;margin-top:0.5em;-ms-flex-item-align:start;align-self:flex-start;line-height:0;border-style:solid;border-width:1px;border-radius:10px;border-radius:var(--attachmentRadius, 10px);border-color:#222;border-color:var(--border, #222);overflow:hidden\n}\n.attachments .non-gallery.attachment.video{-ms-flex:1 0 40%;flex:1 0 40%\n}\n.attachments .non-gallery.attachment .nsfw{height:260px\n}\n.attachments .non-gallery.attachment .small{height:120px;-ms-flex-positive:0;flex-grow:0\n}\n.attachments .non-gallery.attachment .video{height:260px;display:-ms-flexbox;display:flex\n}\n.attachments .non-gallery.attachment video{max-height:100%;object-fit:contain\n}\n.attachments .fullwidth{-ms-flex-preferred-size:100%;flex-basis:100%\n}\n.attachments.video{line-height:0\n}\n.attachments .video-container{display:-ms-flexbox;display:flex;max-height:100%\n}\n.attachments .video{width:100%\n}\n.attachments .play-icon{position:absolute;font-size:64px;top:calc(50% - 32px);left:calc(50% - 32px);color:rgba(255,255,255,0.75);text-shadow:0 0 2px rgba(0,0,0,0.4)\n}\n.attachments .play-icon::before{margin:0\n}\n.attachments.html{-ms-flex-preferred-size:90%;flex-basis:90%;width:100%;display:-ms-flexbox;display:flex\n}\n.attachments .hider{position:absolute;right:0;white-space:nowrap;margin:10px;padding:5px;background:rgba(230,230,230,0.6);font-weight:bold;z-index:4;line-height:1;border-radius:5px;border-radius:var(--tooltipRadius, 5px)\n}\n.attachments video{z-index:0\n}\n.attachments audio{width:100%\n}\n.attachments img.media-upload{line-height:0;max-height:200px;max-width:100%\n}\n.attachments .oembed{line-height:1.2em;-ms-flex:1 0 100%;flex:1 0 100%;width:100%;margin-right:15px;display:-ms-flexbox;display:flex\n}\n.attachments .oembed img{width:100%\n}\n.attachments .oembed .image{-ms-flex:1;flex:1\n}\n.attachments .oembed .image img{border:0px;border-radius:5px;height:100%;object-fit:cover\n}\n.attachments .oembed .text{-ms-flex:2;flex:2;margin:8px;word-break:break-all\n}\n.attachments .oembed .text h1{font-size:14px;margin:0px\n}\n.attachments .image-attachment{width:100%;height:100%\n}\n.attachments .image-attachment.hidden{display:none\n}\n.attachments .image-attachment .nsfw{object-fit:cover;width:100%;height:100%\n}\n.attachments .image-attachment img{image-orientation:from-image\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/attachment/attachment.vue","\n.still-image{position:relative;line-height:0;overflow:hidden;width:100%;height:100%\n}\n.still-image:hover canvas{display:none\n}\n.still-image img{width:100%;height:100%;object-fit:contain\n}\n.still-image.animated:hover::before,.still-image.animated img{visibility:hidden\n}\n.still-image.animated:hover img{visibility:visible\n}\n.still-image.animated::before{content:'gif';position:absolute;line-height:10px;font-size:10px;top:5px;left:5px;background:rgba(127,127,127,0.5);color:#FFF;display:block;padding:2px 4px;border-radius:5px;border-radius:var(--tooltipRadius, 5px);z-index:2\n}\n.still-image canvas{position:absolute;top:0;bottom:0;left:0;right:0;width:100%;height:100%;object-fit:contain\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/still-image/still-image.vue","\n.fav-active{cursor:pointer;animation-duration:0.6s\n}\n.fav-active:hover{color:orange;color:var(--cOrange, orange)\n}\n.favorite-button.icon-star{color:orange;color:var(--cOrange, orange)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/favorite_button/favorite_button.vue","\n.rt-active{cursor:pointer;animation-duration:0.6s\n}\n.rt-active:hover{color:#0fa00f;color:var(--cGreen, #0fa00f)\n}\n.icon-retweet.retweeted{color:#0fa00f;color:var(--cGreen, #0fa00f)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/retweet_button/retweet_button.vue","\n.icon-cancel,.delete-status{cursor:pointer\n}\n.icon-cancel:hover,.delete-status:hover{color:red;color:var(--cRed, red)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/delete_button/delete_button.vue","\n.tribute-container ul{padding:0px\n}\n.tribute-container ul li{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center\n}\n.tribute-container img{padding:3px;width:16px;height:16px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.post-status-form .visibility-tray{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-direction:row-reverse;flex-direction:row-reverse\n}\n.post-status-form .form-bottom,.login .form-bottom{display:-ms-flexbox;display:flex;padding:0.5em;height:32px\n}\n.post-status-form .form-bottom button,.login .form-bottom button{width:10em\n}\n.post-status-form .form-bottom p,.login .form-bottom p{margin:0.35em;padding:0.35em;display:-ms-flexbox;display:flex\n}\n.post-status-form .error,.login .error{text-align:center\n}\n.post-status-form .media-upload-wrapper,.login .media-upload-wrapper{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;min-width:50px;margin-right:.2em;margin-bottom:.5em\n}\n.post-status-form .media-upload-wrapper .icon-cancel,.login .media-upload-wrapper .icon-cancel{display:inline-block;position:static;margin:0;padding-bottom:0;margin-left:10px;margin-left:var(--attachmentRadius, 10px);background-color:#182230;background-color:var(--btn, #182230);border-bottom-left-radius:0;border-bottom-right-radius:0\n}\n.post-status-form .attachments,.login .attachments{padding:0 0.5em\n}\n.post-status-form .attachments .attachment,.login .attachments .attachment{margin:0;position:relative;-ms-flex:0 0 auto;flex:0 0 auto;border:1px solid #222;border:1px solid var(--border, #222);text-align:center\n}\n.post-status-form .attachments .attachment audio,.login .attachments .attachment audio{min-width:300px;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.post-status-form .attachments .attachment a,.login .attachments .attachment a{display:block;text-align:left;line-height:1.2;padding:.5em\n}\n.post-status-form .attachments i,.login .attachments i{position:absolute;margin:10px;padding:5px;background:rgba(230,230,230,0.6);border-radius:10px;border-radius:var(--attachmentRadius, 10px);font-weight:bold\n}\n.post-status-form .btn,.login .btn{cursor:pointer\n}\n.post-status-form .btn[disabled],.login .btn[disabled]{cursor:not-allowed\n}\n.post-status-form form,.login form{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:0.6em\n}\n.post-status-form .form-group,.login .form-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:0.3em 0.5em 0.6em;line-height:24px\n}\n.post-status-form form textarea.form-cw,.login form textarea.form-cw{line-height:16px;resize:none;overflow:hidden;transition:min-height 200ms 100ms;min-height:1px\n}\n.post-status-form form textarea.form-control,.login form textarea.form-control{line-height:16px;resize:none;overflow:hidden;transition:min-height 200ms 100ms;min-height:1px;box-sizing:content-box\n}\n.post-status-form form textarea.form-control:focus,.login form textarea.form-control:focus{min-height:48px\n}\n.post-status-form .btn,.login .btn{cursor:pointer\n}\n.post-status-form .btn[disabled],.login .btn[disabled]{cursor:not-allowed\n}\n.post-status-form .icon-cancel,.login .icon-cancel{cursor:pointer;z-index:4\n}\n.post-status-form .autocomplete-panel,.login .autocomplete-panel{margin:0 0.5em 0 0.5em;border-radius:5px;border-radius:var(--tooltipRadius, 5px);position:absolute;z-index:1;box-shadow:1px 2px 4px rgba(0,0,0,0.5);box-shadow:var(--popupShadow);min-width:75%;background:#121a24;background:var(--bg, #121a24);color:#b9b9ba;color:var(--lightText, #b9b9ba)\n}\n.post-status-form .autocomplete,.login .autocomplete{cursor:pointer;padding:0.2em 0.4em 0.2em 0.4em;border-bottom:1px solid rgba(0,0,0,0.4);display:-ms-flexbox;display:flex\n}\n.post-status-form .autocomplete img,.login .autocomplete img{width:24px;height:24px;border-radius:4px;border-radius:var(--avatarRadius, 4px);object-fit:contain\n}\n.post-status-form .autocomplete span,.login .autocomplete span{line-height:24px;margin:0 0.1em 0 0.2em\n}\n.post-status-form .autocomplete small,.login .autocomplete small{margin-left:.5em;color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.post-status-form .autocomplete.highlighted,.login .autocomplete.highlighted{background-color:#182230;background-color:var(--lightBg, #182230)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/post_status_form/post_status_form.vue","\n.media-upload {\n font-size: 26px;\n -ms-flex: 1;\n flex: 1;\n}\n.icon-upload {\n cursor: pointer;\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/media_upload/media_upload.vue","\n.user-card{background-size:cover;overflow:hidden\n}\n.user-card .panel-heading{padding:.5em 0;text-align:center;box-shadow:none;background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\n}\n.user-card .panel-body{word-wrap:break-word;background:linear-gradient(to bottom, transparent, #121a24 80%);background:linear-gradient(to bottom, transparent, var(--bg, #121a24) 80%)\n}\n.user-card p{margin-bottom:0\n}\n.user-card-bio{text-align:center\n}\n.user-card-bio img{object-fit:contain;vertical-align:middle;max-width:100%;max-height:400px\n}\n.user-card-bio img .emoji{width:32px;height:32px\n}\n.user-card-rounded-t{border-top-left-radius:10px;border-top-left-radius:var(--panelRadius, 10px);border-top-right-radius:10px;border-top-right-radius:var(--panelRadius, 10px)\n}\n.user-card-rounded{border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.user-card-bordered{border-width:1px;border-style:solid;border-color:#222;border-color:var(--border, #222)\n}\n.user-info{color:#b9b9ba;color:var(--lightText, #b9b9ba);padding:0 26px\n}\n.user-info .container{padding:16px 0 6px;display:-ms-flexbox;display:flex;max-height:56px\n}\n.user-info .container .avatar{-ms-flex:1 0 100%;flex:1 0 100%;width:56px;height:56px;box-shadow:0px 1px 8px rgba(0,0,0,0.75);box-shadow:var(--avatarShadow);object-fit:cover\n}\n.user-info:hover .animated.avatar canvas{display:none\n}\n.user-info:hover .animated.avatar img{visibility:visible\n}\n.user-info .usersettings{color:#b9b9ba;color:var(--lightText, #b9b9ba);opacity:.8\n}\n.user-info .name-and-screen-name{display:block;margin-left:0.6em;text-align:left;text-overflow:ellipsis;white-space:nowrap;-ms-flex:1 1 0px;flex:1 1 0;z-index:1\n}\n.user-info .name-and-screen-name img{width:26px;height:26px;vertical-align:middle;object-fit:contain\n}\n.user-info .name-and-screen-name .top-line{display:-ms-flexbox;display:flex\n}\n.user-info .user-name{text-overflow:ellipsis;overflow:hidden;-ms-flex:1 1 auto;flex:1 1 auto;margin-right:1em;font-size:15px\n}\n.user-info .user-name img{object-fit:contain;height:16px;width:16px;vertical-align:middle\n}\n.user-info .user-screen-name{color:#b9b9ba;color:var(--lightText, #b9b9ba);display:inline-block;font-weight:light;font-size:15px;padding-right:0.1em;width:100%;display:-ms-flexbox;display:flex\n}\n.user-info .user-screen-name .dailyAvg{min-width:1px;-ms-flex:0 0 auto;flex:0 0 auto;margin-left:1em;font-size:0.7em;color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.user-info .user-screen-name .handle{min-width:1px;-ms-flex:0 1 auto;flex:0 1 auto;text-overflow:ellipsis;overflow:hidden\n}\n.user-info .user-screen-name .staff{text-transform:capitalize;color:#b9b9ba;color:var(--btnText, #b9b9ba);background-color:#182230;background-color:var(--btn, #182230)\n}\n.user-info .user-meta{margin-bottom:.15em;display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline;font-size:14px;line-height:22px;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.user-info .user-meta .following{-ms-flex:1 0 auto;flex:1 0 auto;margin:0;margin-bottom:.25em;text-align:left\n}\n.user-info .user-meta .highlighter{-ms-flex:0 1 auto;flex:0 1 auto;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-.5em;-ms-flex-item-align:start;align-self:start\n}\n.user-info .user-meta .highlighter .userHighlightCl{padding:2px 10px;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.user-info .user-meta .highlighter .userHighlightSel,.user-info .user-meta .highlighter .userHighlightSel.select{padding-top:0;padding-bottom:0;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.user-info .user-meta .highlighter .userHighlightSel.select i{line-height:22px\n}\n.user-info .user-meta .highlighter .userHighlightText{width:70px;-ms-flex:1 0 auto;flex:1 0 auto\n}\n.user-info .user-meta .highlighter .userHighlightCl,.user-info .user-meta .highlighter .userHighlightText,.user-info .user-meta .highlighter .userHighlightSel,.user-info .user-meta .highlighter .userHighlightSel.select{height:22px;vertical-align:top;margin-right:.5em;margin-bottom:.25em\n}\n.user-info .user-interactions{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-pack:justify;justify-content:space-between;margin-right:-.75em\n}\n.user-info .user-interactions div{-ms-flex:1 0 0px;flex:1 0 0;margin-right:.75em;margin-bottom:.6em;white-space:nowrap\n}\n.user-info .user-interactions .mute{max-width:220px;min-height:28px\n}\n.user-info .user-interactions .remote-follow{max-width:220px;min-height:28px\n}\n.user-info .user-interactions .follow{max-width:220px;min-height:28px\n}\n.user-info .user-interactions button{width:100%;height:100%;margin:0\n}\n.user-info .user-interactions .remote-button{height:28px !important;width:92%\n}\n.user-info .user-interactions .pressed{border-bottom-color:rgba(255,255,255,0.2);border-top-color:rgba(0,0,0,0.2)\n}\n.user-counts{display:-ms-flexbox;display:flex;line-height:16px;padding:.5em 1.5em 0em 1.5em;text-align:center;-ms-flex-pack:justify;justify-content:space-between;color:#b9b9ba;color:var(--lightText, #b9b9ba);-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.user-count{-ms-flex:1 0 auto;flex:1 0 auto;padding:.5em 0 .5em 0;margin:0 .5em\n}\n.user-count h5{font-size:1em;font-weight:bolder;margin:0 0 0.25em\n}\n.user-count a{text-decoration:none\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_card/user_card.vue","\n.avatar.still-image{width:48px;height:48px;box-shadow:var(--avatarStatusShadow);border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.avatar.still-image img{width:100%;height:100%\n}\n.avatar.still-image.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.avatar.still-image.animated::before{display:none\n}\n.avatar.still-image.avatar-compact{width:32px;height:32px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_avatar/user_avatar.vue","\n.gallery-row{height:200px;width:100%;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-line-pack:stretch;align-content:stretch;-ms-flex-positive:1;flex-grow:1;margin-top:0.5em\n}\n.gallery-row .attachments,.gallery-row .attachment{margin:0 0.5em 0 0;-ms-flex-positive:1;flex-grow:1;height:100%;box-sizing:border-box;min-width:2em\n}\n.gallery-row .attachments:last-child,.gallery-row .attachment:last-child{margin:0\n}\n.gallery-row .image-attachment{width:100%;height:100%\n}\n.gallery-row .video-container{height:100%\n}\n.gallery-row.contain-fit img,.gallery-row.contain-fit video{object-fit:contain\n}\n.gallery-row.cover-fit img,.gallery-row.cover-fit video{object-fit:cover\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/gallery/gallery.vue","\n.link-preview-card{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;cursor:pointer;overflow:hidden;margin-top:0.5em;color:#b9b9ba;color:var(--text, #b9b9ba);border-style:solid;border-width:1px;border-radius:10px;border-radius:var(--attachmentRadius, 10px);border-color:#222;border-color:var(--border, #222)\n}\n.link-preview-card .card-image{-ms-flex-negative:0;flex-shrink:0;width:120px;max-width:25%\n}\n.link-preview-card .card-image img{width:100%;height:100%;object-fit:cover;border-radius:10px;border-radius:var(--attachmentRadius, 10px)\n}\n.link-preview-card .small-image{width:80px\n}\n.link-preview-card .card-content{max-height:100%;margin:0.5em;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column\n}\n.link-preview-card .card-host{font-size:12px\n}\n.link-preview-card .card-description{margin:0.5em 0 0 0;overflow:hidden;text-overflow:ellipsis;word-break:break-word;line-height:1.2em;max-height:calc(1.2em * 3 - 1px)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/link-preview/link-preview.vue","\n.spacer{height:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/status_or_conversation/status_or_conversation.vue","\n.user-profile{-ms-flex:2;flex:2;-ms-flex-preferred-size:500px;flex-basis:500px\n}\n.user-profile .userlist-placeholder{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:middle;align-items:middle;padding:2em\n}\n.user-profile .timeline-heading{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center\n}\n.user-profile .timeline-heading .loadmore-button,.user-profile .timeline-heading .alert{-ms-flex:1;flex:1\n}\n.user-profile .timeline-heading .loadmore-button{height:28px;margin:10px .6em\n}\n.user-profile .timeline-heading .title,.user-profile .timeline-heading .loadmore-text{display:none\n}\n.user-profile-placeholder .panel-body{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:middle;align-items:middle;padding:7em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_profile/user_profile.vue","\n.follow-card-content-container{-ms-flex-negative:0;flex-shrink:0;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:1.5em\n}\n.follow-card-content-container .btn{margin-top:0.5em;margin-left:auto;width:10em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/follow_card/follow_card.vue","\n.basic-user-card{display:-ms-flexbox;display:flex;-ms-flex:1 0;flex:1 0;margin:0;padding-top:0.6em;padding-right:1em;padding-bottom:0.6em;padding-left:1em;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)\n}\n.basic-user-card-collapsed-content{margin-left:0.7em;text-align:left;-ms-flex:1;flex:1;min-width:0\n}\n.basic-user-card-user-name img{object-fit:contain;height:16px;width:16px;vertical-align:middle\n}\n.basic-user-card-expanded-content{-ms-flex:1;flex:1;margin-left:0.7em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/basic_user_card/basic_user_card.vue",".with-load-more {\n &-footer {\n padding: 10px;\n text-align: center;\n\n .error {\n font-size: 14px;\n }\n }\n}\n\n\n// WEBPACK FOOTER //\n// webpack:///src/hocs/with_load_more/src/hocs/with_load_more/with_load_more.scss",".with-list {\n &-empty-content {\n text-align: center;\n padding: 10px;\n }\n}\n\n\n// WEBPACK FOOTER //\n// webpack:///src/hocs/with_list/src/hocs/with_list/with_list.scss","\n.setting-item{border-bottom:2px solid var(--fg, #182230);margin:1em 1em 1.4em;padding-bottom:1.4em\n}\n.setting-item>div{margin-bottom:.5em\n}\n.setting-item>div:last-child{margin-bottom:0\n}\n.setting-item:last-child{border-bottom:none;padding-bottom:0;margin-bottom:1em\n}\n.setting-item select{min-width:10em\n}\n.setting-item textarea{width:100%;height:100px\n}\n.setting-item .unavailable,.setting-item .unavailable i{color:var(--cRed, red);color:red\n}\n.setting-item .btn{min-height:28px;min-width:10em;padding:0 2em\n}\n.setting-item .number-input{max-width:6em\n}\n.select-multiple{display:-ms-flexbox;display:flex\n}\n.select-multiple .option-list{margin:0;padding-left:.5em\n}\n.setting-list,.option-list{list-style-type:none;padding-left:2em\n}\n.setting-list li,.option-list li{margin-bottom:0.5em\n}\n.setting-list .suboptions,.option-list .suboptions{margin-top:0.3em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/settings/settings.vue","@import '../../_variables.scss';\n\n.tab-switcher {\n .contents {\n .hidden {\n display: none;\n }\n }\n .tabs {\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n\n &::after, &::before {\n display: block;\n content: '';\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n }\n\n .tab-wrapper {\n height: 28px;\n position: relative;\n display: flex;\n flex: 0 0 auto;\n\n .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: 6px - 99px;\n white-space: nowrap;\n\n &:not(.active) {\n z-index: 4;\n\n &:hover {\n z-index: 6;\n }\n }\n\n &.active {\n background: transparent;\n z-index: 5;\n }\n }\n\n &:not(.active) {\n &::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n }\n }\n }\n\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/tab_switcher/src/components/tab_switcher/tab_switcher.scss","\n.style-switcher .preset-switcher{margin-right:1em\n}\n.style-switcher .style-control{display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline;margin-bottom:5px\n}\n.style-switcher .style-control .label{-ms-flex:1;flex:1\n}\n.style-switcher .style-control.disabled input:not(.exclude-disabled),.style-switcher .style-control.disabled select:not(.exclude-disabled){opacity:.5\n}\n.style-switcher .style-control input,.style-switcher .style-control select{min-width:3em;margin:0;-ms-flex:0;flex:0\n}\n.style-switcher .style-control input[type=color],.style-switcher .style-control select[type=color]{padding:1px;cursor:pointer;height:29px;min-width:2em;border:none;-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch\n}\n.style-switcher .style-control input[type=number],.style-switcher .style-control select[type=number]{min-width:5em\n}\n.style-switcher .style-control input[type=range],.style-switcher .style-control select[type=range]{-ms-flex:1;flex:1;min-width:3em\n}\n.style-switcher .style-control input[type=checkbox]+label,.style-switcher .style-control select[type=checkbox]+label{margin:6px 0\n}\n.style-switcher .style-control input:not([type=number]):not([type=text]),.style-switcher .style-control select:not([type=number]):not([type=text]){-ms-flex-item-align:start;align-self:flex-start\n}\n.style-switcher .tab-switcher{margin:0 -1em\n}\n.style-switcher .reset-container{-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.style-switcher .fonts-container,.style-switcher .reset-container,.style-switcher .apply-container,.style-switcher .radius-container,.style-switcher .color-container{display:-ms-flexbox;display:flex\n}\n.style-switcher .fonts-container,.style-switcher .radius-container{-ms-flex-direction:column;flex-direction:column\n}\n.style-switcher .color-container{-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between\n}\n.style-switcher .color-container>h4{width:99%\n}\n.style-switcher .fonts-container,.style-switcher .color-container,.style-switcher .shadow-container,.style-switcher .radius-container,.style-switcher .presets-container{margin:1em 1em 0\n}\n.style-switcher .tab-header{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:baseline;align-items:baseline;width:100%;min-height:30px;margin-bottom:1em\n}\n.style-switcher .tab-header .btn{min-width:1px;-ms-flex:0 auto;flex:0 auto;padding:0 1em\n}\n.style-switcher .tab-header p{-ms-flex:1;flex:1;margin:0;margin-right:.5em\n}\n.style-switcher .shadow-selector .override{-ms-flex:1;flex:1;margin-left:.5em\n}\n.style-switcher .shadow-selector .select-container{margin-top:-4px;margin-bottom:-3px\n}\n.style-switcher .save-load,.style-switcher .save-load-options{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:baseline;align-items:baseline;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.style-switcher .save-load .presets,.style-switcher .save-load .import-export,.style-switcher .save-load-options .presets,.style-switcher .save-load-options .import-export{margin-bottom:.5em\n}\n.style-switcher .save-load .import-export,.style-switcher .save-load-options .import-export{display:-ms-flexbox;display:flex\n}\n.style-switcher .save-load .override,.style-switcher .save-load-options .override{margin-left:.5em\n}\n.style-switcher .save-load-options{-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:.5em;-ms-flex-pack:center;justify-content:center\n}\n.style-switcher .save-load-options .keep-option{margin:0 .5em .5em;min-width:25%\n}\n.style-switcher .preview-container{border-top:1px dashed;border-bottom:1px dashed;border-color:#222;border-color:var(--border, #222);margin:1em -1em 0;padding:1em;background:var(--body-background-image);background-size:cover;background-position:50% 50%\n}\n.style-switcher .preview-container .dummy .post{font-family:var(--postFont);display:-ms-flexbox;display:flex\n}\n.style-switcher .preview-container .dummy .post .content{-ms-flex:1;flex:1\n}\n.style-switcher .preview-container .dummy .post .content h4{margin-bottom:.25em\n}\n.style-switcher .preview-container .dummy .post .content .icons{margin-top:.5em;display:-ms-flexbox;display:flex\n}\n.style-switcher .preview-container .dummy .post .content .icons i{margin-right:1em\n}\n.style-switcher .preview-container .dummy .after-post{margin-top:1em;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center\n}\n.style-switcher .preview-container .dummy .avatar,.style-switcher .preview-container .dummy .avatar-alt{background:linear-gradient(135deg, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);color:black;font-family:sans-serif;text-align:center;margin-right:1em\n}\n.style-switcher .preview-container .dummy .avatar-alt{-ms-flex:0 auto;flex:0 auto;margin-left:28px;font-size:12px;min-width:20px;min-height:20px;line-height:20px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.style-switcher .preview-container .dummy .avatar{-ms-flex:0 auto;flex:0 auto;width:48px;height:48px;font-size:14px;line-height:48px\n}\n.style-switcher .preview-container .dummy .actions{display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline\n}\n.style-switcher .preview-container .dummy .actions .checkbox{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:baseline;align-items:baseline;margin-right:1em;-ms-flex:1;flex:1\n}\n.style-switcher .preview-container .dummy .separator{margin:1em;border-bottom:1px solid;border-color:#222;border-color:var(--border, #222)\n}\n.style-switcher .preview-container .dummy .panel-heading .badge,.style-switcher .preview-container .dummy .panel-heading .alert,.style-switcher .preview-container .dummy .panel-heading .btn,.style-switcher .preview-container .dummy .panel-heading .faint{margin-left:1em;white-space:nowrap\n}\n.style-switcher .preview-container .dummy .panel-heading .faint{text-overflow:ellipsis;min-width:2em;overflow-x:hidden\n}\n.style-switcher .preview-container .dummy .panel-heading .flex-spacer{-ms-flex:1;flex:1\n}\n.style-switcher .preview-container .dummy .btn{margin-left:0;padding:0 1em;min-width:3em;min-height:30px\n}\n.style-switcher .apply-container{-ms-flex-pack:center;justify-content:center\n}\n.style-switcher .radius-item,.style-switcher .color-item{min-width:20em;margin:5px 6px 0 0;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex:1 1 0px;flex:1 1 0\n}\n.style-switcher .radius-item.wide,.style-switcher .color-item.wide{min-width:60%\n}\n.style-switcher .radius-item:not(.wide):nth-child(2n+1),.style-switcher .color-item:not(.wide):nth-child(2n+1){margin-right:7px\n}\n.style-switcher .radius-item .color,.style-switcher .radius-item .opacity,.style-switcher .color-item .color,.style-switcher .color-item .opacity{display:-ms-flexbox;display:flex;-ms-flex-align:baseline;align-items:baseline\n}\n.style-switcher .radius-item{-ms-flex-preferred-size:auto;flex-basis:auto\n}\n.style-switcher .theme-radius-rn,.style-switcher .theme-color-cl{border:0;box-shadow:none;background:transparent;color:var(--faint, rgba(185,185,186,0.5));-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch\n}\n.style-switcher .theme-color-cl,.style-switcher .theme-radius-in,.style-switcher .theme-color-in{margin-left:4px\n}\n.style-switcher .theme-radius-in{min-width:1em\n}\n.style-switcher .theme-radius-in{max-width:7em;-ms-flex:1;flex:1\n}\n.style-switcher .theme-radius-lb{max-width:50em\n}\n.style-switcher .theme-preview-content{padding:20px\n}\n.style-switcher .btn{margin-left:.25em;margin-right:.25em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/style_switcher/style_switcher.scss","\n.color-control input.text-input{max-width:7em;-ms-flex:1;flex:1\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/color_input/color_input.vue","\n.shadow-control{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:center;justify-content:center;margin-bottom:1em\n}\n.shadow-control .shadow-preview-container,.shadow-control .shadow-tweak{margin:5px 6px 0 0\n}\n.shadow-control .shadow-preview-container{-ms-flex:0;flex:0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.shadow-control .shadow-preview-container input[type=number]{width:5em;min-width:2em\n}\n.shadow-control .shadow-preview-container .x-shift-control,.shadow-control .shadow-preview-container .y-shift-control{display:-ms-flexbox;display:flex;-ms-flex:0;flex:0\n}\n.shadow-control .shadow-preview-container .x-shift-control[disabled=disabled] *,.shadow-control .shadow-preview-container .y-shift-control[disabled=disabled] *{opacity:.5\n}\n.shadow-control .shadow-preview-container .x-shift-control{-ms-flex-align:start;align-items:flex-start\n}\n.shadow-control .shadow-preview-container .x-shift-control .wrap,.shadow-control .shadow-preview-container input[type=range]{margin:0;width:15em;height:2em\n}\n.shadow-control .shadow-preview-container .y-shift-control{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:end;align-items:flex-end\n}\n.shadow-control .shadow-preview-container .y-shift-control .wrap{width:2em;height:15em\n}\n.shadow-control .shadow-preview-container .y-shift-control input[type=range]{transform-origin:1em 1em;transform:rotate(90deg)\n}\n.shadow-control .shadow-preview-container .preview-window{-ms-flex:1;flex:1;background-color:#999999;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;background-image:linear-gradient(45deg, #666 25%, transparent 25%),linear-gradient(-45deg, #666 25%, transparent 25%),linear-gradient(45deg, transparent 75%, #666 75%),linear-gradient(-45deg, transparent 75%, #666 75%);background-size:20px 20px;background-position:0 0, 0 10px, 10px -10px, -10px 0;border-radius:4px;border-radius:var(--inputRadius, 4px)\n}\n.shadow-control .shadow-preview-container .preview-window .preview-block{width:33%;height:33%;background-color:#121a24;background-color:var(--bg, #121a24);border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.shadow-control .shadow-tweak{-ms-flex:1;flex:1;min-width:280px\n}\n.shadow-control .shadow-tweak .id-control{-ms-flex-align:stretch;align-items:stretch\n}\n.shadow-control .shadow-tweak .id-control .select,.shadow-control .shadow-tweak .id-control .btn{min-width:1px;margin-right:5px\n}\n.shadow-control .shadow-tweak .id-control .btn{padding:0 .4em;margin:0 .1em\n}\n.shadow-control .shadow-tweak .id-control .select{-ms-flex:1;flex:1\n}\n.shadow-control .shadow-tweak .id-control .select select{-ms-flex-item-align:initial;-ms-grid-row-align:initial;align-self:initial\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/shadow_control/shadow_control.vue","\n.font-control input.custom-font{min-width:10em\n}\n.font-control.custom .select{border-top-right-radius:0;border-bottom-right-radius:0\n}\n.font-control.custom .custom-font{border-top-left-radius:0;border-bottom-left-radius:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/font_control/font_control.vue","\n.contrast-ratio{display:-ms-flexbox;display:flex;-ms-flex-pack:end;justify-content:flex-end;margin-top:-4px;margin-bottom:5px\n}\n.contrast-ratio .label{margin-right:1em\n}\n.contrast-ratio .rating{display:inline-block;text-align:center\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/contrast_ratio/contrast_ratio.vue","\n.import-export-container{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:baseline;align-items:baseline;-ms-flex-pack:center;justify-content:center\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/export_import/export_import.vue","\n.registration-form{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;margin:0.6em\n}\n.registration-form .container{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row\n}\n.registration-form .terms-of-service{-ms-flex:0 1 50%;flex:0 1 50%;margin:0.8em\n}\n.registration-form .text-fields{margin-top:0.6em;-ms-flex:1 0;flex:1 0;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column\n}\n.registration-form textarea{min-height:100px\n}\n.registration-form .form-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:0.3em 0.0em 0.3em;line-height:24px;margin-bottom:1em\n}\n.registration-form .form-group--error{animation-name:shakeError;animation-duration:.6s;animation-timing-function:ease-in-out\n}\n.registration-form .form-group--error .form--label{color:#f04124;color:var(--cRed, #f04124)\n}\n.registration-form .form-error{margin-top:-0.7em;text-align:left\n}\n.registration-form .form-error span{font-size:12px\n}\n.registration-form .form-error ul{list-style:none;padding:0 0 0 5px;margin-top:0\n}\n.registration-form .form-error ul li::before{content:\"• \"\n}\n.registration-form form textarea{line-height:16px;resize:vertical\n}\n.registration-form .captcha{max-width:350px;margin-bottom:0.4em\n}\n.registration-form .btn{margin-top:0.6em;height:28px\n}\n.registration-form .error{text-align:center\n}\n@media all and (max-width: 800px){\n.registration-form .container{-ms-flex-direction:column-reverse;flex-direction:column-reverse\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/registration/registration.vue","\n.profile-edit .bio{margin:0\n}\n.profile-edit input[type=file]{padding:5px;height:auto\n}\n.profile-edit .banner{max-width:100%\n}\n.profile-edit .uploading{font-size:1.5em;margin:0.25em\n}\n.profile-edit .name-changer{width:100%\n}\n.profile-edit .bg{max-width:100%\n}\n.profile-edit .current-avatar{display:block;width:150px;height:150px;border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.profile-edit .oauth-tokens{width:100%\n}\n.profile-edit .oauth-tokens th{text-align:left\n}\n.profile-edit .oauth-tokens .actions{text-align:right\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_settings/user_settings.vue","\n.image-cropper-img-input{display:none\n}\n.image-cropper-image-container{position:relative\n}\n.image-cropper-image-container img{display:block;max-width:100%\n}\n.image-cropper-buttons-wrapper{margin-top:15px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/image_cropper/image_cropper.vue","/*!\n * Cropper.js v1.4.3\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2018-10-24T13:07:11.429Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: .5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline-color: rgba(51, 153, 255, 0.75);\n outline: 1px solid #39f;\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: .5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: .75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center:before,\n.cropper-center:after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center:before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center:after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: .1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: .75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: .75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se:before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///~/cropperjs/dist/cropper.css","\n.block-card-content-container{margin-top:0.5em;text-align:right\n}\n.block-card-content-container button{width:10em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/block_card/block_card.vue",".with-subscription {\n &-loading {\n padding: 10px;\n text-align: center;\n\n .error {\n font-size: 14px;\n }\n }\n}\n\n\n// WEBPACK FOOTER //\n// webpack:///src/hocs/with_subscription/src/hocs/with_subscription/with_subscription.scss","\n.follow-request-card-content-container{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.follow-request-card-content-container button{margin-top:0.5em;margin-right:0.5em;-ms-flex:1 1;flex:1 1;max-width:12em;min-width:8em\n}\n.follow-request-card-content-container button:last-child{margin-right:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/follow_request_card/follow_request_card.vue","\n.user-search-input-container{margin:0.5em;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center\n}\n.user-search-input-container .search-button{margin-left:0.5em\n}\n.loading-icon{padding:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_search/user_search.vue","\n.notifications{padding-bottom:15em\n}\n.notifications .loadmore-error{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.notifications .notification{position:relative\n}\n.notifications .notification .notification-overlay{position:absolute;top:0;right:0;left:0;bottom:0;pointer-events:none\n}\n.notifications .notification.unseen .notification-overlay{background-image:linear-gradient(135deg, var(--badgeNotification, red) 4px, transparent 10px)\n}\n.notification{box-sizing:border-box;display:-ms-flexbox;display:flex;border-bottom:1px solid;border-color:#222;border-color:var(--border, #222)\n}\n.notification:hover .animated.avatar canvas{display:none\n}\n.notification:hover .animated.avatar img{visibility:visible\n}\n.notification .non-mention{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:0.6em;min-width:0\n}\n.notification .non-mention .avatar-container{width:32px;height:32px\n}\n.notification .non-mention .status-el{padding:0\n}\n.notification .non-mention .status-el .status{padding:0.25em 0;color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.notification .non-mention .status-el .status a{color:var(--faintLink)\n}\n.notification .non-mention .status-el .media-body{margin:0\n}\n.notification .follow-text{padding:0.5em 0\n}\n.notification .status-el{-ms-flex:1;flex:1\n}\n.notification time{white-space:nowrap\n}\n.notification .notification-right{-ms-flex:1;flex:1;padding-left:0.8em;min-width:0\n}\n.notification .notification-details{min-width:0px;word-wrap:break-word;line-height:18px;position:relative;overflow:hidden;width:100%;-ms-flex:1 1 0px;flex:1 1 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:justify;justify-content:space-between\n}\n.notification .notification-details .name-and-action{-ms-flex:1;flex:1;overflow:hidden;text-overflow:ellipsis\n}\n.notification .notification-details .username{font-weight:bolder;max-width:100%;text-overflow:ellipsis;white-space:nowrap\n}\n.notification .notification-details .username img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.notification .notification-details .timeago{margin-right:.2em\n}\n.notification .notification-details .icon-retweet.lit{color:#0fa00f;color:var(--cGreen, #0fa00f)\n}\n.notification .notification-details .icon-user-plus.lit{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.notification .notification-details .icon-reply.lit{color:#0095ff;color:var(--cBlue, #0095ff)\n}\n.notification .notification-details .icon-star.lit{color:orange;color:orange;color:var(--cOrange, orange)\n}\n.notification .notification-details .status-content{margin:0;max-height:300px\n}\n.notification .notification-details h1{word-break:break-all;margin:0 0 0.3em;padding:0;font-size:1em;line-height:20px\n}\n.notification .notification-details h1 small{font-weight:lighter\n}\n.notification .notification-details p{margin:0;margin-top:0;margin-bottom:0.3em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/notifications/notifications.scss","\n.login-form .btn{min-height:28px;width:10em\n}\n.login-form .register{-ms-flex:1 1;flex:1 1\n}\n.login-form .login-bottom{margin-top:1.0em;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between\n}\n.login .error{text-align:center;animation-name:shakeError;animation-duration:0.4s;animation-timing-function:ease-in-out\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/login_form/login_form.vue","\n.floating-chat{position:fixed;right:0px;bottom:0px;z-index:1000;max-width:25em\n}\n.chat-heading{cursor:pointer\n}\n.chat-heading .icon-comment-empty{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.chat-window{overflow-y:auto;overflow-x:hidden;max-height:20em\n}\n.chat-window-container{height:100%\n}\n.chat-message{display:-ms-flexbox;display:flex;padding:0.2em 0.5em\n}\n.chat-avatar img{height:24px;width:24px;border-radius:4px;border-radius:var(--avatarRadius, 4px);margin-right:0.5em;margin-top:0.25em\n}\n.chat-input{display:-ms-flexbox;display:flex\n}\n.chat-input textarea{-ms-flex:1;flex:1;margin:0.6em;min-height:3.5em;resize:none\n}\n.chat-panel .title{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/chat_panel/chat_panel.vue","\n.features-panel li{line-height:24px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/features_panel/features_panel.vue","\n.tos-content{margin:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/terms_of_service_panel/terms_of_service_panel.vue","\n#app{min-height:100vh;max-width:100%;overflow:hidden\n}\n.app-bg-wrapper{position:fixed;z-index:-1;height:100%;width:100%;background-size:cover;background-repeat:no-repeat;background-position:0 50%\n}\ni{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none\n}\nh4{margin:0\n}\n#content{box-sizing:border-box;padding-top:60px;margin:auto;min-height:100vh;max-width:980px;background-color:rgba(0,0,0,0.15);-ms-flex-line-pack:start;align-content:flex-start\n}\n.text-center{text-align:center\n}\nbody{font-family:sans-serif;font-family:var(--interfaceFont, sans-serif);font-size:14px;margin:0;color:#b9b9ba;color:var(--text, #b9b9ba);max-width:100vw;overflow-x:hidden\n}\na{text-decoration:none;color:#d8a070;color:var(--link, #d8a070)\n}\nbutton{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#b9b9ba;color:var(--btnText, #b9b9ba);background-color:#182230;background-color:var(--btn, #182230);border:none;border-radius:4px;border-radius:var(--btnRadius, 4px);cursor:pointer;box-shadow:0px 0px 2px 0px #000,0px 1px 0px 0px rgba(255,255,255,0.2) inset,0px -1px 0px 0px rgba(0,0,0,0.2) inset;box-shadow:var(--buttonShadow);font-size:14px;font-family:sans-serif;font-family:var(--interfaceFont, sans-serif)\n}\nbutton i[class*=icon-]{color:#b9b9ba;color:var(--btnText, #b9b9ba)\n}\nbutton::-moz-focus-inner{border:none\n}\nbutton:hover{box-shadow:0px 0px 4px rgba(255,255,255,0.3);box-shadow:var(--buttonHoverShadow)\n}\nbutton:active{box-shadow:0px 0px 4px 0px rgba(255,255,255,0.3),0px 1px 0px 0px rgba(0,0,0,0.2) inset,0px -1px 0px 0px rgba(255,255,255,0.2) inset;box-shadow:var(--buttonPressedShadow)\n}\nbutton:disabled{cursor:not-allowed;opacity:0.5\n}\nbutton.pressed{color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5));background-color:#121a24;background-color:var(--bg, #121a24)\n}\nlabel.select{padding:0\n}\ninput,textarea,.select{border:none;border-radius:4px;border-radius:var(--inputRadius, 4px);box-shadow:0px 1px 0px 0px rgba(0,0,0,0.2) inset,0px -1px 0px 0px rgba(255,255,255,0.2) inset,0px 0px 2px 0px #000 inset;box-shadow:var(--inputShadow);background-color:#182230;background-color:var(--input, #182230);color:#b9b9ba;color:var(--inputText, #b9b9ba);font-family:sans-serif;font-family:var(--inputFont, sans-serif);font-size:14px;padding:8px .5em;box-sizing:border-box;display:inline-block;position:relative;height:28px;line-height:16px;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none\n}\ninput:disabled,input[disabled=disabled],textarea:disabled,textarea[disabled=disabled],.select:disabled,.select[disabled=disabled]{cursor:not-allowed;opacity:0.5\n}\ninput .icon-down-open,textarea .icon-down-open,.select .icon-down-open{position:absolute;top:0;bottom:0;right:5px;height:100%;color:#b9b9ba;color:var(--text, #b9b9ba);line-height:28px;z-index:0;pointer-events:none\n}\ninput select,textarea select,.select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:none;color:#b9b9ba;color:var(--text, #b9b9ba);margin:0;padding:0 2em 0 .2em;font-family:sans-serif;font-family:var(--inputFont, sans-serif);font-size:14px;width:100%;z-index:1;height:28px;line-height:16px\n}\ninput[type=range],textarea[type=range],.select[type=range]{background:none;border:none;margin:0;box-shadow:none;-ms-flex:1;flex:1\n}\ninput[type=radio],input[type=checkbox],textarea[type=radio],textarea[type=checkbox],.select[type=radio],.select[type=checkbox]{display:none\n}\ninput[type=radio]:checked+label::before,input[type=checkbox]:checked+label::before,textarea[type=radio]:checked+label::before,textarea[type=checkbox]:checked+label::before,.select[type=radio]:checked+label::before,.select[type=checkbox]:checked+label::before{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\ninput[type=radio]:disabled,input[type=radio]:disabled+label,input[type=radio]:disabled+label::before,input[type=checkbox]:disabled,input[type=checkbox]:disabled+label,input[type=checkbox]:disabled+label::before,textarea[type=radio]:disabled,textarea[type=radio]:disabled+label,textarea[type=radio]:disabled+label::before,textarea[type=checkbox]:disabled,textarea[type=checkbox]:disabled+label,textarea[type=checkbox]:disabled+label::before,.select[type=radio]:disabled,.select[type=radio]:disabled+label,.select[type=radio]:disabled+label::before,.select[type=checkbox]:disabled,.select[type=checkbox]:disabled+label,.select[type=checkbox]:disabled+label::before{opacity:.5\n}\ninput[type=radio]+label::before,input[type=checkbox]+label::before,textarea[type=radio]+label::before,textarea[type=checkbox]+label::before,.select[type=radio]+label::before,.select[type=checkbox]+label::before{display:inline-block;content:'✔';transition:color 200ms;width:1.1em;height:1.1em;border-radius:2px;border-radius:var(--checkboxRadius, 2px);box-shadow:0px 0px 2px black inset;box-shadow:var(--inputShadow);margin-right:.5em;background-color:#182230;background-color:var(--input, #182230);vertical-align:top;text-align:center;line-height:1.1em;font-size:1.1em;box-sizing:border-box;color:transparent;overflow:hidden;box-sizing:border-box\n}\noption{color:#b9b9ba;color:var(--text, #b9b9ba);background-color:#121a24;background-color:var(--bg, #121a24)\n}\ni[class*=icon-]{color:#666;color:var(--icon, #666)\n}\n.container{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0;padding:0 10px 0 10px\n}\n.item{-ms-flex:1;flex:1;line-height:50px;height:50px;overflow:hidden;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap\n}\n.item .nav-icon{margin-left:0.4em\n}\n.item.right{-ms-flex-pack:end;justify-content:flex-end\n}\n.auto-size{-ms-flex:1;flex:1\n}\n.nav-bar{padding:0;width:100%;-ms-flex-align:center;align-items:center;position:fixed;height:50px\n}\n.nav-bar .logo{display:-ms-flexbox;display:flex;position:absolute;top:0;bottom:0;left:0;right:0;-ms-flex-align:stretch;align-items:stretch;-ms-flex-pack:center;justify-content:center;-ms-flex:0 0 auto;flex:0 0 auto;z-index:-1;transition:opacity;transition-timing-function:ease-out;transition-duration:100ms\n}\n.nav-bar .logo .mask{-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-size:contain;mask-size:contain;background-color:#182230;background-color:var(--topBarText, #182230);position:absolute;top:0;bottom:0;left:0;right:0\n}\n.nav-bar .logo img{height:100%;object-fit:contain;display:block;-ms-flex:0;flex:0\n}\n.nav-bar .inner-nav{margin:auto;box-sizing:border-box;padding-left:10px;padding-right:10px;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-preferred-size:970px;flex-basis:970px;height:50px\n}\n.nav-bar .inner-nav a,.nav-bar .inner-nav a i{color:#d8a070;color:var(--topBarLink, #d8a070)\n}\nmain-router{-ms-flex:1;flex:1\n}\n.status.compact{color:rgba(0,0,0,0.42);font-weight:300\n}\n.status.compact p{margin:0;font-size:0.8em\n}\n.panel{display:-ms-flexbox;display:flex;position:relative;-ms-flex-direction:column;flex-direction:column;margin:0.5em;background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.panel::after,.panel{border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.panel::after{content:'';position:absolute;top:0;bottom:0;left:0;right:0;pointer-events:none;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow)\n}\n.panel-body:empty::before{content:\"¯\\\\_(ツ)_/¯\";display:block;margin:1em;text-align:center\n}\n.panel-heading{display:-ms-flexbox;display:flex;border-radius:10px 10px 0 0;border-radius:var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;background-size:cover;padding:.6em .6em;text-align:left;line-height:28px;color:var(--panelText);background-color:#182230;background-color:var(--panel, #182230);-ms-flex-align:baseline;align-items:baseline;box-shadow:var(--panelHeaderShadow)\n}\n.panel-heading .title{-ms-flex:1 0 auto;flex:1 0 auto;font-size:1.3em\n}\n.panel-heading .faint{background-color:transparent;color:rgba(185,185,186,0.5);color:var(--panelFaint, rgba(185,185,186,0.5))\n}\n.panel-heading .alert{white-space:nowrap;text-overflow:ellipsis;overflow-x:hidden\n}\n.panel-heading button{-ms-flex-negative:0;flex-shrink:0\n}\n.panel-heading button,.panel-heading .alert{line-height:21px;min-height:0;box-sizing:border-box;margin:0;margin-left:.25em;min-width:1px;-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch\n}\n.panel-heading a{color:#d8a070;color:var(--panelLink, #d8a070)\n}\n.panel-heading.stub{border-radius:10px;border-radius:var(--panelRadius, 10px)\n}\n.panel-footer{border-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius, 10px) var(--panelRadius, 10px)\n}\n.panel-footer .faint{color:rgba(185,185,186,0.5);color:var(--panelFaint, rgba(185,185,186,0.5))\n}\n.panel-footer a{color:#d8a070;color:var(--panelLink, #d8a070)\n}\n.panel-body>p{line-height:18px;padding:1em;margin:0\n}\n.container>*{min-width:0px\n}\n.fa{color:grey\n}\nnav{z-index:1000;color:var(--topBarText);background-color:#182230;background-color:var(--topBar, #182230);color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5));box-shadow:0px 0px 4px rgba(0,0,0,0.6);box-shadow:var(--topBarShadow)\n}\nnav .back-button{display:block;max-width:99px;transition-property:opacity, max-width;transition-duration:300ms;transition-timing-function:ease-out\n}\nnav .back-button i{margin:0 1em\n}\nnav .back-button.hidden{opacity:0;max-width:5px\n}\n.menu-button{display:none;position:relative\n}\n.alert-dot{border-radius:100%;height:8px;width:8px;position:absolute;left:calc(50% - 4px);top:calc(50% - 4px);margin-left:6px;margin-top:-6px;background-color:red;background-color:var(--badgeNotification, red)\n}\n.fade-enter-active,.fade-leave-active{transition:opacity .2s\n}\n.fade-enter,.fade-leave-active{opacity:0\n}\n.main{-ms-flex-preferred-size:50%;flex-basis:50%;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:1;flex-shrink:1\n}\n.sidebar-bounds{-ms-flex:0;flex:0;-ms-flex-preferred-size:35%;flex-basis:35%\n}\n.sidebar-flexer{-ms-flex:1;flex:1;-ms-flex-preferred-size:345px;flex-basis:345px;width:365px\n}\n.mobile-shown{display:none\n}\n.panel-switcher{display:none;width:100%;height:46px\n}\n.panel-switcher button{display:block;-ms-flex:1;flex:1;max-height:32px;margin:0.5em;padding:0.5em\n}\n@media all and (min-width: 800px){\nbody{overflow-y:scroll\n}\nnav .back-button{display:none\n}\n.sidebar-bounds{overflow:hidden;max-height:100vh;width:345px;position:fixed;margin-top:-10px\n}\n.sidebar-bounds .sidebar-scroller{height:96vh;width:365px;padding-top:10px;padding-right:50px;overflow-x:hidden;overflow-y:scroll\n}\n.sidebar-bounds .sidebar{width:345px\n}\n.sidebar-flexer{max-height:96vh;-ms-flex-negative:0;flex-shrink:0;-ms-flex-positive:0;flex-grow:0\n}\n}\n.badge{display:inline-block;border-radius:99px;min-width:22px;max-width:22px;min-height:22px;max-height:22px;font-size:15px;line-height:22px;text-align:center;vertical-align:middle;white-space:nowrap;padding:0\n}\n.badge.badge-notification{background-color:red;background-color:var(--badgeNotification, red);color:white;color:var(--badgeNotificationText, #fff)\n}\n.alert{margin:0.35em;padding:0.25em;border-radius:5px;border-radius:var(--tooltipRadius, 5px);min-height:28px;line-height:28px\n}\n.alert.error{background-color:rgba(211,16,20,0.5);background-color:var(--alertError, rgba(211,16,20,0.5));color:#b9b9ba;color:var(--alertErrorText, #b9b9ba)\n}\n.panel-heading .alert.error{color:#b9b9ba;color:var(--alertErrorPanelText, #b9b9ba)\n}\n.faint{color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.faint-link{color:rgba(185,185,186,0.5);color:var(--faint, rgba(185,185,186,0.5))\n}\n.faint-link:hover{text-decoration:underline\n}\n@media all and (min-width: 800px){\n.logo{opacity:1 !important\n}\n}\n.item.right{text-align:right\n}\n.visibility-tray{font-size:1.2em;padding:3px;cursor:pointer\n}\n.visibility-tray .selected{color:#b9b9ba;color:var(--lightText, #b9b9ba)\n}\n.visibility-tray div{padding-top:5px\n}\n.visibility-notice{padding:.5em;border:1px solid rgba(185,185,186,0.5);border:1px solid var(--faint, rgba(185,185,186,0.5));border-radius:4px;border-radius:var(--inputRadius, 4px)\n}\n@keyframes modal-background-fadein{\nfrom{background-color:transparent\n}\nto{background-color:rgba(0,0,0,0.5)\n}\n}\n.modal-view{z-index:1000;position:fixed;top:0;left:0;right:0;bottom:0;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;overflow:auto;animation-duration:0.2s;background-color:rgba(0,0,0,0.5);animation-name:modal-background-fadein\n}\n.button-icon{font-size:1.2em\n}\n@keyframes shakeError{\n0%{transform:translateX(0)\n}\n15%{transform:translateX(0.375rem)\n}\n30%{transform:translateX(-0.375rem)\n}\n45%{transform:translateX(0.375rem)\n}\n60%{transform:translateX(-0.375rem)\n}\n75%{transform:translateX(0.375rem)\n}\n90%{transform:translateX(-0.375rem)\n}\n100%{transform:translateX(0)\n}\n}\n@media all and (max-width: 800px){\n.mobile-hidden{display:none\n}\n.panel-switcher{display:-ms-flexbox;display:flex\n}\n.container{padding:0\n}\n.panel{margin:0.5em 0 0.5em 0\n}\n.menu-button{display:block;margin-right:0.8em\n}\n}\n.login-hint{text-align:center\n}\n@media all and (min-width: 801px){\n.login-hint{display:none\n}\n}\n.login-hint a{display:inline-block;padding:1em 0px;width:100%\n}\n.btn.btn-default{min-height:28px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/App.scss","\n.nav-panel .panel{overflow:hidden;box-shadow:var(--panelShadow)\n}\n.nav-panel ul{list-style:none;margin:0;padding:0\n}\n.follow-request-count{margin:-6px 10px;background-color:#121a24;background-color:var(--input, rgba(185,185,186,0.5))\n}\n.nav-panel li{border-bottom:1px solid;border-color:#222;border-color:var(--border, #222);padding:0\n}\n.nav-panel li:first-child a{border-top-right-radius:10px;border-top-right-radius:var(--panelRadius, 10px);border-top-left-radius:10px;border-top-left-radius:var(--panelRadius, 10px)\n}\n.nav-panel li:last-child a{border-bottom-right-radius:10px;border-bottom-right-radius:var(--panelRadius, 10px);border-bottom-left-radius:10px;border-bottom-left-radius:var(--panelRadius, 10px)\n}\n.nav-panel li:last-child{border:none\n}\n.nav-panel a{display:block;padding:0.8em 0.85em\n}\n.nav-panel a:hover{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.nav-panel a.router-link-active{font-weight:bolder;background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n.nav-panel a.router-link-active:hover{text-decoration:underline\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/nav_panel/nav_panel.vue","\n.user-finder-container{max-width:100%;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:baseline;align-items:baseline;vertical-align:baseline\n}\n.user-finder-container .user-finder-input,.user-finder-container .search-button{height:29px\n}\n.user-finder-container .user-finder-input{max-width:calc(100% - 30px - 30px - 20px)\n}\n.user-finder-container .search-button{margin-left:.5em;margin-right:.5em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_finder/user_finder.vue","\n.who-to-follow *{vertical-align:middle\n}\n.who-to-follow img{width:32px;height:32px\n}\n.who-to-follow{padding:0.5em 1em 0.5em 1em;margin:0px;line-height:40px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/who_to_follow_panel/who_to_follow_panel.vue","\n.media-modal-view:hover .modal-view-button-arrow{opacity:0.75\n}\n.media-modal-view:hover .modal-view-button-arrow:focus,.media-modal-view:hover .modal-view-button-arrow:hover{outline:none;box-shadow:none\n}\n.media-modal-view:hover .modal-view-button-arrow:hover{opacity:1\n}\n.modal-image{max-width:90%;max-height:90%;box-shadow:0px 5px 15px 0 rgba(0,0,0,0.5)\n}\n.modal-view-button-arrow{position:absolute;display:block;top:50%;margin-top:-50px;width:70px;height:100px;border:0;padding:0;opacity:0;box-shadow:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:visible;cursor:pointer;transition:opacity 333ms cubic-bezier(0.4, 0, 0.22, 1)\n}\n.modal-view-button-arrow .arrow-icon{position:absolute;top:35px;height:30px;width:32px;font-size:14px;line-height:30px;color:#FFF;text-align:center;background-color:rgba(0,0,0,0.3)\n}\n.modal-view-button-arrow--prev{left:0\n}\n.modal-view-button-arrow--prev .arrow-icon{left:6px\n}\n.modal-view-button-arrow--next{right:0\n}\n.modal-view-button-arrow--next .arrow-icon{right:6px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/media_modal/media_modal.vue","\n.side-drawer-container{position:fixed;z-index:1000;top:0;left:0;width:100%;height:100%;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;align-items:stretch\n}\n.side-drawer-container-open{transition:0.35s;transition-property:background-color;background-color:rgba(0,0,0,0.5)\n}\n.side-drawer-container-closed{left:-100%;background-color:transparent\n}\n.side-drawer-click-outside{-ms-flex:1 1 100%;flex:1 1 100%\n}\n.side-drawer{overflow-x:hidden;transition:0.35s;transition-timing-function:cubic-bezier(0, 1, 0.5, 1);margin:0 0 0 -100px;padding:0 0 1em 100px;width:80%;max-width:20em;-ms-flex:0 0 80%;flex:0 0 80%;box-shadow:1px 1px 4px rgba(0,0,0,0.6);box-shadow:var(--panelShadow);background-color:#121a24;background-color:var(--bg, #121a24)\n}\n.side-drawer-logo-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:0.85em\n}\n.side-drawer-logo-wrapper img{-ms-flex:none;flex:none;height:50px;margin-right:0.85em\n}\n.side-drawer-logo-wrapper span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap\n}\n.side-drawer-click-outside-closed{-ms-flex:0 0 0px;flex:0 0 0\n}\n.side-drawer-closed{transform:translate(-100%)\n}\n.side-drawer-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch;display:-ms-flexbox;display:flex;padding:0;margin:0\n}\n.side-drawer ul{list-style:none;margin:0;padding:0;border-bottom:1px solid;border-color:#222;border-color:var(--border, #222);margin:0.2em 0\n}\n.side-drawer ul:last-child{border:0\n}\n.side-drawer li{padding:0\n}\n.side-drawer li a{display:block;padding:0.5em 0.85em\n}\n.side-drawer li a:hover{background-color:#151e2a;background-color:var(--lightBg, #151e2a)\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/side_drawer/side_drawer.vue","\n.post-form-modal-view{max-height:100%;display:block\n}\n.post-form-modal-panel{-ms-flex-negative:0;flex-shrink:0;margin:25% 0 4em 0;width:100%\n}\n.new-status-button{width:5em;height:5em;border-radius:100%;position:fixed;bottom:1.5em;right:1.5em;background-color:#182230;background-color:var(--btn, #182230);display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;box-shadow:0px 2px 2px rgba(0,0,0,0.3),0px 4px 6px rgba(0,0,0,0.3);z-index:10;transition:0.35s transform;transition-timing-function:cubic-bezier(0, 1, 0.5, 1)\n}\n.new-status-button.hidden{transform:translateY(150%)\n}\n.new-status-button i{font-size:1.5em;color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n@media all and (min-width: 801px){\n.new-status-button{display:none\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/mobile_post_status_modal/mobile_post_status_modal.vue"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/LICENSE.txt b/priv/static/static/font/LICENSE.txt old mode 100755 new mode 100644 diff --git a/priv/static/static/font/README.txt b/priv/static/static/font/README.txt old mode 100755 new mode 100644 diff --git a/priv/static/static/font/config.json b/priv/static/static/font/config.json old mode 100755 new mode 100644 index d72b622c0..b73f2ad40 --- a/priv/static/static/font/config.json +++ b/priv/static/static/font/config.json @@ -239,6 +239,18 @@ "css": "pencil", "code": 59416, "src": "fontawesome" + }, + { + "uid": "671f29fa10dda08074a4c6a341bb4f39", + "css": "bell-alt", + "code": 61683, + "src": "fontawesome" + }, + { + "uid": "5bb103cd29de77e0e06a52638527b575", + "css": "wrench", + "code": 59418, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/priv/static/static/font/css/fontello-codes.css b/priv/static/static/font/css/fontello-codes.css index 49175c8fe..b57c56203 100755 Binary files a/priv/static/static/font/css/fontello-codes.css and b/priv/static/static/font/css/fontello-codes.css differ diff --git a/priv/static/static/font/css/fontello-embedded.css b/priv/static/static/font/css/fontello-embedded.css index c43ad321d..c69c8b9f6 100755 Binary files a/priv/static/static/font/css/fontello-embedded.css and b/priv/static/static/font/css/fontello-embedded.css differ diff --git a/priv/static/static/font/css/fontello-ie7-codes.css b/priv/static/static/font/css/fontello-ie7-codes.css index 56e114470..981463a84 100755 Binary files a/priv/static/static/font/css/fontello-ie7-codes.css and b/priv/static/static/font/css/fontello-ie7-codes.css differ diff --git a/priv/static/static/font/css/fontello-ie7.css b/priv/static/static/font/css/fontello-ie7.css index edced9cb6..c2e8bc242 100755 Binary files a/priv/static/static/font/css/fontello-ie7.css and b/priv/static/static/font/css/fontello-ie7.css differ diff --git a/priv/static/static/font/css/fontello.css b/priv/static/static/font/css/fontello.css index 64a7a938e..fc23c41aa 100755 Binary files a/priv/static/static/font/css/fontello.css and b/priv/static/static/font/css/fontello.css differ diff --git a/priv/static/static/font/demo.html b/priv/static/static/font/demo.html index 2c89a505d..1a1147afd 100755 --- a/priv/static/static/font/demo.html +++ b/priv/static/static/font/demo.html @@ -229,11 +229,11 @@ body { } @font-face { font-family: 'fontello'; - src: url('./font/fontello.eot?50378338'); - src: url('./font/fontello.eot?50378338#iefix') format('embedded-opentype'), - url('./font/fontello.woff?50378338') format('woff'), - url('./font/fontello.ttf?50378338') format('truetype'), - url('./font/fontello.svg?50378338#fontello') format('svg'); + src: url('./font/fontello.eot?60799712'); + src: url('./font/fontello.eot?60799712#iefix') format('embedded-opentype'), + url('./font/fontello.woff?60799712') format('woff'), + url('./font/fontello.ttf?60799712') format('truetype'), + url('./font/fontello.svg?60799712#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -335,24 +335,29 @@ body {
icon-pencil0xe818
+
icon-verified0xe819
+
icon-wrench0xe81a
icon-spin30xe832
+
+
icon-spin40xe834
icon-link-ext0xf08e
-
-
icon-link-ext-alt0xf08f
icon-menu0xf0c9
-
icon-mail-alt0xf0e0
-
icon-comment-empty0xf0e5
+
icon-mail-alt0xf0e0
+
icon-comment-empty0xf0e5
+
icon-bell-alt0xf0f3
icon-plus-squared0xf0fe
+
+
icon-reply0xf112
icon-lock-open-alt0xf13e
icon-play-circled0xf144
+
icon-thumbs-up-alt0xf164
-
icon-thumbs-up-alt0xf164
icon-binoculars0xf1e5
icon-user-plus0xf234
diff --git a/priv/static/static/font/font/fontello.eot b/priv/static/static/font/font/fontello.eot index a72671b0d..b9cdfcb5d 100755 Binary files a/priv/static/static/font/font/fontello.eot and b/priv/static/static/font/font/fontello.eot differ diff --git a/priv/static/static/font/font/fontello.svg b/priv/static/static/font/font/fontello.svg index 91aba5ef6..0e460ea5e 100755 --- a/priv/static/static/font/font/fontello.svg +++ b/priv/static/static/font/font/fontello.svg @@ -56,6 +56,10 @@ + + + + @@ -70,6 +74,8 @@ + + diff --git a/priv/static/static/font/font/fontello.ttf b/priv/static/static/font/font/fontello.ttf index 9d36bc118..f1b9f19d2 100755 Binary files a/priv/static/static/font/font/fontello.ttf and b/priv/static/static/font/font/fontello.ttf differ diff --git a/priv/static/static/font/font/fontello.woff b/priv/static/static/font/font/fontello.woff index 35eea15d7..141abc65a 100755 Binary files a/priv/static/static/font/font/fontello.woff and b/priv/static/static/font/font/fontello.woff differ diff --git a/priv/static/static/font/font/fontello.woff2 b/priv/static/static/font/font/fontello.woff2 index c88c4b24f..efed5cf73 100755 Binary files a/priv/static/static/font/font/fontello.woff2 and b/priv/static/static/font/font/fontello.woff2 differ diff --git a/priv/static/static/img/nsfw.74818f9.png b/priv/static/static/img/nsfw.74818f9.png index e32525aa5..d25137767 100644 Binary files a/priv/static/static/img/nsfw.74818f9.png and b/priv/static/static/img/nsfw.74818f9.png differ diff --git a/priv/static/static/js/app.77434de4e756a5d79672.js b/priv/static/static/js/app.77434de4e756a5d79672.js deleted file mode 100644 index df6755cb4..000000000 Binary files a/priv/static/static/js/app.77434de4e756a5d79672.js and /dev/null differ diff --git a/priv/static/static/js/app.77434de4e756a5d79672.js.map b/priv/static/static/js/app.77434de4e756a5d79672.js.map deleted file mode 100644 index 5f68977a7..000000000 Binary files a/priv/static/static/js/app.77434de4e756a5d79672.js.map and /dev/null differ diff --git a/priv/static/static/js/app.c914d9a57d5da7aa5553.js b/priv/static/static/js/app.c914d9a57d5da7aa5553.js new file mode 100644 index 000000000..e7b09c97e Binary files /dev/null and b/priv/static/static/js/app.c914d9a57d5da7aa5553.js differ diff --git a/priv/static/static/js/app.c914d9a57d5da7aa5553.js.map b/priv/static/static/js/app.c914d9a57d5da7aa5553.js.map new file mode 100644 index 000000000..f469d271c Binary files /dev/null and b/priv/static/static/js/app.c914d9a57d5da7aa5553.js.map differ diff --git a/priv/static/static/js/manifest.0b2f423dda42f0dbbf65.js b/priv/static/static/js/manifest.0b2f423dda42f0dbbf65.js deleted file mode 100644 index ecc4a13d3..000000000 Binary files a/priv/static/static/js/manifest.0b2f423dda42f0dbbf65.js and /dev/null differ diff --git a/priv/static/static/js/manifest.bf15f24d205c8cf4ee4a.js b/priv/static/static/js/manifest.bf15f24d205c8cf4ee4a.js new file mode 100644 index 000000000..b6de44a86 Binary files /dev/null and b/priv/static/static/js/manifest.bf15f24d205c8cf4ee4a.js differ diff --git a/priv/static/static/js/manifest.0b2f423dda42f0dbbf65.js.map b/priv/static/static/js/manifest.bf15f24d205c8cf4ee4a.js.map similarity index 92% rename from priv/static/static/js/manifest.0b2f423dda42f0dbbf65.js.map rename to priv/static/static/js/manifest.bf15f24d205c8cf4ee4a.js.map index 8aabe15dd..c0cd90ac0 100644 Binary files a/priv/static/static/js/manifest.0b2f423dda42f0dbbf65.js.map and b/priv/static/static/js/manifest.bf15f24d205c8cf4ee4a.js.map differ diff --git a/priv/static/static/js/vendor.0d1eeaf25aa1d2fc51b0.js b/priv/static/static/js/vendor.0d1eeaf25aa1d2fc51b0.js new file mode 100644 index 000000000..7e0119cc8 Binary files /dev/null and b/priv/static/static/js/vendor.0d1eeaf25aa1d2fc51b0.js differ diff --git a/priv/static/static/js/vendor.0d1eeaf25aa1d2fc51b0.js.map b/priv/static/static/js/vendor.0d1eeaf25aa1d2fc51b0.js.map new file mode 100644 index 000000000..ddc023b43 Binary files /dev/null and b/priv/static/static/js/vendor.0d1eeaf25aa1d2fc51b0.js.map differ diff --git a/priv/static/static/js/vendor.e4475fde034685231799.js b/priv/static/static/js/vendor.e4475fde034685231799.js deleted file mode 100644 index 989b44b7a..000000000 Binary files a/priv/static/static/js/vendor.e4475fde034685231799.js and /dev/null differ diff --git a/priv/static/static/js/vendor.e4475fde034685231799.js.map b/priv/static/static/js/vendor.e4475fde034685231799.js.map deleted file mode 100644 index 6813973f8..000000000 Binary files a/priv/static/static/js/vendor.e4475fde034685231799.js.map and /dev/null differ diff --git a/priv/static/static/logo.png b/priv/static/static/logo.png index c3c92914b..7744b1acc 100644 Binary files a/priv/static/static/logo.png and b/priv/static/static/logo.png differ diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index b69b1b7e7..3c00d22d5 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/test/bookmark_test.exs b/test/bookmark_test.exs new file mode 100644 index 000000000..b81c102ef --- /dev/null +++ b/test/bookmark_test.exs @@ -0,0 +1,52 @@ +defmodule Pleroma.BookmarkTest do + use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.Bookmark + alias Pleroma.Web.CommonAPI + + describe "create/2" do + test "with valid params" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "Some cool information"}) + {:ok, bookmark} = Bookmark.create(user.id, activity.id) + assert bookmark.user_id == user.id + assert bookmark.activity_id == activity.id + end + + test "with invalid params" do + {:error, changeset} = Bookmark.create(nil, "") + refute changeset.valid? + + assert changeset.errors == [ + user_id: {"can't be blank", [validation: :required]}, + activity_id: {"can't be blank", [validation: :required]} + ] + end + end + + describe "destroy/2" do + test "with valid params" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "Some cool information"}) + {:ok, _bookmark} = Bookmark.create(user.id, activity.id) + + {:ok, _deleted_bookmark} = Bookmark.destroy(user.id, activity.id) + end + end + + describe "get/2" do + test "gets a bookmark" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => + "Scientists Discover The Secret Behind Tenshi Eating A Corndog Being So Cute – Science Daily" + }) + + {:ok, bookmark} = Bookmark.create(user.id, activity.id) + assert bookmark == Bookmark.get(user.id, activity.id) + end + end +end diff --git a/test/emoji_test.exs b/test/emoji_test.exs index cb1d62d00..2eaa26be6 100644 --- a/test/emoji_test.exs +++ b/test/emoji_test.exs @@ -15,7 +15,7 @@ test "first emoji", %{emoji_list: emoji_list} do assert tuple_size(emoji) == 3 assert is_binary(code) assert is_binary(path) - assert is_binary(tags) + assert is_list(tags) end test "random emoji", %{emoji_list: emoji_list} do @@ -25,7 +25,7 @@ test "random emoji", %{emoji_list: emoji_list} do assert tuple_size(emoji) == 3 assert is_binary(code) assert is_binary(path) - assert is_binary(tags) + assert is_list(tags) end end diff --git a/test/formatter_test.exs b/test/formatter_test.exs index e74985c4e..97eb2f583 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -245,10 +245,10 @@ test "it can parse mentions and return the relevant users" do end test "it adds cool emoji" do - text = "I love :moominmamma:" + text = "I love :firefox:" expected_result = - "I love \"moominmamma\"" + "I love \"firefox\"" assert Formatter.emojify(text) == expected_result end @@ -269,10 +269,10 @@ test "it does not add XSS emoji" do end test "it returns the emoji used in the text" do - text = "I love :moominmamma:" + text = "I love :firefox:" assert Formatter.get_emoji(text) == [ - {"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"} + {"firefox", "/emoji/Firefox.gif", ["Gif", "Fun"]} ] end diff --git a/test/healthcheck_test.exs b/test/healthcheck_test.exs new file mode 100644 index 000000000..e05061220 --- /dev/null +++ b/test/healthcheck_test.exs @@ -0,0 +1,22 @@ +defmodule Pleroma.HealthcheckTest do + use Pleroma.DataCase + alias Pleroma.Healthcheck + + test "system_info/0" do + result = Healthcheck.system_info() |> Map.from_struct() + + assert Map.keys(result) == [:active, :healthy, :idle, :memory_used, :pool_size] + end + + describe "check_health/1" do + test "pool size equals active connections" do + result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 10}) + refute result.healthy + end + + test "chech_health/1" do + result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 9}) + assert result.healthy + end + end +end diff --git a/test/html_test.exs b/test/html_test.exs index 0b5d3d892..08738276e 100644 --- a/test/html_test.exs +++ b/test/html_test.exs @@ -20,6 +20,18 @@ defmodule Pleroma.HTMLTest do """ + @html_span_class_sample """ + hi + """ + + @html_span_microformats_sample """ + @foo + """ + + @html_span_invalid_microformats_sample """ + @foo + """ + describe "StripTags scrubber" do test "works as expected" do expected = """ @@ -64,6 +76,36 @@ test "does not allow attribute-based XSS" do assert expected == HTML.filter_tags(@html_onerror_sample, Pleroma.HTML.Scrubber.TwitterText) end + + test "does not allow spans with invalid classes" do + expected = """ + hi + """ + + assert expected == + HTML.filter_tags(@html_span_class_sample, Pleroma.HTML.Scrubber.TwitterText) + end + + test "does allow microformats" do + expected = """ + @foo + """ + + assert expected == + HTML.filter_tags(@html_span_microformats_sample, Pleroma.HTML.Scrubber.TwitterText) + end + + test "filters invalid microformats markup" do + expected = """ + @foo + """ + + assert expected == + HTML.filter_tags( + @html_span_invalid_microformats_sample, + Pleroma.HTML.Scrubber.TwitterText + ) + end end describe "default scrubber" do @@ -88,5 +130,34 @@ test "does not allow attribute-based XSS" do assert expected == HTML.filter_tags(@html_onerror_sample, Pleroma.HTML.Scrubber.Default) end + + test "does not allow spans with invalid classes" do + expected = """ + hi + """ + + assert expected == HTML.filter_tags(@html_span_class_sample, Pleroma.HTML.Scrubber.Default) + end + + test "does allow microformats" do + expected = """ + @foo + """ + + assert expected == + HTML.filter_tags(@html_span_microformats_sample, Pleroma.HTML.Scrubber.Default) + end + + test "filters invalid microformats markup" do + expected = """ + @foo + """ + + assert expected == + HTML.filter_tags( + @html_span_invalid_microformats_sample, + Pleroma.HTML.Scrubber.Default + ) + end end end diff --git a/test/media_proxy_test.exs b/test/media_proxy_test.exs index ddbadfbf5..a4331478e 100644 --- a/test/media_proxy_test.exs +++ b/test/media_proxy_test.exs @@ -177,4 +177,13 @@ defp decode_result(encoded) do {:ok, decoded} = decode_url(sig, base64) decoded end + + test "mediaproxy whitelist" do + Pleroma.Config.put([:media_proxy, :enabled], true) + Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) + url = "https://feld.me/foo.png" + + unencoded = url(url) + assert unencoded == url + end end diff --git a/test/notification_test.exs b/test/notification_test.exs index 3bbce8fcf..eec0a4829 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -48,7 +48,7 @@ test "it creates a notification for subscribed users" do describe "create_notification" do test "it doesn't create a notification for user if the user blocks the activity author" do activity = insert(:note_activity) - author = User.get_by_ap_id(activity.data["actor"]) + author = User.get_cached_by_ap_id(activity.data["actor"]) user = insert(:user) {:ok, user} = User.block(user, author) @@ -126,7 +126,7 @@ test "it disables notifications from people the user follows" do test "it doesn't create a notification for user if he is the activity author" do activity = insert(:note_activity) - author = User.get_by_ap_id(activity.data["actor"]) + author = User.get_cached_by_ap_id(activity.data["actor"]) assert nil == Notification.create_notification(activity, author) end diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs index 535dc3756..9d260da3e 100644 --- a/test/tasks/relay_test.exs +++ b/test/tasks/relay_test.exs @@ -31,7 +31,7 @@ test "relay is followed" do local_user = Relay.get_actor() assert local_user.ap_id =~ "/relay" - target_user = User.get_by_ap_id(target_instance) + target_user = User.get_cached_by_ap_id(target_instance) refute target_user.local activity = Utils.fetch_latest_follow(local_user, target_user) @@ -48,7 +48,7 @@ test "relay is unfollowed" do Mix.Tasks.Pleroma.Relay.run(["follow", target_instance]) %User{ap_id: follower_id} = local_user = Relay.get_actor() - target_user = User.get_by_ap_id(target_instance) + target_user = User.get_cached_by_ap_id(target_instance) follow_activity = Utils.fetch_latest_follow(local_user, target_user) Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance]) diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 242265da5..eaf4ecf84 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -50,7 +50,7 @@ test "user is created" do assert_received {:mix_shell, :info, [message]} assert message =~ "created" - user = User.get_by_nickname(unsaved.nickname) + user = User.get_cached_by_nickname(unsaved.nickname) assert user.name == unsaved.name assert user.email == unsaved.email assert user.bio == unsaved.bio @@ -75,7 +75,7 @@ test "user is not created" do assert_received {:mix_shell, :info, [message]} assert message =~ "will not be created" - refute User.get_by_nickname(unsaved.nickname) + refute User.get_cached_by_nickname(unsaved.nickname) end end @@ -88,7 +88,7 @@ test "user is deleted" do assert_received {:mix_shell, :info, [message]} assert message =~ " deleted" - user = User.get_by_nickname(user.nickname) + user = User.get_cached_by_nickname(user.nickname) assert user.info.deactivated end @@ -109,7 +109,7 @@ test "user is deactivated" do assert_received {:mix_shell, :info, [message]} assert message =~ " deactivated" - user = User.get_by_nickname(user.nickname) + user = User.get_cached_by_nickname(user.nickname) assert user.info.deactivated end @@ -121,7 +121,7 @@ test "user is activated" do assert_received {:mix_shell, :info, [message]} assert message =~ " activated" - user = User.get_by_nickname(user.nickname) + user = User.get_cached_by_nickname(user.nickname) refute user.info.deactivated end @@ -150,7 +150,7 @@ test "user is unsubscribed" do assert_received {:mix_shell, :info, [message]} assert message =~ "Successfully unsubscribed" - user = User.get_by_nickname(user.nickname) + user = User.get_cached_by_nickname(user.nickname) assert Enum.empty?(user.following) assert user.info.deactivated end @@ -178,7 +178,7 @@ test "All statuses set" do assert_received {:mix_shell, :info, [message]} assert message =~ ~r/Admin status .* true/ - user = User.get_by_nickname(user.nickname) + user = User.get_cached_by_nickname(user.nickname) assert user.info.is_moderator assert user.info.locked assert user.info.is_admin @@ -204,7 +204,7 @@ test "All statuses unset" do assert_received {:mix_shell, :info, [message]} assert message =~ ~r/Admin status .* false/ - user = User.get_by_nickname(user.nickname) + user = User.get_cached_by_nickname(user.nickname) refute user.info.is_moderator refute user.info.locked refute user.info.is_admin diff --git a/test/user_test.exs b/test/user_test.exs index d11d94263..a4d51ac95 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -123,9 +123,9 @@ test "follow takes a user and another user" do {:ok, user} = User.follow(user, followed) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) - followed = User.get_by_ap_id(followed.ap_id) + followed = User.get_cached_by_ap_id(followed.ap_id) assert followed.info.follower_count == 1 assert User.ap_followers(followed) in user.following @@ -188,7 +188,7 @@ test "unfollow takes a user and another user" do {:ok, user, _activity} = User.unfollow(user, followed) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.following == [] end @@ -198,7 +198,7 @@ test "unfollow doesn't unfollow yourself" do {:error, _} = User.unfollow(user, user) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.following == [user.ap_id] end @@ -556,8 +556,8 @@ test "gets all friends (followed users) for a given user" do {:ok, res} = User.get_friends(user) - followed_one = User.get_by_ap_id(followed_one.ap_id) - followed_two = User.get_by_ap_id(followed_two.ap_id) + followed_one = User.get_cached_by_ap_id(followed_one.ap_id) + followed_two = User.get_cached_by_ap_id(followed_two.ap_id) assert Enum.member?(res, followed_one) assert Enum.member?(res, followed_two) refute Enum.member?(res, not_followed) @@ -568,7 +568,7 @@ test "gets all friends (followed users) for a given user" do test "it sets the info->note_count property" do note = insert(:note) - user = User.get_by_ap_id(note.data["actor"]) + user = User.get_cached_by_ap_id(note.data["actor"]) assert user.info.note_count == 0 @@ -579,7 +579,7 @@ test "it sets the info->note_count property" do test "it increases the info->note_count property" do note = insert(:note) - user = User.get_by_ap_id(note.data["actor"]) + user = User.get_cached_by_ap_id(note.data["actor"]) assert user.info.note_count == 0 @@ -594,7 +594,7 @@ test "it increases the info->note_count property" do test "it decreases the info->note_count property" do note = insert(:note) - user = User.get_by_ap_id(note.data["actor"]) + user = User.get_cached_by_ap_id(note.data["actor"]) assert user.info.note_count == 0 @@ -696,7 +696,7 @@ test "blocks tear down cyclical follow relationships" do assert User.following?(blocked, blocker) {:ok, blocker} = User.block(blocker, blocked) - blocked = User.get_by_id(blocked.id) + blocked = User.get_cached_by_id(blocked.id) assert User.blocks?(blocker, blocked) @@ -714,7 +714,7 @@ test "blocks tear down blocker->blocked follow relationships" do refute User.following?(blocked, blocker) {:ok, blocker} = User.block(blocker, blocked) - blocked = User.get_by_id(blocked.id) + blocked = User.get_cached_by_id(blocked.id) assert User.blocks?(blocker, blocked) @@ -732,7 +732,7 @@ test "blocks tear down blocked->blocker follow relationships" do assert User.following?(blocked, blocker) {:ok, blocker} = User.block(blocker, blocked) - blocked = User.get_by_id(blocked.id) + blocked = User.get_cached_by_id(blocked.id) assert User.blocks?(blocker, blocked) @@ -852,9 +852,9 @@ test ".delete deactivates a user, all follow relationships and all create activi {:ok, _} = User.delete(user) - followed = User.get_by_id(followed.id) - follower = User.get_by_id(follower.id) - user = User.get_by_id(user.id) + followed = User.get_cached_by_id(followed.id) + follower = User.get_cached_by_id(follower.id) + user = User.get_cached_by_id(user.id) assert user.info.deactivated @@ -1005,13 +1005,17 @@ test "does not yield false-positive matches" do end test "works with URIs" do - results = User.search("http://mastodon.example.org/users/admin", resolve: true) - result = results |> List.first() + [result] = User.search("http://mastodon.example.org/users/admin", resolve: true) - user = User.get_by_ap_id("http://mastodon.example.org/users/admin") + expected = + result + |> Map.put(:search_rank, nil) + |> Map.put(:search_type, nil) + |> Map.put(:last_digest_emailed_at, nil) - assert length(results) == 1 - assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil) + user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin") + + assert user == expected end end @@ -1125,33 +1129,6 @@ test "Adds rel=me on linkbacked urls" do end end - test "bookmarks" do - user = insert(:user) - - {:ok, activity1} = - CommonAPI.post(user, %{ - "status" => "heweoo!" - }) - - id1 = Object.normalize(activity1).data["id"] - - {:ok, activity2} = - CommonAPI.post(user, %{ - "status" => "heweoo!" - }) - - id2 = Object.normalize(activity2).data["id"] - - assert {:ok, user_state1} = User.bookmark(user, id1) - assert user_state1.bookmarks == [id1] - - assert {:ok, user_state2} = User.unbookmark(user, id1) - assert user_state2.bookmarks == [] - - assert {:ok, user_state3} = User.bookmark(user, id2) - assert user_state3.bookmarks == [id2] - end - test "follower count is updated when a follower is blocked" do user = insert(:user) follower = insert(:user) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 7b1c60f15..30adfda36 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -50,7 +50,7 @@ test "it returns a json representation of the user with accept application/json" |> put_req_header("accept", "application/json") |> get("/users/#{user.nickname}") - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) end @@ -65,7 +65,7 @@ test "it returns a json representation of the user with accept application/activ |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}") - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) end @@ -83,7 +83,7 @@ test "it returns a json representation of the user with accept application/ld+js ) |> get("/users/#{user.nickname}") - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) end @@ -572,7 +572,7 @@ test "it works for more than 10 users", %{conn: conn} do user = insert(:user) Enum.each(1..15, fn _ -> - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) other_user = insert(:user) User.follow(user, other_user) end) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 389aa02a1..f8e987e58 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -228,18 +228,30 @@ test "increases user note count only for public activities" do user = insert(:user) {:ok, _} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "1", + "visibility" => "public" + }) {:ok, _} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "2", + "visibility" => "unlisted" + }) {:ok, _} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "2", + "visibility" => "private" + }) {:ok, _} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "3", + "visibility" => "direct" + }) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.info.note_count == 2 end @@ -772,23 +784,35 @@ test "decrements user note count only for public activities" do user = insert(:user, info: %{note_count: 10}) {:ok, a1} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "yeah", + "visibility" => "public" + }) {:ok, a2} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "yeah", + "visibility" => "unlisted" + }) {:ok, a3} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "yeah", + "visibility" => "private" + }) {:ok, a4} = - CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"}) + CommonAPI.post(User.get_cached_by_id(user.id), %{ + "status" => "yeah", + "visibility" => "direct" + }) {:ok, _} = Object.normalize(a1) |> ActivityPub.delete() {:ok, _} = Object.normalize(a2) |> ActivityPub.delete() {:ok, _} = Object.normalize(a3) |> ActivityPub.delete() {:ok, _} = Object.normalize(a4) |> ActivityPub.delete() - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.info.note_count == 10 end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6bb81a054..78429c7c6 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -99,7 +99,7 @@ test "it works for incoming notices" do assert object["sensitive"] == true - user = User.get_by_ap_id(object["actor"]) + user = User.get_cached_by_ap_id(object["actor"]) assert user.info.note_count == 1 end @@ -212,7 +212,27 @@ test "it works for incoming follow requests" do assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["type"] == "Follow" assert data["id"] == "http://mastodon.example.org/users/admin#follows/2" - assert User.following?(User.get_by_ap_id(data["actor"]), user) + assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) + end + + test "it rejects incoming follow requests from blocked users when deny_follow_blocked is enabled" do + Pleroma.Config.put([:user, :deny_follow_blocked], true) + + user = insert(:user) + target = User.get_or_fetch("http://mastodon.example.org/users/admin") + + {:ok, user} = User.block(user, target) + + data = + File.read!("test/fixtures/mastodon-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data) + + %Activity{} = activity = Activity.get_by_ap_id(id) + + assert activity.data["state"] == "reject" end test "it works for incoming follow requests from hubzilla" do @@ -229,7 +249,7 @@ test "it works for incoming follow requests from hubzilla" do assert data["actor"] == "https://hubzilla.example.org/channel/kaniini" assert data["type"] == "Follow" assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2" - assert User.following?(User.get_by_ap_id(data["actor"]), user) + assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) end test "it works for incoming likes" do @@ -540,7 +560,7 @@ test "it works for incomming unfollows with an existing follow" do assert data["object"]["object"] == user.ap_id assert data["actor"] == "http://mastodon.example.org/users/admin" - refute User.following?(User.get_by_ap_id(data["actor"]), user) + refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) end test "it works for incoming blocks" do @@ -557,7 +577,7 @@ test "it works for incoming blocks" do assert data["object"] == user.ap_id assert data["actor"] == "http://mastodon.example.org/users/admin" - blocker = User.get_by_ap_id(data["actor"]) + blocker = User.get_cached_by_ap_id(data["actor"]) assert User.blocks?(blocker, user) end @@ -584,8 +604,8 @@ test "incoming blocks successfully tear down any follow relationship" do assert data["object"] == blocked.ap_id assert data["actor"] == blocker.ap_id - blocker = User.get_by_ap_id(data["actor"]) - blocked = User.get_by_ap_id(data["object"]) + blocker = User.get_cached_by_ap_id(data["actor"]) + blocked = User.get_cached_by_ap_id(data["object"]) assert User.blocks?(blocker, blocked) @@ -614,7 +634,7 @@ test "it works for incoming unblocks with an existing block" do assert data["object"]["object"] == user.ap_id assert data["actor"] == "http://mastodon.example.org/users/admin" - blocker = User.get_by_ap_id(data["actor"]) + blocker = User.get_cached_by_ap_id(data["actor"]) refute User.blocks?(blocker, user) end @@ -645,7 +665,7 @@ test "it works for incoming accepts which were pre-accepted" do assert activity.data["object"] == follow_activity.data["id"] - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == true end @@ -667,7 +687,7 @@ test "it works for incoming accepts which were orphaned" do {:ok, activity} = Transmogrifier.handle_incoming(accept_data) assert activity.data["object"] == follow_activity.data["id"] - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == true end @@ -687,7 +707,7 @@ test "it works for incoming accepts which are referenced by IRI only" do {:ok, activity} = Transmogrifier.handle_incoming(accept_data) assert activity.data["object"] == follow_activity.data["id"] - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == true end @@ -706,7 +726,7 @@ test "it fails for incoming accepts which cannot be correlated" do :error = Transmogrifier.handle_incoming(accept_data) - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) refute User.following?(follower, followed) == true end @@ -725,7 +745,7 @@ test "it fails for incoming rejects which cannot be correlated" do :error = Transmogrifier.handle_incoming(accept_data) - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) refute User.following?(follower, followed) == true end @@ -750,7 +770,7 @@ test "it works for incoming rejects which are orphaned" do {:ok, activity} = Transmogrifier.handle_incoming(reject_data) refute activity.local - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == false end @@ -772,7 +792,7 @@ test "it works for incoming rejects which are referenced by IRI only" do {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data) - follower = User.get_by_id(follower.id) + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == false end @@ -946,7 +966,7 @@ test "it strips internal hashtag data" do test "it strips internal fields" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :moominmamma:"}) + {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) @@ -1026,7 +1046,7 @@ test "it upgrades a user to activitypub" do {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.info.note_count == 1 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") @@ -1034,7 +1054,7 @@ test "it upgrades a user to activitypub" do assert user.info.note_count == 1 assert user.follower_address == "https://niu.moe/users/rye/followers" - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.info.note_count == 1 activity = Activity.get_by_id(activity.id) @@ -1063,7 +1083,7 @@ test "it upgrades a user to activitypub" do unrelated_activity = Activity.get_by_id(unrelated_activity.id) refute user.follower_address in unrelated_activity.recipients - user_two = User.get_by_id(user_two.id) + user_two = User.get_cached_by_id(user_two.id) assert user.follower_address in user_two.following refute "..." in user_two.following end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index 758214e68..c57fae437 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -1,7 +1,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do use Pleroma.DataCase alias Pleroma.Activity - alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils @@ -12,8 +11,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do describe "fetch the latest Follow" do test "fetches the latest Follow activity" do %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) - follower = Repo.get_by(User, ap_id: activity.data["actor"]) - followed = Repo.get_by(User, ap_id: activity.data["object"]) + follower = User.get_cached_by_ap_id(activity.data["actor"]) + followed = User.get_cached_by_ap_id(activity.data["object"]) assert activity == Utils.fetch_latest_follow(follower, followed) end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index b3167a861..b89c42327 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -89,8 +89,8 @@ test "allows to force-follow another user" do "followed" => user.nickname }) - user = User.get_by_id(user.id) - follower = User.get_by_id(follower.id) + user = User.get_cached_by_id(user.id) + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, user) end @@ -112,8 +112,8 @@ test "allows to force-unfollow another user" do "followed" => user.nickname }) - user = User.get_by_id(user.id) - follower = User.get_by_id(follower.id) + user = User.get_cached_by_id(user.id) + follower = User.get_cached_by_id(follower.id) refute User.following?(follower, user) end @@ -145,13 +145,13 @@ test "it appends specified tags to users with specified nicknames", %{ user2: user2 } do assert json_response(conn, :no_content) - assert User.get_by_id(user1.id).tags == ["x", "foo", "bar"] - assert User.get_by_id(user2.id).tags == ["y", "foo", "bar"] + assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"] + assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"] end test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do assert json_response(conn, :no_content) - assert User.get_by_id(user3.id).tags == ["unchanged"] + assert User.get_cached_by_id(user3.id).tags == ["unchanged"] end end @@ -181,13 +181,13 @@ test "it removes specified tags from users with specified nicknames", %{ user2: user2 } do assert json_response(conn, :no_content) - assert User.get_by_id(user1.id).tags == [] - assert User.get_by_id(user2.id).tags == ["y"] + assert User.get_cached_by_id(user1.id).tags == [] + assert User.get_cached_by_id(user2.id).tags == ["y"] end test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do assert json_response(conn, :no_content) - assert User.get_by_id(user3.id).tags == ["unchanged"] + assert User.get_cached_by_id(user3.id).tags == ["unchanged"] end end @@ -257,7 +257,7 @@ test "deactivates the user", %{conn: conn} do conn |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false}) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.info.deactivated == true assert json_response(conn, :no_content) end @@ -269,7 +269,7 @@ test "activates the user", %{conn: conn} do conn |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true}) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) assert user.info.deactivated == false assert json_response(conn, :no_content) end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 3d2bb8929..a5b07c446 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -40,19 +40,19 @@ test "it de-duplicates tags" do test "it adds emoji in the object" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => ":moominmamma:"}) + {:ok, activity} = CommonAPI.post(user, %{"status" => ":firefox:"}) - assert Object.normalize(activity).data["emoji"]["moominmamma"] + assert Object.normalize(activity).data["emoji"]["firefox"] end test "it adds emoji when updating profiles" do - user = insert(:user, %{name: ":karjalanpiirakka:"}) + user = insert(:user, %{name: ":firefox:"}) CommonAPI.update(user) user = User.get_cached_by_ap_id(user.ap_id) - [karjalanpiirakka] = user.info.source_data["tag"] + [firefox] = user.info.source_data["tag"] - assert karjalanpiirakka["name"] == ":karjalanpiirakka:" + assert firefox["name"] == ":firefox:" end describe "posting" do diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index f0c59d5c3..ab4c62b35 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -37,21 +37,21 @@ test "correct password given" do end test "parses emoji from name and bio" do - {:ok, user} = UserBuilder.insert(%{name: ":karjalanpiirakka:", bio: ":perkele:"}) + {:ok, user} = UserBuilder.insert(%{name: ":blank:", bio: ":firefox:"}) expected = [ %{ "type" => "Emoji", - "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}/finmoji/128px/perkele-128.png"}, - "name" => ":perkele:" + "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}/emoji/Firefox.gif"}, + "name" => ":firefox:" }, %{ "type" => "Emoji", "icon" => %{ "type" => "Image", - "url" => "#{Endpoint.url()}/finmoji/128px/karjalanpiirakka-128.png" + "url" => "#{Endpoint.url()}/emoji/blank.png" }, - "name" => ":karjalanpiirakka:" + "name" => ":blank:" } ] @@ -119,6 +119,31 @@ test "works for bare text/markdown" do assert output == expected end + test "works for bare text/bbcode" do + text = "[b]hello world[/b]" + expected = "hello world" + + {output, [], []} = Utils.format_input(text, "text/bbcode") + + assert output == expected + + text = "[b]hello world![/b]\n\nsecond paragraph!" + expected = "hello world!
\n
\nsecond paragraph!" + + {output, [], []} = Utils.format_input(text, "text/bbcode") + + assert output == expected + + text = "[b]hello world![/b]\n\nsecond paragraph!" + + expected = + "hello world!
\n
\n<strong>second paragraph!</strong>" + + {output, [], []} = Utils.format_input(text, "text/bbcode") + + assert output == expected + end + test "works for text/markdown with mentions" do {:ok, user} = UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"}) diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index d7487bed9..a24f2a050 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -56,14 +56,17 @@ test "Represent a user account" do bot: false, source: %{ note: "", - privacy: "public", - sensitive: false + sensitive: false, + pleroma: %{} }, pleroma: %{ confirmation_pending: false, tags: [], is_admin: false, is_moderator: false, + hide_favorites: true, + hide_followers: false, + hide_follows: false, relationship: %{} } } @@ -81,8 +84,12 @@ test "Represent the user account for the account owner" do "follows" => true } - assert %{pleroma: %{notification_settings: ^notification_settings}} = - AccountView.render("account.json", %{user: user, for: user}) + privacy = user.info.default_scope + + assert %{ + pleroma: %{notification_settings: ^notification_settings}, + source: %{privacy: ^privacy} + } = AccountView.render("account.json", %{user: user, for: user}) end test "Represent a Service(bot) account" do @@ -114,14 +121,17 @@ test "Represent a Service(bot) account" do bot: true, source: %{ note: "", - privacy: "public", - sensitive: false + sensitive: false, + pleroma: %{} }, pleroma: %{ confirmation_pending: false, tags: [], is_admin: false, is_moderator: false, + hide_favorites: true, + hide_followers: false, + hide_follows: false, relationship: %{} } } @@ -169,15 +179,15 @@ test "represent a relationship" do test "represent an embedded relationship" do user = insert(:user, %{ - info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}}, + info: %{note_count: 5, follower_count: 0, source_data: %{"type" => "Service"}}, nickname: "shp@shitposter.club", inserted_at: ~N[2017-08-15 15:47:06.597036] }) other_user = insert(:user) - {:ok, other_user} = User.follow(other_user, user) {:ok, other_user} = User.block(other_user, user) + {:ok, _} = User.follow(insert(:user), user) expected = %{ id: to_string(user.id), @@ -186,7 +196,7 @@ test "represent an embedded relationship" do display_name: user.name, locked: false, created_at: "2017-08-15T15:47:06.000Z", - followers_count: 3, + followers_count: 1, following_count: 0, statuses_count: 5, note: user.bio, @@ -200,14 +210,17 @@ test "represent an embedded relationship" do bot: true, source: %{ note: "", - privacy: "public", - sensitive: false + sensitive: false, + pleroma: %{} }, pleroma: %{ confirmation_pending: false, tags: [], is_admin: false, is_moderator: false, + hide_favorites: true, + hide_followers: false, + hide_follows: false, relationship: %{ id: to_string(user.id), following: false, diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 786af2088..c2a12d3c7 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -445,7 +445,7 @@ test "get a status", %{conn: conn} do describe "deleting a status" do test "when you created it", %{conn: conn} do activity = insert(:note_activity) - author = User.get_by_ap_id(activity.data["actor"]) + author = User.get_cached_by_ap_id(activity.data["actor"]) conn = conn @@ -1021,6 +1021,8 @@ test "reblogged status for another user", %{conn: conn} do user1 = insert(:user) user2 = insert(:user) user3 = insert(:user) + CommonAPI.favorite(activity.id, user2) + {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) @@ -1031,7 +1033,9 @@ test "reblogged status for another user", %{conn: conn} do assert %{ "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, - "reblogged" => false + "reblogged" => false, + "favourited" => false, + "bookmarked" => false } = json_response(conn_res, 200) conn_res = @@ -1041,7 +1045,9 @@ test "reblogged status for another user", %{conn: conn} do assert %{ "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, - "reblogged" => true + "reblogged" => true, + "favourited" => true, + "bookmarked" => true } = json_response(conn_res, 200) assert to_string(activity.id) == id @@ -1161,7 +1167,7 @@ test "gets a users statuses", %{conn: conn} do test "unimplemented pinned statuses feature", %{conn: conn} do note = insert(:note_activity) - user = User.get_by_ap_id(note.data["actor"]) + user = User.get_cached_by_ap_id(note.data["actor"]) conn = conn @@ -1172,7 +1178,7 @@ test "unimplemented pinned statuses feature", %{conn: conn} do test "gets an users media", %{conn: conn} do note = insert(:note_activity) - user = User.get_by_ap_id(note.data["actor"]) + user = User.get_cached_by_ap_id(note.data["actor"]) file = %Plug.Upload{ content_type: "image/jpg", @@ -1247,8 +1253,8 @@ test "/api/v1/follow_requests works" do {:ok, _activity} = ActivityPub.follow(other_user, user) - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == false @@ -1267,8 +1273,8 @@ test "/api/v1/follow_requests/:id/authorize works" do {:ok, _activity} = ActivityPub.follow(other_user, user) - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == false @@ -1280,8 +1286,8 @@ test "/api/v1/follow_requests/:id/authorize works" do assert relationship = json_response(conn, 200) assert to_string(other_user.id) == relationship["id"] - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == true end @@ -1304,7 +1310,7 @@ test "/api/v1/follow_requests/:id/reject works" do {:ok, _activity} = ActivityPub.follow(other_user, user) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) conn = build_conn() @@ -1314,8 +1320,8 @@ test "/api/v1/follow_requests/:id/reject works" do assert relationship = json_response(conn, 200) assert to_string(other_user.id) == relationship["id"] - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == false end @@ -1600,7 +1606,7 @@ test "following / unfollowing a user", %{conn: conn} do assert %{"id" => _id, "following" => true} = json_response(conn, 200) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) conn = build_conn() @@ -1609,7 +1615,7 @@ test "following / unfollowing a user", %{conn: conn} do assert %{"id" => _id, "following" => false} = json_response(conn, 200) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) conn = build_conn() @@ -1620,6 +1626,44 @@ test "following / unfollowing a user", %{conn: conn} do assert id == to_string(other_user.id) end + test "following without reblogs" do + follower = insert(:user) + followed = insert(:user) + other_user = insert(:user) + + conn = + build_conn() + |> assign(:user, follower) + |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false") + + assert %{"showing_reblogs" => false} = json_response(conn, 200) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) + {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) + + conn = + build_conn() + |> assign(:user, User.get_cached_by_id(follower.id)) + |> get("/api/v1/timelines/home") + + assert [] == json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, follower) + |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true") + + assert %{"showing_reblogs" => true} = json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, User.get_cached_by_id(follower.id)) + |> get("/api/v1/timelines/home") + + expected_activity_id = reblog.id + assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) + end + test "following / unfollowing errors" do user = insert(:user) @@ -1665,7 +1709,7 @@ test "muting / unmuting a user", %{conn: conn} do assert %{"id" => _id, "muting" => true} = json_response(conn, 200) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) conn = build_conn() @@ -1720,7 +1764,7 @@ test "blocking / unblocking a user", %{conn: conn} do assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) conn = build_conn() @@ -1944,6 +1988,199 @@ test "returns the favorites of a user", %{conn: conn} do assert [] = json_response(third_conn, 200) end + describe "getting favorites timeline of specified user" do + setup do + [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) + [current_user: current_user, user: user] + end + + test "returns list of statuses favorited by specified user", %{ + conn: conn, + current_user: current_user, + user: user + } do + [activity | _] = insert_pair(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + [like] = response + + assert length(response) == 1 + assert like["id"] == activity.id + end + + test "returns favorites for specified user_id when user is not logged in", %{ + conn: conn, + user: user + } do + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + end + + test "returns favorited DM only when user is logged in and he is one of recipients", %{ + conn: conn, + current_user: current_user, + user: user + } do + {:ok, direct} = + CommonAPI.post(current_user, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + + anonymous_response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(anonymous_response) == 0 + end + + test "does not return others' favorited DM when user is not one of recipients", %{ + conn: conn, + current_user: current_user, + user: user + } do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_two, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 0 + end + + test "paginates favorites using since_id and max_id", %{ + conn: conn, + current_user: current_user, + user: user + } do + activities = insert_list(10, :note_activity) + + Enum.each(activities, fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + third_activity = Enum.at(activities, 2) + seventh_activity = Enum.at(activities, 6) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ + since_id: third_activity.id, + max_id: seventh_activity.id + }) + |> json_response(:ok) + + assert length(response) == 3 + refute third_activity in response + refute seventh_activity in response + end + + test "limits favorites using limit parameter", %{ + conn: conn, + current_user: current_user, + user: user + } do + 7 + |> insert_list(:note_activity) + |> Enum.each(fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) + |> json_response(:ok) + + assert length(response) == 3 + end + + test "returns empty response when user does not have any favorited statuses", %{ + conn: conn, + current_user: current_user, + user: user + } do + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "returns 404 error when specified user is not exist", %{conn: conn} do + conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "returns 403 error when user has hidden own favorites", %{ + conn: conn, + current_user: current_user + } do + user = insert(:user, %{info: %{hide_favorites: true}}) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + + test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do + user = insert(:user) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert user.info.hide_favorites + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + end + describe "updating credentials" do test "updates the user's bio", %{conn: conn} do user = insert(:user) @@ -1977,6 +2214,78 @@ test "updates the user's locking status", %{conn: conn} do assert user["locked"] == true end + test "updates the user's default scope", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) + + assert user = json_response(conn, 200) + assert user["source"]["privacy"] == "cofe" + end + + test "updates the user's hide_followers status", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"}) + + assert user = json_response(conn, 200) + assert user["pleroma"]["hide_followers"] == true + end + + test "updates the user's hide_follows status", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"}) + + assert user = json_response(conn, 200) + assert user["pleroma"]["hide_follows"] == true + end + + test "updates the user's hide_favorites status", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) + + assert user = json_response(conn, 200) + assert user["pleroma"]["hide_favorites"] == true + end + + test "updates the user's show_role status", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"}) + + assert user = json_response(conn, 200) + assert user["source"]["pleroma"]["show_role"] == false + end + + test "updates the user's no_rich_text status", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) + + assert user = json_response(conn, 200) + assert user["source"]["pleroma"]["no_rich_text"] == true + end + test "updates the user's name", %{conn: conn} do user = insert(:user) @@ -2080,7 +2389,7 @@ test "get instance stats", %{conn: conn} do {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"}) # Stats should count users with missing or nil `info.deactivated` value - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) info_change = Changeset.change(user.info, %{deactivated: nil}) {:ok, _user} = diff --git a/test/web/mastodon_api/notification_view_test.exs b/test/web/mastodon_api/notification_view_test.exs index f2c1eb76c..977ea1e87 100644 --- a/test/web/mastodon_api/notification_view_test.exs +++ b/test/web/mastodon_api/notification_view_test.exs @@ -21,7 +21,7 @@ test "Mention notification" do mentioned_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"}) {:ok, [notification]} = Notification.create_notifications(activity) - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) expected = %{ id: to_string(notification.id), diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index a02c7c210..5fddc6c58 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Bookmark alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -128,6 +129,7 @@ test "a note activity" do pleroma: %{ local: true, conversation_id: convo_id, + in_reply_to_account_acct: nil, content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])}, spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])} } @@ -152,6 +154,25 @@ test "tells if the message is muted for some reason" do assert status.muted == true end + test "tells if the status is bookmarked" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"}) + status = StatusView.render("status.json", %{activity: activity}) + + assert status.bookmarked == false + + status = StatusView.render("status.json", %{activity: activity, for: user}) + + assert status.bookmarked == false + + {:ok, _bookmark} = Bookmark.create(user.id, activity.id) + + status = StatusView.render("status.json", %{activity: activity, for: user}) + + assert status.bookmarked == true + end + test "a reply" do note = insert(:note_activity) user = insert(:user) @@ -178,7 +199,7 @@ test "contains mentions" do status = StatusView.render("status.json", %{activity: activity}) - actor = User.get_by_ap_id(activity.actor) + actor = User.get_cached_by_ap_id(activity.actor) assert status.mentions == Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end) diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index fb505fab3..6e96537ec 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -365,6 +365,27 @@ test "renders authentication page", %{app: app, conn: conn} do assert html_response(conn, 200) =~ ~s(type="submit") end + test "properly handles internal calls with `authorization`-wrapped params", %{ + app: app, + conn: conn + } do + conn = + get( + conn, + "/oauth/authorize", + %{ + "authorization" => %{ + "response_type" => "code", + "client_id" => app.client_id, + "redirect_uri" => app.redirect_uris, + "scope" => "read" + } + } + ) + + assert html_response(conn, 200) =~ ~s(type="submit") + end + test "renders authentication page if user is already authenticated but `force_login` is tru-ish", %{app: app, conn: conn} do token = insert(:oauth_token, app_id: app.id) diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 2950f11c0..7441e5fce 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory alias Pleroma.Object - alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.OStatus.ActivityRepresenter @@ -41,7 +40,8 @@ test "decodes a salmon with a changed magic key", %{conn: conn} do assert response(conn, 200) # Set a wrong magic-key for a user so it has to refetch - salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1") + salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1") + # Wrong key info_cng = User.Info.remote_user_creation(salmon_user.info, %{ @@ -52,7 +52,7 @@ test "decodes a salmon with a changed magic key", %{conn: conn} do salmon_user |> Ecto.Changeset.change() |> Ecto.Changeset.put_embed(:info, info_cng) - |> Repo.update() + |> User.update_and_set_cache() conn = build_conn() @@ -86,7 +86,7 @@ test "returns 404 for a missing feed", %{conn: conn} do test "gets an object", %{conn: conn} do note_activity = insert(:note_activity) - user = User.get_by_ap_id(note_activity.data["actor"]) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"])) url = "/objects/#{uuid}" diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs index 50467c71f..2916caf8d 100644 --- a/test/web/ostatus/ostatus_test.exs +++ b/test/web/ostatus/ostatus_test.exs @@ -30,7 +30,7 @@ test "handle incoming note - GS, Salmon" do {:ok, [activity]} = OStatus.handle_incoming(incoming) object = Object.normalize(activity.data["object"]) - user = User.get_by_ap_id(activity.data["actor"]) + user = User.get_cached_by_ap_id(activity.data["actor"]) assert user.info.note_count == 1 assert activity.data["type"] == "Create" assert object.data["type"] == "Note" @@ -296,8 +296,8 @@ test "handle incoming follows" do assert activity.data["object"] == "https://pawoo.net/users/pekorino" refute activity.local - follower = User.get_by_ap_id(activity.data["actor"]) - followed = User.get_by_ap_id(activity.data["object"]) + follower = User.get_cached_by_ap_id(activity.data["actor"]) + followed = User.get_cached_by_ap_id(activity.data["object"]) assert User.following?(follower, followed) end @@ -320,8 +320,8 @@ test "handle incoming unfollows with existing follow" do assert activity.data["object"]["object"] == "https://pawoo.net/users/pekorino" refute activity.local - follower = User.get_by_ap_id(activity.data["actor"]) - followed = User.get_by_ap_id(activity.data["object"]["object"]) + follower = User.get_cached_by_ap_id(activity.data["actor"]) + followed = User.get_cached_by_ap_id(activity.data["object"]["object"]) refute User.following?(follower, followed) end @@ -355,7 +355,7 @@ test "tries to use the information in poco fields" do {:ok, user} = OStatus.find_or_make_user(uri) - user = Pleroma.User.get_by_id(user.id) + user = Pleroma.User.get_cached_by_id(user.id) assert user.name == "Constance Variable" assert user.nickname == "lambadalambda@social.heldscal.la" assert user.local == false diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 6bac2c9f6..1e948086a 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -5,6 +5,8 @@ defmodule Pleroma.Web.Push.ImplTest do use Pleroma.DataCase + alias Pleroma.Object + alias Pleroma.Web.CommonAPI alias Pleroma.Web.Push.Impl alias Pleroma.Web.Push.Subscription @@ -52,16 +54,12 @@ test "performs sending notifications" do data: %{alerts: %{"follow" => true, "mention" => false}} ) + {:ok, activity} = CommonAPI.post(user, %{"status" => " "Create", - "actor" => user.ap_id, - "object" => %{"content" => " + "Lorem ipsum dolor sit amet, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." + }) + + object = Object.normalize(activity) + assert Impl.format_body( %{ - activity: %{ - data: %{ - "type" => "Create", - "object" => %{ - "content" => - "Lorem ipsum dolor sit amet, consectetur :bear: adipiscing elit. Fusce sagittis finibus turpis." - } - } - } + activity: activity }, - %{nickname: "Bob"} + user, + object ) == "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..." end test "renders body for follow activity" do - assert Impl.format_body(%{activity: %{data: %{"type" => "Follow"}}}, %{nickname: "Bob"}) == + user = insert(:user, nickname: "Bob") + other_user = insert(:user) + {:ok, _, _, activity} = CommonAPI.follow(user, other_user) + object = Object.normalize(activity) + + assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you" end test "renders body for announce activity" do user = insert(:user) - note = - insert(:note, %{ - data: %{ - "content" => - "Lorem ipsum dolor sit amet, consectetur :bear: adipiscing elit. Fusce sagittis finibus turpis." - } + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => + "Lorem ipsum dolor sit amet, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." }) - note_activity = insert(:note_activity, %{note: note}) - announce_activity = insert(:announce_activity, %{user: user, note_activity: note_activity}) + {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) + object = Object.normalize(activity) - assert Impl.format_body(%{activity: announce_activity}, user) == + assert Impl.format_body(%{activity: announce_activity}, user, object) == "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..." end test "renders body for like activity" do - assert Impl.format_body(%{activity: %{data: %{"type" => "Like"}}}, %{nickname: "Bob"}) == + user = insert(:user, nickname: "Bob") + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => + "Lorem ipsum dolor sit amet, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." + }) + + {:ok, activity, _} = CommonAPI.favorite(activity.id, user) + object = Object.normalize(activity) + + assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" end end diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs index 35503259b..7532578ca 100644 --- a/test/web/salmon/salmon_test.exs +++ b/test/web/salmon/salmon_test.exs @@ -99,7 +99,7 @@ test "it pushes an activity to remote accounts it's addressed to" do } {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]}) - user = User.get_by_ap_id(activity.data["actor"]) + user = User.get_cached_by_ap_id(activity.data["actor"]) {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) poster = fn url, _data, _headers -> diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index 9a9630c19..43ad71a16 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -270,7 +270,7 @@ test "returns 200 to authenticated request when the instance is public", test "returns one status", %{conn: conn} do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey!"}) - actor = Repo.get_by!(User, ap_id: activity.data["actor"]) + actor = User.get_cached_by_ap_id(activity.data["actor"]) conn = conn @@ -720,7 +720,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> with_credentials(current_user.nickname, "test") |> post("/api/friendships/create.json", %{user_id: followed.id}) - current_user = User.get_by_id(current_user.id) + current_user = User.get_cached_by_id(current_user.id) assert User.ap_followers(followed) in current_user.following assert json_response(conn, 200) == @@ -735,8 +735,8 @@ test "for restricted account", %{conn: conn, user: current_user} do |> with_credentials(current_user.nickname, "test") |> post("/api/friendships/create.json", %{user_id: followed.id}) - current_user = User.get_by_id(current_user.id) - followed = User.get_by_id(followed.id) + current_user = User.get_cached_by_id(current_user.id) + followed = User.get_cached_by_id(followed.id) refute User.ap_followers(followed) in current_user.following @@ -765,7 +765,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> with_credentials(current_user.nickname, "test") |> post("/api/friendships/destroy.json", %{user_id: followed.id}) - current_user = User.get_by_id(current_user.id) + current_user = User.get_cached_by_id(current_user.id) assert current_user.following == [current_user.ap_id] assert json_response(conn, 200) == @@ -789,7 +789,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> with_credentials(current_user.nickname, "test") |> post("/api/blocks/create.json", %{user_id: blocked.id}) - current_user = User.get_by_id(current_user.id) + current_user = User.get_cached_by_id(current_user.id) assert User.blocks?(current_user, blocked) assert json_response(conn, 200) == @@ -816,7 +816,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> with_credentials(current_user.nickname, "test") |> post("/api/blocks/destroy.json", %{user_id: blocked.id}) - current_user = User.get_by_id(current_user.id) + current_user = User.get_cached_by_id(current_user.id) assert current_user.info.blocks == [] assert json_response(conn, 200) == @@ -847,7 +847,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> with_credentials(current_user.nickname, "test") |> post("/api/qvitter/update_avatar.json", %{img: avatar_image}) - current_user = User.get_by_id(current_user.id) + current_user = User.get_cached_by_id(current_user.id) assert is_map(current_user.avatar) assert json_response(conn, 200) == @@ -956,7 +956,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> post(request_path) activity = Activity.get_by_id(note_activity.id) - activity_user = User.get_by_ap_id(note_activity.data["actor"]) + activity_user = User.get_cached_by_ap_id(note_activity.data["actor"]) assert json_response(response, 200) == ActivityView.render("activity.json", %{ @@ -994,7 +994,7 @@ test "with credentials", %{conn: conn, user: current_user} do |> post(request_path) activity = Activity.get_by_id(note_activity.id) - activity_user = User.get_by_ap_id(note_activity.data["actor"]) + activity_user = User.get_cached_by_ap_id(note_activity.data["actor"]) assert json_response(response, 200) == ActivityView.render("activity.json", %{ @@ -1022,7 +1022,7 @@ test "it creates a new user", %{conn: conn} do user = json_response(conn, 200) - fetched_user = User.get_by_nickname("lain") + fetched_user = User.get_cached_by_nickname("lain") assert user == UserView.render("show.json", %{user: fetched_user}) end @@ -1116,7 +1116,7 @@ test "it redirects to root url", %{conn: conn, user: user} do test "it confirms the user account", %{conn: conn, user: user} do get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}") - user = User.get_by_id(user.id) + user = User.get_cached_by_id(user.id) refute user.info.confirmation_pending refute user.info.confirmation_token @@ -1742,7 +1742,7 @@ test "with credentials, valid password and matching new password and confirmatio }) assert json_response(conn, 200) == %{"status" => "success"} - fetched_user = User.get_by_id(current_user.id) + fetched_user = User.get_cached_by_id(current_user.id) assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true end end @@ -1783,8 +1783,8 @@ test "it lists friend requests" do {:ok, _activity} = ActivityPub.follow(other_user, user) - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == false @@ -1823,8 +1823,8 @@ test "it approves a friend request" do {:ok, _activity} = ActivityPub.follow(other_user, user) - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == false @@ -1846,8 +1846,8 @@ test "it denies a friend request" do {:ok, _activity} = ActivityPub.follow(other_user, user) - user = User.get_by_id(user.id) - other_user = User.get_by_id(other_user.id) + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) assert User.following?(other_user, user) == false @@ -1916,7 +1916,7 @@ test "it performs the upload and sets `data[actor]` with AP id of uploader user" describe "POST /api/media/metadata/create" do setup do object = insert(:note) - user = User.get_by_ap_id(object.data["actor"]) + user = User.get_cached_by_ap_id(object.data["actor"]) %{object: object, user: user} end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 5bea1037a..d601c8f1f 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -41,7 +41,7 @@ test "create a status" do input = %{ "status" => - "Hello again, @shp.\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric", + "Hello again, @shp.\nThis is on another :firefox: line. #2hu #epic #phantasmagoric", "media_ids" => [object.id] } @@ -49,7 +49,7 @@ test "create a status" do object = Object.normalize(activity.data["object"]) expected_text = - "Hello again, @shp.<script></script>
This is on another :moominmamma: line.
image.jpg" + "Hello again, @shp.<script></script>
This is on another :firefox: line.
image.jpg" assert get_in(object.data, ["content"]) == expected_text assert get_in(object.data, ["type"]) == "Note" @@ -65,8 +65,7 @@ test "create a status" do assert Enum.member?(get_in(activity.data, ["to"]), "shp") assert activity.local == true - assert %{"moominmamma" => "http://localhost:4001/finmoji/128px/moominmamma-128.png"} = - object.data["emoji"] + assert %{"firefox" => "http://localhost:4001/emoji/Firefox.gif"} = object.data["emoji"] # hashtags assert object.data["tag"] == ["2hu", "epic", "phantasmagoric"] @@ -79,7 +78,7 @@ test "create a status" do assert activity.data["object"] == object.data["id"] - user = User.get_by_ap_id(user.ap_id) + user = User.get_cached_by_ap_id(user.ap_id) assert user.info.note_count == 1 end @@ -130,7 +129,7 @@ test "Follow another user using screen_name" do assert User.ap_followers(followed) in user.following - followed = User.get_by_ap_id(followed.ap_id) + followed = User.get_cached_by_ap_id(followed.ap_id) assert followed.info.follower_count == 1 {:error, msg} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname}) @@ -282,7 +281,7 @@ test "it registers a new user and returns the user." do {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("lain") + fetched_user = User.get_cached_by_nickname("lain") assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) @@ -300,7 +299,7 @@ test "it registers a new user with empty string in bio and returns the user." do {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("lain") + fetched_user = User.get_cached_by_nickname("lain") assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) @@ -395,7 +394,7 @@ test "returns user on success" do {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("vinny") + fetched_user = User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true @@ -418,7 +417,7 @@ test "returns error on invalid token" do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Invalid token" - refute User.get_by_nickname("GrimReaper") + refute User.get_cached_by_nickname("GrimReaper") end test "returns error on expired token" do @@ -438,7 +437,7 @@ test "returns error on expired token" do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" - refute User.get_by_nickname("GrimReaper") + refute User.get_cached_by_nickname("GrimReaper") end end @@ -463,7 +462,7 @@ test "returns error on expired token" do check_fn = fn invite -> data = Map.put(data, "token", invite.token) {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("vinny") + fetched_user = User.get_cached_by_nickname("vinny") assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) @@ -500,7 +499,7 @@ test "returns an error on overdue date", %{data: data} do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" - refute User.get_by_nickname("vinny") + refute User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) refute invite.used @@ -535,7 +534,7 @@ test "returns user on success, after him registration fails" do } {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("vinny") + fetched_user = User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true @@ -556,7 +555,7 @@ test "returns user on success, after him registration fails" do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" - refute User.get_by_nickname("GrimReaper") + refute User.get_cached_by_nickname("GrimReaper") end end @@ -586,7 +585,7 @@ test "returns user on success" do } {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("vinny") + fetched_user = User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) refute invite.used @@ -611,7 +610,7 @@ test "error after max uses" do } {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_by_nickname("vinny") + fetched_user = User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true @@ -631,7 +630,7 @@ test "error after max uses" do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" - refute User.get_by_nickname("GrimReaper") + refute User.get_cached_by_nickname("GrimReaper") end test "returns error on overdue date" do @@ -651,7 +650,7 @@ test "returns error on overdue date" do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" - refute User.get_by_nickname("GrimReaper") + refute User.get_cached_by_nickname("GrimReaper") end test "returns error on with overdue date and after max" do @@ -673,7 +672,7 @@ test "returns error on with overdue date and after max" do {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" - refute User.get_by_nickname("GrimReaper") + refute User.get_cached_by_nickname("GrimReaper") end end @@ -689,7 +688,7 @@ test "it returns the error on registration problems" do {:error, error_object} = TwitterAPI.register_user(data) assert is_binary(error_object[:error]) - refute User.get_by_nickname("lain") + refute User.get_cached_by_nickname("lain") end test "it assigns an integer conversation_id" do @@ -710,7 +709,7 @@ test "fetches a user by uri" do id = "https://mastodon.social/users/lambadalambda" user = insert(:user) {:ok, represented} = TwitterAPI.get_external_profile(user, id) - remote = User.get_by_ap_id(id) + remote = User.get_cached_by_ap_id(id) assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"] diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index c58b49ea4..56474447b 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -245,4 +245,10 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d assert html_response(response, 200) =~ "Log in to follow" end end + + test "GET /api/pleroma/healthcheck", %{conn: conn} do + conn = get(conn, "/api/pleroma/healthcheck") + + assert conn.status in [200, 503] + end end diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs index b5440c612..d84ab7420 100644 --- a/test/web/twitter_api/views/activity_view_test.exs +++ b/test/web/twitter_api/views/activity_view_test.exs @@ -91,16 +91,16 @@ test "a create activity with a html status" do test "a create activity with a summary containing emoji" do {:ok, activity} = CommonAPI.post(insert(:user), %{ - "spoiler_text" => ":woollysocks: meow", + "spoiler_text" => ":firefox: meow", "status" => "." }) result = ActivityView.render("activity.json", activity: activity) - expected = ":woollysocks: meow" + expected = ":firefox: meow" expected_html = - "\"woollysocks\" meow" + "\"firefox\" meow" assert result["summary"] == expected assert result["summary_html"] == expected_html diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 0feaf4b64..c99dbddeb 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -89,29 +89,34 @@ test "A user" do "following" => false, "follows_you" => false, "statusnet_blocking" => false, - "rights" => %{ - "delete_others_notice" => false, - "admin" => false - }, "statusnet_profile_url" => user.ap_id, "cover_photo" => banner, "background_image" => nil, "is_local" => true, "locked" => false, - "default_scope" => "public", - "no_rich_text" => false, "hide_follows" => false, "hide_followers" => false, "fields" => [], "pleroma" => %{ "confirmation_pending" => false, "tags" => [] - } + }, + "rights" => %{"admin" => false, "delete_others_notice" => false}, + "role" => "member" } assert represented == UserView.render("show.json", %{user: user}) end + test "User exposes settings for themselves and only for themselves", %{user: user} do + as_user = UserView.render("show.json", %{user: user, for: user}) + assert as_user["default_scope"] == user.info.default_scope + assert as_user["no_rich_text"] == user.info.no_rich_text + as_stranger = UserView.render("show.json", %{user: user}) + refute as_stranger["default_scope"] + refute as_stranger["no_rich_text"] + end + test "A user for a given other follower", %{user: user} do follower = insert(:user, %{following: [User.ap_followers(user)]}) {:ok, user} = User.update_follower_count(user) @@ -137,24 +142,20 @@ test "A user for a given other follower", %{user: user} do "following" => true, "follows_you" => false, "statusnet_blocking" => false, - "rights" => %{ - "delete_others_notice" => false, - "admin" => false - }, "statusnet_profile_url" => user.ap_id, "cover_photo" => banner, "background_image" => nil, "is_local" => true, "locked" => false, - "default_scope" => "public", - "no_rich_text" => false, "hide_follows" => false, "hide_followers" => false, "fields" => [], "pleroma" => %{ "confirmation_pending" => false, "tags" => [] - } + }, + "rights" => %{"admin" => false, "delete_others_notice" => false}, + "role" => "member" } assert represented == UserView.render("show.json", %{user: user, for: follower}) @@ -186,24 +187,20 @@ test "A user that follows you", %{user: user} do "following" => false, "follows_you" => true, "statusnet_blocking" => false, - "rights" => %{ - "delete_others_notice" => false, - "admin" => false - }, "statusnet_profile_url" => follower.ap_id, "cover_photo" => banner, "background_image" => nil, "is_local" => true, "locked" => false, - "default_scope" => "public", - "no_rich_text" => false, "hide_follows" => false, "hide_followers" => false, "fields" => [], "pleroma" => %{ "confirmation_pending" => false, "tags" => [] - } + }, + "rights" => %{"admin" => false, "delete_others_notice" => false}, + "role" => "member" } assert represented == UserView.render("show.json", %{user: follower, for: user}) @@ -272,27 +269,23 @@ test "A blocked user for the blocker" do "following" => false, "follows_you" => false, "statusnet_blocking" => true, - "rights" => %{ - "delete_others_notice" => false, - "admin" => false - }, "statusnet_profile_url" => user.ap_id, "cover_photo" => banner, "background_image" => nil, "is_local" => true, "locked" => false, - "default_scope" => "public", - "no_rich_text" => false, "hide_follows" => false, "hide_followers" => false, "fields" => [], "pleroma" => %{ "confirmation_pending" => false, "tags" => [] - } + }, + "rights" => %{"admin" => false, "delete_others_notice" => false}, + "role" => "member" } - blocker = User.get_by_id(blocker.id) + blocker = User.get_cached_by_id(blocker.id) assert represented == UserView.render("show.json", %{user: user, for: blocker}) end diff --git a/uploads/.gitignore b/uploads/.gitignore new file mode 100644 index 000000000..523e584a7 --- /dev/null +++ b/uploads/.gitignore @@ -0,0 +1,3 @@ +# Git will ignore everything in this directory except this file. +* +!.gitignore