diff --git a/.gitignore b/.gitignore index b71dfa9fa..72fe2ce43 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ /.elixir_ls /test/fixtures/test_tmp.txt /test/fixtures/image_tmp.jpg +/test/tmp/ /doc +/instance # Prevent committing custom emojis /priv/static/emoji/custom/* @@ -31,4 +33,4 @@ erl_crash.dump .env # Editor config -/.vscode +/.vscode/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7c240d9f1..8ea652086 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,7 +13,6 @@ cache: key: ${CI_COMMIT_REF_SLUG} paths: - deps - - _build stages: - lint - test diff --git a/config/config.exs b/config/config.exs index 5477c0c0e..73242e4ff 100644 --- a/config/config.exs +++ b/config/config.exs @@ -10,6 +10,13 @@ config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes +config :pleroma, Pleroma.Captcha, + enabled: false, + seconds_retained: 180, + method: Pleroma.Captcha.Kocaptcha + +config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch" + # Upload configuration config :pleroma, Pleroma.Upload, uploader: Pleroma.Uploaders.Local, @@ -50,6 +57,15 @@ # Configures the endpoint config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], + http: [ + dispatch: [ + {:_, + [ + {"/api/v1/streaming", Elixir.Pleroma.Web.MastodonAPI.WebsocketHandler, []}, + {:_, Plug.Adapters.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} + ]} + ] + ], protocol: "https", secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", signing_salt: "CqaoopA2", @@ -65,6 +81,7 @@ config :mime, :types, %{ "application/xml" => ["xml"], "application/xrd+xml" => ["xrd+xml"], + "application/jrd+json" => ["jrd+json"], "application/activity+json" => ["activity+json"], "application/ld+json" => ["activity+json"] } @@ -93,6 +110,7 @@ public: true, quarantined_instances: [], managed_config: true, + static_dir: "instance/static/", allowed_post_formats: [ "text/plain", "text/html", diff --git a/config/config.md b/config/config.md index d4dad77b1..edabd6e0f 100644 --- a/config/config.md +++ b/config/config.md @@ -7,7 +7,7 @@ If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherw * `uploader`: Select which `Pleroma.Uploaders` to use * `filters`: List of `Pleroma.Upload.Filter` to use. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host. -* `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it. +* `proxy_remote`: If you\'re using a remote uploader, Pleroma will proxy media requests instead of redirecting to it. * `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation. Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. @@ -67,7 +67,8 @@ config :pleroma, Pleroma.Mailer, * `avatar_upload_limit`: File size limit of user’s profile avatars * `background_upload_limit`: File size limit of user’s profile backgrounds * `banner_upload_limit`: File size limit of user’s profile banners -* `registrations_open`: Enable registrations for anyone, invitations can be used when false. +* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false. +* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`). * `federating`: Enable federation with other instances * `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance * `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default: @@ -162,3 +163,15 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen * ``subject``: a mailto link for the administrative contact. It’s best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise can’t respond, someone else on the list can. * ``public_key``: VAPID public key * ``private_key``: VAPID private key + +## Pleroma.Captcha +* `enabled`: Whether the captcha should be shown on registration +* `method`: The method/service to use for captcha +* `seconds_retained`: The time in seconds for which the captcha is valid (stored in the cache) + +### Pleroma.Captcha.Kocaptcha +Kocaptcha is a very simple captcha service with a single API endpoint, +the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint +`https://captcha.kotobank.ch` is hosted by the developer. + +* `endpoint`: the kocaptcha endpoint to use \ No newline at end of file diff --git a/config/dev.exs b/config/dev.exs index 080a2f8db..8f89aa03c 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -12,6 +12,7 @@ protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192] ], protocol: "http", + secure_cookie_flag: false, debug_errors: true, code_reloader: true, check_origin: false, diff --git a/config/test.exs b/config/test.exs index 5c6acfead..51aace407 100644 --- a/config/test.exs +++ b/config/test.exs @@ -4,7 +4,14 @@ # you can enable the server option below. config :pleroma, Pleroma.Web.Endpoint, http: [port: 4001], - server: false + url: [port: 4001], + server: true + +# Disable captha for tests +config :pleroma, Pleroma.Captcha, + enabled: true, + # A fake captcha service for tests + method: Pleroma.Captcha.Mock # Print only warnings and errors during test config :logger, level: :warn diff --git a/installation/netbsd/rc.d/pleroma b/installation/netbsd/rc.d/pleroma new file mode 100755 index 000000000..1114668ee --- /dev/null +++ b/installation/netbsd/rc.d/pleroma @@ -0,0 +1,57 @@ +#!/bin/sh +# PROVIDE: pleroma +# REQUIRE: DAEMON pgsql + +if [ -f /etc/rc.subr ]; then + . /etc/rc.subr +fi + +name="pleroma" +rcvar=${name} +command="/usr/pkg/bin/elixir" +command_args="--detached -S /usr/pkg/bin/mix phx.server" +start_precmd="ulimit -n unlimited" +pidfile="/dev/null" + +pleroma_chdir="${pleroma_home}/pleroma" +pleroma_env="HOME=${pleroma_home} MIX_ENV=prod" + +check_pidfile() +{ + pid=$(pgrep -U "${pleroma_user}" /bin/beam.smp$) + echo -n "${pid}" +} + +if [ -f /etc/rc.subr -a -d /etc/rc.d -a -f /etc/rc.d/DAEMON ]; then + # newer NetBSD + load_rc_config ${name} + run_rc_command "$1" +else + # ancient NetBSD, Solaris and illumos, Linux, etc... + cmd=${1:-start} + + case ${cmd} in + start) + echo "Starting ${name}." + ${start_cmd} + ;; + + stop) + echo "Stopping ${name}." + check_pidfile + ! [ -n ${pid} ] && kill ${pid} + ;; + + restart) + ( $0 stop ) + sleep 5 + $0 start + ;; + + *) + echo 1>&2 "Usage: $0 [start|stop|restart]" + exit 1 + ;; + esac + exit 0 +fi diff --git a/installation/openbsd/httpd.conf b/installation/openbsd/httpd.conf new file mode 100644 index 000000000..82f4803fd --- /dev/null +++ b/installation/openbsd/httpd.conf @@ -0,0 +1,36 @@ +# +# Default httpd.conf file for Pleroma on OpenBSD +# Simple installation instructions +# 1. Place file in /etc +# 2. Replace with your public IP address +# 3. If using IPv6, uncomment IPv6 lines and replace with your public IPv6 address +# 4. Check file using 'doas httpd -n' +# 5. Enable and start httpd: +# # doas rcctl enable httpd +# # doas rcctl start httpd +# + +ext_inet="" +#ext_inet6="" + +server "default" { + listen on $ext_inet port 80 # Comment to disable listening on IPv4 +# listen on $ext_inet6 port 80 # Comment to disable listening on IPv6 + listen on 127.0.0.1 port 80 # Do NOT comment this line + + log syslog + directory no index + + location "/.well-known/acme-challenge/*" { + root "/acme" + request strip 2 + } + + location "/robots.txt" { root "/htdocs/local/" } + location "/*" { block return 302 "https://$HTTP_HOST$REQUEST_URI" } +} + +types { + include "/usr/share/misc/mime.types" +} + diff --git a/installation/openbsd/rc.d/pleromad b/installation/openbsd/rc.d/pleromad new file mode 100755 index 000000000..19ac4bb51 --- /dev/null +++ b/installation/openbsd/rc.d/pleromad @@ -0,0 +1,34 @@ +#!/bin/ksh +# +# Default init file for Pleroma on OpenBSD +# +# Simple installation instructions: +# 1. Install Pleroma per wiki instructions +# 2. Place this pleromad file in /etc/rc.d +# 3. Enable and start Pleroma +# # doas rcctl enable pleromad +# # doas rcctl start pleromad +# + +daemon="/usr/local/bin/elixir" +daemon_flags="--detached -S /usr/local/bin/mix phx.server" +daemon_user="_pleroma" + +. /etc/rc.d/rc.subr + +rc_reload=NO +pexp="phx.server" + +rc_check() { + pgrep -q -U _pleroma -f "phx.server" +} + +rc_start() { + ${rcexec} "cd pleroma; ${daemon} ${daemon_flags}" +} + +rc_stop() { + pkill -q -U _pleroma -f "phx.server" +} + +rc_cmd $1 diff --git a/installation/openbsd/relayd.conf b/installation/openbsd/relayd.conf new file mode 100644 index 000000000..31c2c1129 --- /dev/null +++ b/installation/openbsd/relayd.conf @@ -0,0 +1,44 @@ +# +# Default relayd.conf file for Pleroma on OpenBSD +# Simple installation instructions: +# 1. Place in /etc +# 2. Replace with your public IPv4 address +# 3. If using IPv6i, uncomment IPv6 lines and replace with your public IPv6 address +# 4. Check file using 'doas relayd -n' +# 5. Reload/start relayd +# # doas rcctl enable relayd +# # doas rcctl start relayd +# + +ext_inet="" +#ext_inet6="" + +table { 127.0.0.1 } +table { 127.0.0.1 } + +http protocol plerup { # Protocol for upstream pleroma server + #tcp { nodelay, sack, socket buffer 65536, backlog 128 } # Uncomment and adjust as you see fit + tls ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA0-POLY1305" + tls ecdhe secp384r1 + + # Forward some paths to the local server (as pleroma won't respond to them as you might want) + pass request quick path "/robots.txt" forward to + + # Append a bunch of headers + match request header append "X-Forwarded-For" value "$REMOTE_ADDR" # This two header and the next one are not strictl required by pleroma but adding them won't hurt + match request header append "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT" + + match request header append "Connection" value "upgrade" + +} + +relay wwwtls { + listen on $ext_inet port https tls # Comment to disable listening on IPv4 +# listen on $ext_inet6 port https tls # Comment to disable listening on IPv6 + + protocol plerup + + forward to port 4000 check http "/" code 200 + forward to port 80 check http "/robots.txt" code 200 +} + diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 3be856115..02e1ce27d 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -58,12 +58,15 @@ def run(["gen" | rest]) do proceed? = Enum.empty?(will_overwrite) or Keyword.get(options, :force, false) unless not proceed? do - domain = - Common.get_option( - options, - :domain, - "What domain will your instance use? (e.g pleroma.soykaf.com)" - ) + [domain, port | _] = + String.split( + Common.get_option( + options, + :domain, + "What domain will your instance use? (e.g pleroma.soykaf.com)" + ), + ":" + ) ++ [443] name = Common.get_option( @@ -104,6 +107,7 @@ def run(["gen" | rest]) do EEx.eval_file( "sample_config.eex" |> Path.expand(__DIR__), domain: domain, + port: port, email: email, name: name, dbhost: dbhost, diff --git a/lib/mix/tasks/pleroma/sample_config.eex b/lib/mix/tasks/pleroma/sample_config.eex index 0cd572797..740b9f8d1 100644 --- a/lib/mix/tasks/pleroma/sample_config.eex +++ b/lib/mix/tasks/pleroma/sample_config.eex @@ -6,7 +6,7 @@ use Mix.Config config :pleroma, Pleroma.Web.Endpoint, - url: [host: "<%= domain %>", scheme: "https", port: 443], + url: [host: "<%= domain %>", scheme: "https", port: <%= port %>], secret_key_base: "<%= secret %>" config :pleroma, :instance, diff --git a/lib/mix/tasks/pleroma/sample_psql.eex b/lib/mix/tasks/pleroma/sample_psql.eex index c89b34ef2..f0ac05e57 100644 --- a/lib/mix/tasks/pleroma/sample_psql.eex +++ b/lib/mix/tasks/pleroma/sample_psql.eex @@ -1,6 +1,6 @@ -CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>'; -CREATE DATABASE pleroma_dev OWNER pleroma; -\c pleroma_dev; +CREATE USER <%= dbuser %> WITH ENCRYPTED PASSWORD '<%= dbpass %>'; +CREATE DATABASE <%= dbname %> OWNER <%= dbuser %>; +\c <%= dbname %>; --Extensions made by ecto.migrate that need superuser access CREATE EXTENSION IF NOT EXISTS citext; CREATE EXTENSION IF NOT EXISTS pg_trgm; diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index fe6e6935f..3d30e3a81 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -94,16 +94,14 @@ def run(["new", nickname, email | rest]) do unless not proceed? do Common.start_pleroma() - params = - %{ - nickname: nickname, - email: email, - password: password, - password_confirmation: password, - name: name, - bio: bio - } - |> IO.inspect() + params = %{ + nickname: nickname, + email: email, + password: password, + password_confirmation: password, + name: name, + bio: bio + } user = User.register_changeset(%User{}, params) Repo.insert!(user) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 8705395a4..e15991957 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -24,6 +24,7 @@ def start(_type, _args) do # Start the Ecto repository supervisor(Pleroma.Repo, []), worker(Pleroma.Emoji, []), + worker(Pleroma.Captcha, []), worker( Cachex, [ diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex new file mode 100644 index 000000000..5630f6b57 --- /dev/null +++ b/lib/pleroma/captcha/captcha.ex @@ -0,0 +1,66 @@ +defmodule Pleroma.Captcha do + use GenServer + + @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] + + @doc false + def start_link() do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @doc false + def init(_) do + # Create a ETS table to store captchas + ets_name = Module.concat(method(), Ets) + ^ets_name = :ets.new(Module.concat(method(), Ets), @ets_options) + + # Clean up old captchas every few minutes + seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) + Process.send_after(self(), :cleanup, 1000 * seconds_retained) + + {:ok, nil} + end + + @doc """ + Ask the configured captcha service for a new captcha + """ + def new() do + GenServer.call(__MODULE__, :new) + end + + @doc """ + Ask the configured captcha service to validate the captcha + """ + def validate(token, captcha) do + GenServer.call(__MODULE__, {:validate, token, captcha}) + end + + @doc false + def handle_call(:new, _from, state) do + enabled = Pleroma.Config.get([__MODULE__, :enabled]) + + if !enabled do + {:reply, %{type: :none}, state} + else + {:reply, method().new(), state} + end + end + + @doc false + def handle_call({:validate, token, captcha}, _from, state) do + {:reply, method().validate(token, captcha), state} + end + + @doc false + def handle_info(:cleanup, state) do + :ok = method().cleanup() + + seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) + # Schedule the next clenup + Process.send_after(self(), :cleanup, 1000 * seconds_retained) + + {:noreply, state} + end + + defp method, do: Pleroma.Config.get!([__MODULE__, :method]) +end diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex new file mode 100644 index 000000000..8d0b76f86 --- /dev/null +++ b/lib/pleroma/captcha/captcha_service.ex @@ -0,0 +1,28 @@ +defmodule Pleroma.Captcha.Service do + @doc """ + Request new captcha from a captcha service. + + Returns: + + Service-specific data for using the newly created captcha + """ + @callback new() :: map + + @doc """ + Validated the provided captcha solution. + + Arguments: + * `token` the captcha is associated with + * `captcha` solution of the captcha to validate + + Returns: + + `true` if captcha is valid, `false` if not + """ + @callback validate(token :: String.t(), captcha :: String.t()) :: boolean + + @doc """ + This function is called periodically to clean up old captchas + """ + @callback cleanup() :: :ok +end diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex new file mode 100644 index 000000000..51900d123 --- /dev/null +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -0,0 +1,67 @@ +defmodule Pleroma.Captcha.Kocaptcha do + alias Calendar.DateTime + + alias Pleroma.Captcha.Service + @behaviour Service + + @ets __MODULE__.Ets + + @impl Service + def new() do + endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) + + case Tesla.get(endpoint <> "/new") do + {:error, _} -> + %{error: "Kocaptcha service unavailable"} + + {:ok, res} -> + json_resp = Poison.decode!(res.body) + + token = json_resp["token"] + + true = + :ets.insert( + @ets, + {token, json_resp["md5"], DateTime.now_utc() |> DateTime.Format.unix()} + ) + + %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]} + end + end + + @impl Service + def validate(token, captcha) do + with false <- is_nil(captcha), + [{^token, saved_md5, _}] <- :ets.lookup(@ets, token), + true <- :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(saved_md5) do + # Clear the saved value + :ets.delete(@ets, token) + + true + else + _ -> false + end + end + + @impl Service + def cleanup() do + seconds_retained = Pleroma.Config.get!([Pleroma.Captcha, :seconds_retained]) + # If the time in ETS is less than current_time - seconds_retained, then the time has + # already passed + delete_after = + DateTime.subtract!(DateTime.now_utc(), seconds_retained) |> DateTime.Format.unix() + + :ets.select_delete( + @ets, + [ + { + {:_, :_, :"$1"}, + [{:<, :"$1", {:const, delete_after}}], + [true] + } + ] + ) + + :ok + end +end diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 9cdf002f3..7e3e9b020 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -37,4 +37,30 @@ def password_reset_email(user, password_reset_token) when is_binary(password_res |> subject("Password reset") |> html_body(html_body) end + + def user_invitation_email( + user, + %Pleroma.UserInviteToken{} = user_invite_token, + to_email, + to_name \\ nil + ) do + registration_url = + Router.Helpers.redirect_url( + Endpoint, + :registration_page, + user_invite_token.token + ) + + html_body = """ +

You are invited to #{instance_name()}

+

#{user.name} invites you to join #{instance_name()}, an instance of Pleroma federated social networking platform.

+

Click the following link to register: accept invitation.

+ """ + + new() + |> to(recipient(to_email, to_name)) + |> from(sender()) + |> subject("Invitation to #{instance_name()}") + |> html_body(html_body) + end end diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 1d5dc0c99..8024d97c2 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Formatter do alias Pleroma.Emoji @tag_regex ~r/\#\w+/u + @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ + def parse_tags(text, data \\ %{}) do Regex.scan(@tag_regex, text) |> Enum.map(fn ["#" <> tag = full_tag] -> {full_tag, String.downcase(tag)} end) @@ -76,6 +78,18 @@ def html_escape(text, "text/plain") do |> Enum.join("") end + @doc """ + Escapes a special characters in mention names. + """ + @spec mentions_escape(String.t(), list({String.t(), any()})) :: String.t() + def mentions_escape(text, mentions) do + mentions + |> Enum.reduce(text, fn {name, _}, acc -> + escape_name = String.replace(name, @markdown_characters_regex, "\\\\\\1") + String.replace(acc, name, escape_name) + end) + end + @doc "changes scheme:... urls to html links" def add_links({subs, text}) do links = diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 8a0333461..583f05aeb 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -17,15 +17,9 @@ def filter_tags(html, nil) do end) end - def filter_tags(html, scrubber) do - html |> Scrubber.scrub(scrubber) - end - + def filter_tags(html, scrubber), do: Scrubber.scrub(html, scrubber) def filter_tags(html), do: filter_tags(html, nil) - - def strip_tags(html) do - html |> Scrubber.scrub(Scrubber.StripTags) - end + def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags) end defmodule Pleroma.HTML.Scrubber.TwitterText do diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index f0442ca15..b5326d97b 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -11,7 +11,8 @@ def call(conn, _opts) do else conn |> put_status(404) - |> Phoenix.Controller.render(Pleroma.Web.ErrorView, "404.json") + |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView) + |> Phoenix.Controller.render("404.json") |> halt() end end diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex new file mode 100644 index 000000000..46ee77e11 --- /dev/null +++ b/lib/pleroma/plugs/instance_static.ex @@ -0,0 +1,54 @@ +defmodule Pleroma.Plugs.InstanceStatic do + @moduledoc """ + This is a shim to call `Plug.Static` but with runtime `from` configuration. + + Mountpoints are defined directly in the module to avoid calling the configuration for every request including non-static ones. + """ + @behaviour Plug + + def file_path(path) do + instance_path = + Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path) + + if File.exists?(instance_path) do + instance_path + else + Path.join(Application.app_dir(:pleroma, "priv/static/"), path) + end + end + + @only ~w(index.html static emoji packs sounds images instance favicon.png) + + def init(opts) do + opts + |> Keyword.put(:from, "__unconfigured_instance_static_plug") + |> Keyword.put(:at, "/__unconfigured_instance_static_plug") + |> Plug.Static.init() + end + + for only <- @only do + at = Plug.Router.Utils.split("/") + + def call(conn = %{request_path: "/" <> unquote(only) <> _}, opts) do + call_static( + conn, + opts, + unquote(at), + Pleroma.Config.get([:instance, :static_dir], "instance/static") + ) + end + end + + def call(conn, _) do + conn + end + + defp call_static(conn, opts, at, from) do + opts = + opts + |> Map.put(:from, from) + |> Map.put(:at, at) + + Plug.Static.call(conn, opts) + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 28ff08a39..c86ad4afe 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -851,7 +851,7 @@ def tag(nickname, tags) when is_binary(nickname), do: tag(User.get_by_nickname(nickname), tags) def tag(%User{} = user, tags), - do: update_tags(user, Enum.uniq(user.tags ++ normalize_tags(tags))) + do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags))) def untag(user_identifiers, tags) when is_list(user_identifiers) do Repo.transaction(fn -> @@ -862,7 +862,8 @@ def untag(user_identifiers, tags) when is_list(user_identifiers) do def untag(nickname, tags) when is_binary(nickname), do: untag(User.get_by_nickname(nickname), tags) - def untag(%User{} = user, tags), do: update_tags(user, user.tags -- normalize_tags(tags)) + def untag(%User{} = user, tags), + do: update_tags(user, (user.tags || []) -- normalize_tags(tags)) defp update_tags(%User{} = user, new_tags) do {:ok, updated_user} = diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index d81b45b8d..a3785447c 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -149,9 +149,12 @@ def mastodon_profile_update(info, params) do ]) end - def mastodon_settings_update(info, params) do + def mastodon_settings_update(info, settings) do + params = %{settings: settings} + info |> cast(params, [:settings]) + |> validate_required([:settings]) end def set_source_data(info, source_data) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 06c3c7c81..4d73cf219 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -147,6 +147,19 @@ def relay_unfollow(conn, %{"relay_url" => target}) do end end + @doc "Sends registration invite via email" + def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do + with true <- + Pleroma.Config.get([:instance, :invites_enabled]) && + !Pleroma.Config.get([:instance, :registrations_open]), + {:ok, invite_token} <- Pleroma.UserInviteToken.create_token(), + email <- + Pleroma.UserEmail.user_invitation_email(user, invite_token, email, params["name"]), + {:ok, _} <- Pleroma.Mailer.deliver(email) do + json_response(conn, :no_content, "") + end + end + @doc "Get a account registeration invite token (base64 string)" def get_invite_token(conn, _params) do {:ok, token} = Pleroma.UserInviteToken.create_token() diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index 07ddee169..9918d3b49 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -6,10 +6,6 @@ defmodule Pleroma.Web.UserSocket do # channel "room:*", Pleroma.Web.RoomChannel channel("chat:*", Pleroma.Web.ChatChannel) - ## Transports - transport(:websocket, Phoenix.Transports.WebSocket) - # transport :longpoll, Phoenix.Transports.LongPoll - # Socket params are passed from the client and can # be used to verify and authenticate a user. After # verification, you can put default assigns into diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index ce0926b99..142283684 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -112,6 +112,9 @@ def add_attachments(text, attachments) do Enum.join([text | attachment_text], "
") end + @doc """ + Formatting text to plain text. + """ def format_input(text, mentions, tags, "text/plain") do text |> Formatter.html_escape("text/plain") @@ -123,6 +126,9 @@ def format_input(text, mentions, tags, "text/plain") do |> Formatter.finalize() end + @doc """ + Formatting text to html. + """ def format_input(text, mentions, _tags, "text/html") do text |> Formatter.html_escape("text/html") @@ -132,8 +138,12 @@ def format_input(text, mentions, _tags, "text/html") do |> Formatter.finalize() end + @doc """ + Formatting text to markdown. + """ def format_input(text, mentions, tags, "text/markdown") do text + |> Formatter.mentions_escape(mentions) |> Earmark.as_html!() |> Formatter.html_escape("text/html") |> String.replace(~r/\r?\n/, "") diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index c5f9d51d9..d79f61b2e 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -3,8 +3,6 @@ defmodule Pleroma.Web.Endpoint do socket("/socket", Pleroma.Web.UserSocket) - socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket) - # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phoenix.digest @@ -14,6 +12,10 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Plugs.UploadedMedia) + # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files + # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well + plug(Pleroma.Plugs.InstanceStatic, at: "/") + plug( Plug.Static, at: "/", diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 0414d73d8..665b75437 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -226,7 +226,8 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:home_timeline, activities) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def public_timeline(%{assigns: %{user: user}} = conn, params) do @@ -244,7 +245,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only}) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do @@ -259,7 +261,8 @@ def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do conn |> add_link_headers(:user_statuses, activities, params["id"]) - |> render(StatusView, "index.json", %{ + |> put_view(StatusView) + |> render("index.json", %{ activities: activities, for: reading_user, as: :activity @@ -278,13 +281,16 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:dm_timeline, activities) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Repo.get(Activity, id), true <- ActivityPub.visible_for_user?(activity, user) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user}) end end @@ -347,7 +353,9 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do {:ok, activity} = Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end) - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do @@ -363,28 +371,36 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do 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 - try_render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: announce, for: user, as: :activity}) end end 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_activity_by_object_ap_id(id) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end @@ -433,7 +449,10 @@ def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) targets = Repo.all(q) - render(conn, AccountView, "relationships.json", %{user: user, targets: targets}) + + conn + |> put_view(AccountView) + |> render("relationships.json", %{user: user, targets: targets}) end # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. @@ -452,7 +471,10 @@ def update_media(%{assigns: %{user: user}} = conn, data) do |> Repo.update() attachment_data = Map.put(new_data, "id", object.id) - render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) + + conn + |> put_view(StatusView) + |> render("attachment.json", %{attachment: attachment_data}) end end @@ -463,7 +485,10 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do description: Map.get(data, "description") ) do attachment_data = Map.put(object.data, "id", object.id) - render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) + + conn + |> put_view(StatusView) + |> render("attachment.json", %{attachment: attachment_data}) end end @@ -471,7 +496,10 @@ def favourited_by(conn, %{"id" => id}) do with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^likes) users = Repo.all(q) - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + + conn + |> put_view(AccountView) + |> render(AccountView, "accounts.json", %{users: users, as: :user}) else _ -> json(conn, []) end @@ -481,7 +509,10 @@ def reblogged_by(conn, %{"id" => id}) do with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^announces) users = Repo.all(q) - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: users, as: :user}) else _ -> json(conn, []) end @@ -503,7 +534,8 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only}) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do @@ -516,7 +548,9 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do true -> followers end - render(conn, AccountView, "accounts.json", %{users: followers, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: followers, as: :user}) end end @@ -530,13 +564,17 @@ def following(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do true -> followers end - render(conn, AccountView, "accounts.json", %{users: followers, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: followers, as: :user}) end end def follow_requests(%{assigns: %{user: followed}} = conn, _params) do with {:ok, follow_requests} <- User.get_follow_requests(followed) do - render(conn, AccountView, "accounts.json", %{users: follow_requests, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: follow_requests, as: :user}) end end @@ -552,7 +590,9 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id} object: follow_activity.data["id"], type: "Accept" }) do - render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: followed, target: follower}) else {:error, message} -> conn @@ -572,7 +612,9 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d object: follow_activity.data["id"], type: "Reject" }) do - render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: followed, target: follower}) else {:error, message} -> conn @@ -591,7 +633,9 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do follower, followed ) do - render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) else {:error, message} -> conn @@ -604,7 +648,9 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with %User{} = followed <- Repo.get_by(User, nickname: uri), {:ok, follower} <- User.maybe_direct_follow(follower, followed), {:ok, _activity} <- ActivityPub.follow(follower, followed) do - render(conn, AccountView, "account.json", %{user: followed, for: follower}) + conn + |> put_view(AccountView) + |> render("account.json", %{user: followed, for: follower}) else {:error, message} -> conn @@ -617,7 +663,9 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with %User{} = followed <- Repo.get(User, id), {:ok, _activity} <- ActivityPub.unfollow(follower, followed), {:ok, follower, _} <- User.unfollow(follower, followed) do - render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) end end @@ -625,7 +673,9 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do with %User{} = blocked <- Repo.get(User, id), {:ok, blocker} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do - render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> conn @@ -638,7 +688,9 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do with %User{} = blocked <- Repo.get(User, id), {:ok, blocker} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do - render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> conn @@ -763,7 +815,8 @@ def favourites(%{assigns: %{user: user}} = conn, _) do |> Enum.reverse() conn - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def get_lists(%{assigns: %{user: user}} = conn, opts) do @@ -831,7 +884,9 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Pleroma.List{} = list <- Pleroma.List.get(id, user), {:ok, users} = Pleroma.List.get_following(list) do - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: users, as: :user}) end end @@ -864,7 +919,8 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) |> Enum.reverse() conn - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) else _e -> conn @@ -929,7 +985,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do ] }, settings: - Map.get(user.info, :settings) || + user.info.settings || %{ onboarded: true, home: %{ @@ -968,7 +1024,8 @@ def index(%{assigns: %{user: user}} = conn, _params) do conn |> put_layout(false) - |> render(MastodonView, "index.html", %{initial_state: initial_state}) + |> put_view(MastodonView) + |> render("index.html", %{initial_state: initial_state}) else conn |> redirect(to: "/web/login") @@ -978,13 +1035,15 @@ def index(%{assigns: %{user: user}} = conn, _params) do def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do info_cng = User.Info.mastodon_settings_update(user.info, settings) - with changeset <- User.update_changeset(user), + with changeset <- Ecto.Changeset.change(user), changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), {:ok, _user} <- User.update_and_set_cache(changeset) do json(conn, %{}) else e -> - json(conn, %{error: inspect(e)}) + conn + |> put_resp_content_type("application/json") + |> send_resp(500, Jason.encode!(%{"error" => inspect(e)})) end end @@ -1039,7 +1098,9 @@ def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do Logger.debug("Unimplemented, returning unmodified relationship") with %User{} = target <- Repo.get(User, id) do - render(conn, AccountView, "relationship.json", %{user: user, target: target}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: user, target: target}) end end @@ -1240,9 +1301,9 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do end end - def try_render(conn, renderer, target, params) + def try_render(conn, target, params) when is_binary(target) do - res = render(conn, renderer, target, params) + res = render(conn, target, params) if res == nil do conn @@ -1253,7 +1314,7 @@ def try_render(conn, renderer, target, params) end end - def try_render(conn, _, _, _) do + def try_render(conn, _, _) do conn |> put_status(501) |> json(%{error: "Can't display this activity"}) diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex deleted file mode 100644 index 755ac5730..000000000 --- a/lib/pleroma/web/mastodon_api/mastodon_socket.ex +++ /dev/null @@ -1,81 +0,0 @@ -defmodule Pleroma.Web.MastodonAPI.MastodonSocket do - use Phoenix.Socket - - alias Pleroma.Web.OAuth.Token - alias Pleroma.{User, Repo} - - transport( - :websocket, - Phoenix.Transports.WebSocket.Raw, - # We never receive data. - timeout: :infinity - ) - - @spec connect(params :: map(), Phoenix.Socket.t()) :: {:ok, Phoenix.Socket.t()} | :error - def connect(%{"access_token" => token} = params, socket) do - with %Token{user_id: user_id} <- Repo.get_by(Token, token: token), - %User{} = user <- Repo.get(User, user_id), - stream - when stream in [ - "public", - "public:local", - "public:media", - "public:local:media", - "user", - "direct", - "list", - "hashtag" - ] <- params["stream"] do - topic = - case stream do - "hashtag" -> "hashtag:#{params["tag"]}" - "list" -> "list:#{params["list"]}" - _ -> stream - end - - socket = - socket - |> assign(:topic, topic) - |> assign(:user, user) - - Pleroma.Web.Streamer.add_socket(topic, socket) - {:ok, socket} - else - _e -> :error - end - end - - def connect(%{"stream" => stream} = params, socket) - when stream in ["public", "public:local", "hashtag"] do - topic = - case stream do - "hashtag" -> "hashtag:#{params["tag"]}" - _ -> stream - end - - socket = - socket - |> assign(:topic, topic) - - Pleroma.Web.Streamer.add_socket(topic, socket) - {:ok, socket} - end - - def connect(_params, _socket), do: :error - - def id(_), do: nil - - def handle(:text, message, _state) do - # | :ok - # | state - # | {:text, message} - # | {:text, message, state} - # | {:close, "Goodbye!"} - {:text, message} - end - - def handle(:closed, _, %{socket: socket}) do - topic = socket.assigns[:topic] - Pleroma.Web.Streamer.remove_socket(topic, socket) - end -end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index c3c735d5d..46c559e3a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -140,7 +140,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity} visibility: get_visibility(object), media_attachments: attachments |> Enum.take(4), mentions: mentions, - tags: tags, + tags: build_tags(tags), application: %{ name: "Web", website: nil @@ -234,6 +234,27 @@ def render_content(%{"type" => object_type} = object) def render_content(object), do: object["content"] || "" + @doc """ + Builds a dictionary tags. + + ## Examples + + iex> Pleroma.Web.MastodonAPI.StatusView.build_tags(["fediverse", "nextcloud"]) + [{"name": "fediverse", "url": "/tag/fediverse"}, + {"name": "nextcloud", "url": "/tag/nextcloud"}] + + """ + @spec build_tags(list(any())) :: list(map()) + def build_tags(object_tags) when is_list(object_tags) do + object_tags = for tag when is_binary(tag) <- object_tags, do: tag + + Enum.reduce(object_tags, [], fn tag, tags -> + tags ++ [%{name: tag, url: "/tag/#{tag}"}] + end) + end + + def build_tags(_), do: [] + @doc """ Builds list emojis. diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex new file mode 100644 index 000000000..11e0e1696 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -0,0 +1,120 @@ +defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do + require Logger + + alias Pleroma.Web.OAuth.Token + alias Pleroma.{User, Repo} + + @behaviour :cowboy_websocket_handler + + @streams [ + "public", + "public:local", + "public:media", + "public:local:media", + "user", + "direct", + "list", + "hashtag" + ] + @anonymous_streams ["public", "public:local", "hashtag"] + + # Handled by periodic keepalive in Pleroma.Web.Streamer. + @timeout :infinity + + def init(_type, _req, _opts) do + {:upgrade, :protocol, :cowboy_websocket} + end + + def websocket_init(_type, req, _opts) do + with {qs, req} <- :cowboy_req.qs(req), + params <- :cow_qs.parse_qs(qs), + access_token <- List.keyfind(params, "access_token", 0), + {_, stream} <- List.keyfind(params, "stream", 0), + {:ok, user} <- allow_request(stream, access_token), + topic when is_binary(topic) <- expand_topic(stream, params) do + send(self(), :subscribe) + {:ok, req, %{user: user, topic: topic}, @timeout} + else + {:error, code} -> + Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") + {:ok, req} = :cowboy_req.reply(code, req) + {:shutdown, req} + + error -> + Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}") + {:shutdown, req} + end + end + + # We never receive messages. + def websocket_handle(_frame, req, state) do + {:ok, req, state} + end + + def websocket_info(:subscribe, req, state) do + Logger.debug( + "#{__MODULE__} accepted websocket connection for user #{ + (state.user || %{id: "anonymous"}).id + }, topic #{state.topic}" + ) + + Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) + {:ok, req, state} + end + + def websocket_info({:text, message}, req, state) do + {:reply, {:text, message}, req, state} + end + + def websocket_terminate(reason, _req, state) do + Logger.debug( + "#{__MODULE__} terminating websocket connection for user #{ + (state.user || %{id: "anonymous"}).id + }, topic #{state.topic || "?"}: #{inspect(reason)}" + ) + + Pleroma.Web.Streamer.remove_socket(state.topic, streamer_socket(state)) + :ok + end + + # Public streams without authentication. + defp allow_request(stream, nil) when stream in @anonymous_streams do + {:ok, nil} + end + + # 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{} <- Repo.get(User, user_id) do + {:ok, user} + else + _ -> {:error, 403} + end + end + + # Not authenticated. + defp allow_request(stream, _) when stream in @streams, do: {:error, 403} + + # No matching stream. + defp allow_request(_, _), do: {:error, 404} + + defp expand_topic("hashtag", params) do + case List.keyfind(params, "tag", 0) do + {_, tag} -> "hashtag:#{tag}" + _ -> nil + end + end + + defp expand_topic("list", params) do + case List.keyfind(params, "list", 0) do + {_, list} -> "list:#{list}" + _ -> nil + end + end + + defp expand_topic(topic, _), do: topic + + defp streamer_socket(state) do + %{transport_pid: self(), assigns: state} + end +end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 277dc6ba1..44c11f40a 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -132,6 +132,7 @@ def nodeinfo(conn, %{"version" => "2.0"}) do banner: Keyword.get(instance, :banner_upload_limit), background: Keyword.get(instance, :background_upload_limit) }, + invitesEnabled: Keyword.get(instance, :invites_enabled, false), features: features } } diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 751624e80..49b007422 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -99,6 +99,7 @@ defmodule Pleroma.Web.Router do get("/password_reset/:token", UtilController, :show_password_reset) post("/password_reset", UtilController, :password_reset) get("/emoji", UtilController, :emoji) + get("/captcha", UtilController, :captcha) end scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do @@ -117,6 +118,8 @@ defmodule Pleroma.Web.Router do delete("/relay", AdminAPIController, :relay_unfollow) get("/invite_token", AdminAPIController, :get_invite_token) + post("/email_invite", AdminAPIController, :email_invite) + get("/password_reset", AdminAPIController, :get_password_reset) end @@ -481,7 +484,7 @@ def redirector_with_meta(conn, params) do end def index_file_path do - Application.app_dir(:pleroma, "priv/static/index.html") + Pleroma.Plugs.InstanceStatic.file_path("index.html")) end def registration_page(conn, params) do diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 29c44e9d5..e1eecba4d 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -4,17 +4,9 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.{User, Notification, Activity, Object, Repo} alias Pleroma.Web.ActivityPub.ActivityPub - def init(args) do - {:ok, args} - end + @keepalive_interval :timer.seconds(30) def start_link do - spawn(fn -> - # 30 seconds - Process.sleep(1000 * 30) - GenServer.cast(__MODULE__, %{action: :ping}) - end) - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) end @@ -30,6 +22,16 @@ def stream(topic, item) do GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item}) end + def init(args) do + spawn(fn -> + # 30 seconds + Process.sleep(@keepalive_interval) + GenServer.cast(__MODULE__, %{action: :ping}) + end) + + {:ok, args} + end + def handle_cast(%{action: :ping}, topics) do Map.values(topics) |> List.flatten() @@ -40,7 +42,7 @@ def handle_cast(%{action: :ping}, topics) do spawn(fn -> # 30 seconds - Process.sleep(1000 * 30) + Process.sleep(@keepalive_interval) GenServer.cast(__MODULE__, %{action: :ping}) end) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index a8e3467c4..38653f0b8 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -173,7 +173,8 @@ def config(conn, _params) do uploadlimit: uploadlimit, closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"), private: if(Keyword.get(instance, :public, true), do: "0", else: "1"), - vapidPublicKey: vapid_public_key + vapidPublicKey: vapid_public_key, + invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0") } pleroma_fe = %{ @@ -283,4 +284,8 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do json(conn, %{error: msg}) end end + + def captcha(conn, _params) do + json(conn, Pleroma.Captcha.new()) + end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 1e764f24a..90b8345c5 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -132,38 +132,55 @@ def register_user(params) do bio: User.parse_bio(params["bio"]), email: params["email"], password: params["password"], - password_confirmation: params["confirm"] + password_confirmation: params["confirm"], + captcha_solution: params["captcha_solution"], + captcha_token: params["captcha_token"] } - registrations_open = Pleroma.Config.get([:instance, :registrations_open]) - - # no need to query DB if registration is open - token = - unless registrations_open || is_nil(tokenString) do - Repo.get_by(UserInviteToken, %{token: tokenString}) + captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled]) + # true if captcha is disabled or enabled and valid, false otherwise + captcha_ok = + if !captcha_enabled do + true + else + Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) end - cond do - registrations_open || (!is_nil(token) && !token.used) -> - changeset = User.register_changeset(%User{info: %{}}, params) + # Captcha invalid + if not captcha_ok do + # I have no idea how this error handling works + {:error, %{error: Jason.encode!(%{captcha: ["Invalid CAPTCHA"]})}} + else + registrations_open = Pleroma.Config.get([:instance, :registrations_open]) - with {:ok, user} <- Repo.insert(changeset) do - !registrations_open && UserInviteToken.mark_as_used(token.token) - {:ok, user} - else - {:error, changeset} -> - errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) - |> Jason.encode!() - - {:error, %{error: errors}} + # no need to query DB if registration is open + token = + unless registrations_open || is_nil(tokenString) do + Repo.get_by(UserInviteToken, %{token: tokenString}) end - !registrations_open && is_nil(token) -> - {:error, "Invalid token"} + cond do + registrations_open || (!is_nil(token) && !token.used) -> + changeset = User.register_changeset(%User{info: %{}}, params) - !registrations_open && token.used -> - {:error, "Expired token"} + with {:ok, user} <- Repo.insert(changeset) do + !registrations_open && UserInviteToken.mark_as_used(token.token) + {:ok, user} + else + {:error, changeset} -> + errors = + Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + |> Jason.encode!() + + {:error, %{error: errors}} + end + + !registrations_open && is_nil(token) -> + {:error, "Invalid token"} + + !registrations_open && token.used -> + {:error, "Expired token"} + end end end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 38eff8191..327620302 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -17,7 +17,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def verify_credentials(%{assigns: %{user: user}} = conn, _params) do token = Phoenix.Token.sign(conn, "user socket", user.id) - render(conn, UserView, "show.json", %{user: user, token: token}) + + conn + |> put_view(UserView) + |> render("show.json", %{user: user, token: token}) end def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do @@ -58,7 +61,8 @@ def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_public_activities(params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def public_timeline(%{assigns: %{user: user}} = conn, params) do @@ -71,7 +75,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_public_activities(params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def friends_timeline(%{assigns: %{user: user}} = conn, params) do @@ -86,16 +91,22 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do |> ActivityPub.contain_timeline(user) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def show_user(conn, params) do with {:ok, shown} <- TwitterAPI.get_user(params) do - if user = conn.assigns.user do - render(conn, UserView, "show.json", %{user: shown, for: user}) - else - render(conn, UserView, "show.json", %{user: shown}) - end + params = + if user = conn.assigns.user do + %{user: shown, for: user} + else + %{user: shown} + end + + conn + |> put_view(UserView) + |> render("show.json", params) else {:error, msg} -> bad_request_reply(conn, msg) @@ -108,7 +119,8 @@ def user_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_user_activities(target_user, user, params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) {:error, msg} -> bad_request_reply(conn, msg) @@ -124,7 +136,8 @@ def mentions_timeline(%{assigns: %{user: user}} = conn, params) do activities = ActivityPub.fetch_activities([user.ap_id], params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def dm_timeline(%{assigns: %{user: user}} = conn, params) do @@ -137,14 +150,16 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do activities = Repo.all(query) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def notifications(%{assigns: %{user: user}} = conn, params) do notifications = Notification.for_user(user, params) conn - |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) + |> put_view(NotificationView) + |> render("notification.json", %{notifications: notifications, for: user}) end def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do @@ -153,7 +168,8 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest notifications = Notification.for_user(user, params) conn - |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) + |> put_view(NotificationView) + |> render("notification.json", %{notifications: notifications, for: user}) end def notifications_read(%{assigns: %{user: _user}} = conn, _) do @@ -163,7 +179,9 @@ def notifications_read(%{assigns: %{user: _user}} = conn, _) do def follow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.follow(user, params) do {:ok, user, followed, _activity} -> - render(conn, UserView, "show.json", %{user: followed, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: followed, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -173,7 +191,9 @@ def follow(%{assigns: %{user: user}} = conn, params) do def block(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.block(user, params) do {:ok, user, blocked} -> - render(conn, UserView, "show.json", %{user: blocked, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: blocked, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -183,7 +203,9 @@ def block(%{assigns: %{user: user}} = conn, params) do def unblock(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.unblock(user, params) do {:ok, user, blocked} -> - render(conn, UserView, "show.json", %{user: blocked, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: blocked, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -192,14 +214,18 @@ def unblock(%{assigns: %{user: user}} = conn, params) do def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {:ok, activity} <- TwitterAPI.delete(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def unfollow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.unfollow(user, params) do {:ok, user, unfollowed} -> - render(conn, UserView, "show.json", %{user: unfollowed, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: unfollowed, for: user}) {:error, msg} -> forbidden_json_reply(conn, msg) @@ -209,7 +235,9 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Repo.get(Activity, id), true <- ActivityPub.visible_for_user?(activity, user) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end @@ -223,7 +251,8 @@ def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do "user" => user }) do conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end end @@ -290,34 +319,44 @@ def get_by_id_or_ap_id(id) do def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.fav(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.unfav(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.repeat(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, {:ok, activity} <- TwitterAPI.unrepeat(user, id) do - render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + conn + |> put_view(ActivityView) + |> render("activity.json", %{activity: activity, for: user}) end end def register(conn, params) do with {:ok, user} <- TwitterAPI.register_user(params) do - render(conn, UserView, "show.json", %{user: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: user}) else {:error, errors} -> conn @@ -339,7 +378,9 @@ def update_avatar(%{assigns: %{user: user}} = conn, params) do {:ok, user} = User.update_and_set_cache(change) CommonAPI.update(user) - render(conn, UserView, "show.json", %{user: user, for: user}) + conn + |> put_view(UserView) + |> render("show.json", %{user: user, for: user}) end def update_banner(%{assigns: %{user: user}} = conn, params) do @@ -394,7 +435,9 @@ def followers(%{assigns: %{user: for_user}} = conn, params) do true -> followers end - render(conn, UserView, "index.json", %{users: followers, for: conn.assigns[:user]}) + conn + |> put_view(UserView) + |> render("index.json", %{users: followers, for: conn.assigns[:user]}) else _e -> bad_request_reply(conn, "Can't get followers") end @@ -410,7 +453,9 @@ def friends(%{assigns: %{user: for_user}} = conn, params) do true -> friends end - render(conn, UserView, "index.json", %{users: friends, for: conn.assigns[:user]}) + conn + |> put_view(UserView) + |> render("index.json", %{users: friends, for: conn.assigns[:user]}) else _e -> bad_request_reply(conn, "Can't get friends") end @@ -419,7 +464,9 @@ def friends(%{assigns: %{user: for_user}} = conn, params) do def friend_requests(conn, params) do with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), {:ok, friend_requests} <- User.get_follow_requests(user) do - render(conn, UserView, "index.json", %{users: friend_requests, for: conn.assigns[:user]}) + conn + |> put_view(UserView) + |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]}) else _e -> bad_request_reply(conn, "Can't get friend requests") end @@ -439,7 +486,9 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do object: follow_activity.data["id"], type: "Accept" }) do - render(conn, UserView, "show.json", %{user: follower, for: followed}) + conn + |> put_view(UserView) + |> render("show.json", %{user: follower, for: followed}) else e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}") end @@ -458,7 +507,9 @@ def deny_friend_request(conn, %{"user_id" => uid} = _params) do object: follow_activity.data["id"], type: "Reject" }) do - render(conn, UserView, "show.json", %{user: follower, for: followed}) + conn + |> put_view(UserView) + |> render("show.json", %{user: follower, for: followed}) else e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}") end @@ -522,7 +573,10 @@ def update_profile(%{assigns: %{user: user}} = conn, params) do changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), {:ok, user} <- User.update_and_set_cache(changeset) do CommonAPI.update(user) - render(conn, UserView, "user.json", %{user: user, for: user}) + + conn + |> put_view(UserView) + |> render("user.json", %{user: user, for: user}) else error -> Logger.debug("Can't update user: #{inspect(error)}") @@ -534,14 +588,16 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do activities = TwitterAPI.search(user, params) conn - |> render(ActivityView, "index.json", %{activities: activities, for: user}) + |> put_view(ActivityView) + |> render("index.json", %{activities: activities, for: user}) end def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do users = User.search(query, true) conn - |> render(UserView, "index.json", %{users: users, for: user}) + |> put_view(UserView) + |> render("index.json", %{users: users, for: user}) end defp bad_request_reply(conn, error_message) do diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index e5caed28f..91d086740 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do alias Pleroma.HTML import Ecto.Query + require Logger defp query_context_ids([]), do: [] @@ -239,9 +240,15 @@ def render( {summary, content} = render_content(object) html = - HTML.filter_tags(content, User.html_filter_policy(opts[:for])) + content + |> HTML.filter_tags(User.html_filter_policy(opts[:for])) |> Formatter.emojify(object["emoji"]) + text = + content + |> String.replace(~r//, "\n") + |> HTML.strip_tags() + reply_parent = Activity.get_in_reply_to_activity(activity) reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor) @@ -251,7 +258,7 @@ def render( "uri" => activity.data["object"]["id"], "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), "statusnet_html" => html, - "text" => HTML.strip_tags(content), + "text" => text, "is_local" => activity.local, "is_post_verb" => true, "created_at" => created_at, @@ -276,6 +283,11 @@ def render( } end + def render("activity.json", %{activity: unhandled_activity}) do + Logger.warn("#{__MODULE__} unhandled activity: #{inspect(unhandled_activity)}") + nil + end + def render_content(%{"type" => "Note"} = object) do summary = object["summary"] diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index b3459af9a..8a88d72b1 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -86,7 +86,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do } if assigns[:token] do - Map.put(data, "token", assigns[:token]) + Map.put(data, "token", token_string(assigns[:token])) else data end @@ -111,4 +111,7 @@ def render("short.json", %{ defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(_), do: nil + + defp token_string(%Pleroma.Web.OAuth.Token{token: token_str}), do: token_str + defp token_string(token), do: token end diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex index 002353166..8c60300a4 100644 --- a/lib/pleroma/web/web_finger/web_finger_controller.ex +++ b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -35,4 +35,8 @@ def webfinger(conn, %{"resource" => resource}) do send_resp(conn, 404, "Unsupported format") end end + + def webfinger(conn, _params) do + send_resp(conn, 400, "Bad Request") + end end diff --git a/mix.exs b/mix.exs index e5fb654dd..31ebcc5f3 100644 --- a/mix.exs +++ b/mix.exs @@ -8,12 +8,7 @@ def project do elixir: "~> 1.4", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), - elixirc_options: - if Mix.env() == :test do - [] - else - [warnings_as_errors: true] - end, + elixirc_options: [warnings_as_errors: true], start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), @@ -48,12 +43,13 @@ defp elixirc_paths(_), do: ["lib"] # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.3.3"}, - {:phoenix_pubsub, "~> 1.0.2"}, + # Until Phoenix 1.4.1 is released + {:phoenix, github: "phoenixframework/phoenix", branch: "v1.4"}, + {:plug_cowboy, "~> 1.0"}, + {:phoenix_pubsub, "~> 1.1"}, {:phoenix_ecto, "~> 3.3"}, {:postgrex, ">= 0.13.5"}, {:gettext, "~> 0.15"}, - {:cowboy, "~> 1.1.2", override: true}, {:comeonin, "~> 4.1.1"}, {:pbkdf2_elixir, "~> 0.12.3"}, {:trailing_format_plug, "~> 0.0.7"}, @@ -68,7 +64,7 @@ defp deps do {:mogrify, "~> 0.6.1"}, {:ex_aws, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"}, - {:earmark, "~> 1.2"}, + {:earmark, "~> 1.3"}, {:ex_machina, "~> 2.2", only: :test}, {:credo, "~> 0.9.3", only: [:dev, :test]}, {:mock, "~> 0.3.1", only: :test}, @@ -78,7 +74,8 @@ defp deps do {:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false}, {:web_push_encryption, "~> 0.2.1"}, {:swoosh, "~> 0.20"}, - {:gen_smtp, "~> 0.13"} + {:gen_smtp, "~> 0.13"}, + {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} ] end diff --git a/mix.lock b/mix.lock index 007079652..3521f82bb 100644 --- a/mix.lock +++ b/mix.lock @@ -13,7 +13,7 @@ "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, @@ -33,7 +33,7 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, @@ -41,11 +41,13 @@ "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix": {:git, "https://github.com/phoenixframework/phoenix.git", "ea22dc50b574178a300ecd19253443960407df93", [branch: "v1.4"]}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"}, + "plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_cowboy": {:hex, :plug_cowboy, "1.0.0", "2e2a7d3409746d335f451218b8bb0858301c3de6d668c3052716c909936eb57a", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, @@ -58,4 +60,5 @@ "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"}, "web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, } diff --git a/priv/static/static/css/app.a37e07355ec1b0b45e84807615297c27.css b/priv/static/static/css/app.a37e07355ec1b0b45e84807615297c27.css new file mode 100644 index 000000000..b4f4d2c65 Binary files /dev/null and b/priv/static/static/css/app.a37e07355ec1b0b45e84807615297c27.css differ diff --git a/priv/static/static/css/app.a37e07355ec1b0b45e84807615297c27.css.map b/priv/static/static/css/app.a37e07355ec1b0b45e84807615297c27.css.map new file mode 100644 index 000000000..ec956d2a3 --- /dev/null +++ b/priv/static/static/css/app.a37e07355ec1b0b45e84807615297c27.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///webpack:///src/App.scss","webpack:///webpack:///src/components/user_panel/user_panel.vue","webpack:///webpack:///src/components/login_form/login_form.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_content/user_card_content.vue","webpack:///webpack:///src/components/still-image/still-image.vue","webpack:///webpack:///src/components/nav_panel/nav_panel.vue","webpack:///webpack:///src/components/notifications/notifications.scss","webpack:///webpack:///src/components/status/status.vue","webpack:///webpack:///src/components/attachment/attachment.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/user_finder/user_finder.vue","webpack:///webpack:///src/components/features_panel/features_panel.vue","webpack:///webpack:///src/components/who_to_follow_panel/who_to_follow_panel.vue","webpack:///webpack:///src/components/chat_panel/chat_panel.vue","webpack:///webpack:///src/components/timeline/timeline.vue","webpack:///webpack:///src/components/status_or_conversation/status_or_conversation.vue","webpack:///webpack:///src/components/user_card/user_card.vue","webpack:///webpack:///src/components/user_profile/user_profile.vue","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"],"names":[],"mappings":"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","file":"static/css/app.a37e07355ec1b0b45e84807615297c27.css","sourcesContent":["\n#app{background-size:cover;background-attachment:fixed;background-repeat:no-repeat;background-position:0 50px;min-height:100vh;max-width:100%;overflow:hidden\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{font-size:1.1em;margin-left:0.4em\n}\n.item.right{-ms-flex-pack:end;justify-content:flex-end;padding-right:20px\n}\n.auto-size{-ms-flex:1;flex:1\n}\nnav{width:100%;-ms-flex-align:center;align-items:center;position:fixed;height:50px\n}\nnav .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}\nnav .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}\nnav .logo img{height:100%;object-fit:contain;display:block;-ms-flex:0;flex:0\n}\nnav .inner-nav{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-preferred-size:970px;flex-basis:970px;margin:auto;height:50px\n}\nnav .inner-nav a,nav .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 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:20px\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:60%;flex-basis:60%;-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: 960px){\nbody{overflow-y:scroll\n}\nnav .back-button{display:none\n}\nnav .site-name{padding-left:20px\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@media all and (min-width: 959px){\n.logo{opacity:1 !important\n}\n}\n@media all and (max-width: 959px){\n.mobile-hidden{display:none\n}\n.panel-switcher{display:-ms-flexbox;display:flex\n}\n.container{padding:0 0 0 0\n}\n.panel{margin:0.5em 0 0.5em 0\n}\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 .text-format{float:right\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\n\n\n// WEBPACK FOOTER //\n// webpack:///src/App.scss","\n.user-panel .profile-panel-background .panel-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_panel/user_panel.vue","\n.login-form .btn{min-height:28px;width:10em\n}\n.login-form .error{text-align:center\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\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/login_form/login_form.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 .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.profile-panel-background{background-size:cover;border-radius:10px;border-radius:var(--panelRadius, 10px);overflow:hidden;border-bottom-left-radius:0;border-bottom-right-radius:0\n}\n.profile-panel-background .panel-heading{padding:0.6em 0em;text-align:center;box-shadow:none\n}\n.profile-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.profile-panel-body .profile-bio{text-align:center\n}\n.user-info{color:#b9b9ba;color:var(--lightText, #b9b9ba);padding:0 16px\n}\n.user-info .container{padding:16px 10px 6px 10px;display:-ms-flexbox;display:flex;max-height:56px\n}\n.user-info .container .avatar{border-radius:4px;border-radius:var(--avatarRadius, 4px);-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 .container .avatar.better-shadow{box-shadow:var(--avatarShadowInset);filter:var(--avatarShadowFilter)\n}\n.user-info .container .avatar.animated::before{display:none\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 .user-name{text-overflow:ellipsis;overflow:hidden\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\n}\n.user-info .user-meta{margin-bottom:.4em\n}\n.user-info .user-meta .following{font-size:14px;-ms-flex:0 0 100%;flex:0 0 100%;margin:0;padding-left:16px;text-align:left;float:left\n}\n.user-info .user-meta .floater{margin:0\n}\n.user-info .user-meta::after{display:block;content:'';clear:both\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\n}\n.user-info .user-interactions div{-ms-flex:1;flex:1\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:92%;height:100%\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)\n}\n.user-counts.clickable .user-count{cursor:pointer\n}\n.user-counts.clickable .user-count:hover:not(.selected){transition:border-bottom 100ms;border-bottom:3px solid #d8a070;border-bottom:3px solid var(--link, #d8a070)\n}\n.user-count{-ms-flex:1;flex:1;padding:.5em 0 .5em 0;margin:0 .5em\n}\n.user-count.selected{transition:none;border-bottom:5px solid #d8a070;border-bottom:5px solid var(--link, #d8a070);border-radius:4px;border-radius:var(--btnRadius, 4px)\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.dailyAvg{margin-left:1em;font-size:0.7em;color:#CCC\n}\n.floater{float:right;margin-top:16px\n}\n.floater .userHighlightCl{padding:2px 10px\n}\n.floater .userHighlightSel,.floater .userHighlightSel.select{padding-top:0;padding-bottom:0\n}\n.floater .userHighlightSel.select i{line-height:22px\n}\n.floater .userHighlightText{width:70px\n}\n.floater .userHighlightCl,.floater .userHighlightText,.floater .userHighlightSel,.floater .userHighlightSel.select{height:22px;vertical-align:top;margin-right:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_card_content/user_card_content.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.nav-panel .panel{overflow:hidden\n}\n.nav-panel ul{list-style:none;margin:0;padding:0\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.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 .broken-favorite{border-radius:5px;border-radius:var(--tooltipRadius, 5px);color:#b9b9ba;color:var(--alertErrorText, #b9b9ba);background-color:rgba(211,16,20,0.5);background-color:var(--alertError, rgba(211,16,20,0.5));padding:2px .5em\n}\n.notification .avatar-compact{width:32px;height:32px;box-shadow:var(--avatarStatusShadow);border-radius:10px;border-radius:var(--avatarAltRadius, 10px);overflow:hidden;line-height:0\n}\n.notification .avatar-compact.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.notification .avatar-compact.animated::before{display:none\n}\n.notification:hover .animated.avatar-compact canvas{display:none\n}\n.notification:hover .animated.avatar-compact img{visibility:visible\n}\n.notification .notification-usercard{margin:0\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\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{float:right;font-size:12px\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.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.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;line-height:18px;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;margin:0 0 0.25em 0.8em\n}\n.status-el .usercard{margin-bottom:.7em\n}\n.status-el .media-heading{-ms-flex-wrap:nowrap;flex-wrap:nowrap;line-height:18px\n}\n.status-el .media-heading-left{padding:0;vertical-align:bottom;-ms-flex-preferred-size:100%;flex-basis:100%\n}\n.status-el .media-heading-left small{font-weight:lighter\n}\n.status-el .media-heading-left h4{white-space:nowrap;font-size:14px;margin-right:0.25em;overflow:hidden;text-overflow:ellipsis\n}\n.status-el .media-heading-left .name-and-links{padding:0;-ms-flex:1 0;flex:1 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:baseline;align-items:baseline\n}\n.status-el .media-heading-left .name-and-links .user-name{margin-right:.45em\n}\n.status-el .media-heading-left .name-and-links .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.status-el .media-heading-left .links{display:-ms-flexbox;display:flex;font-size:12px;color:#d8a070;color:var(--link, #d8a070);max-width:100%\n}\n.status-el .media-heading-left .links a{max-width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap\n}\n.status-el .media-heading-left .reply-info{display:-ms-flexbox;display:flex\n}\n.status-el .media-heading-left .replies{line-height:16px\n}\n.status-el .media-heading-left .reply-link{margin-right:0.2em\n}\n.status-el .media-heading-right{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-negative:0;flex-shrink:0;-ms-flex-wrap:nowrap;flex-wrap:nowrap;margin-left:.25em;-ms-flex-item-align:baseline;align-self:baseline\n}\n.status-el .media-heading-right .timeago{margin-right:0.2em;font-size:12px;-ms-flex-item-align:last baseline;-ms-grid-row-align:last baseline;align-self:last baseline\n}\n.status-el .media-heading-right>*{margin-left:0.2em\n}\n.status-el .media-heading-right a:hover i{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.status-el a{display:inline-block;word-break:break-all\n}\n.status-el .tall-status{position:relative;height:220px;overflow-x:hidden;overflow-y:hidden\n}\n.status-el .tall-status-hider{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\n}\n.status-el .status-content{margin-right:0.5em;font-family:var(--postFont, sans-serif)\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 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;margin-top:0.2em;margin-bottom:0.5em\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 0.6em 0 0.6em;margin:0\n}\n.status-el .retweet-info .avatar{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\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\n}\n.status-actions div,.status-actions favorite-button{padding-top:0.25em;max-width:6em;-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 .avatar-compact{width:32px;height:32px;box-shadow:var(--avatarStatusShadow);border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.status .avatar-compact.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.avatar{width:48px;height:48px;box-shadow:var(--avatarStatusShadow);border-radius:4px;border-radius:var(--avatarRadius, 4px);overflow:hidden;position:relative\n}\n.avatar.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.avatar img{width:100%;height:100%\n}\n.avatar.animated::before{display:none\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:0.6em\n}\n.status.is-retweet{padding-top:0.1em\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-bottom-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: 960px){\n.status-el .retweet-info .avatar{margin-left:20px\n}\n.status{max-width:100%\n}\n.status .avatar{width:40px;height:40px\n}\n.status .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:300px;max-width:100%\n}\n.attachments .placeholder{margin-right:0.5em\n}\n.attachments .nsfw-placeholder{cursor:pointer\n}\n.attachments .nsfw-placeholder.loading{cursor:progress\n}\n.attachments .small-attachment{max-height:100px\n}\n.attachments .small-attachment.image,.attachments .small-attachment.video{max-width:35%\n}\n.attachments .attachment{position:relative;-ms-flex:1 0 30%;flex:1 0 30%;margin:0.5em 0.7em 0.6em 0.0em;-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 .fullwidth{-ms-flex-preferred-size:100%;flex-basis:100%\n}\n.attachments.video{line-height: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;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 .small{max-height:100px\n}\n.attachments video{max-height:500px;height:100%;width:100%;z-index:0\n}\n.attachments audio{width:100%\n}\n.attachments img.media-upload{line-height:0;max-height:300px;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{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1\n}\n.attachments .image-attachment.hidden{display:none\n}\n.attachments .image-attachment .still-image{width:100%;height:100%\n}\n.attachments .image-attachment .small img{max-height:100px\n}\n.attachments .image-attachment img{object-fit:contain;width:100%;height:100%;max-height:500px;image-orientation:from-image\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/attachment/attachment.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.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:80%\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.features-panel li{line-height:24px\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/features_panel/features_panel.vue","\n.who-to-follow *{vertical-align:middle\n}\n.who-to-follow img{width:32px;height:32px\n}\n.who-to-follow p{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.floating-chat{position:fixed;right:0px;bottom:0px;z-index:1000\n}\n.chat-heading{cursor:pointer\n}\n.chat-heading .icon-comment-empty{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.chat-window{width:345px;max-height:40vh;overflow-y:auto;overflow-x:hidden\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\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/chat_panel/chat_panel.vue","\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.spacer{height:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/status_or_conversation/status_or_conversation.vue","\n.name-and-screen-name{margin-left:0.7em;margin-top:0.0em;text-align:left;width:100%\n}\n.follows-you{margin-left:2em;float:right\n}\n.card{display:-ms-flexbox;display:flex;-ms-flex:1 0;flex:1 0;padding-top:0.6em;padding-right:1em;padding-bottom:0.6em;padding-left:1em;border-bottom:1px solid;margin:0;border-bottom-color:#222;border-bottom-color:var(--border, #222)\n}\n.card .avatar{margin-top:0.2em;width:32px;height:32px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.usercard{width:-webkit-fill-available;width:-moz-available;width:fill-available;margin:0.2em 0 0.7em 0;border-radius:10px;border-radius:var(--panelRadius, 10px);border-style:solid;border-color:#222;border-color:var(--border, #222);border-width:1px;overflow:hidden\n}\n.usercard .panel-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\n}\n.usercard p{margin-bottom:0\n}\n.approval button{width:100%;margin-bottom:0.5em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_card/user_card.vue","\n.user-profile{-ms-flex:2;flex:2;-ms-flex-preferred-size:500px;flex-basis:500px;padding-bottom:10px\n}\n.user-profile .panel-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\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.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 .old-avatar{width:128px;border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.setting-item .new-avatar{object-fit:cover;width:128px;height:128px;border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.setting-item .btn{min-height:28px;min-width:10em;padding:0 2em\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 overflow: hidden;\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 .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@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.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: 959px){\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:400px\n}\n.profile-edit .uploading{font-size:1.5em;margin:0.25em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_settings/user_settings.vue"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.ea00efb3229c8591fcc5249f0241b986.css b/priv/static/static/css/app.ea00efb3229c8591fcc5249f0241b986.css deleted file mode 100644 index 9c8f24554..000000000 Binary files a/priv/static/static/css/app.ea00efb3229c8591fcc5249f0241b986.css and /dev/null differ diff --git a/priv/static/static/css/app.ea00efb3229c8591fcc5249f0241b986.css.map b/priv/static/static/css/app.ea00efb3229c8591fcc5249f0241b986.css.map deleted file mode 100644 index b8822f69d..000000000 --- a/priv/static/static/css/app.ea00efb3229c8591fcc5249f0241b986.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///webpack:///src/App.scss","webpack:///webpack:///src/components/user_panel/user_panel.vue","webpack:///webpack:///src/components/login_form/login_form.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_content/user_card_content.vue","webpack:///webpack:///src/components/still-image/still-image.vue","webpack:///webpack:///src/components/nav_panel/nav_panel.vue","webpack:///webpack:///src/components/notifications/notifications.scss","webpack:///webpack:///src/components/status/status.vue","webpack:///webpack:///src/components/attachment/attachment.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/user_finder/user_finder.vue","webpack:///webpack:///src/components/features_panel/features_panel.vue","webpack:///webpack:///src/components/who_to_follow_panel/who_to_follow_panel.vue","webpack:///webpack:///src/components/chat_panel/chat_panel.vue","webpack:///webpack:///src/components/timeline/timeline.vue","webpack:///webpack:///src/components/status_or_conversation/status_or_conversation.vue","webpack:///webpack:///src/components/user_card/user_card.vue","webpack:///webpack:///src/components/user_profile/user_profile.vue","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"],"names":[],"mappings":"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","file":"static/css/app.ea00efb3229c8591fcc5249f0241b986.css","sourcesContent":["\n#app{background-size:cover;background-attachment:fixed;background-repeat:no-repeat;background-position:0 50px;min-height:100vh;max-width:100%;overflow:hidden\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{font-size:1.1em;margin-left:0.4em\n}\n.item.right{-ms-flex-pack:right;justify-content:right;padding-right:20px\n}\n.auto-size{-ms-flex:1;flex:1\n}\nnav{width:100%;-ms-flex-align:center;align-items:center;position:fixed;height:50px\n}\nnav .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\n}\nnav .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}\nnav .logo img{height:100%;object-fit:contain;display:block;-ms-flex:0;flex:0\n}\nnav .inner-nav{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-preferred-size:970px;flex-basis:970px;margin:auto;height:50px\n}\nnav .inner-nav a,nav .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 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:20px\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:60%;flex-basis:60%;-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: 960px){\nbody{overflow-y:scroll\n}\nnav .back-button{display:none\n}\nnav .site-name{padding-left:20px\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@media all and (max-width: 959px){\n.mobile-hidden{display:none\n}\n.panel-switcher{display:-ms-flexbox;display:flex\n}\n.container{padding:0 0 0 0\n}\n.panel{margin:0.5em 0 0.5em 0\n}\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 .text-format{float:right\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\n\n\n// WEBPACK FOOTER //\n// webpack:///src/App.scss","\n.user-panel .profile-panel-background .panel-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_panel/user_panel.vue","\n.login-form .btn{min-height:28px;width:10em\n}\n.login-form .error{text-align:center\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\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/login_form/login_form.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 .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.profile-panel-background{background-size:cover;border-radius:10px;border-radius:var(--panelRadius, 10px);overflow:hidden;border-bottom-left-radius:0;border-bottom-right-radius:0\n}\n.profile-panel-background .panel-heading{padding:0.6em 0em;text-align:center;box-shadow:none\n}\n.profile-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.profile-panel-body .profile-bio{text-align:center\n}\n.user-info{color:#b9b9ba;color:var(--lightText, #b9b9ba);padding:0 16px\n}\n.user-info .container{padding:16px 10px 6px 10px;display:-ms-flexbox;display:flex;max-height:56px\n}\n.user-info .container .avatar{border-radius:4px;border-radius:var(--avatarRadius, 4px);-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 .container .avatar.better-shadow{box-shadow:var(--avatarShadowInset);filter:var(--avatarShadowFilter)\n}\n.user-info .container .avatar.animated::before{display:none\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 .user-name{text-overflow:ellipsis;overflow:hidden\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\n}\n.user-info .user-meta{margin-bottom:.4em\n}\n.user-info .user-meta .following{font-size:14px;-ms-flex:0 0 100%;flex:0 0 100%;margin:0;padding-left:16px;text-align:left;float:left\n}\n.user-info .user-meta .floater{margin:0\n}\n.user-info .user-meta::after{display:block;content:'';clear:both\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\n}\n.user-info .user-interactions div{-ms-flex:1;flex:1\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:92%;height:100%\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)\n}\n.user-counts.clickable .user-count{cursor:pointer\n}\n.user-counts.clickable .user-count:hover:not(.selected){transition:border-bottom 100ms;border-bottom:3px solid #d8a070;border-bottom:3px solid var(--link, #d8a070)\n}\n.user-count{-ms-flex:1;flex:1;padding:.5em 0 .5em 0;margin:0 .5em\n}\n.user-count.selected{transition:none;border-bottom:5px solid #d8a070;border-bottom:5px solid var(--link, #d8a070);border-radius:4px;border-radius:var(--btnRadius, 4px)\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.dailyAvg{margin-left:1em;font-size:0.7em;color:#CCC\n}\n.floater{float:right;margin-top:16px\n}\n.floater .userHighlightCl{padding:2px 10px\n}\n.floater .userHighlightSel,.floater .userHighlightSel.select{padding-top:0;padding-bottom:0\n}\n.floater .userHighlightSel.select i{line-height:22px\n}\n.floater .userHighlightText{width:70px\n}\n.floater .userHighlightCl,.floater .userHighlightText,.floater .userHighlightSel,.floater .userHighlightSel.select{height:22px;vertical-align:top;margin-right:0\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_card_content/user_card_content.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.nav-panel .panel{overflow:hidden\n}\n.nav-panel ul{list-style:none;margin:0;padding:0\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.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 .broken-favorite{border-radius:5px;border-radius:var(--tooltipRadius, 5px);color:#b9b9ba;color:var(--alertErrorText, #b9b9ba);background-color:rgba(211,16,20,0.5);background-color:var(--alertError, rgba(211,16,20,0.5));padding:2px .5em\n}\n.notification .avatar-compact{width:32px;height:32px;box-shadow:var(--avatarStatusShadow);border-radius:10px;border-radius:var(--avatarAltRadius, 10px);overflow:hidden;line-height:0\n}\n.notification .avatar-compact.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.notification .avatar-compact.animated::before{display:none\n}\n.notification:hover .animated.avatar-compact canvas{display:none\n}\n.notification:hover .animated.avatar-compact img{visibility:visible\n}\n.notification .notification-usercard{margin:0\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\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{float:right;font-size:12px\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.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.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;line-height:18px;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;margin:0 0 0.25em 0.8em\n}\n.status-el .usercard{margin-bottom:.7em\n}\n.status-el .media-heading{-ms-flex-wrap:nowrap;flex-wrap:nowrap;line-height:18px\n}\n.status-el .media-heading-left{padding:0;vertical-align:bottom;-ms-flex-preferred-size:100%;flex-basis:100%\n}\n.status-el .media-heading-left small{font-weight:lighter\n}\n.status-el .media-heading-left h4{white-space:nowrap;font-size:14px;margin-right:0.25em;overflow:hidden;text-overflow:ellipsis\n}\n.status-el .media-heading-left .name-and-links{padding:0;-ms-flex:1 0;flex:1 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:baseline;align-items:baseline\n}\n.status-el .media-heading-left .name-and-links .user-name{margin-right:.45em\n}\n.status-el .media-heading-left .name-and-links .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain\n}\n.status-el .media-heading-left .links{display:-ms-flexbox;display:flex;font-size:12px;color:#d8a070;color:var(--link, #d8a070);max-width:100%\n}\n.status-el .media-heading-left .links a{max-width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap\n}\n.status-el .media-heading-left .reply-info{display:-ms-flexbox;display:flex\n}\n.status-el .media-heading-left .replies{line-height:16px\n}\n.status-el .media-heading-left .reply-link{margin-right:0.2em\n}\n.status-el .media-heading-right{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-negative:0;flex-shrink:0;-ms-flex-wrap:nowrap;flex-wrap:nowrap;margin-left:.25em;-ms-flex-item-align:baseline;align-self:baseline\n}\n.status-el .media-heading-right .timeago{margin-right:0.2em;font-size:12px;-ms-flex-item-align:last baseline;-ms-grid-row-align:last baseline;align-self:last baseline\n}\n.status-el .media-heading-right>*{margin-left:0.2em\n}\n.status-el .media-heading-right a:hover i{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.status-el a{display:inline-block;word-break:break-all\n}\n.status-el .tall-status{position:relative;height:220px;overflow-x:hidden;overflow-y:hidden\n}\n.status-el .tall-status-hider{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\n}\n.status-el .status-content{margin-right:0.5em;font-family:var(--postFont, sans-serif)\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 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;margin-top:0.2em;margin-bottom:0.5em\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 0.6em 0 0.6em;margin:0\n}\n.status-el .retweet-info .avatar{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\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\n}\n.status-actions div,.status-actions favorite-button{padding-top:0.25em;max-width:6em;-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 .avatar-compact{width:32px;height:32px;box-shadow:var(--avatarStatusShadow);border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.status .avatar-compact.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.avatar{width:48px;height:48px;box-shadow:var(--avatarStatusShadow);border-radius:4px;border-radius:var(--avatarRadius, 4px);overflow:hidden;position:relative\n}\n.avatar.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)\n}\n.avatar img{width:100%;height:100%\n}\n.avatar.animated::before{display:none\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:0.6em\n}\n.status.is-retweet{padding-top:0.1em\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-bottom-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: 960px){\n.status-el .retweet-info .avatar{margin-left:20px\n}\n.status{max-width:100%\n}\n.status .avatar{width:40px;height:40px\n}\n.status .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:300px;max-width:100%\n}\n.attachments .placeholder{margin-right:0.5em\n}\n.attachments .nsfw-placeholder{cursor:pointer\n}\n.attachments .nsfw-placeholder.loading{cursor:progress\n}\n.attachments .small-attachment{max-height:100px\n}\n.attachments .small-attachment.image,.attachments .small-attachment.video{max-width:35%\n}\n.attachments .attachment{position:relative;-ms-flex:1 0 30%;flex:1 0 30%;margin:0.5em 0.7em 0.6em 0.0em;-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 .fullwidth{-ms-flex-preferred-size:100%;flex-basis:100%\n}\n.attachments.video{line-height: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;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 .small{max-height:100px\n}\n.attachments video{max-height:500px;height:100%;width:100%;z-index:0\n}\n.attachments audio{width:100%\n}\n.attachments img.media-upload{line-height:0;max-height:300px;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{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1\n}\n.attachments .image-attachment.hidden{display:none\n}\n.attachments .image-attachment .still-image{width:100%;height:100%\n}\n.attachments .image-attachment .small img{max-height:100px\n}\n.attachments .image-attachment img{object-fit:contain;width:100%;height:100%;max-height:500px;image-orientation:from-image\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/attachment/attachment.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.user-finder-container{max-width:100%\n}\n.user-finder-input{max-width:80%;vertical-align:middle\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_finder/user_finder.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.who-to-follow *{vertical-align:middle\n}\n.who-to-follow img{width:32px;height:32px\n}\n.who-to-follow p{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.floating-chat{position:fixed;right:0px;bottom:0px;z-index:1000\n}\n.chat-heading{cursor:pointer\n}\n.chat-heading .icon-comment-empty{color:#b9b9ba;color:var(--text, #b9b9ba)\n}\n.chat-window{width:345px;max-height:40vh;overflow-y:auto;overflow-x:hidden\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\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/chat_panel/chat_panel.vue","\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.spacer{height:1em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/status_or_conversation/status_or_conversation.vue","\n.name-and-screen-name{margin-left:0.7em;margin-top:0.0em;text-align:left;width:100%\n}\n.follows-you{margin-left:2em;float:right\n}\n.card{display:-ms-flexbox;display:flex;-ms-flex:1 0;flex:1 0;padding-top:0.6em;padding-right:1em;padding-bottom:0.6em;padding-left:1em;border-bottom:1px solid;margin:0;border-bottom-color:#222;border-bottom-color:var(--border, #222)\n}\n.card .avatar{margin-top:0.2em;width:32px;height:32px;border-radius:10px;border-radius:var(--avatarAltRadius, 10px)\n}\n.usercard{width:-webkit-fill-available;width:-moz-available;width:fill-available;margin:0.2em 0 0.7em 0;border-radius:10px;border-radius:var(--panelRadius, 10px);border-style:solid;border-color:#222;border-color:var(--border, #222);border-width:1px;overflow:hidden\n}\n.usercard .panel-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\n}\n.usercard p{margin-bottom:0\n}\n.approval button{width:100%;margin-bottom:0.5em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_card/user_card.vue","\n.user-profile{-ms-flex:2;flex:2;-ms-flex-preferred-size:500px;flex-basis:500px;padding-bottom:10px\n}\n.user-profile .panel-heading{background:transparent;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch\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.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 .old-avatar{width:128px;border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.setting-item .new-avatar{object-fit:cover;width:128px;height:128px;border-radius:4px;border-radius:var(--avatarRadius, 4px)\n}\n.setting-item .btn{min-height:28px;min-width:10em;padding:0 2em\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 justify-content: center;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n height: 32px;\n box-sizing: border-box;\n\n &::after, &::before {\n display: block;\n content: '';\n flex: 1 1 auto;\n }\n\n .tab, &::after, &::before {\n border-bottom: 1px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n }\n\n .tab {\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 5px 1em 99px;\n white-space: nowrap;\n\n &:not(.active) {\n z-index: 4;\n\n &:hover {\n z-index: 6;\n }\n\n &::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n top: 26px;\n border-bottom: 1px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n }\n }\n\n &.active {\n background: transparent;\n border-bottom: none;\n z-index: 5;\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 .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@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.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: 959px){\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:400px\n}\n.profile-edit .uploading{font-size:1.5em;margin:0.25em\n}\n\n\n\n// WEBPACK FOOTER //\n// webpack:///src/components/user_settings/user_settings.vue"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/config.json b/priv/static/static/font/config.json index 3abeffe90..59aab164c 100644 --- a/priv/static/static/font/config.json +++ b/priv/static/static/font/config.json @@ -1,4 +1,5 @@ { + "name": "", "css_prefix_text": "icon-", "css_use_suffix": false, "hinting": true, @@ -50,7 +51,7 @@ { "uid": "09feb4465d9bd1364f4e301c9ddbaa92", "css": "retweet", - "code": 59398, + "code": 59396, "src": "fontawesome" }, { @@ -185,10 +186,16 @@ "code": 59411, "src": "iconic" }, + { + "uid": "9dd9e835aebe1060ba7190ad2b2ed951", + "css": "search", + "code": 59398, + "src": "fontawesome" + }, { "uid": "ca90da02d2c6a3183f2458e4dc416285", "css": "adjust", - "code": 59396, + "code": 59414, "src": "fontawesome" }, { diff --git a/priv/static/static/font/css/fontello-codes.css b/priv/static/static/font/css/fontello-codes.css index 5cfcbf6a2..3cd9f19cf 100644 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 58a574563..99c20e7c2 100644 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 fa7c10021..f1b0c3bfa 100644 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 b37a63cd5..be4f280a1 100644 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 38caec46f..babb5ff19 100644 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 cb1aa970f..98f47f9e5 100644 --- 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?15755415'); - src: url('./font/fontello.eot?15755415#iefix') format('embedded-opentype'), - url('./font/fontello.woff?15755415') format('woff'), - url('./font/fontello.ttf?15755415') format('truetype'), - url('./font/fontello.svg?15755415#fontello') format('svg'); + src: url('./font/fontello.eot?32716429'); + src: url('./font/fontello.eot?32716429#iefix') format('embedded-opentype'), + url('./font/fontello.woff?32716429') format('woff'), + url('./font/fontello.ttf?32716429') format('truetype'), + url('./font/fontello.svg?32716429#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -304,9 +304,9 @@ body {
icon-star-empty0xe803
-
icon-adjust0xe804
+
icon-retweet0xe804
icon-eye-off0xe805
-
icon-retweet0xe806
+
icon-search0xe806
icon-cog0xe807
@@ -330,22 +330,23 @@ body {
icon-attention0xe814
icon-plus0xe815
+
icon-adjust0xe816
icon-spin30xe832
-
icon-spin40xe834
+
icon-spin40xe834
icon-link-ext0xf08e
icon-link-ext-alt0xf08f
icon-menu0xf0c9
-
icon-mail-alt0xf0e0
+
icon-mail-alt0xf0e0
icon-comment-empty0xf0e5
icon-plus-squared0xf0fe
icon-reply0xf112
-
icon-lock-open-alt0xf13e
+
icon-lock-open-alt0xf13e
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 7dfef262d..5aa0acd4a 100644 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 6e5616a16..3c2345b62 100644 --- a/priv/static/static/font/font/fontello.svg +++ b/priv/static/static/font/font/fontello.svg @@ -14,11 +14,11 @@ - + - + @@ -50,6 +50,8 @@ + + diff --git a/priv/static/static/font/font/fontello.ttf b/priv/static/static/font/font/fontello.ttf index 1fe7c6313..11bde81c8 100644 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 fc65e3251..090704899 100644 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 8513d894e..25fa5ebbf 100644 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/js/app.0220742f52d6912415d5.js b/priv/static/static/js/app.0220742f52d6912415d5.js new file mode 100644 index 000000000..0c370af97 Binary files /dev/null and b/priv/static/static/js/app.0220742f52d6912415d5.js differ diff --git a/priv/static/static/js/app.0220742f52d6912415d5.js.map b/priv/static/static/js/app.0220742f52d6912415d5.js.map new file mode 100644 index 000000000..21d449c09 Binary files /dev/null and b/priv/static/static/js/app.0220742f52d6912415d5.js.map differ diff --git a/priv/static/static/js/app.67f548ecb9e9fd6b25b0.js b/priv/static/static/js/app.67f548ecb9e9fd6b25b0.js deleted file mode 100644 index fab28cbf7..000000000 Binary files a/priv/static/static/js/app.67f548ecb9e9fd6b25b0.js and /dev/null differ diff --git a/priv/static/static/js/app.67f548ecb9e9fd6b25b0.js.map b/priv/static/static/js/app.67f548ecb9e9fd6b25b0.js.map deleted file mode 100644 index 158c10917..000000000 Binary files a/priv/static/static/js/app.67f548ecb9e9fd6b25b0.js.map and /dev/null differ diff --git a/priv/static/static/js/manifest.e076977b8e6c6844fb00.js b/priv/static/static/js/manifest.e076977b8e6c6844fb00.js deleted file mode 100644 index a7afd4706..000000000 Binary files a/priv/static/static/js/manifest.e076977b8e6c6844fb00.js and /dev/null differ diff --git a/priv/static/static/js/manifest.f0b8300215e3fdbb725f.js b/priv/static/static/js/manifest.f0b8300215e3fdbb725f.js new file mode 100644 index 000000000..1e5e12107 Binary files /dev/null and b/priv/static/static/js/manifest.f0b8300215e3fdbb725f.js differ diff --git a/priv/static/static/js/manifest.e076977b8e6c6844fb00.js.map b/priv/static/static/js/manifest.f0b8300215e3fdbb725f.js.map similarity index 92% rename from priv/static/static/js/manifest.e076977b8e6c6844fb00.js.map rename to priv/static/static/js/manifest.f0b8300215e3fdbb725f.js.map index df9d1eb84..9f0b56d7a 100644 Binary files a/priv/static/static/js/manifest.e076977b8e6c6844fb00.js.map and b/priv/static/static/js/manifest.f0b8300215e3fdbb725f.js.map differ diff --git a/priv/static/static/js/vendor.cc4190750f6ed4d697bd.js b/priv/static/static/js/vendor.b578d16088622c18d886.js similarity index 88% rename from priv/static/static/js/vendor.cc4190750f6ed4d697bd.js rename to priv/static/static/js/vendor.b578d16088622c18d886.js index 196c3fb5f..b286ec0ed 100644 Binary files a/priv/static/static/js/vendor.cc4190750f6ed4d697bd.js and b/priv/static/static/js/vendor.b578d16088622c18d886.js differ diff --git a/priv/static/static/js/vendor.b578d16088622c18d886.js.map b/priv/static/static/js/vendor.b578d16088622c18d886.js.map new file mode 100644 index 000000000..09b08784a Binary files /dev/null and b/priv/static/static/js/vendor.b578d16088622c18d886.js.map differ diff --git a/priv/static/static/js/vendor.cc4190750f6ed4d697bd.js.map b/priv/static/static/js/vendor.cc4190750f6ed4d697bd.js.map deleted file mode 100644 index d9179c6f9..000000000 Binary files a/priv/static/static/js/vendor.cc4190750f6ed4d697bd.js.map and /dev/null differ diff --git a/priv/static/sw.js b/priv/static/sw.js index c6411dc0d..2549a1902 100644 Binary files a/priv/static/sw.js and b/priv/static/sw.js differ diff --git a/test/captcha_test.exs b/test/captcha_test.exs new file mode 100644 index 000000000..54ffbd92f --- /dev/null +++ b/test/captcha_test.exs @@ -0,0 +1,40 @@ +defmodule Pleroma.CaptchaTest do + use ExUnit.Case + + import Tesla.Mock + + alias Pleroma.Captcha.Kocaptcha + + @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] + + describe "Kocaptcha" do + setup do + ets_name = Kocaptcha.Ets + ^ets_name = :ets.new(ets_name, @ets_options) + + mock(fn + %{method: :get, url: "https://captcha.kotobank.ch/new"} -> + json(%{ + md5: "63615261b77f5354fb8c4e4986477555", + token: "afa1815e14e29355e6c8f6b143a39fa2", + url: "/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + }) + end) + + :ok + end + + test "new and validate" do + assert Kocaptcha.new() == %{ + type: :kocaptcha, + token: "afa1815e14e29355e6c8f6b143a39fa2", + url: "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + } + + assert Kocaptcha.validate( + "afa1815e14e29355e6c8f6b143a39fa2", + "7oEy8c" + ) + end + end +end diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 428227d78..bb318b7d5 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -257,4 +257,23 @@ test "it doesn't die when text is absent" do text = nil assert Formatter.get_emoji(text) == [] end + + describe "/mentions_escape" do + test "it returns text with escaped mention names" do + text = """ + @a_breakin_glass@cybre.space + (also, little voice inside my head thinking "maybe this will encourage people + pronouncing it properly instead of saying _raKEWdo_ ") + """ + + escape_text = """ + @a\\_breakin\\_glass@cybre\\.space + (also, little voice inside my head thinking \"maybe this will encourage people + pronouncing it properly instead of saying _raKEWdo_ \") + """ + + mentions = [{"@a_breakin_glass@cybre.space", %{}}] + assert Formatter.mentions_escape(text, mentions) == escape_text + end + end end diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs new file mode 100644 index 000000000..b5f3d3a47 --- /dev/null +++ b/test/integration/mastodon_websocket_test.exs @@ -0,0 +1,100 @@ +defmodule Pleroma.Integration.MastodonWebsocketTest do + use Pleroma.DataCase + + import Pleroma.Factory + + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth + alias Pleroma.Integration.WebsocketClient + alias Pleroma.Web.Streamer + + @path Pleroma.Web.Endpoint.url() + |> URI.parse() + |> Map.put(:scheme, "ws") + |> Map.put(:path, "/api/v1/streaming") + |> URI.to_string() + + setup do + GenServer.start(Streamer, %{}, name: Streamer) + + on_exit(fn -> + if pid = Process.whereis(Streamer) do + Process.exit(pid, :kill) + end + end) + end + + def start_socket(qs \\ nil, headers \\ []) do + path = + case qs do + nil -> @path + qs -> @path <> qs + end + + WebsocketClient.start_link(self(), path, headers) + end + + test "refuses invalid requests" do + assert {:error, {400, _}} = start_socket() + assert {:error, {404, _}} = start_socket("?stream=ncjdk") + end + + test "requires authentication and a valid token for protected streams" do + assert {:error, {403, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa") + assert {:error, {403, _}} = start_socket("?stream=user") + end + + test "allows public streams without authentication" do + assert {:ok, _} = start_socket("?stream=public") + assert {:ok, _} = start_socket("?stream=public:local") + assert {:ok, _} = start_socket("?stream=hashtag&tag=lain") + end + + test "receives well formatted events" do + user = insert(:user) + {:ok, _} = start_socket("?stream=public") + {:ok, activity} = CommonAPI.post(user, %{"status" => "nice echo chamber"}) + + assert_receive {:text, raw_json}, 1_000 + assert {:ok, json} = Jason.decode(raw_json) + + assert "update" == json["event"] + assert json["payload"] + assert {:ok, json} = Jason.decode(json["payload"]) + + # Note: we remove the "statuses_count" from this result as it changes in the meantime + + view_json = + Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: activity, for: nil) + |> Jason.encode!() + |> Jason.decode!() + |> put_in(["account", "statuses_count"], 0) + + assert json == view_json + end + + describe "with a valid user token" do + setup do + {:ok, app} = + Pleroma.Repo.insert( + OAuth.App.register_changeset(%OAuth.App{}, %{ + client_name: "client", + scopes: "scope", + redirect_uris: "url" + }) + ) + + user = insert(:user) + + {:ok, auth} = OAuth.Authorization.create_authorization(app, user) + + {:ok, token} = OAuth.Token.exchange_token(app, auth) + + %{user: user, token: token} + end + + test "accepts valid tokens", state do + assert {:ok, _} = start_socket("?stream=user&access_token=#{state.token.token}") + end + end +end diff --git a/test/plugs/instance_static_test.exs b/test/plugs/instance_static_test.exs new file mode 100644 index 000000000..526679aae --- /dev/null +++ b/test/plugs/instance_static_test.exs @@ -0,0 +1,43 @@ +defmodule Pleroma.Web.RuntimeStaticPlugTest do + use Pleroma.Web.ConnCase + + @dir "test/tmp/instance_static" + + setup do + static_dir = Pleroma.Config.get([:instance, :static_dir]) + Pleroma.Config.put([:instance, :static_dir], @dir) + File.mkdir_p!(@dir) + + on_exit(fn -> + Pleroma.Config.put([:instance, :static_dir], static_dir) + File.rm_rf(@dir) + end) + end + + test "overrides index" do + bundled_index = get(build_conn(), "/") + assert html_response(bundled_index, 200) == File.read!("priv/static/index.html") + + File.write!(@dir <> "/index.html", "hello world") + + index = get(build_conn(), "/") + assert html_response(index, 200) == "hello world" + end + + test "overrides any file in static/static" do + bundled_index = get(build_conn(), "/static/terms-of-service.html") + + assert html_response(bundled_index, 200) == + File.read!("priv/static/static/terms-of-service.html") + + File.mkdir!(@dir <> "/static") + File.write!(@dir <> "/static/terms-of-service.html", "plz be kind") + + index = get(build_conn(), "/static/terms-of-service.html") + assert html_response(index, 200) == "plz be kind" + + File.write!(@dir <> "/static/kaniini.html", "

rabbit hugs as a service

") + index = get(build_conn(), "/static/kaniini.html") + assert html_response(index, 200) == "

rabbit hugs as a service

" + end +end diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex new file mode 100644 index 000000000..898aa17b8 --- /dev/null +++ b/test/support/captcha_mock.ex @@ -0,0 +1,13 @@ +defmodule Pleroma.Captcha.Mock do + alias Pleroma.Captcha.Service + @behaviour Service + + @impl Service + def new(), do: %{type: :mock} + + @impl Service + def validate(_token, _captcha), do: true + + @impl Service + def cleanup(), do: :ok +end diff --git a/test/support/websocket_client.ex b/test/support/websocket_client.ex new file mode 100644 index 000000000..57e9bb17f --- /dev/null +++ b/test/support/websocket_client.ex @@ -0,0 +1,58 @@ +defmodule Pleroma.Integration.WebsocketClient do + # https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs + + @doc """ + Starts the WebSocket server for given ws URL. Received Socket.Message's + are forwarded to the sender pid + """ + def start_link(sender, url, headers \\ []) do + :crypto.start() + :ssl.start() + + :websocket_client.start_link( + String.to_charlist(url), + __MODULE__, + [sender], + extra_headers: headers + ) + end + + @doc """ + Closes the socket + """ + def close(socket) do + send(socket, :close) + end + + @doc """ + Sends a low-level text message to the client. + """ + def send_text(server_pid, msg) do + send(server_pid, {:text, msg}) + end + + @doc false + def init([sender], _conn_state) do + {:ok, %{sender: sender}} + end + + @doc false + def websocket_handle(frame, _conn_state, state) do + send(state.sender, frame) + {:ok, state} + end + + @doc false + def websocket_info({:text, msg}, _conn_state, state) do + {:reply, {:text, msg}, state} + end + + def websocket_info(:close, _conn_state, _state) do + {:close, <<>>, "done"} + end + + @doc false + def websocket_terminate(_reason, _conn_state, _state) do + :ok + end +end diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs new file mode 100644 index 000000000..737293865 --- /dev/null +++ b/test/tasks/relay_test.exs @@ -0,0 +1,65 @@ +defmodule Mix.Tasks.Pleroma.RelayTest do + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.{ActivityPub, Relay, Utils} + alias Pleroma.User + use Pleroma.DataCase + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok + end + + describe "running follow" do + test "relay is followed" do + target_instance = "http://mastodon.example.org/users/admin" + + Mix.Tasks.Pleroma.Relay.run(["follow", target_instance]) + + local_user = Relay.get_actor() + assert local_user.ap_id =~ "/relay" + + target_user = User.get_by_ap_id(target_instance) + refute target_user.local + + activity = Utils.fetch_latest_follow(local_user, target_user) + assert activity.data["type"] == "Follow" + assert activity.data["actor"] == local_user.ap_id + assert activity.data["object"] == target_user.ap_id + end + end + + describe "running unfollow" do + test "relay is unfollowed" do + target_instance = "http://mastodon.example.org/users/admin" + + 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) + follow_activity = Utils.fetch_latest_follow(local_user, target_user) + + Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance]) + + cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"]) + assert cancelled_activity.data["state"] == "cancelled" + + [undo_activity] = + ActivityPub.fetch_activities([], %{ + "type" => "Undo", + "actor_id" => follower_id, + "limit" => 1 + }) + + assert undo_activity.data["type"] == "Undo" + assert undo_activity.data["actor"] == local_user.ap_id + assert undo_activity.data["object"] == cancelled_activity.data + end + end +end diff --git a/test/tasks/uploads_test.exs b/test/tasks/uploads_test.exs new file mode 100644 index 000000000..93035abb6 --- /dev/null +++ b/test/tasks/uploads_test.exs @@ -0,0 +1,52 @@ +defmodule Mix.Tasks.Pleroma.UploadsTest do + alias Pleroma.Upload + use Pleroma.DataCase + + import Mock + + setup_all do + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok + end + + describe "running migrate_local" do + test "uploads migrated" do + with_mock Upload, + store: fn %Upload{name: _file, path: _path}, _opts -> {:ok, %{}} end do + Mix.Tasks.Pleroma.Uploads.run(["migrate_local", "S3"]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Migrating files from local" + + assert_received {:mix_shell, :info, [message]} + + assert %{"total_count" => total_count} = + Regex.named_captures(~r"^Found (?\d+) uploads$", message) + + assert_received {:mix_shell, :info, [message]} + + # @logevery in Mix.Tasks.Pleroma.Uploads + count = + min(50, String.to_integer(total_count)) + |> to_string() + + assert %{"count" => ^count, "total_count" => ^total_count} = + Regex.named_captures( + ~r"^Uploaded (?\d+)/(?\d+) files$", + message + ) + end + end + + test "nonexistent uploader" do + assert_raise RuntimeError, ~r/The uploader .* is not an existing/, fn -> + Mix.Tasks.Pleroma.Uploads.run(["migrate_local", "nonexistent"]) + end + end + end +end diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs new file mode 100644 index 000000000..7479bf749 --- /dev/null +++ b/test/tasks/user_test.exs @@ -0,0 +1,247 @@ +defmodule Mix.Tasks.Pleroma.UserTest do + alias Pleroma.User + use Pleroma.DataCase + + import Pleroma.Factory + import ExUnit.CaptureIO + + setup_all do + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok + end + + describe "running new" do + test "user is created" do + # just get random data + unsaved = build(:user) + + # prepare to answer yes + send(self(), {:mix_shell_input, :yes?, true}) + + Mix.Tasks.Pleroma.User.run([ + "new", + unsaved.nickname, + unsaved.email, + "--name", + unsaved.name, + "--bio", + unsaved.bio, + "--password", + "test", + "--moderator", + "--admin" + ]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "user will be created" + + assert_received {:mix_shell, :yes?, [message]} + assert message =~ "Continue" + + assert_received {:mix_shell, :info, [message]} + assert message =~ "created" + + user = User.get_by_nickname(unsaved.nickname) + assert user.name == unsaved.name + assert user.email == unsaved.email + assert user.bio == unsaved.bio + assert user.info.is_moderator + assert user.info.is_admin + end + + test "user is not created" do + unsaved = build(:user) + + # prepare to answer no + send(self(), {:mix_shell_input, :yes?, false}) + + Mix.Tasks.Pleroma.User.run(["new", unsaved.nickname, unsaved.email]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "user will be created" + + assert_received {:mix_shell, :yes?, [message]} + assert message =~ "Continue" + + assert_received {:mix_shell, :info, [message]} + assert message =~ "will not be created" + + refute User.get_by_nickname(unsaved.nickname) + end + end + + describe "running rm" do + test "user is deleted" do + user = insert(:user) + + Mix.Tasks.Pleroma.User.run(["rm", user.nickname]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ " deleted" + + user = User.get_by_nickname(user.nickname) + assert user.info.deactivated + end + + test "no user to delete" do + Mix.Tasks.Pleroma.User.run(["rm", "nonexistent"]) + + assert_received {:mix_shell, :error, [message]} + assert message =~ "No local user" + end + end + + describe "running toggle_activated" do + test "user is deactivated" do + user = insert(:user) + + Mix.Tasks.Pleroma.User.run(["toggle_activated", user.nickname]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ " deactivated" + + user = User.get_by_nickname(user.nickname) + assert user.info.deactivated + end + + test "user is activated" do + user = insert(:user, info: %{deactivated: true}) + + Mix.Tasks.Pleroma.User.run(["toggle_activated", user.nickname]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ " activated" + + user = User.get_by_nickname(user.nickname) + refute user.info.deactivated + end + + test "no user to toggle" do + Mix.Tasks.Pleroma.User.run(["toggle_activated", "nonexistent"]) + + assert_received {:mix_shell, :error, [message]} + assert message =~ "No user" + end + end + + describe "running unsubscribe" do + test "user is unsubscribed" do + followed = insert(:user) + user = insert(:user, %{following: [User.ap_followers(followed)]}) + + Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Deactivating" + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Unsubscribing" + + # Note that the task has delay :timer.sleep(500) + assert_received {:mix_shell, :info, [message]} + assert message =~ "Successfully unsubscribed" + + user = User.get_by_nickname(user.nickname) + assert length(user.following) == 0 + assert user.info.deactivated + end + + test "no user to unsubscribe" do + Mix.Tasks.Pleroma.User.run(["unsubscribe", "nonexistent"]) + + assert_received {:mix_shell, :error, [message]} + assert message =~ "No user" + end + end + + describe "running set" do + test "All statuses set" do + user = insert(:user) + + Mix.Tasks.Pleroma.User.run(["set", user.nickname, "--moderator", "--admin", "--locked"]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Moderator status .* true/ + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Locked status .* true/ + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Admin status .* true/ + + user = User.get_by_nickname(user.nickname) + assert user.info.is_moderator + assert user.info.locked + assert user.info.is_admin + end + + test "All statuses unset" do + user = insert(:user, info: %{is_moderator: true, locked: true, is_admin: true}) + + Mix.Tasks.Pleroma.User.run([ + "set", + user.nickname, + "--no-moderator", + "--no-admin", + "--no-locked" + ]) + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Moderator status .* false/ + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Locked status .* false/ + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Admin status .* false/ + + user = User.get_by_nickname(user.nickname) + refute user.info.is_moderator + refute user.info.locked + refute user.info.is_admin + end + + test "no user to set status" do + Mix.Tasks.Pleroma.User.run(["set", "nonexistent", "--moderator"]) + + assert_received {:mix_shell, :error, [message]} + assert message =~ "No local user" + end + end + + describe "running reset_password" do + test "password reset token is generated" do + user = insert(:user) + + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run(["reset_password", user.nickname]) + end) =~ "URL:" + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Generated" + end + + test "no user to reset password" do + Mix.Tasks.Pleroma.User.run(["reset_password", "nonexistent"]) + + assert_received {:mix_shell, :error, [message]} + assert message =~ "No local user" + end + end + + describe "running invite" do + test "invite token is generated" do + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run(["invite"]) + end) =~ "http" + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Generated" + end + end +end diff --git a/test/user_test.exs b/test/user_test.exs index 9baa5ef24..1e73770df 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -11,6 +11,23 @@ defmodule Pleroma.UserTest do :ok end + describe "when tags are nil" do + test "tagging a user" do + user = insert(:user, %{tags: nil}) + user = User.tag(user, ["cool", "dude"]) + + assert "cool" in user.tags + assert "dude" in user.tags + end + + test "untagging a user" do + user = insert(:user, %{tags: nil}) + user = User.untag(user, ["cool", "dude"]) + + assert user.tags == [] + end + end + test "ap_id returns the activity pub id for the user" do user = UserBuilder.build() diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 4c12dd988..e183da3a1 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -154,6 +154,105 @@ test "/:right DELETE, can remove from a permission group" do end end + describe "POST /api/pleroma/admin/email_invite, with valid config" do + setup do + registrations_open = Pleroma.Config.get([:instance, :registrations_open]) + invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) + Pleroma.Config.put([:instance, :registrations_open], false) + Pleroma.Config.put([:instance, :invites_enabled], true) + + on_exit(fn -> + Pleroma.Config.put([:instance, :registrations_open], registrations_open) + Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) + :ok + end) + + [user: insert(:user, info: %{is_admin: true})] + end + + test "sends invitation and returns 204", %{conn: conn, user: user} do + recipient_email = "foo@bar.com" + recipient_name = "J. D." + + conn = + conn + |> assign(:user, user) + |> post("/api/pleroma/admin/email_invite?email=#{recipient_email}&name=#{recipient_name}") + + assert json_response(conn, :no_content) + + token_record = List.last(Pleroma.Repo.all(Pleroma.UserInviteToken)) + assert token_record + refute token_record.used + + Swoosh.TestAssertions.assert_email_sent( + Pleroma.UserEmail.user_invitation_email( + user, + token_record, + recipient_email, + recipient_name + ) + ) + end + + test "it returns 403 if requested by a non-admin", %{conn: conn} do + non_admin_user = insert(:user) + + conn = + conn + |> assign(:user, non_admin_user) + |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD") + + assert json_response(conn, :forbidden) + end + end + + describe "POST /api/pleroma/admin/email_invite, with invalid config" do + setup do + [user: insert(:user, info: %{is_admin: true})] + end + + test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn, user: user} do + registrations_open = Pleroma.Config.get([:instance, :registrations_open]) + invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) + Pleroma.Config.put([:instance, :registrations_open], false) + Pleroma.Config.put([:instance, :invites_enabled], false) + + on_exit(fn -> + Pleroma.Config.put([:instance, :registrations_open], registrations_open) + Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) + :ok + end) + + conn = + conn + |> assign(:user, user) + |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD") + + assert json_response(conn, :internal_server_error) + end + + test "it returns 500 if `registrations_open` is enabled", %{conn: conn, user: user} do + registrations_open = Pleroma.Config.get([:instance, :registrations_open]) + invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) + Pleroma.Config.put([:instance, :registrations_open], true) + Pleroma.Config.put([:instance, :invites_enabled], true) + + on_exit(fn -> + Pleroma.Config.put([:instance, :registrations_open], registrations_open) + Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) + :ok + end) + + conn = + conn + |> assign(:user, user) + |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD") + + assert json_response(conn, :internal_server_error) + end + end + test "/api/pleroma/admin/invite_token" do admin = insert(:user, info: %{is_admin: true}) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index e8275d4ab..aec0f851c 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1415,4 +1415,18 @@ test "get instance information", %{conn: conn} do assert result["stats"]["user_count"] == 2 assert result["stats"]["status_count"] == 1 end + + test "put settings", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) + + assert result = json_response(conn, 200) + + user = User.get_cached_by_ap_id(user.ap_id) + assert user.info.settings == %{"programming" => "socks"} + end end diff --git a/test/web/mastodon_api/mastodon_socket_test.exs b/test/web/mastodon_api/mastodon_socket_test.exs deleted file mode 100644 index 5d9b96861..000000000 --- a/test/web/mastodon_api/mastodon_socket_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Pleroma.Web.MastodonApi.MastodonSocketTest do - use Pleroma.DataCase - - alias Pleroma.Web.{Streamer, CommonAPI} - - import Pleroma.Factory - - test "public is working when non-authenticated" do - user = insert(:user) - - task = - Task.async(fn -> - assert_receive {:text, _}, 4_000 - end) - - fake_socket = %{ - transport_pid: task.pid, - assigns: %{} - } - - topics = %{ - "public" => [fake_socket] - } - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"}) - - Streamer.push_to_socket(topics, "public", activity) - - Task.await(task) - end -end diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index d10d59d6c..b7ac92760 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -62,7 +62,12 @@ test "a note activity" do visibility: "public", media_attachments: [], mentions: [], - tags: note.data["object"]["tag"], + tags: [ + %{ + name: "#{note.data["object"]["tag"]}", + url: "/tag/#{note.data["object"]["tag"]}" + } + ], application: %{ name: "Web", website: nil @@ -151,4 +156,25 @@ test "a reblog" do assert represented[:reblog][:id] == to_string(activity.id) assert represented[:emojis] == [] end + + describe "build_tags/1" do + test "it returns a a dictionary tags" do + object_tags = [ + "fediverse", + "mastodon", + "nextcloud", + %{ + "href" => "https://kawen.space/users/lain", + "name" => "@lain@kawen.space", + "type" => "Mention" + } + ] + + assert StatusView.build_tags(object_tags) == [ + %{name: "fediverse", url: "/tag/fediverse"}, + %{name: "mastodon", url: "/tag/mastodon"}, + %{name: "nextcloud", url: "/tag/nextcloud"} + ] + end + 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 77b8d99e7..8aa9e3130 100644 --- a/test/web/twitter_api/views/activity_view_test.exs +++ b/test/web/twitter_api/views/activity_view_test.exs @@ -14,6 +14,22 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do import Pleroma.Factory import Mock + test "a create activity with a html status" do + text = """ + #Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg + """ + + {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text}) + + result = ActivityView.render("activity.json", activity: activity) + + assert result["statusnet_html"] == + "#Bike log - Commute Tuesday
https://pla.bike/posts/20181211/
#cycling #CHScycling #commute
MVIMG_20181211_054020.jpg" + + assert result["text"] == + "#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg" + end + test "a create activity with a note" do user = insert(:user) other_user = insert(:user, %{nickname: "shp"}) diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs new file mode 100644 index 000000000..844ff51d2 --- /dev/null +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -0,0 +1,42 @@ +defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + import Tesla.Mock + + setup do + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "Webfinger JRD" do + user = insert(:user) + + response = + build_conn() + |> put_req_header("accept", "application/jrd+json") + |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") + + assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost" + end + + test "Webfinger XML" do + user = insert(:user) + + response = + build_conn() + |> put_req_header("accept", "application/xrd+xml") + |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") + + assert response(response, 200) + end + + test "Sends a 400 when resource param is missing" do + response = + build_conn() + |> put_req_header("accept", "application/xrd+xml,application/jrd+json") + |> get("/.well-known/webfinger") + + assert response(response, 400) + end +end