From 6c39fa20b191f985a2be704089c20acbcfe0035a Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 17 Dec 2019 17:00:46 +0700 Subject: [PATCH 01/28] Add support for `account_id` param to filter notifications by the account --- CHANGELOG.md | 1 + lib/pleroma/web/mastodon_api/mastodon_api.ex | 11 ++++++++++- .../controllers/notification_controller_test.exs | 23 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c133cd9ec..f0274ca01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field. - Captcha: Support native provider - Captcha: Enable by default +- Mastodon API: Add support for `account_id` param to filter notifications by the account ### Fixed diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index b1816370e..6c13d4df6 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -56,6 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do user |> Notification.for_user_query(options) |> restrict(:exclude_types, options) + |> restrict(:account_id, options) |> Pagination.fetch_paginated(params) end @@ -71,7 +72,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do exclude_visibilities: {:array, :string}, reblogs: :boolean, with_muted: :boolean, - with_move: :boolean + with_move: :boolean, + account_id: :string } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) @@ -88,5 +90,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) end + defp restrict(query, :account_id, %{account_id: account_id}) do + case User.get_cached_by_id(account_id) do + %{ap_id: ap_id} -> where(query, [n, a], a.actor == ^ap_id) + _ -> where(query, [n, a], a.actor == "fake ap id") + end + end + defp restrict(query, _, _), do: query end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 6635ea7a2..3458776ab 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -463,6 +463,29 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do assert length(json_response(conn, 200)) == 1 end + describe "from specified user" do + test "account_id", %{conn: conn} do + user = insert(:user) + %{id: account_id} = other_user1 = insert(:user) + other_user2 = insert(:user) + + {:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"}) + {:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"}) + + assert [%{"account" => %{"id" => ^account_id}}] = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{account_id: account_id}) + |> json_response(200) + + assert [] = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{account_id: "cofe"}) + |> json_response(200) + end + end + defp get_notification_id_by_activity(%{id: id}) do Notification |> Repo.get_by(activity_id: id) From 34d85f8a5473fe0f85e8a8e9e8f58e40b3964ba4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 19 Dec 2019 20:45:44 +0700 Subject: [PATCH 02/28] Return 404 if account to filter notifications from is not found --- .../mastodon_api/controllers/notification_controller.ex | 17 +++++++++++++++++ lib/pleroma/web/mastodon_api/mastodon_api.ex | 11 ++++------- .../controllers/notification_controller_test.exs | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 16759be6a..f2508aca4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -23,6 +23,23 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) # GET /api/v1/notifications + def index(conn, %{"account_id" => account_id} = params) do + case Pleroma.User.get_cached_by_id(account_id) do + %{ap_id: account_ap_id} -> + params = + params + |> Map.delete("account_id") + |> Map.put("account_ap_id", account_ap_id) + + index(conn, params) + + _ -> + conn + |> put_status(:not_found) + |> json(%{"error" => "Account is not found"}) + end + end + def index(%{assigns: %{user: user}} = conn, params) do notifications = MastodonAPI.get_notifications(user, params) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 6c13d4df6..390a2b190 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do user |> Notification.for_user_query(options) |> restrict(:exclude_types, options) - |> restrict(:account_id, options) + |> restrict(:account_ap_id, options) |> Pagination.fetch_paginated(params) end @@ -73,7 +73,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do reblogs: :boolean, with_muted: :boolean, with_move: :boolean, - account_id: :string + account_ap_id: :string } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) @@ -90,11 +90,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) end - defp restrict(query, :account_id, %{account_id: account_id}) do - case User.get_cached_by_id(account_id) do - %{ap_id: ap_id} -> where(query, [n, a], a.actor == ^ap_id) - _ -> where(query, [n, a], a.actor == "fake ap id") - end + defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do + where(query, [n, a], a.actor == ^account_ap_id) end defp restrict(query, _, _), do: query diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 3458776ab..24d0d49ed 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -478,11 +478,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do |> get("/api/v1/notifications", %{account_id: account_id}) |> json_response(200) - assert [] = + assert %{"error" => "Account is not found"} = conn |> assign(:user, user) |> get("/api/v1/notifications", %{account_id: "cofe"}) - |> json_response(200) + |> json_response(404) end end From 385356aad0dd7eac0695bb1597ba1e52b5f17b40 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 24 Dec 2019 20:45:46 +0300 Subject: [PATCH 03/28] fix oauth scopes for AdminApi#reports_update --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- test/web/admin_api/admin_api_controller_test.exs | 24 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index c8abeff06..ddae139c6 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -66,7 +66,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["write:reports"], admin: true} - when action in [:report_update_state, :report_respond] + when action in [:reports_update] ) plug( diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 49ff005b6..4156ef50d 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1418,6 +1418,30 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do } end + test "requires write:reports scope", %{conn: conn, id: id, admin: admin} do + read_token = insert(:oauth_token, user: admin, scopes: ["read"]) + write_token = insert(:oauth_token, user: admin, scopes: ["write:reports"]) + + response = + conn + |> assign(:token, read_token) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [%{"state" => "resolved", "id" => id}] + }) + |> json_response(403) + + assert response == %{ + "error" => "Insufficient permissions: admin:write:reports | write:reports." + } + + conn + |> assign(:token, write_token) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [%{"state" => "resolved", "id" => id}] + }) + |> json_response(:no_content) + end + test "mark report as resolved", %{conn: conn, id: id, admin: admin} do conn |> patch("/api/pleroma/admin/reports", %{ From 6c94b7498b889ffe13691123c94bbe5440786852 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 10 Jan 2020 10:52:21 +0300 Subject: [PATCH 04/28] [#1478] OAuth admin tweaks: enforced OAuth admin scopes usage by default, migrated existing OAuth records. Adjusted tests. --- CHANGELOG.md | 1 + config/config.exs | 2 +- lib/pleroma/plugs/user_is_admin_plug.ex | 1 + lib/pleroma/user.ex | 23 ++----- lib/pleroma/web/oauth/oauth_controller.ex | 10 +-- lib/pleroma/web/oauth/scopes.ex | 24 +------ ...4645_add_scopes_to_pleroma_feo_auth_records.exs | 17 +++++ test/web/oauth/oauth_controller_test.exs | 78 +++++++++++----------- .../controllers/emoji_api_controller_test.exs | 4 ++ 9 files changed, 79 insertions(+), 81 deletions(-) create mode 100644 priv/repo/migrations/20191220174645_add_scopes_to_pleroma_feo_auth_records.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f199b3d..02ddb6213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - **Breaking:** Elixir >=1.8 is now required (was >= 1.7) - **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default +- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features. - Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings) - Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler - Enabled `:instance, extended_nickname_format` in the default config diff --git a/config/config.exs b/config/config.exs index c8d42e83e..eeb88174a 100644 --- a/config/config.exs +++ b/config/config.exs @@ -565,7 +565,7 @@ config :ueberauth, config :pleroma, :auth, - enforce_oauth_admin_scope_usage: false, + enforce_oauth_admin_scope_usage: true, oauth_consumer_strategies: oauth_consumer_strategies config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 582fb1f92..3190163d3 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do token && OAuth.Scopes.contains_admin_scopes?(token.scopes) -> # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. + # Admin might opt out of admin scope for some apps to block any admin actions from them. conn true -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 706aee2ff..40cc93e03 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1847,22 +1847,13 @@ defmodule Pleroma.User do end def admin_api_update(user, params) do - changeset = - cast(user, params, [ - :is_moderator, - :is_admin, - :show_role - ]) - - with {:ok, updated_user} <- update_and_set_cache(changeset) do - if user.is_admin && !updated_user.is_admin do - # Tokens & authorizations containing any admin scopes must be revoked (revoking all). - # This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins). - global_sign_out(user) - end - - {:ok, updated_user} - end + user + |> cast(params, [ + :is_moderator, + :is_admin, + :show_role + ]) + |> update_and_set_cache() end @doc "Signs user out of all applications" diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 87acdec97..d31a3d91c 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -222,7 +222,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:user_active, true} <- {:user_active, !user.deactivated}, {:password_reset_pending, false} <- {:password_reset_pending, user.password_reset_pending}, - {:ok, scopes} <- validate_scopes(app, params, user), + {:ok, scopes} <- validate_scopes(app, params), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build(user, token)) @@ -471,7 +471,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)}, %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), - {:ok, scopes} <- validate_scopes(app, auth_attrs, user), + {:ok, scopes} <- validate_scopes(app, auth_attrs), {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do Authorization.create_authorization(app, user, scopes) end @@ -487,12 +487,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), do: put_session(conn, :registration_id, registration_id) - @spec validate_scopes(App.t(), map(), User.t()) :: + @spec validate_scopes(App.t(), map()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(%App{} = app, params, %User{} = user) do + defp validate_scopes(%App{} = app, params) do params |> Scopes.fetch_scopes(app.scopes) - |> Scopes.validate(app.scopes, user) + |> Scopes.validate(app.scopes) end def default_redirect_uri(%App{} = app) do diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 00da225b9..151467494 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.OAuth.Scopes do """ alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.User @doc """ Fetch scopes from request params. @@ -56,35 +55,18 @@ defmodule Pleroma.Web.OAuth.Scopes do @doc """ Validates scopes. """ - @spec validate(list() | nil, list(), User.t()) :: + @spec validate(list() | nil, list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []], + def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []], do: {:error, :missing_scopes} - def validate(scopes, app_scopes, %User{} = user) do - with {:ok, _} <- ensure_scopes_support(scopes, app_scopes), - {:ok, scopes} <- authorize_admin_scopes(scopes, app_scopes, user) do - {:ok, scopes} - end - end - - defp ensure_scopes_support(scopes, app_scopes) do + def validate(scopes, app_scopes) do case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do ^scopes -> {:ok, scopes} _ -> {:error, :unsupported_scopes} end end - defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do - if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do - {:ok, scopes} - else - # Gracefully dropping admin scopes from requested scopes if user isn't an admin (not raising) - scopes = scopes -- OAuthScopesPlug.filter_descendants(scopes, ["admin"]) - validate(scopes, app_scopes, user) - end - end - def contains_admin_scopes?(scopes) do scopes |> OAuthScopesPlug.filter_descendants(["admin"]) diff --git a/priv/repo/migrations/20191220174645_add_scopes_to_pleroma_feo_auth_records.exs b/priv/repo/migrations/20191220174645_add_scopes_to_pleroma_feo_auth_records.exs new file mode 100644 index 000000000..6b160ad16 --- /dev/null +++ b/priv/repo/migrations/20191220174645_add_scopes_to_pleroma_feo_auth_records.exs @@ -0,0 +1,17 @@ +defmodule Pleroma.Repo.Migrations.AddScopesToPleromaFEOAuthRecords do + use Ecto.Migration + + def up do + update_scopes_clause = "SET scopes = '{read,write,follow,push,admin}'" + apps_where = "WHERE apps.client_name like 'PleromaFE_%' or apps.client_name like 'AdminFE_%'" + app_id_subquery_where = "WHERE app_id IN (SELECT apps.id FROM apps #{apps_where})" + + execute("UPDATE apps #{update_scopes_clause} #{apps_where}") + + for table <- ["oauth_authorizations", "oauth_tokens"] do + execute("UPDATE #{table} #{update_scopes_clause} #{app_id_subquery_where}") + end + end + + def down, do: :noop +end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index 901f2ae41..7a629da4f 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -568,29 +568,34 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do describe "POST /oauth/authorize" do test "redirects with oauth authorization, " <> - "keeping only non-admin scopes for non-admin user" do - app = insert(:oauth_app, scopes: ["read", "write", "admin"]) + "granting requested app-supported scopes to both admin- and non-admin users" do + app_scopes = ["read", "write", "admin", "secret_scope"] + app = insert(:oauth_app, scopes: app_scopes) redirect_uri = OAuthController.default_redirect_uri(app) non_admin = insert(:user, is_admin: false) admin = insert(:user, is_admin: true) + scopes_subset = ["read:subscope", "write", "admin"] - for {user, expected_scopes} <- %{ - non_admin => ["read:subscope", "write"], - admin => ["read:subscope", "write", "admin"] - } do + # In case scope param is missing, expecting _all_ app-supported scopes to be granted + for user <- [non_admin, admin], + {requested_scopes, expected_scopes} <- + %{scopes_subset => scopes_subset, nil => app_scopes} do conn = - build_conn() - |> post("/oauth/authorize", %{ - "authorization" => %{ - "name" => user.nickname, - "password" => "test", - "client_id" => app.client_id, - "redirect_uri" => redirect_uri, - "scope" => "read:subscope write admin", - "state" => "statepassed" + post( + build_conn(), + "/oauth/authorize", + %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "scope" => requested_scopes, + "state" => "statepassed" + } } - }) + ) target = redirected_to(conn) assert target =~ redirect_uri @@ -631,34 +636,31 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert result =~ "Invalid Username/Password" end - test "returns 401 for missing scopes " <> - "(including all admin-only scopes for non-admin user)" do + test "returns 401 for missing scopes" do user = insert(:user, is_admin: false) app = insert(:oauth_app, scopes: ["read", "write", "admin"]) redirect_uri = OAuthController.default_redirect_uri(app) - for scope_param <- ["", "admin:read admin:write"] do - result = - build_conn() - |> post("/oauth/authorize", %{ - "authorization" => %{ - "name" => user.nickname, - "password" => "test", - "client_id" => app.client_id, - "redirect_uri" => redirect_uri, - "state" => "statepassed", - "scope" => scope_param - } - }) - |> html_response(:unauthorized) + result = + build_conn() + |> post("/oauth/authorize", %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "state" => "statepassed", + "scope" => "" + } + }) + |> html_response(:unauthorized) - # Keep the details - assert result =~ app.client_id - assert result =~ redirect_uri + # Keep the details + assert result =~ app.client_id + assert result =~ redirect_uri - # Error message - assert result =~ "This action is outside the authorized scopes" - end + # Error message + assert result =~ "This action is outside the authorized scopes" end test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do diff --git a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs index 3d3becefd..101c4d7d7 100644 --- a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -14,6 +14,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do "emoji" ) + clear_config([:auth, :enforce_oauth_admin_scope_usage]) do + Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false) + end + test "shared & non-shared pack information in list_packs is ok" do conn = build_conn() resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) From 2ee6754095f5b81807efe97c73ada42e2c990ede Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 14 Jan 2020 17:24:26 +0100 Subject: [PATCH 05/28] Mix Tasks: Add pleroma.benchmarks.tags --- benchmarks/load_testing/generator.ex | 44 ++++++++++++++++- lib/mix/tasks/pleroma/benchmarks/tags.ex | 57 ++++++++++++++++++++++ .../controllers/timeline_controller.ex | 14 ++++-- 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 lib/mix/tasks/pleroma/benchmarks/tags.ex diff --git a/benchmarks/load_testing/generator.ex b/benchmarks/load_testing/generator.ex index a957e0ffb..3f88fefd7 100644 --- a/benchmarks/load_testing/generator.ex +++ b/benchmarks/load_testing/generator.ex @@ -9,7 +9,7 @@ defmodule Pleroma.LoadTesting.Generator do {time, _} = :timer.tc(fn -> Task.async_stream( - Enum.take_random(posts, count_likes), + Enum.take_random(posts, count_likes), fn post -> {:ok, _, _} = CommonAPI.favorite(post.id, user) end, max_concurrency: 10, timeout: 30_000 @@ -142,6 +142,48 @@ defmodule Pleroma.LoadTesting.Generator do CommonAPI.post(Enum.random(users), post) end + def generate_power_intervals(opts \\ []) do + count = Keyword.get(opts, :count, 20) + power = Keyword.get(opts, :power, 2) + IO.puts("Generating #{count} intervals for a power #{power} series...") + counts = Enum.map(1..count, fn n -> :math.pow(n, power) end) + sum = Enum.sum(counts) + + densities = + Enum.map(counts, fn c -> + c / sum + end) + + densities + |> Enum.reduce(0, fn density, acc -> + if acc == 0 do + [{0, density}] + else + [{_, lower} | _] = acc + [{lower, lower + density} | acc] + end + end) + |> Enum.reverse() + end + + def generate_tagged_activities(opts \\ []) do + tag_count = Keyword.get(opts, :tag_count, 20) + users = Keyword.get(opts, :users, Repo.all(User)) + activity_count = Keyword.get(opts, :count, 200_000) + + intervals = generate_power_intervals(count: tag_count) + + IO.puts( + "Generating #{activity_count} activities using #{tag_count} different tags of format `tag_n`, starting at tag_0" + ) + + Enum.each(1..activity_count, fn _ -> + random = :rand.uniform() + i = Enum.find_index(intervals, fn {lower, upper} -> lower <= random && upper > random end) + CommonAPI.post(Enum.random(users), %{"status" => "a post with the tag #tag_#{i}"}) + end) + end + defp do_generate_activity_with_mention(user, users) do mentions_cnt = Enum.random([2, 3, 4, 5]) with_user = Enum.random([true, false]) diff --git a/lib/mix/tasks/pleroma/benchmarks/tags.ex b/lib/mix/tasks/pleroma/benchmarks/tags.ex new file mode 100644 index 000000000..73796b5f9 --- /dev/null +++ b/lib/mix/tasks/pleroma/benchmarks/tags.ex @@ -0,0 +1,57 @@ +defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do + use Mix.Task + alias Pleroma.Repo + alias Pleroma.LoadTesting.Generator + import Ecto.Query + + def run(_args) do + Mix.Pleroma.start_pleroma() + activities_count = Repo.aggregate(from(a in Pleroma.Activity), :count, :id) + + if activities_count == 0 do + IO.puts("Did not find any activities, cleaning and generating") + clean_tables() + Generator.generate_users(users_max: 10) + Generator.generate_tagged_activities() + else + IO.puts("Found #{activities_count} activities, won't generate new ones") + end + + tags = Enum.map(0..20, fn i -> {"For #tag_#{i}", "tag_#{i}"} end) + + Enum.each(tags, fn {_, tag} -> + query = + from(o in Pleroma.Object, + where: fragment("(?)->'tag' \\? (?)", o.data, ^tag) + ) + + count = Repo.aggregate(query, :count, :id) + IO.puts("Database contains #{count} posts tagged with #{tag}") + end) + + user = Repo.all(Pleroma.User) |> List.first() + + Benchee.run( + %{ + "Hashtag fetching" => fn tag -> + Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching( + %{ + "tag" => tag + }, + user, + false + ) + end + }, + inputs: tags, + time: 5 + ) + end + + defp clean_tables do + IO.puts("Deleting old data...\n") + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE users CASCADE;") + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE activities CASCADE;") + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE objects CASCADE;") + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 384159336..29964a1d4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -77,10 +77,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> render("index.json", activities: activities, for: user, as: :activity) end - # GET /api/v1/timelines/tag/:tag - def hashtag(%{assigns: %{user: user}} = conn, params) do - local_only = truthy_param?(params["local"]) - + def hashtag_fetching(params, user, local_only) do tags = [params["tag"], params["any"]] |> List.flatten() @@ -98,7 +95,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.get("none", []) |> Enum.map(&String.downcase(&1)) - activities = + _activities = params |> Map.put("type", "Create") |> Map.put("local_only", local_only) @@ -109,6 +106,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.put("tag_all", tag_all) |> Map.put("tag_reject", tag_reject) |> ActivityPub.fetch_public_activities() + end + + # GET /api/v1/timelines/tag/:tag + def hashtag(%{assigns: %{user: user}} = conn, params) do + local_only = truthy_param?(params["local"]) + + activities = hashtag_fetching(params, user, local_only) conn |> add_link_headers(activities, %{"local" => local_only}) From fe57e5139fb8396e2b929998850710d73de587ff Mon Sep 17 00:00:00 2001 From: jp Date: Tue, 14 Jan 2020 12:32:36 -0500 Subject: [PATCH 06/28] Remove cache from docker jobs. Split devlop and stable branches into their own jobs --- .gitlab-ci.yml | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b34c7e98d..ba6e41aef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,10 +6,6 @@ variables: &global_variables POSTGRES_PASSWORD: postgres DB_HOST: postgres MIX_ENV: test - DOCKER_DRIVER: overlay2 - DOCKER_HOST: unix:///var/run/docker.sock - DOCKER_IMAGE: $CI_REGISTRY_IMAGE:latest - DOCKER_IMAGE_SHA: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA cache: &global_cache_policy key: ${CI_COMMIT_REF_SLUG} @@ -274,16 +270,43 @@ arm64-musl: docker: stage: docker image: docker:latest - tags: - - dind + cache: {} + variables: &docker-variables + DOCKER_DRIVER: overlay2 + DOCKER_HOST: unix:///var/run/docker.sock + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA + IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest + IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable before_script: &before-docker - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - export CI_JOB_TIMESTAMP=$(date --utc -Iseconds) - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA script: - - docker pull $DOCKER_IMAGE || true - - docker build --cache-from $DOCKER_IMAGE --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $DOCKER_IMAGE_SHA -t $DOCKER_IMAGE . - - docker push $DOCKER_IMAGE_SHA - - docker push $DOCKER_IMAGE + - docker pull $IMAGE_TAG_SLUG || true + - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST . + - docker push $IMAGE_TAG + - docker push $IMAGE_TAG_SLUG + - docker push $IMAGE_TAG_LATEST + tags: + - dind only: - develop + - features/docker-updates + +docker-stable: + stage: docker + image: docker:latest + cache: {} + variables: *docker-variables + before_script: *before-docker + script: + - docker pull $IMAGE_TAG_SLUG || true + - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE . + - docker push $IMAGE_TAG + - docker push $IMAGE_TAG_SLUG + - docker push $IMAGE_TAG_LATEST_STABLE + tags: + - dind + only: + - stable From 964d188a96f8d746b6d1c0ca06c9abc5bc682a11 Mon Sep 17 00:00:00 2001 From: jp Date: Tue, 14 Jan 2020 12:43:28 -0500 Subject: [PATCH 07/28] Add allow_failure to docker jobs --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba6e41aef..b47bfb598 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -282,6 +282,7 @@ docker: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - export CI_JOB_TIMESTAMP=$(date --utc -Iseconds) - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA + allow_failure: true script: - docker pull $IMAGE_TAG_SLUG || true - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST . @@ -292,7 +293,6 @@ docker: - dind only: - develop - - features/docker-updates docker-stable: stage: docker @@ -300,6 +300,7 @@ docker-stable: cache: {} variables: *docker-variables before_script: *before-docker + allow_failure: true script: - docker pull $IMAGE_TAG_SLUG || true - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE . From ff15d6ef139a397002954da565b15861fdc65ca1 Mon Sep 17 00:00:00 2001 From: jp Date: Tue, 14 Jan 2020 13:45:47 -0500 Subject: [PATCH 08/28] Remove artifacts passing by setting `dependencies: []` --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b47bfb598..af9cf5f24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -271,6 +271,7 @@ docker: stage: docker image: docker:latest cache: {} + dependencies: [] variables: &docker-variables DOCKER_DRIVER: overlay2 DOCKER_HOST: unix:///var/run/docker.sock @@ -293,11 +294,13 @@ docker: - dind only: - develop + - features/docker-updates docker-stable: stage: docker image: docker:latest cache: {} + dependencies: [] variables: *docker-variables before_script: *before-docker allow_failure: true From a58a0a7b1b85f900c52c63fc9a4c55a84431d0b0 Mon Sep 17 00:00:00 2001 From: jp Date: Tue, 14 Jan 2020 13:47:36 -0500 Subject: [PATCH 09/28] Remove forked test branch matching --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af9cf5f24..2b2601082 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -294,7 +294,6 @@ docker: - dind only: - develop - - features/docker-updates docker-stable: stage: docker From 76c1948880687ed74e0275e51808c0ddc6be887d Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 15 Jan 2020 12:11:23 +0300 Subject: [PATCH 10/28] [#1478] Adjusted AdminAPIController admin subscope requirements. --- lib/pleroma/web/admin_api/admin_api_controller.ex | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index c8abeff06..529169c1b 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -32,19 +32,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["read:accounts"], admin: true} - when action in [:list_users, :user_show, :right_get, :invites] + when action in [:list_users, :user_show, :right_get] ) plug( OAuthScopesPlug, %{scopes: ["write:accounts"], admin: true} when action in [ - :get_invite_token, - :revoke_invite, - :email_invite, :get_password_reset, - :user_follow, - :user_unfollow, :user_delete, :users_create, :user_toggle_activation, @@ -57,6 +52,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ] ) + plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites) + + plug( + OAuthScopesPlug, + %{scopes: ["write:invites"], admin: true} + when action in [:create_invite_token, :revoke_invite, :email_invite] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:follows"], admin: true} + when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow] + ) + plug( OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} @@ -90,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["write"], admin: true} - when action in [:relay_follow, :relay_unfollow, :config_update] + when action == :config_update ) @users_page_size 50 From 167e9c45eccf5ddb89077c979b1d587318f78cc0 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 15 Jan 2020 12:37:50 +0100 Subject: [PATCH 11/28] Benchmarks: Move to correct folder --- {lib => benchmarks}/mix/tasks/pleroma/benchmarks/tags.ex | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {lib => benchmarks}/mix/tasks/pleroma/benchmarks/tags.ex (100%) diff --git a/lib/mix/tasks/pleroma/benchmarks/tags.ex b/benchmarks/mix/tasks/pleroma/benchmarks/tags.ex similarity index 100% rename from lib/mix/tasks/pleroma/benchmarks/tags.ex rename to benchmarks/mix/tasks/pleroma/benchmarks/tags.ex From 023b7f605b5736b561f5b3a59de4d602933d7c71 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 15 Jan 2020 16:51:09 +0400 Subject: [PATCH 12/28] Fix notification controller test --- test/web/mastodon_api/controllers/notification_controller_test.exs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 38161982a..6f0606250 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -458,8 +458,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do end describe "from specified user" do - test "account_id", %{conn: conn} do - user = insert(:user) + test "account_id" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + %{id: account_id} = other_user1 = insert(:user) other_user2 = insert(:user) From d1e9768e10673b99565883b6450c7fd57657cbad Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 15 Jan 2020 18:38:18 +0300 Subject: [PATCH 13/28] [#1478] Bundled PleromaFE 7397636914a9d3e7fd30373034c25175273ab808. --- priv/static/index.html | 2 +- .../static/static/font/fontello.1576166651574.woff | Bin 12248 -> 0 bytes .../static/font/fontello.1576166651574.woff2 | Bin 10388 -> 0 bytes ...576166651574.eot => fontello.1579102213354.eot} | Bin 20152 -> 21916 bytes ...576166651574.svg => fontello.1579102213354.svg} | 14 +++- ...576166651574.ttf => fontello.1579102213354.ttf} | Bin 19984 -> 21748 bytes .../static/static/font/fontello.1579102213354.woff | Bin 0 -> 13324 bytes .../static/font/fontello.1579102213354.woff2 | Bin 0 -> 11280 bytes ...576166651574.css => fontello.1579102213354.css} | 24 ++++-- priv/static/static/fontello.json | 36 +++++++++ priv/static/static/js/2.8896ea39a0ea8016391a.js | 2 + .../static/static/js/2.8896ea39a0ea8016391a.js.map | 1 + priv/static/static/js/2.c96b30ae9f2d3f46f0ad.js | 2 - priv/static/static/js/app.a43640742dacfb13b6b0.js | 2 + .../static/js/app.a43640742dacfb13b6b0.js.map | 1 + priv/static/static/js/app.a9b3f4c3e79baf3fa8b7.js | 2 - .../static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js | 82 -------------------- .../static/js/vendors~app.86bc6d5e06d2e17976c5.js | 85 +++++++++++++++++++++ .../js/vendors~app.86bc6d5e06d2e17976c5.js.map | 1 + priv/static/static/styles.json | 1 + priv/static/sw-pleroma.js | 6 +- priv/static/sw-pleroma.js.map | 2 +- 22 files changed, 165 insertions(+), 98 deletions(-) delete mode 100644 priv/static/static/font/fontello.1576166651574.woff delete mode 100644 priv/static/static/font/fontello.1576166651574.woff2 rename priv/static/static/font/{fontello.1576166651574.eot => fontello.1579102213354.eot} (80%) rename priv/static/static/font/{fontello.1576166651574.svg => fontello.1579102213354.svg} (89%) mode change 100755 => 100644 rename priv/static/static/font/{fontello.1576166651574.ttf => fontello.1579102213354.ttf} (80%) create mode 100644 priv/static/static/font/fontello.1579102213354.woff create mode 100644 priv/static/static/font/fontello.1579102213354.woff2 rename priv/static/static/{fontello.1576166651574.css => fontello.1579102213354.css} (80%) create mode 100644 priv/static/static/js/2.8896ea39a0ea8016391a.js create mode 100644 priv/static/static/js/2.8896ea39a0ea8016391a.js.map delete mode 100644 priv/static/static/js/2.c96b30ae9f2d3f46f0ad.js create mode 100644 priv/static/static/js/app.a43640742dacfb13b6b0.js create mode 100644 priv/static/static/js/app.a43640742dacfb13b6b0.js.map delete mode 100644 priv/static/static/js/app.a9b3f4c3e79baf3fa8b7.js delete mode 100644 priv/static/static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js create mode 100644 priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js create mode 100644 priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js.map diff --git a/priv/static/index.html b/priv/static/index.html index 2467aa22a..b0aadb1a1 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/font/fontello.1576166651574.woff b/priv/static/static/font/fontello.1576166651574.woff deleted file mode 100644 index bbffd6413b8b832eba13afee4e78f761caaa2d19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12248 zcmY+K18^oy*skB$wrxAvH@0otwvCOkv2AC=jkB@sjcq$Q`TqaZsXASA*K}X=+}$%h zRb4Y(kB5S|H~<9jU6@t?NdKMXk^i^-U;Y0t5-MuK004;QHwAu6l{lKhTS0}1^_wMs zw?)3C&d;RcX<}#O_{~c5(5BWh5!Hn?OVi_OH({~@63#0OQLg-;TTSEyjtlT6{ zfr8kqEPs{uIZ%^`$f6pzy{=tuFzM@EQ`PFZcov@+(Zbw19<(mh$2V)<(t^wkN8{`O z9}f4vqE|B(KASHTgdRsw^zR>mPyD!+GFu!pP0m`iW1SnRVgyS{d^Hws+S6z6=FT2a zg?~=Jk5>nc50|r-T7ixai`K8#fq%89Z(30PMHqzF2@?L7RMm|`D!Dh=Snyt>bW2{7 zc;cOAA#yFA!<<0>D!s>*ryTz4L4 zYA;0U=9Xk_1L+zd#WWm+0wTpUGKB(K$x=}gRgALF4+;eWifJN>w7{e+!BMyTeR)M^ z7V36_WL*^LOBjl@up~T@(dL5vOGW1(>UObY-I8T_$2?0^UD^ZEHmx!GHvKU($Km9v z?LwO6K0B4ez7!Sv5T7#o;PEoVkY#`ydM9pqS>IW(H)f}B`TWeM$f?(LWx-U)G7{IX z-e)d$=mw+zd6$ARDm#>(0*=Aj=vdT~nI zzCakEMiXMa?3$GPXmsF7_f6#1w!xk0n#(blzxEX#6M4e#Bgf2NMIJ|!ixcZ+sM<*@ zaA%3hSxkcqOO&zayf7!>a^p(A)Ic8+v#8N62qom2)U{TE2vJc|#pc?2QRFv5txQwj zVfLz>h&qCftsd zp&Sgn|Eyjq%2uzJX_1qDZ?j5l!gKO#WPkKWJ(1 zCymo2w${w%EDXxRM-m(SV>&$ygEn(Iy_3FHz{kLiE6fVjA$tBAl*vywY}e2JPZeu!oJJr~5cnQH}x^DF)L^JP$A|^10mVk+9>Iak1W564=LyKr)8bOa! zVE5meVD~%Ygx7agn=LM=NbRj!Zi5EY3Ucz8-zy)gbHk>*T#Xa)eC@)2a2|#=;E~!#<8BY%U zs=CGt&$XqhSB=;>?XkFU7)j_p1I(O4c;zpQtsK z8}Cc#2oWP~wqgxdgVDK4#e$Uak9->3Nk@rOa&2V_JQ4x_F3{^P2b|LAv=U)oekUB^ z8N$fE&x=a8(7T8};!^DVj=K0=r)*dBvD`AIw`mp6sP0O5%)tqsoc*Q5A6^xspaPo% zV}@PXz8unEXtl#mcnI;HjMjaiv3hr481pG5$qpyfxA^B`QBmHKexZEjhQB#_+Eq?B zL?1B+OMuUot5d?*AMH_A2|F;AR@@Yo`GwZm zBSZLiq?G`P2e6qZlVfKD#_6X_v^@L!9ovlBLaej0V=iW!ns%)Y$c-6Suw4>~nz*(x z1m7}9C-35R{1cLGlTD0WkY38b!ELn7=9ohjK+#JK4}b5`gwc#CVhF<_^NRTtZZDz> z_Bm@rAE67jZ(p`sAXCNw@%PoAJn*^9Q~=L0d`JV9h`!|sqiY10OEBMYd`K1-Z1M@C z`nr=P<_QM<8NK`#A+>xk6$Q3s zJhKOV;2c$#GY(&-d7f5Vw^|)qi!arybT%2Zg@5`7BNuwXh5$fVQ<iIV_oATg-n;2NGj_qJ-pR(T zulKvp2m8nb+QS?SS%@I}9u2tu$;a`e1lUrmhnJ3amw<*pX z+S2{B)ao`Rg=7o*y|fG>YMxfLb31A0#gir%4GL=&$R4{5ax_P4c(dfi*{wC!hq4`~ ze(O?Q#($?)KH2>7rbGw&s?6s)aZew4c#~mA-EBU1BH`p|54ItG_;~;LRX^Sy#fQJO z$bW9EkHGp~(DlMNXB6tzDB8uh&sCym>9W%QkNmt`v9K%fHY^uB zqD8TU@4NlH@EftdUBG|lZ=K<_6;Xh*B^a|mMe@|d=zp-R#{oRTRbm5^t7daoY{1Wz zo{xh>pRc!QneL}9$SF4K?kC=~&V$`NAj_qZXZzfZh*^pNxT3RDc@55`15xjO#;X-;LM=0# zdl8y+sV9+WnxAr{diU&1MD5OQ7^)qa3eB~J^Dt6B!4vDADO7qsQwyDw8PoedmYBA8 zr|{w&AeV5*ibF+Sz32|w^Y^lbmQP|U?$TX>6VKTR<{((8^ea2A>1hmSjX144jox{; zXmwtzXI-}L+-c1?gEq!&VP}O-|Ae%Oh3s`vHJ-&bdfYiDIO~Bc1P?dPP!`UuQKZc_ zd{F!?_ELml(hxlT=oJ&BFiP3m)L<9^7U{It9^o)fwkQ?YO4hD6Z9O&X@|My*4lPSa z&CYU*_|!+gf8#df7-I}pg(jIvsK$|x6>n}{QGF=kg)6gY4_Fr`74aC6TdYK`wMp@h z7}_zU)ln$b2{wBeLKF+QzoFy9cybcJa|a=S<@T1S^=M*f$l8U>az1*_?yb697+^-{ zMA7)>+9P2nMy`K~>OXM5+vh%l znt2Jf$b7pVi^oMMh|{2C;nr}|u^PF%Et2wAqNRI-gHy@lr!}z>5CjeXFZ(GgQ%_A% zu3*mKRgV@OcnlkWr4Rh)>?o52n>%+gwOyCuCDfvOIEbw=Td|1%QL0qjf^e75?s=BQ z4OWt0$YT{05gzcZ?*~!G)%{)B6<%;`o%nX7&(y62BVQH&>a%JgvX|T5bz<4)!-+|V z^X8643!nS-qNI#DV^kMus10{N=h8AkCcYkBhOU{j;X!U(;9ne$46ydlQ7vVqCT^4T zK?<0CY=6(KoIf7_TH0F^WZ;^n%+c%(C@+rGNU(~UT{6t1GxlAqm2kh|L%IlRtM!|?5A{(U z6gXV`ZD)58P6;;bMmK}9Spc~p7pHnpIA2G!+4+h3rzw?{-s6cBm}HYzEh^iYw=q8Q%!YRPM8{e-=gP`LCfN})OG(=&pI ze$sAzDObBvE0eFKyuu=-usXgqvjs{qrNgH;J$)xllkkG@lmSl-f>twyPFF3CRm>5h zL=l#f94L)i37S_Lj4iQB9*J(dF#6jJZnd-2HUFRUdmytLY-lRz|@Mw0p8{1b<%HO{vw}G*9<CLWPaBqaX`h47=WCNc^uifVwyjZ;W$mm_dmTb4By?Z;2_Z9G4 z-g+g&FbZ29da7tlxg(`^Ar5I+DKgl@O3l9#9H$G%C|($FQvbZ)rgjjr4hWwYS5&ts ztNf}H&obmG7WuJbQ`Yee&ZD)|CxVDDR74i7$$rZd`uf7XM^%FU`E`KKTE4RT~opLYW5R-j|~{RNkHE;I$)$X}%v#IO!nHR3*g5~Mb)pZ!7Q zq$Loj9X)D>AJyUw21q3xP~N*H7pS4iinrhMJ1k7s}LhuUo@ru?X~DzpB)XUlvOlY4Uy%JjEHbWFl4~*dU0{*Ljp2t zL#0?suQpNeGaWd-YD3jgr8pJUZksWjig@SdvozW_v{=Q4MM>g>7<;5|U4Dn<%I4}< zHy->B(ZParV&E}3iBV?+T!B*L`QQf*@DF^(bg4V|R8Ap$EDV>bC@02O3zh&m18Gx7 zQ-f<8Bu92TP-e>p48a9y!?O{+dB&)yY<^ro8IgV<8+xY9hjoPVq7Z}ec=?w-6L^w_>}{Zj6Q>pUVeIC#eWv#D{NwAKCjI!TM->PyB* zK6LivVD%Ux#&PAb=?ll^w)K8KZ7;uf6#dG68BDIm?jNl2DM996jTEze_n@$G4rdS) zsx$jc^YDUn#kVkR@7{gnO|8Rd@v_bspQQiB86asZ)`eNK9$vM)(y1LC!M!F^1VLgm z)M+hJGZddM9PRU11bJrlUSG17%eM15q*vNbWMc?(!ecFnaO8H}`qFf_6|B>P5!7F_aj9*{FG8R zG;7JUvoNTIsxo8L>K~phkBKRCTKoNQ;GTR)0ZWRGZqFT+&;wz}@h;Xoo!*>bh1>p< z5p@04nah2o%K)mBrS`_pULbrfju^*+HF{ELN?C{8Y98Z27taBq`et@?)Ej*zbrNq0 zsTN;zY4%W57(_iVSg0EXWRKct16oKgi4X`gXjGoBs^%OTf0HD!ovq?{E?E(CNKr*0 zM5lJg!@5H~BX-X>QkhnDZ9`QOQ%#M4h}nXH`jw#b$ItGP2sO;ED}-*m8D(|#d9Kpb z_-`b`5gk<22Qz4-X%S_LnO^ap4PdPk)s~;bgN%zutsMI8nRC0_i?H!Uj*CI% zzxAA_XV)?pE4RDw9K1@p)YNgAcsuNMZfCyIky^8;TKGpvGr~XDXyqXs3w>eUgnh6g zzk8pR$T>O?0JUO4FrgjHnJ^YrJAqO1B5iP3ry=EYtk{;kGepB-7z7N#XPp2TQ9Xb| zY4({z`=|T$$=5SH($3z|l@7<$uzH@2?`Gcq{NO)IIbZ1?8v8I91S6aE2;TKQQ>va&pSay{4!vC4XZ4`c&xpr- zctY}QH*aS;1_EKWqNZ=SUf|F7U;c==czqmT{%|X$abE$U>DjIxh5ACv5@@=>F?Kae za;FZ(2b|K786!xJC6DRIt1P-j|bcaf#D`laO6N_fKO@I~5r z!VB4l6(6_PQWyxnCYTiDYwcW6c9C{i-+|Ap8z4R_t*d9}GUw6f)2mo35-Smj;~#TW#0(O>wjafu{V_Ss+Q4u2bs%;bAT=u#!;C8pdtv=*RN)rG#{e$R*&BlQxi$wEZHKAnRf$Kd z=`u2otCMBW;t2WHm2l*Z?4XxtmwAzFQ}6xAri}|lbGQEfoT^yDUw|D5(5H zhCw-GTzI(($}yqzwH09xJT$)AY5qI{6ejeONK`%jJ8$dSkI+hLBQek7aQNE!45Ghc$*5C@!WiKvmDI$R z!RCgX936MiPzmh#ES=v$!b~FJWb%n5i-m>mw>{eI;ar(VlZ#Bj#H0j=LW9IHL(|@V z;-lKAW`oU1Ny}-l%NT>?7!KzAr0|0NT&|B+l-EJ z5=ANB#&n%=CaT_>vZrq&yeun6TWO49vpq)-7FoEBDw+V7Cw~Znz^ zz>WYfw(LrtM668;Oc#DG*G@bZPw&WsQ#R)(4^+J&k~cm_%&&?d}LBAMS#NQIQTYPA!!&B%BfX+ry27 zb^~WeF$c_lmcnvDV#JMRJ!JEZ5+czc=RhSG6y5RPN6A+#6}(b%CV)BFE3HtJoj0NW zi`xd(W*!L|M6L*u7C~UObu)J@ZePZsf}FSykeZYWV#E|C530p%Ch;$HN^T=$_YEI3 z6Li_r&O@^h<+9$VmRSa(Al~H*uYecOUavyvZtN86wOLoauvbR%k|2v#;)WF=H}_{5 zc-kI?Bm6PkorUpaTwHuE?XrP@FDvc#c{e*A{fgGBqN3csQdX%L;|)wT=5lVmBwM!`v($hR+bCd=Z~s+ zZLh6o$TEjADpHaO3$5F~kV(cM8#ln~C{MHO%cY$^(Y4`HQu~l?q(>VTWhis1IAaNG zrTdnqL{+GS3cyY)zNh>N6vu&sU_fe_4~{}TnS`8A=GCr0w%ph$*;I(T zxlzGClc{g(s|h^0F;6>Ubs{0~f3hy^6(Iq*)&d9FP-I|8a+MsL6@$#(F~&u2rA@Y@R?M$@ z8n^Thvg>82q(#UqUrqOzDD(>q_#I0c&KaInNgKav{1QiCh8Me#+V~419-96b%t~Gf z(d{qwvFnL(R5w_K#3b6_m!f$1Kv;L#OVe%y*r1`xbrjj6G7I@vzNJ2f$p``UqC_`D z2_Tv?E9*4XB>kYdUURUD`NkHNndRE*qdSj$kr)4%LCU^|MG$OlQTD3#P5T6#w;C0u zQeS@}awk)xqc+2MKGV|hcKA*n3GOXhl74Kre&EFmH8N`kc4PR>Jdd)VI_TbIKUd=g zG&?Ppyb2=P87lCHLT0S)>kam`?jf{rH2-E+yQO!Cg>o zoH!#8JWt)H1N)l;ZQilXiInxYI!biAjScZ@Z2jhjfQZ-UF{DDyF8rqK>85{)v8O#D z=2LDj)UIwQCv>m*rpKvKr)>K?OTh`4AO5?do#bE3g8SZ0vOp&_(a;0*La;ujDT;fW zKbMNE*%DGIvjqymiSbgzidx=7TaY(c5X$X(HI`h6s>hC8iVt@1FNT7I=`u2D!Dd_- zvGaPSV>rn74vi;m!ufi=c!ij2uBo$!t7m-o;rXRKQnoTXvTMwf@jDeT=iN9e_(Yf^8+ zGocVt?992{DUj@1L0)k!Oaz{5>JFDm_C>3(e5-j_n~50?lgtzD0MpgAKOM*6{$D;1 zZ17y_rn1Z;Yzfg|RS~olS&jQVz+X4WMAPft@5bw`u$b%J#{wCsqS5>o6`Pq5F81ku zQwx7?K;W{DeuaeJ&y8(JJL|TG>LTLr zsFlqXj&9^R>=$-Iw?hjC@`xP$pj>TqoWxT6hN@7KQvv)6KKTX=QFX2SP zdDqK;hn-+J0pVHJUIgCwF`|zxVO z1g0?%={L+lM;9_@_bRx>|M*R_JfARGsd~HVw(z#sF{Q^X&T|H~i`Ax)W6SWTMqVE3 z^zZ3Wo|-9Fagbi8FJw1%4Ro9XpI* z240D)#pTXSzSAp9c9h0c_wkojrbZ>7nH=9uf2!DF9XL{*9U4lsC{LzR$HKK5lR#Z$ zz3K>#sKM>z`@mZES?|wkCwD+oFd61kK_74t>I(``Z+CdDM*1MK=c=hwBX;0T)Mi6|UONg}+X;V;-C7fJp7^Wk%>m_|H)6#PzPmhmlqx zn&nw1d*+zk@%D`yUk=f;Gp)MbZQ4OpioADw3Or?()kA!lNCwIA6^5GaXC5wI7Vk?6 z;!&h_H6P-|&2jkNRF$7eWK%{bim;1m_td>yY1;%~F z(9CqV4UJ>7wPS(XF9Kw;AE_LRu`jS{Xl#%(k(8WdeSEv@A<$|Egsk(=1cLGCS7`7# zJF|90c*`|}+U@vpjK8Kqg6&v2{K8>C2H&=mtJrwQ&{RQt1q;g zmQB}&HorfT&v6b1$$NCM&-eHS+KZm4-m1WQbZI~@oEi$NDt%rlTRdakS5&Z1cl_vH z@upkW-b>xSng~k$E|y!`P0sfASu;L_SM-*BzL=%dU!QBmK z*$dr_QbzS?$sy-5a4-LQAg)&n**p=IpzbY1R?`Cu1}DdhQ3nNK9Q|Ov^3bzqZ!-nz zvL{y^Zalv*8;F2p-ZB@@UvHlsH?_-$``C5c#|xmEDG!1Z>PQ zxDrV~Fw&nia{%*Ss1W=21R{E;iH@jR^6&}#9i=*zPp=50pv~S!Efnk>-uG*d$`15+ z^t2%zUEklMrf2tV91A?%@vBPn^2%myjG5L(Sbb_Qt)85BX{QdQk|h|?XhG3S20d1! z%zVRR2F%f=VfQ8LvL*O7ua>iat=AhWH_H7Iwx};B;R&v_OHGPzg*-?ij$h3{G-Gw8 zt}4=?K`W*&qp2~NShHs88^#x!7^|-?Xe3Pqr!uyY`ne_rvK(6r0*&y9eSIOkd-@zP zTt^9e^BHuBEZ|j_bSgt5!H+2KZD^=Wbj%t<5pwN-8uq;lUIiPe>EwI5&~noRG9Q9V zPH>YR)U>xwl=n|f-}v+79Qfe^4EW-y zWl0hZPsU|T?O&g)VI0gUYSTj`#)&-n{QXlOt#~*&G4FqB6-QZNaQsL242LER70Yn; zzu@Hbe1gy~An=ynTR?(J*N$O6QzI_qYU=MDh?beIuAO_ars{nD=s{UP}MBub+OZv3iY{A*()1yT)L-(|XxVRY5Wxj1$Jw>7 zqH<%JCY3T=%ub0tHh|pu>jcW~B)U~*>v#dPC#Dq&SHQtaLbBO)n+mFoP{7Z63)z6h z@AiBcN!39I-3LrlU}~82bq#@Trqx(1Z_A?Pt2Na-y7iLjpS!a+!~-7>2l;qJyn_yY zsm&1G3rzA6Ja8j<1f7<}m3T!-9t0+g<1X~L64@2SKA-dLpB#7#iKztQPeG_+Ldew9 z^Pj6bfzygwkgT{?e6H;NK#*Kfz7<|lsZvVZo%Idmqh0#83yFw-a`6HDIC|NTJ&x1! z?-f3gQc~zl6MLD+o%8)1C*Ebkgfy7}9b|xn&{=F)jL$Y?%8grrETiSZBKqf|*3>Ks z$mpp9^Wy^IF+n5D_BCim1T3ru_@#}vFAvTbJAYKGE^ z3D9vjeoQKzssmrnGVx&d@NWJjtKM-FEq&;o3Lzt&m?#XfSi zsE9b!RL!ghm?-dV-ZX4IcaoYqE~IgXZ5bMsBu-<09tQAHu=T^mD$O4Wg6O+lSb9xW zwoZpwWRb$t(ur>ekno;^{~``DLN}Wy!3qvzE+=DZ#IYMom_NX_yEa$rd&PF2h%6zX zTOgDP>rCVyPEA#)Y2B4XYWq|TTrIarZAdwzWVf;Q@+z6#>bJowtHn6htTlB z-Msv0XrQ+q49S53&#GR9p2l=>G3rqU{6%0V1pGOItZ`K5wYi-l_o*dQH~<~DX}ROE|Tdx>4& z?otJ`Rx?&&TXn#iQ1z})`~9u+VNRDVPGng<`?k$NWv4B{@1Xg6gjy$NpHC1qG7MYBY(7O%9(&cu(xN!b1^% zNl$ZKW7zd}ZtGDQI4@){U?Ly+K^0{ElLcPK$WEo6qn9_H7j0616lK-@0bwRzg6C&P z#6Y!hro(G^_*0A_eF*u7H@EvHWY3Rq+78Sm5)}XSc${c&xaaxU?&@+K|M!h`A<`#v zx1XQ$V8>~cE0Amc*+d-<=CKC^0_`au@V|MQhK2y+$zNLrgLmt`X4~feAm~PI6eM#9 zMPC6Qd4HArvJv;4nVEw`K^%qP=l-APznsn9UggKHv#;sz>?%PNbVC53ND^}0Gg~fp z7A24vUc{#tRJNa&eitC$-;>1+gSbsKRGK^=@6k@4FvN)3G{>*3WcDvmldnwRx)?OU zF$#G+rM^l%<^{9WZmy~BpNr4kX6>d}PS@-v)2TB2TizGf;S!uw7#GHOit2Zf226pV zg4lxGf);~8f{lPng9n12LYP9fK%PSpL#abkLfgYT!`CCQBlse;A=V->BUK`!ey>W@ zD4(ctX!K}NXxHe57^oP;xQ6oKM_}@BTr)#SHQcfctMS04RVS zzzcBsu2sT)=gR&!4{izL^8JG7;a`c-u#vcO8$am4SW;znK~Ygr;cZ<-;B{1_tz@Jl zWT8;?2RyEgd|~ni^W2#sjk8XCkFwY-wx=9iQ6!3ZAvRTGnz{`%c7-s}@7?TX> z(-a4@4p=|_9KaGHYu4^U5>*pN9mQZi|E71OU}VVK7T)+TRGTr$Csn2mqb_y)KKB5Y zlC)WOuTp|Z@|F{o>z40)NMO{} z^ zN4%t>PLs`Rf2vOQYl z9!zoIxKJpUx96MonalJs8_M2DT^k#>UT7(+e4>%H%ax?0wUI7UF*JTo6sEUQOguPJ zK8aNcC-5Cop`+R%y9%rO(geDgRVJ)D*nIr6G-KrXh+7W`gd-TwkhL~1xSvxik|et* z&bf2r^%HE;p4fMH>K&LS=S4`NDvMW3PbuBp_13dUV}A-d z>S`)IOKD)W+7ZbD_3DmcjOvjjN}~AA9NG5J$asHtt?#+V4NT@roDvcX0Pjxro!S0>09{;0H~;_u diff --git a/priv/static/static/font/fontello.1576166651574.woff2 b/priv/static/static/font/fontello.1576166651574.woff2 deleted file mode 100644 index d35dce862a5511876f5e2f9a25562e77c48d5a87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10388 zcmV;FC~MbuPew8T0RR9104S6I4*&oF08S7904O^E0RR9100000000000000000000 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 <6!3SB`b!ag!0iX_k`H=TqnI$fkr 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 diff --git a/priv/static/static/font/fontello.1576166651574.eot b/priv/static/static/font/fontello.1579102213354.eot similarity index 80% rename from priv/static/static/font/fontello.1576166651574.eot rename to priv/static/static/font/fontello.1579102213354.eot index fb27d403748544f9caca1248ced57460dd3301bb..160cfa9f6859fbf0375e4ea8bc5440b2a6377712 100644 GIT binary patch delta 2479 zcma)7eQZIdAt1W3rT%8dxyAW;YjB#ylh zScWRRb*SssZh+jin*>$G5JGH!Fp@De@y|9^wVOIoZK}4Z6Z}C@Hcc9A>>mX2&WnvI zb(%Ck|L*y?_nmw0@1Fbajq`Z!M<}0r5#UkktQ--;9akb}DuK7@JB@W*^+^ zCKWFOi1((BFL~s``XhjyACuE_C_9t>_SrMzWIqA0WM?LiX31|O`wt}5nfb3Cy13(w zD1c1{5n7%#em2xIh6N>9iYFB?B?0@((JV z*8vin$X-ZKE@$zzvP)!tpKQ;&Aj7C{8T!-KR`YJj+chcuZ1 zVh#RT0)$s?$)ES;nt%+G=Jf(}v7}Ea*R1j=YRII84~lWU}rAA=m z+BZIydSP^E_pZT2e{XEtb2Uy!P1IM_WUG`KT2WSfUOgJ>3c4{GjLJ?2I%HpySNDfv z5;<86dodCXxusF4!D7}F$&?Dy&ZNI2xzx`HvR4*H8jax`XW-?pLlIPMTz z`kV9?&c5+NV%?P-MPaph^leRYb71G_K+R^G>h0D&wsL&>Zt8f7{`jqvSHH$yT0Mb@ zSYYV6eeJxZr75{d>=-QdPxEA3+po1+EOXjM*1wn-8A&vB_jiYM%~fB^G+V8iQ*8eenlAXA|ET8&)YXFnHZ^Y&-bGfk8O(nzt3P0m(>^F1-hM+gzv#Xu~ zEu6zfxRS(54kGsH7wfHTHZz&YCsv^m55@mP<~>l2c*6?2Yq^m+ZVvO#W` zNDKF}h!TylXaI>=B(kd=vf{=@)Ec=%DM!Cfk4!HSlaXkQM)}>yE03(sbp{7sbk;H1 ziVOwkSTB{Aiyb3)Wc8!fBOi?>C97Iz5|ZG^%u2muY#`W~dn@a)@4MK)V;IYJrtrsm zP7Ry3R!hRHpb*GqisF`Bm9|YwiIG#o!>2w@9QT)N<|Yf5WHU0=j6$m3YTC_%9idlt zw{Gz-11~%++~5azC4G1TbfR{}2R3<_ES1ZQp$nM+J_ssEvMNnLjt&izae|0J5DX?j zD2hl?nJRlh5z4Sr({!1xF8Or@jX_g9T;q)3K#%RTg&A%&?;qyX+TLvX%0XV z5{PsT6p)!BPXJj`3M5JFB}ELkwQTWMX+DQ4)wiNUK{`;+Q>Ezsa3~V(qDHGmEoyhj zM1>yp>%B;)iVDI{?9e$4>5&e6=G55tEbAXy+>XZ;4%EsYJKPrRu-w|8xTbh0RU=td zz19-JkiIILX_rc##D1L4_f}}RpRO+__M26UNiI`Vj5On`G*~cIlsx-s{YW3uGs4cd z(Y;a^cL^j~GQf~!IK?~U>#FL~C8?$rp;~hqpMz5F{;-csV-;b$&l|ufGhzQVhueAB z?L4Qs@k6&Wch2dV(cH|zx2^y6O`FT5HDkU%C+~`9GT+k^#YldsXW1n>3I$^TA1^$ms|AFe z>~{e2fA4vvyE0b!yQ+bz+YXx}?$n%@w4`>STCHAoZE@YK8LRnat*iE8U3uN1y36$= z4O+u5D88|_$jaN#tH^?#`OC3Y9)!-71HHSECcF}#uwoFqFoa=@U^H*;`^(m2N2eE$ zimBPjCF|jZLz(ue!;4e%(+8#b%*^2hc{Y=tZVy@~7Z)>MX`ec_czpU`{(j$2R({am GX#Ed;2IOr3 delta 717 zcmZXRO-vI}5Xb*-yJ?AqfGyBM8e0%Q3RQ~HDiP6Eq<+K(YtqDMq-!bdp|u-nObrK4 z8Y~Uc?1=;t561Y>5R!W6fhY$rP0H1S38n{0dUO2@2U48n#gjYv?f=cpdoyqHmfo_` zGSim=KuaL6_lPTnvNL~SZYBe;F#w@RQj)(_Z*G$R3J}wghiQj?xBd(8Zd2YdDaVr8 z{K753X9bLMOq!A@-a>hmv>}#wGz)%*ATU2&f%;^TB(_p>O?X6s-`)I!#BI=arfrD0J z&8Fmpl>H7gySry${AWt1PNFh;C9m62RKd{PsMQumv_F0Q#U*i^&71&g1P+j%;3oGNzL%(6v)hLC}*4DXy zs;v7?GqOv7wyCCZ!ECnEPt#4>q3X8qu%+Njxxes o935$3!oZ+1cIlmN0E0!t=v79wg8s1b-oL4ATwX0sU+HT63z=NKkpKVy diff --git a/priv/static/static/font/fontello.1576166651574.svg b/priv/static/static/font/fontello.1579102213354.svg old mode 100755 new mode 100644 similarity index 89% rename from priv/static/static/font/fontello.1576166651574.svg rename to priv/static/static/font/fontello.1579102213354.svg index f5e497ce4..44beba9a2 --- a/priv/static/static/font/fontello.1576166651574.svg +++ b/priv/static/static/font/fontello.1579102213354.svg @@ -1,7 +1,7 @@ -Copyright (C) 2019 by original authors @ fontello.com +Copyright (C) 2020 by original authors @ fontello.com @@ -64,6 +64,18 @@ + + + + + + + + + + + + diff --git a/priv/static/static/font/fontello.1576166651574.ttf b/priv/static/static/font/fontello.1579102213354.ttf similarity index 80% rename from priv/static/static/font/fontello.1576166651574.ttf rename to priv/static/static/font/fontello.1579102213354.ttf index c49743ec6ef008b9485ad4d2b6f2d8921b21ce3c..44753f8c1da2405dc8cd6547166b09f0a6dec00a 100644 GIT binary patch delta 2479 zcma)7eQaA-6+h?R`#$^;$4;JKoF;J`J9%!K#7P~0WJz$H*iG6d4Po-4ra+sJrb(MP zHfdTyHO**LV|=zF<>QYHv8t*D;*ZjVZbJPBDANWEbplm@hFYX<3j9M01x?i&b6)If z2!w>6fA@Ucd(S=hch7zA{EPVDdw4!jcp3o50APGNJC(n8x0_Ua1wgzpeR|m^7dIaO z?0tuvzGL~>?3Z3RKSA~f0Ji+>)X6;gon-%(q&mCs*<+XXT!;h6;pZDcpkWtUfe);C`KJvqz-ez%aDp8C$SU;PR|YCGAB*{PL0e!1c@ z+21DHw>XuZdF!Pb581B)u($HLlgnRNJn$62;PYe;=a**k@AkJ|2goeE@czf~rPU11 z?&t%g$$&B7TUr}QD-G`hfh3_Bd>}w<_*d^@u5k{`=fu{t0RQ!@F0rY~PS^!u=z<7D zDXzTwC-(kolG!Tl<~oeh-}*(@Huw=NGyrT6z)P|WXe7N1S{ydYfF{z*06*zvK!Egc z36w5uL`r}bfO}sn0SuDoN`N5AH%ll0QF-r939y6Y-4b9Y$#+YDR+4`&0qAV*ZI%G- z0Qaj(fDlQd-UM`zOq2kfB>PJsUf_PZ1Y!v8AEm8Q14I_wr^QSVZSc1;Ai7pBf6QNy zi^bxnMF*b%7tQO1G>pR$bY1(XgK>sK!&jQd4km}dW>GCRHS0u`SyVP(iHutqpI1<# zla(-Qw=$IJMCL&j^q+Siub>VNlPGM%1Xd*#3Nl{FUG@h*{z`_2b%mwFp`kS$r895WAU(8)KG`t zh{VD=DIrWSUDD)WzzD{ok)YlcPGC(qSQ=8GF~f&?hTD_4N9-JEGFmwM{FA9oPkIc6 z-RU!Wn&jrt-m$^j9ZuEXZTOrHeDr$ebcX);#b>U3jy=7822+X9@DqpHc}q)Edb`*& zRPLYQ>9)3?>)Wux?Ht|wL~3+2)zCfA9WiuI{WhjMw^><1HzM5w_3T`)88Nf@*NJD? znQJTZ+26E=FwOf%o$d;c2g}`5LJXkzD&Jtgpery8yOVwU>KV|+IqV?j`H&Sk4u+AX zsV*=_JVOpRV>|<#vx7i~Gcu4&^acC@A-P+zpgAz$56a3Gxltl5x|_w6c!I@4NW>zM zU8Bj07aLJ;je* z{fpjTG-|dF z73+|-pw62*H8`{M`o~xsjEtXQZ(l}@SbNgc)+ zhi&twRjYhicif`MFFSN*A;*t4S5k+qs?8!-C@RL9@p+mpm@3Mi!$b(uoAtbK@gDln z)FnLviKiSeWI0X?91C<+cNtPas%=H6(cR_?qP&}6G(e`guc#~F4`G~HaNw%ubszV- zU(~($HVLuLRBjaswf+3C4nJ6`+KZJuqH>Kt{)>RzcI zZO|KjLh;SL#n%4Wry>h?em$|y!_c|*YGOaqdMr6<$1rwb1fv+k`0A&Uf82HI3g=M4Y= delta 687 zcmZXRUr19?9LK+B8_SwfZMw}EZT_pK%Vv}%nXzRlMPU|$D4Mr9w}-7=bi|?upNd*y zllv4@PZ25-!S$gBp&oh}tf!!dz}kbt-n!NbpJw0drKj%U!{_{dzjMy-9By?%6rPJh z+jI^PBLE_?v?Tv1UtMK94_LCXyIG%gt5yXgLw1$+>?m4njCnrVlj&@3>3wAD8xtiaKBO`+ zX{dD42lUS{zMYnGvUt$&nz7!%cSlOcOE0`Fz-cx$eUdX%*)Q)N-v`c=88^t2ad|z| zwE|pvG`D#}JX5ZTgg*$u%@Cb1Z_fhsE-nwkf*K3$@Ns6Tc$77f@!o_!Zf%b-{5O$?v#|?0pW-}dh13&oy9llMr zUTIY~x@``Rzqs@!y1V~3Y~~6}`08$a^3RGpd_n}3k#J5ahaU#^*6g#6fFtkF8poRK zO|P5-&P5koGp;}G%kDyRZ*y(m0=EhhR9~L06Q-U%Wqxqk+=D(fdhUYYS&Qe#6z9;Y N(lPv2{XE=K{}&Fpw=4hv diff --git a/priv/static/static/font/fontello.1579102213354.woff b/priv/static/static/font/fontello.1579102213354.woff new file mode 100644 index 0000000000000000000000000000000000000000..23351a090c6c38992a16fdd676625ddbecf0317a GIT binary patch literal 13324 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 literal 0 HcmV?d00001 diff --git a/priv/static/static/font/fontello.1579102213354.woff2 b/priv/static/static/font/fontello.1579102213354.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..9c354e7f6302a7ce93b427f608e2871333b7d054 GIT binary patch literal 11280 zcmV+rEbr5IPew8T0RR9104xvy4*&oF095n<04uft0RR9100000000000000000000 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%0A0rtJQuhbtAW+sbeayIdtEe{JrA;kwN5# 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 GT>$_#1l;fd literal 0 HcmV?d00001 diff --git a/priv/static/static/fontello.1576166651574.css b/priv/static/static/fontello.1579102213354.css similarity index 80% rename from priv/static/static/fontello.1576166651574.css rename to priv/static/static/fontello.1579102213354.css index 54f9fe05f..0f81954a5 100644 --- a/priv/static/static/fontello.1576166651574.css +++ b/priv/static/static/fontello.1579102213354.css @@ -1,11 +1,11 @@ @font-face { font-family: "Icons"; - src: url("./font/fontello.1576166651574.eot"); - src: url("./font/fontello.1576166651574.eot") format("embedded-opentype"), - url("./font/fontello.1576166651574.woff2") format("woff2"), - url("./font/fontello.1576166651574.woff") format("woff"), - url("./font/fontello.1576166651574.ttf") format("truetype"), - url("./font/fontello.1576166651574.svg") format("svg"); + src: url("./font/fontello.1579102213354.eot"); + src: url("./font/fontello.1579102213354.eot") format("embedded-opentype"), + url("./font/fontello.1579102213354.woff2") format("woff2"), + url("./font/fontello.1579102213354.woff") format("woff"), + url("./font/fontello.1579102213354.ttf") format("truetype"), + url("./font/fontello.1579102213354.svg") format("svg"); font-weight: normal; font-style: normal; } @@ -122,3 +122,15 @@ .icon-zoom-in::before { content: "\e81c"; } .icon-gauge::before { content: "\f0e4"; } + +.icon-users::before { content: "\e81d"; } + +.icon-info-circled::before { content: "\e81f"; } + +.icon-home-2::before { content: "\e821"; } + +.icon-chat::before { content: "\e81e"; } + +.icon-login::before { content: "\e820"; } + +.icon-arrow-curved::before { content: "\e822"; } diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json index c0cf17271..829241b55 100755 --- a/priv/static/static/fontello.json +++ b/priv/static/static/fontello.json @@ -303,6 +303,42 @@ "css": "gauge", "code": 61668, "src": "fontawesome" + }, + { + "uid": "31972e4e9d080eaa796290349ae6c1fd", + "css": "users", + "code": 59421, + "src": "fontawesome" + }, + { + "uid": "e82cedfa1d5f15b00c5a81c9bd731ea2", + "css": "info-circled", + "code": 59423, + "src": "fontawesome" + }, + { + "uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6", + "css": "home-2", + "code": 59425, + "src": "typicons" + }, + { + "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3", + "css": "chat", + "code": 59422, + "src": "fontawesome" + }, + { + "uid": "3a00327e61b997b58518bd43ed83c3df", + "css": "login", + "code": 59424, + "src": "fontawesome" + }, + { + "uid": "f3ebd6751c15a280af5cc5f4a764187d", + "css": "arrow-curved", + "code": 59426, + "src": "iconic" } ] } \ No newline at end of file diff --git a/priv/static/static/js/2.8896ea39a0ea8016391a.js b/priv/static/static/js/2.8896ea39a0ea8016391a.js new file mode 100644 index 000000000..ece883546 --- /dev/null +++ b/priv/static/static/js/2.8896ea39a0ea8016391a.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{567:function(t,e,i){var c=i(568);"string"==typeof c&&(c=[[t.i,c,""]]),c.locals&&(t.exports=c.locals);(0,i(3).default)("cc6cdea4",c,!0,{})},568:function(t,e,i){(t.exports=i(2)(!1)).push([t.i,".sticker-picker{width:100%}.sticker-picker .contents{min-height:250px}.sticker-picker .contents .sticker-picker-content{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 4px}.sticker-picker .contents .sticker-picker-content .sticker{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;margin:4px;width:56px;height:56px}.sticker-picker .contents .sticker-picker-content .sticker img{height:100%}.sticker-picker .contents .sticker-picker-content .sticker img:hover{filter:drop-shadow(0 0 5px var(--link,#d8a070))}",""])},569:function(t,e,i){"use strict";i.r(e);var c=i(88),n={components:{TabSwitcher:i(49).a},data:function(){return{meta:{stickers:[]},path:""}},computed:{pack:function(){return this.$store.state.instance.stickers||[]}},methods:{clear:function(){this.meta={stickers:[]}},pick:function(t,e){var i=this,n=this.$store;fetch(t).then(function(t){t.blob().then(function(t){var a=new File([t],e,{mimetype:"image/png"}),s=new FormData;s.append("file",a),c.a.uploadMedia({store:n,formData:s}).then(function(t){i.$emit("uploaded",t),i.clear()},function(t){console.warn("Can't attach sticker"),console.warn(t),i.$emit("upload-failed","default")})})})}}},a=i(0);var s=function(t){i(567)},r=Object(a.a)(n,function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"sticker-picker"},[i("tab-switcher",{staticClass:"tab-switcher",attrs:{"render-only-focused":!0,"scrollable-tabs":""}},t._l(t.pack,function(e){return i("div",{key:e.path,staticClass:"sticker-picker-content",attrs:{"image-tooltip":e.meta.title,image:e.path+e.meta.tabIcon}},t._l(e.meta.stickers,function(c){return i("div",{key:c,staticClass:"sticker",on:{click:function(i){i.stopPropagation(),i.preventDefault(),t.pick(e.path+c,e.meta.title)}}},[i("img",{attrs:{src:e.path+c}})])}),0)}),0)],1)},[],!1,s,null,null);e.default=r.exports}}]); +//# sourceMappingURL=2.8896ea39a0ea8016391a.js.map \ No newline at end of file diff --git a/priv/static/static/js/2.8896ea39a0ea8016391a.js.map b/priv/static/static/js/2.8896ea39a0ea8016391a.js.map new file mode 100644 index 000000000..4a5dc5be7 --- /dev/null +++ b/priv/static/static/js/2.8896ea39a0ea8016391a.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./src/components/sticker_picker/sticker_picker.vue?d6cd","webpack:///./src/components/sticker_picker/sticker_picker.vue?d5ea","webpack:///./src/components/sticker_picker/sticker_picker.js","webpack:///./src/components/sticker_picker/sticker_picker.vue","webpack:///./src/components/sticker_picker/sticker_picker.vue?53f6"],"names":["content","__webpack_require__","module","i","locals","exports","add","default","push","StickerPicker","components","TabSwitcher","data","meta","stickers","path","computed","pack","this","$store","state","instance","methods","clear","pick","sticker","name","_this","store","fetch","then","res","blob","file","File","mimetype","formData","FormData","append","statusPosterService","uploadMedia","fileData","$emit","error","console","warn","__vue_styles__","context","Component","Object","component_normalizer","sticker_picker","_vm","_h","$createElement","_c","_self","staticClass","attrs","render-only-focused","scrollable-tabs","_l","stickerpack","key","image-tooltip","title","image","tabIcon","on","click","$event","stopPropagation","preventDefault","src","__webpack_exports__"],"mappings":"6EAGA,IAAAA,EAAcC,EAAQ,KACtB,iBAAAD,MAAA,EAA4CE,EAAAC,EAASH,EAAA,MACrDA,EAAAI,SAAAF,EAAAG,QAAAL,EAAAI,SAGAE,EADUL,EAAQ,GAAgEM,SAClF,WAAAP,GAAA,4BCRAE,EAAAG,QAA2BJ,EAAQ,EAARA,EAA0D,IAKrFO,KAAA,CAAcN,EAAAC,EAAS,0iBAA0iB,0DC8CljBM,EA/CO,CACpBC,WAAY,CACVC,qBAEFC,KAJoB,WAKlB,MAAO,CACLC,KAAM,CACJC,SAAU,IAEZC,KAAM,KAGVC,SAAU,CACRC,KADQ,WAEN,OAAOC,KAAKC,OAAOC,MAAMC,SAASP,UAAY,KAGlDQ,QAAS,CACPC,MADO,WAELL,KAAKL,KAAO,CACVC,SAAU,KAGdU,KANO,SAMDC,EAASC,GAAM,IAAAC,EAAAT,KACbU,EAAQV,KAAKC,OAEnBU,MAAMJ,GACHK,KAAK,SAACC,GACLA,EAAIC,OAAOF,KAAK,SAACE,GACf,IAAIC,EAAO,IAAIC,KAAK,CAACF,GAAON,EAAM,CAAES,SAAU,cAC1CC,EAAW,IAAIC,SACnBD,EAASE,OAAO,OAAQL,GACxBM,IAAoBC,YAAY,CAAEZ,QAAOQ,aACtCN,KAAK,SAACW,GACLd,EAAKe,MAAM,WAAYD,GACvBd,EAAKJ,SACJ,SAACoB,GACFC,QAAQC,KAAK,wBACbD,QAAQC,KAAKF,GACbhB,EAAKe,MAAM,gBAAiB,2BCnC5C,IAEAI,EAVA,SAAAC,GACE9C,EAAQ,MAeV+C,EAAgBC,OAAAC,EAAA,EAAAD,CACdE,ECjBF,WAA0B,IAAAC,EAAAlC,KAAamC,EAAAD,EAAAE,eAA0BC,EAAAH,EAAAI,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,OAAiBE,YAAA,kBAA6B,CAAAF,EAAA,gBAAqBE,YAAA,eAAAC,MAAA,CAAkCC,uBAAA,EAAAC,kBAAA,KAAiDR,EAAAS,GAAAT,EAAA,cAAAU,GAAyC,OAAAP,EAAA,OAAiBQ,IAAAD,EAAA/C,KAAA0C,YAAA,yBAAAC,MAAA,CAAiEM,gBAAAF,EAAAjD,KAAAoD,MAAAC,MAAAJ,EAAA/C,KAAA+C,EAAAjD,KAAAsD,UAA4Ff,EAAAS,GAAAC,EAAAjD,KAAA,kBAAAY,GAAsD,OAAA8B,EAAA,OAAiBQ,IAAAtC,EAAAgC,YAAA,UAAAW,GAAA,CAAsCC,MAAA,SAAAC,GAAyBA,EAAAC,kBAAyBD,EAAAE,iBAAwBpB,EAAA5B,KAAAsC,EAAA/C,KAAAU,EAAAqC,EAAAjD,KAAAoD,UAA+D,CAAAV,EAAA,OAAYG,MAAA,CAAOe,IAAAX,EAAA/C,KAAAU,SAAsC,KAAK,QAC1vB,IDOA,EAaAqB,EATA,KAEA,MAYe4B,EAAA,QAAA1B,EAAiB","file":"static/js/2.8896ea39a0ea8016391a.js","sourcesContent":["// style-loader: Adds some css to the DOM by adding a \n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./checkbox.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!../../../node_modules/vue-loader/lib/selector?type=script&index=0!./checkbox.vue\"\nimport __vue_script__ from \"!!babel-loader!../../../node_modules/vue-loader/lib/selector?type=script&index=0!./checkbox.vue\"\n/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-58c9b3c4\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./checkbox.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('label',{staticClass:\"checkbox\",class:{ disabled: _vm.disabled, indeterminate: _vm.indeterminate }},[_c('input',{attrs:{\"type\":\"checkbox\",\"disabled\":_vm.disabled},domProps:{\"checked\":_vm.checked,\"indeterminate\":_vm.indeterminate},on:{\"change\":function($event){_vm.$emit('change', $event.target.checked)}}}),_vm._v(\" \"),_c('i',{staticClass:\"checkbox-indicator\"}),_vm._v(\" \"),(!!_vm.$slots.default)?_c('span',{staticClass:\"label\"},[_vm._t(\"default\")],2):_vm._e()])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","// TODO this func might as well take the entire file and use its mimetype\n// or the entire service could be just mimetype service that only operates\n// on mimetypes and not files. Currently the naming is confusing.\nconst fileType = mimetype => {\n if (mimetype.match(/text\\/html/)) {\n return 'html'\n }\n\n if (mimetype.match(/image/)) {\n return 'image'\n }\n\n if (mimetype.match(/video/)) {\n return 'video'\n }\n\n if (mimetype.match(/audio/)) {\n return 'audio'\n }\n\n return 'unknown'\n}\n\nconst fileMatchesSomeType = (types, file) =>\n types.some(type => fileType(file.mimetype) === type)\n\nconst fileTypeService = {\n fileType,\n fileMatchesSomeType\n}\n\nexport default fileTypeService\n","const DialogModal = {\n props: {\n darkOverlay: {\n default: true,\n type: Boolean\n },\n onCancel: {\n default: () => {},\n type: Function\n }\n }\n}\n\nexport default DialogModal\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./dialog_modal.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./dialog_modal.js\"\nimport __vue_script__ from \"!!babel-loader!./dialog_modal.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-eafd78a6\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./dialog_modal.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('span',{class:{ 'dark-overlay': _vm.darkOverlay },on:{\"click\":function($event){if($event.target !== $event.currentTarget){ return null; }$event.stopPropagation();_vm.onCancel()}}},[_c('div',{staticClass:\"dialog-modal panel panel-default\",on:{\"click\":function($event){$event.stopPropagation();}}},[_c('div',{staticClass:\"panel-heading dialog-modal-heading\"},[_c('div',{staticClass:\"title\"},[_vm._t(\"header\")],2)]),_vm._v(\" \"),_c('div',{staticClass:\"dialog-modal-content\"},[_vm._t(\"default\")],2),_vm._v(\" \"),_c('div',{staticClass:\"dialog-modal-footer user-interactions panel-footer\"},[_vm._t(\"footer\")],2)])])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import DialogModal from '../dialog_modal/dialog_modal.vue'\n\nconst FORCE_NSFW = 'mrf_tag:media-force-nsfw'\nconst STRIP_MEDIA = 'mrf_tag:media-strip'\nconst FORCE_UNLISTED = 'mrf_tag:force-unlisted'\nconst DISABLE_REMOTE_SUBSCRIPTION = 'mrf_tag:disable-remote-subscription'\nconst DISABLE_ANY_SUBSCRIPTION = 'mrf_tag:disable-any-subscription'\nconst SANDBOX = 'mrf_tag:sandbox'\nconst QUARANTINE = 'mrf_tag:quarantine'\n\nconst ModerationTools = {\n props: [\n 'user'\n ],\n data () {\n return {\n showDropDown: false,\n tags: {\n FORCE_NSFW,\n STRIP_MEDIA,\n FORCE_UNLISTED,\n DISABLE_REMOTE_SUBSCRIPTION,\n DISABLE_ANY_SUBSCRIPTION,\n SANDBOX,\n QUARANTINE\n },\n showDeleteUserDialog: false\n }\n },\n components: {\n DialogModal\n },\n computed: {\n tagsSet () {\n return new Set(this.user.tags)\n },\n hasTagPolicy () {\n return this.$store.state.instance.tagPolicyAvailable\n }\n },\n methods: {\n hasTag (tagName) {\n return this.tagsSet.has(tagName)\n },\n toggleTag (tag) {\n const store = this.$store\n if (this.tagsSet.has(tag)) {\n store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {\n if (!response.ok) { return }\n store.commit('untagUser', { user: this.user, tag })\n })\n } else {\n store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {\n if (!response.ok) { return }\n store.commit('tagUser', { user: this.user, tag })\n })\n }\n },\n toggleRight (right) {\n const store = this.$store\n if (this.user.rights[right]) {\n store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {\n if (!response.ok) { return }\n store.commit('updateRight', { user: this.user, right, value: false })\n })\n } else {\n store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {\n if (!response.ok) { return }\n store.commit('updateRight', { user: this.user, right, value: true })\n })\n }\n },\n toggleActivationStatus () {\n this.$store.dispatch('toggleActivationStatus', { user: this.user })\n },\n deleteUserDialog (show) {\n this.showDeleteUserDialog = show\n },\n deleteUser () {\n const store = this.$store\n const user = this.user\n const { id, name } = user\n store.state.api.backendInteractor.deleteUser({ user })\n .then(e => {\n this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)\n const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'\n const isTargetUser = this.$route.params.name === name || this.$route.params.id === id\n if (isProfile && isTargetUser) {\n window.history.back()\n }\n })\n }\n }\n}\n\nexport default ModerationTools\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./moderation_tools.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./moderation_tools.js\"\nimport __vue_script__ from \"!!babel-loader!./moderation_tools.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-756a6c4e\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./moderation_tools.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('v-popover',{staticClass:\"moderation-tools-popover\",attrs:{\"trigger\":\"click\",\"placement\":\"bottom-end\"},on:{\"show\":function($event){_vm.showDropDown = true},\"hide\":function($event){_vm.showDropDown = false}}},[_c('div',{attrs:{\"slot\":\"popover\"},slot:\"popover\"},[_c('div',{staticClass:\"dropdown-menu\"},[(_vm.user.is_local)?_c('span',[_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleRight(\"admin\")}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t(!!_vm.user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin'))+\"\\n \")]),_vm._v(\" \"),_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleRight(\"moderator\")}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t(!!_vm.user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator'))+\"\\n \")]),_vm._v(\" \"),_c('div',{staticClass:\"dropdown-divider\",attrs:{\"role\":\"separator\"}})]):_vm._e(),_vm._v(\" \"),_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleActivationStatus()}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t(!!_vm.user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account'))+\"\\n \")]),_vm._v(\" \"),_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.deleteUserDialog(true)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.delete_account'))+\"\\n \")]),_vm._v(\" \"),(_vm.hasTagPolicy)?_c('div',{staticClass:\"dropdown-divider\",attrs:{\"role\":\"separator\"}}):_vm._e(),_vm._v(\" \"),(_vm.hasTagPolicy)?_c('span',[_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.FORCE_NSFW)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.force_nsfw'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.FORCE_NSFW) }})]),_vm._v(\" \"),_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.STRIP_MEDIA)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.strip_media'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.STRIP_MEDIA) }})]),_vm._v(\" \"),_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.FORCE_UNLISTED)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.force_unlisted'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.FORCE_UNLISTED) }})]),_vm._v(\" \"),_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.SANDBOX)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.sandbox'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.SANDBOX) }})]),_vm._v(\" \"),(_vm.user.is_local)?_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.DISABLE_REMOTE_SUBSCRIPTION)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.disable_remote_subscription'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.DISABLE_REMOTE_SUBSCRIPTION) }})]):_vm._e(),_vm._v(\" \"),(_vm.user.is_local)?_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.DISABLE_ANY_SUBSCRIPTION)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.disable_any_subscription'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.DISABLE_ANY_SUBSCRIPTION) }})]):_vm._e(),_vm._v(\" \"),(_vm.user.is_local)?_c('button',{staticClass:\"dropdown-item\",on:{\"click\":function($event){_vm.toggleTag(_vm.tags.QUARANTINE)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.quarantine'))+\"\\n \"),_c('span',{staticClass:\"menu-checkbox\",class:{ 'menu-checkbox-checked': _vm.hasTag(_vm.tags.QUARANTINE) }})]):_vm._e()]):_vm._e()])]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-default btn-block\",class:{ pressed: _vm.showDropDown }},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.moderation'))+\"\\n \")])]),_vm._v(\" \"),_c('portal',{attrs:{\"to\":\"modal\"}},[(_vm.showDeleteUserDialog)?_c('DialogModal',{attrs:{\"on-cancel\":_vm.deleteUserDialog.bind(this, false)}},[_c('template',{slot:\"header\"},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.delete_user'))+\"\\n \")]),_vm._v(\" \"),_c('p',[_vm._v(_vm._s(_vm.$t('user_card.admin_menu.delete_user_confirmation')))]),_vm._v(\" \"),_c('template',{slot:\"footer\"},[_c('button',{staticClass:\"btn btn-default\",on:{\"click\":function($event){_vm.deleteUserDialog(false)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('general.cancel'))+\"\\n \")]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-default danger\",on:{\"click\":function($event){_vm.deleteUser()}}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.admin_menu.delete_user'))+\"\\n \")])])],2):_vm._e()],1)],1)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import ProgressButton from '../progress_button/progress_button.vue'\n\nconst AccountActions = {\n props: [\n 'user'\n ],\n data () {\n return { }\n },\n components: {\n ProgressButton\n },\n methods: {\n showRepeats () {\n this.$store.dispatch('showReblogs', this.user.id)\n },\n hideRepeats () {\n this.$store.dispatch('hideReblogs', this.user.id)\n },\n blockUser () {\n this.$store.dispatch('blockUser', this.user.id)\n },\n unblockUser () {\n this.$store.dispatch('unblockUser', this.user.id)\n },\n reportUser () {\n this.$store.dispatch('openUserReportingModal', this.user.id)\n }\n }\n}\n\nexport default AccountActions\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./account_actions.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./account_actions.js\"\nimport __vue_script__ from \"!!babel-loader!./account_actions.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-3967dbf7\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./account_actions.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"account-actions\"},[_c('v-popover',{staticClass:\"account-tools-popover\",attrs:{\"trigger\":\"click\",\"container\":false,\"placement\":\"bottom-end\",\"offset\":5}},[_c('div',{attrs:{\"slot\":\"popover\"},slot:\"popover\"},[_c('div',{staticClass:\"dropdown-menu\"},[(_vm.user.following)?[(_vm.user.showing_reblogs)?_c('button',{staticClass:\"btn btn-default dropdown-item\",on:{\"click\":_vm.hideRepeats}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.hide_repeats'))+\"\\n \")]):_vm._e(),_vm._v(\" \"),(!_vm.user.showing_reblogs)?_c('button',{staticClass:\"btn btn-default dropdown-item\",on:{\"click\":_vm.showRepeats}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.show_repeats'))+\"\\n \")]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"dropdown-divider\",attrs:{\"role\":\"separator\"}})]:_vm._e(),_vm._v(\" \"),(_vm.user.statusnet_blocking)?_c('button',{staticClass:\"btn btn-default btn-block dropdown-item\",on:{\"click\":_vm.unblockUser}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.unblock'))+\"\\n \")]):_c('button',{staticClass:\"btn btn-default btn-block dropdown-item\",on:{\"click\":_vm.blockUser}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.block'))+\"\\n \")]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-default btn-block dropdown-item\",on:{\"click\":_vm.reportUser}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.report'))+\"\\n \")])],2)]),_vm._v(\" \"),_c('div',{staticClass:\"btn btn-default ellipsis-button\"},[_c('i',{staticClass:\"icon-ellipsis trigger-button\"})])])],1)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import UserAvatar from '../user_avatar/user_avatar.vue'\nimport RemoteFollow from '../remote_follow/remote_follow.vue'\nimport ProgressButton from '../progress_button/progress_button.vue'\nimport FollowButton from '../follow_button/follow_button.vue'\nimport ModerationTools from '../moderation_tools/moderation_tools.vue'\nimport AccountActions from '../account_actions/account_actions.vue'\nimport { hex2rgb } from '../../services/color_convert/color_convert.js'\nimport generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'\nimport { mapGetters } from 'vuex'\n\nexport default {\n props: [\n 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar'\n ],\n data () {\n return {\n followRequestInProgress: false,\n betterShadow: this.$store.state.interface.browserSupport.cssFilter\n }\n },\n created () {\n this.$store.dispatch('fetchUserRelationship', this.user.id)\n },\n computed: {\n classes () {\n return [{\n 'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius\n 'user-card-rounded': this.rounded === true, // set border-radius for all sides\n 'user-card-bordered': this.bordered === true // set border for all sides\n }]\n },\n style () {\n const color = this.$store.getters.mergedConfig.customTheme.colors\n ? this.$store.getters.mergedConfig.customTheme.colors.bg // v2\n : this.$store.getters.mergedConfig.colors.bg // v1\n\n if (color) {\n const rgb = (typeof color === 'string') ? hex2rgb(color) : color\n const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)`\n\n return {\n backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,\n backgroundImage: [\n `linear-gradient(to bottom, ${tintColor}, ${tintColor})`,\n `url(${this.user.cover_photo})`\n ].join(', ')\n }\n }\n },\n isOtherUser () {\n return this.user.id !== this.$store.state.users.currentUser.id\n },\n subscribeUrl () {\n // eslint-disable-next-line no-undef\n const serverUrl = new URL(this.user.statusnet_profile_url)\n return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`\n },\n loggedIn () {\n return this.$store.state.users.currentUser\n },\n dailyAvg () {\n const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000))\n return Math.round(this.user.statuses_count / days)\n },\n userHighlightType: {\n get () {\n const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name]\n return (data && data.type) || 'disabled'\n },\n set (type) {\n const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name]\n if (type !== 'disabled') {\n this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: (data && data.color) || '#FFFFFF', type })\n } else {\n this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined })\n }\n },\n ...mapGetters(['mergedConfig'])\n },\n userHighlightColor: {\n get () {\n const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name]\n return data && data.color\n },\n set (color) {\n this.$store.dispatch('setHighlight', { user: this.user.screen_name, color })\n }\n },\n visibleRole () {\n const rights = this.user.rights\n if (!rights) { return }\n const validRole = rights.admin || rights.moderator\n const roleTitle = rights.admin ? 'admin' : 'moderator'\n return validRole && roleTitle\n },\n hideFollowsCount () {\n return this.isOtherUser && this.user.hide_follows_count\n },\n hideFollowersCount () {\n return this.isOtherUser && this.user.hide_followers_count\n },\n ...mapGetters(['mergedConfig'])\n },\n components: {\n UserAvatar,\n RemoteFollow,\n ModerationTools,\n AccountActions,\n ProgressButton,\n FollowButton\n },\n methods: {\n muteUser () {\n this.$store.dispatch('muteUser', this.user.id)\n },\n unmuteUser () {\n this.$store.dispatch('unmuteUser', this.user.id)\n },\n subscribeUser () {\n return this.$store.dispatch('subscribeUser', this.user.id)\n },\n unsubscribeUser () {\n return this.$store.dispatch('unsubscribeUser', this.user.id)\n },\n setProfileView (v) {\n if (this.switcher) {\n const store = this.$store\n store.commit('setProfileView', { v })\n }\n },\n linkClicked ({ target }) {\n if (target.tagName === 'SPAN') {\n target = target.parentNode\n }\n if (target.tagName === 'A') {\n window.open(target.href, '_blank')\n }\n },\n userProfileLink (user) {\n return generateProfileLink(\n user.id, user.screen_name,\n this.$store.state.instance.restrictedNicknames\n )\n },\n zoomAvatar () {\n const attachment = {\n url: this.user.profile_image_url_original,\n mimetype: 'image'\n }\n this.$store.dispatch('setMedia', [attachment])\n this.$store.dispatch('setCurrent', attachment)\n },\n mentionUser () {\n this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })\n }\n }\n}\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./user_card.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./user_card.js\"\nimport __vue_script__ from \"!!babel-loader!./user_card.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-d0b51e66\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./user_card.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"user-card\",class:_vm.classes},[_c('div',{staticClass:\"background-image\",class:{ 'hide-bio': _vm.hideBio },style:(_vm.style)}),_vm._v(\" \"),_c('div',{staticClass:\"panel-heading\"},[_c('div',{staticClass:\"user-info\"},[_c('div',{staticClass:\"container\"},[(_vm.allowZoomingAvatar)?_c('a',{staticClass:\"user-info-avatar-link\",on:{\"click\":_vm.zoomAvatar}},[_c('UserAvatar',{attrs:{\"better-shadow\":_vm.betterShadow,\"user\":_vm.user}}),_vm._v(\" \"),_vm._m(0)],1):_c('router-link',{attrs:{\"to\":_vm.userProfileLink(_vm.user)}},[_c('UserAvatar',{attrs:{\"better-shadow\":_vm.betterShadow,\"user\":_vm.user}})],1),_vm._v(\" \"),_c('div',{staticClass:\"user-summary\"},[_c('div',{staticClass:\"top-line\"},[(_vm.user.name_html)?_c('div',{staticClass:\"user-name\",attrs:{\"title\":_vm.user.name},domProps:{\"innerHTML\":_vm._s(_vm.user.name_html)}}):_c('div',{staticClass:\"user-name\",attrs:{\"title\":_vm.user.name}},[_vm._v(\"\\n \"+_vm._s(_vm.user.name)+\"\\n \")]),_vm._v(\" \"),(!_vm.isOtherUser)?_c('router-link',{attrs:{\"to\":{ name: 'user-settings' }}},[_c('i',{staticClass:\"button-icon icon-wrench usersettings\",attrs:{\"title\":_vm.$t('tool_tip.user_settings')}})]):_vm._e(),_vm._v(\" \"),(_vm.isOtherUser && !_vm.user.is_local)?_c('a',{attrs:{\"href\":_vm.user.statusnet_profile_url,\"target\":\"_blank\"}},[_c('i',{staticClass:\"icon-link-ext usersettings\"})]):_vm._e(),_vm._v(\" \"),(_vm.isOtherUser && _vm.loggedIn)?_c('AccountActions',{attrs:{\"user\":_vm.user}}):_vm._e()],1),_vm._v(\" \"),_c('div',{staticClass:\"bottom-line\"},[_c('router-link',{staticClass:\"user-screen-name\",attrs:{\"to\":_vm.userProfileLink(_vm.user)}},[_vm._v(\"\\n @\"+_vm._s(_vm.user.screen_name)+\"\\n \")]),_vm._v(\" \"),(!_vm.hideBio && !!_vm.visibleRole)?_c('span',{staticClass:\"alert staff\"},[_vm._v(_vm._s(_vm.visibleRole))]):_vm._e(),_vm._v(\" \"),(_vm.user.locked)?_c('span',[_c('i',{staticClass:\"icon icon-lock\"})]):_vm._e(),_vm._v(\" \"),(!_vm.mergedConfig.hideUserStats && !_vm.hideBio)?_c('span',{staticClass:\"dailyAvg\"},[_vm._v(_vm._s(_vm.dailyAvg)+\" \"+_vm._s(_vm.$t('user_card.per_day')))]):_vm._e()],1)])],1),_vm._v(\" \"),_c('div',{staticClass:\"user-meta\"},[(_vm.user.follows_you && _vm.loggedIn && _vm.isOtherUser)?_c('div',{staticClass:\"following\"},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.follows_you'))+\"\\n \")]):_vm._e(),_vm._v(\" \"),(_vm.isOtherUser && (_vm.loggedIn || !_vm.switcher))?_c('div',{staticClass:\"highlighter\"},[(_vm.userHighlightType !== 'disabled')?_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.userHighlightColor),expression:\"userHighlightColor\"}],staticClass:\"userHighlightText\",attrs:{\"id\":'userHighlightColorTx'+_vm.user.id,\"type\":\"text\"},domProps:{\"value\":(_vm.userHighlightColor)},on:{\"input\":function($event){if($event.target.composing){ return; }_vm.userHighlightColor=$event.target.value}}}):_vm._e(),_vm._v(\" \"),(_vm.userHighlightType !== 'disabled')?_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.userHighlightColor),expression:\"userHighlightColor\"}],staticClass:\"userHighlightCl\",attrs:{\"id\":'userHighlightColor'+_vm.user.id,\"type\":\"color\"},domProps:{\"value\":(_vm.userHighlightColor)},on:{\"input\":function($event){if($event.target.composing){ return; }_vm.userHighlightColor=$event.target.value}}}):_vm._e(),_vm._v(\" \"),_c('label',{staticClass:\"userHighlightSel select\",attrs:{\"for\":\"style-switcher\"}},[_c('select',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.userHighlightType),expression:\"userHighlightType\"}],staticClass:\"userHighlightSel\",attrs:{\"id\":'userHighlightSel'+_vm.user.id},on:{\"change\":function($event){var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = \"_value\" in o ? o._value : o.value;return val}); _vm.userHighlightType=$event.target.multiple ? $$selectedVal : $$selectedVal[0]}}},[_c('option',{attrs:{\"value\":\"disabled\"}},[_vm._v(\"No highlight\")]),_vm._v(\" \"),_c('option',{attrs:{\"value\":\"solid\"}},[_vm._v(\"Solid bg\")]),_vm._v(\" \"),_c('option',{attrs:{\"value\":\"striped\"}},[_vm._v(\"Striped bg\")]),_vm._v(\" \"),_c('option',{attrs:{\"value\":\"side\"}},[_vm._v(\"Side stripe\")])]),_vm._v(\" \"),_c('i',{staticClass:\"icon-down-open\"})])]):_vm._e()]),_vm._v(\" \"),(_vm.loggedIn && _vm.isOtherUser)?_c('div',{staticClass:\"user-interactions\"},[_c('div',{staticClass:\"btn-group\"},[_c('FollowButton',{attrs:{\"user\":_vm.user}}),_vm._v(\" \"),(_vm.user.following)?[(!_vm.user.subscribed)?_c('ProgressButton',{staticClass:\"btn btn-default\",attrs:{\"click\":_vm.subscribeUser,\"title\":_vm.$t('user_card.subscribe')}},[_c('i',{staticClass:\"icon-bell-alt\"})]):_c('ProgressButton',{staticClass:\"btn btn-default pressed\",attrs:{\"click\":_vm.unsubscribeUser,\"title\":_vm.$t('user_card.unsubscribe')}},[_c('i',{staticClass:\"icon-bell-ringing-o\"})])]:_vm._e()],2),_vm._v(\" \"),_c('div',[(_vm.user.muted)?_c('button',{staticClass:\"btn btn-default btn-block pressed\",on:{\"click\":_vm.unmuteUser}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.muted'))+\"\\n \")]):_c('button',{staticClass:\"btn btn-default btn-block\",on:{\"click\":_vm.muteUser}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.mute'))+\"\\n \")])]),_vm._v(\" \"),_c('div',[_c('button',{staticClass:\"btn btn-default btn-block\",on:{\"click\":_vm.mentionUser}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('user_card.mention'))+\"\\n \")])]),_vm._v(\" \"),(_vm.loggedIn.role === \"admin\")?_c('ModerationTools',{attrs:{\"user\":_vm.user}}):_vm._e()],1):_vm._e(),_vm._v(\" \"),(!_vm.loggedIn && _vm.user.is_local)?_c('div',{staticClass:\"user-interactions\"},[_c('RemoteFollow',{attrs:{\"user\":_vm.user}})],1):_vm._e()])]),_vm._v(\" \"),(!_vm.hideBio)?_c('div',{staticClass:\"panel-body\"},[(!_vm.mergedConfig.hideUserStats && _vm.switcher)?_c('div',{staticClass:\"user-counts\"},[_c('div',{staticClass:\"user-count\",on:{\"click\":function($event){$event.preventDefault();_vm.setProfileView('statuses')}}},[_c('h5',[_vm._v(_vm._s(_vm.$t('user_card.statuses')))]),_vm._v(\" \"),_c('span',[_vm._v(_vm._s(_vm.user.statuses_count)+\" \"),_c('br')])]),_vm._v(\" \"),_c('div',{staticClass:\"user-count\",on:{\"click\":function($event){$event.preventDefault();_vm.setProfileView('friends')}}},[_c('h5',[_vm._v(_vm._s(_vm.$t('user_card.followees')))]),_vm._v(\" \"),_c('span',[_vm._v(_vm._s(_vm.hideFollowsCount ? _vm.$t('user_card.hidden') : _vm.user.friends_count))])]),_vm._v(\" \"),_c('div',{staticClass:\"user-count\",on:{\"click\":function($event){$event.preventDefault();_vm.setProfileView('followers')}}},[_c('h5',[_vm._v(_vm._s(_vm.$t('user_card.followers')))]),_vm._v(\" \"),_c('span',[_vm._v(_vm._s(_vm.hideFollowersCount ? _vm.$t('user_card.hidden') : _vm.user.followers_count))])])]):_vm._e(),_vm._v(\" \"),(!_vm.hideBio && _vm.user.description_html)?_c('p',{staticClass:\"user-card-bio\",domProps:{\"innerHTML\":_vm._s(_vm.user.description_html)},on:{\"click\":function($event){$event.preventDefault();return _vm.linkClicked($event)}}}):(!_vm.hideBio)?_c('p',{staticClass:\"user-card-bio\"},[_vm._v(\"\\n \"+_vm._s(_vm.user.description)+\"\\n \")]):_vm._e()]):_vm._e()])}\nvar staticRenderFns = [function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"user-info-avatar-link-overlay\"},[_c('i',{staticClass:\"button-icon icon-zoom-in\"})])}]\nexport { render, staticRenderFns }","import StillImage from '../still-image/still-image.vue'\nimport VideoAttachment from '../video_attachment/video_attachment.vue'\nimport nsfwImage from '../../assets/nsfw.png'\nimport fileTypeService from '../../services/file_type/file_type.service.js'\n\nconst Attachment = {\n props: [\n 'attachment',\n 'nsfw',\n 'statusId',\n 'size',\n 'allowPlay',\n 'setMedia',\n 'naturalSizeLoad'\n ],\n data () {\n return {\n nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,\n hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,\n preloadImage: this.$store.getters.mergedConfig.preloadImage,\n loading: false,\n img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),\n modalOpen: false,\n showHidden: false\n }\n },\n components: {\n StillImage,\n VideoAttachment\n },\n computed: {\n usePlaceHolder () {\n return this.size === 'hide' || this.type === 'unknown'\n },\n referrerpolicy () {\n return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'\n },\n type () {\n return fileTypeService.fileType(this.attachment.mimetype)\n },\n hidden () {\n return this.nsfw && this.hideNsfwLocal && !this.showHidden\n },\n isEmpty () {\n return (this.type === 'html' && !this.attachment.oembed) || this.type === 'unknown'\n },\n isSmall () {\n return this.size === 'small'\n },\n fullwidth () {\n return this.type === 'html' || this.type === 'audio'\n }\n },\n methods: {\n linkClicked ({ target }) {\n if (target.tagName === 'A') {\n window.open(target.href, '_blank')\n }\n },\n openModal (event) {\n const modalTypes = this.$store.getters.mergedConfig.playVideosInModal\n ? ['image', 'video']\n : ['image']\n if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||\n this.usePlaceHolder\n ) {\n event.stopPropagation()\n event.preventDefault()\n this.setMedia()\n this.$store.dispatch('setCurrent', this.attachment)\n }\n },\n toggleHidden (event) {\n if (this.$store.getters.mergedConfig.useOneClickNsfw && !this.showHidden) {\n this.openModal(event)\n return\n }\n if (this.img && !this.preloadImage) {\n if (this.img.onload) {\n this.img.onload()\n } else {\n this.loading = true\n this.img.src = this.attachment.url\n this.img.onload = () => {\n this.loading = false\n this.showHidden = !this.showHidden\n }\n }\n } else {\n this.showHidden = !this.showHidden\n }\n },\n onImageLoad (image) {\n const width = image.naturalWidth\n const height = image.naturalHeight\n this.naturalSizeLoad && this.naturalSizeLoad({ width, height })\n }\n }\n}\n\nexport default Attachment\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./attachment.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./attachment.js\"\nimport __vue_script__ from \"!!babel-loader!./attachment.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-47ab0ca0\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./attachment.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {\nvar _obj;\nvar _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.usePlaceHolder)?_c('div',{on:{\"click\":_vm.openModal}},[(_vm.type !== 'html')?_c('a',{staticClass:\"placeholder\",attrs:{\"target\":\"_blank\",\"href\":_vm.attachment.url}},[_vm._v(\"\\n [\"+_vm._s(_vm.nsfw ? \"NSFW/\" : \"\")+_vm._s(_vm.type.toUpperCase())+\"]\\n \")]):_vm._e()]):_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(!_vm.isEmpty),expression:\"!isEmpty\"}],staticClass:\"attachment\",class:( _obj = {}, _obj[_vm.type] = true, _obj.loading = _vm.loading, _obj['fullwidth'] = _vm.fullwidth, _obj['nsfw-placeholder'] = _vm.hidden, _obj )},[(_vm.hidden)?_c('a',{staticClass:\"image-attachment\",attrs:{\"href\":_vm.attachment.url},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleHidden($event)}}},[_c('img',{key:_vm.nsfwImage,staticClass:\"nsfw\",class:{'small': _vm.isSmall},attrs:{\"src\":_vm.nsfwImage}}),_vm._v(\" \"),(_vm.type === 'video')?_c('i',{staticClass:\"play-icon icon-play-circled\"}):_vm._e()]):_vm._e(),_vm._v(\" \"),(_vm.nsfw && _vm.hideNsfwLocal && !_vm.hidden)?_c('div',{staticClass:\"hider\"},[_c('a',{attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleHidden($event)}}},[_vm._v(\"Hide\")])]):_vm._e(),_vm._v(\" \"),(_vm.type === 'image' && (!_vm.hidden || _vm.preloadImage))?_c('a',{staticClass:\"image-attachment\",class:{'hidden': _vm.hidden && _vm.preloadImage },attrs:{\"href\":_vm.attachment.url,\"target\":\"_blank\",\"title\":_vm.attachment.description},on:{\"click\":_vm.openModal}},[_c('StillImage',{attrs:{\"referrerpolicy\":_vm.referrerpolicy,\"mimetype\":_vm.attachment.mimetype,\"src\":_vm.attachment.large_thumb_url || _vm.attachment.url,\"image-load-handler\":_vm.onImageLoad}})],1):_vm._e(),_vm._v(\" \"),(_vm.type === 'video' && !_vm.hidden)?_c('a',{staticClass:\"video-container\",class:{'small': _vm.isSmall},attrs:{\"href\":_vm.allowPlay ? undefined : _vm.attachment.url},on:{\"click\":_vm.openModal}},[_c('VideoAttachment',{staticClass:\"video\",attrs:{\"attachment\":_vm.attachment,\"controls\":_vm.allowPlay}}),_vm._v(\" \"),(!_vm.allowPlay)?_c('i',{staticClass:\"play-icon icon-play-circled\"}):_vm._e()],1):_vm._e(),_vm._v(\" \"),(_vm.type === 'audio')?_c('audio',{attrs:{\"src\":_vm.attachment.url,\"controls\":\"\"}}):_vm._e(),_vm._v(\" \"),(_vm.type === 'html' && _vm.attachment.oembed)?_c('div',{staticClass:\"oembed\",on:{\"click\":function($event){$event.preventDefault();return _vm.linkClicked($event)}}},[(_vm.attachment.thumb_url)?_c('div',{staticClass:\"image\"},[_c('img',{attrs:{\"src\":_vm.attachment.thumb_url}})]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"text\"},[_c('h1',[_c('a',{attrs:{\"href\":_vm.attachment.url}},[_vm._v(_vm._s(_vm.attachment.oembed.title))])]),_vm._v(\" \"),_c('div',{domProps:{\"innerHTML\":_vm._s(_vm.attachment.oembed.oembedHTML)}})])]):_vm._e()])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import { mapGetters } from 'vuex'\n\nconst FavoriteButton = {\n props: ['status', 'loggedIn'],\n data () {\n return {\n animated: false\n }\n },\n methods: {\n favorite () {\n if (!this.status.favorited) {\n this.$store.dispatch('favorite', { id: this.status.id })\n } else {\n this.$store.dispatch('unfavorite', { id: this.status.id })\n }\n this.animated = true\n setTimeout(() => {\n this.animated = false\n }, 500)\n }\n },\n computed: {\n classes () {\n return {\n 'icon-star-empty': !this.status.favorited,\n 'icon-star': this.status.favorited,\n 'animate-spin': this.animated\n }\n },\n ...mapGetters(['mergedConfig'])\n }\n}\n\nexport default FavoriteButton\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./favorite_button.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./favorite_button.js\"\nimport __vue_script__ from \"!!babel-loader!./favorite_button.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-2ced002f\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./favorite_button.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.loggedIn)?_c('div',[_c('i',{staticClass:\"button-icon favorite-button fav-active\",class:_vm.classes,attrs:{\"title\":_vm.$t('tool_tip.favorite')},on:{\"click\":function($event){$event.preventDefault();_vm.favorite()}}}),_vm._v(\" \"),(!_vm.mergedConfig.hidePostStats && _vm.status.fave_num > 0)?_c('span',[_vm._v(_vm._s(_vm.status.fave_num))]):_vm._e()]):_c('div',[_c('i',{staticClass:\"button-icon favorite-button\",class:_vm.classes,attrs:{\"title\":_vm.$t('tool_tip.favorite')}}),_vm._v(\" \"),(!_vm.mergedConfig.hidePostStats && _vm.status.fave_num > 0)?_c('span',[_vm._v(_vm._s(_vm.status.fave_num))]):_vm._e()])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import { mapGetters } from 'vuex'\n\nconst RetweetButton = {\n props: ['status', 'loggedIn', 'visibility'],\n data () {\n return {\n animated: false\n }\n },\n methods: {\n retweet () {\n if (!this.status.repeated) {\n this.$store.dispatch('retweet', { id: this.status.id })\n } else {\n this.$store.dispatch('unretweet', { id: this.status.id })\n }\n this.animated = true\n setTimeout(() => {\n this.animated = false\n }, 500)\n }\n },\n computed: {\n classes () {\n return {\n 'retweeted': this.status.repeated,\n 'retweeted-empty': !this.status.repeated,\n 'animate-spin': this.animated\n }\n },\n ...mapGetters(['mergedConfig'])\n }\n}\n\nexport default RetweetButton\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./retweet_button.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./retweet_button.js\"\nimport __vue_script__ from \"!!babel-loader!./retweet_button.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-538410cc\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./retweet_button.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.loggedIn)?_c('div',[(_vm.visibility !== 'private' && _vm.visibility !== 'direct')?[_c('i',{staticClass:\"button-icon retweet-button icon-retweet rt-active\",class:_vm.classes,attrs:{\"title\":_vm.$t('tool_tip.repeat')},on:{\"click\":function($event){$event.preventDefault();_vm.retweet()}}}),_vm._v(\" \"),(!_vm.mergedConfig.hidePostStats && _vm.status.repeat_num > 0)?_c('span',[_vm._v(_vm._s(_vm.status.repeat_num))]):_vm._e()]:[_c('i',{staticClass:\"button-icon icon-lock\",class:_vm.classes,attrs:{\"title\":_vm.$t('timeline.no_retweet_hint')}})]],2):(!_vm.loggedIn)?_c('div',[_c('i',{staticClass:\"button-icon icon-retweet\",class:_vm.classes,attrs:{\"title\":_vm.$t('tool_tip.repeat')}}),_vm._v(\" \"),(!_vm.mergedConfig.hidePostStats && _vm.status.repeat_num > 0)?_c('span',[_vm._v(_vm._s(_vm.status.repeat_num))]):_vm._e()]):_vm._e()}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import Timeago from '../timeago/timeago.vue'\nimport { forEach, map } from 'lodash'\n\nexport default {\n name: 'Poll',\n props: ['basePoll'],\n components: { Timeago },\n data () {\n return {\n loading: false,\n choices: []\n }\n },\n created () {\n if (!this.$store.state.polls.pollsObject[this.pollId]) {\n this.$store.dispatch('mergeOrAddPoll', this.basePoll)\n }\n this.$store.dispatch('trackPoll', this.pollId)\n },\n destroyed () {\n this.$store.dispatch('untrackPoll', this.pollId)\n },\n computed: {\n pollId () {\n return this.basePoll.id\n },\n poll () {\n const storePoll = this.$store.state.polls.pollsObject[this.pollId]\n return storePoll || {}\n },\n options () {\n return (this.poll && this.poll.options) || []\n },\n expiresAt () {\n return (this.poll && this.poll.expires_at) || 0\n },\n expired () {\n return (this.poll && this.poll.expired) || false\n },\n loggedIn () {\n return this.$store.state.users.currentUser\n },\n showResults () {\n return this.poll.voted || this.expired || !this.loggedIn\n },\n totalVotesCount () {\n return this.poll.votes_count\n },\n containerClass () {\n return {\n loading: this.loading\n }\n },\n choiceIndices () {\n // Convert array of booleans into an array of indices of the\n // items that were 'true', so [true, false, false, true] becomes\n // [0, 3].\n return this.choices\n .map((entry, index) => entry && index)\n .filter(value => typeof value === 'number')\n },\n isDisabled () {\n const noChoice = this.choiceIndices.length === 0\n return this.loading || noChoice\n }\n },\n methods: {\n percentageForOption (count) {\n return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)\n },\n resultTitle (option) {\n return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`\n },\n fetchPoll () {\n this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })\n },\n activateOption (index) {\n // forgive me father: doing checking the radio/checkboxes\n // in code because of customized input elements need either\n // a) an extra element for the actual graphic, or b) use a\n // pseudo element for the label. We use b) which mandates\n // using \"for\" and \"id\" matching which isn't nice when the\n // same poll appears multiple times on the site (notifs and\n // timeline for example). With code we can make sure it just\n // works without altering the pseudo element implementation.\n const allElements = this.$el.querySelectorAll('input')\n const clickedElement = this.$el.querySelector(`input[value=\"${index}\"]`)\n if (this.poll.multiple) {\n // Checkboxes, toggle only the clicked one\n clickedElement.checked = !clickedElement.checked\n } else {\n // Radio button, uncheck everything and check the clicked one\n forEach(allElements, element => { element.checked = false })\n clickedElement.checked = true\n }\n this.choices = map(allElements, e => e.checked)\n },\n optionId (index) {\n return `poll${this.poll.id}-${index}`\n },\n vote () {\n if (this.choiceIndices.length === 0) return\n this.loading = true\n this.$store.dispatch(\n 'votePoll',\n { id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices }\n ).then(poll => {\n this.loading = false\n })\n }\n }\n}\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./poll.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./poll.js\"\nimport __vue_script__ from \"!!babel-loader!./poll.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-6feb4525\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./poll.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"poll\",class:_vm.containerClass},[_vm._l((_vm.options),function(option,index){return _c('div',{key:index,staticClass:\"poll-option\"},[(_vm.showResults)?_c('div',{staticClass:\"option-result\",attrs:{\"title\":_vm.resultTitle(option)}},[_c('div',{staticClass:\"option-result-label\"},[_c('span',{staticClass:\"result-percentage\"},[_vm._v(\"\\n \"+_vm._s(_vm.percentageForOption(option.votes_count))+\"%\\n \")]),_vm._v(\" \"),_c('span',[_vm._v(_vm._s(option.title))])]),_vm._v(\" \"),_c('div',{staticClass:\"result-fill\",style:({ 'width': ((_vm.percentageForOption(option.votes_count)) + \"%\") })})]):_c('div',{on:{\"click\":function($event){_vm.activateOption(index)}}},[(_vm.poll.multiple)?_c('input',{attrs:{\"type\":\"checkbox\",\"disabled\":_vm.loading},domProps:{\"value\":index}}):_c('input',{attrs:{\"type\":\"radio\",\"disabled\":_vm.loading},domProps:{\"value\":index}}),_vm._v(\" \"),_c('label',{staticClass:\"option-vote\"},[_c('div',[_vm._v(_vm._s(option.title))])])])])}),_vm._v(\" \"),_c('div',{staticClass:\"footer faint\"},[(!_vm.showResults)?_c('button',{staticClass:\"btn btn-default poll-vote-button\",attrs:{\"type\":\"button\",\"disabled\":_vm.isDisabled},on:{\"click\":_vm.vote}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('polls.vote'))+\"\\n \")]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"total\"},[_vm._v(\"\\n \"+_vm._s(_vm.totalVotesCount)+\" \"+_vm._s(_vm.$t(\"polls.votes\"))+\" · \\n \")]),_vm._v(\" \"),_c('i18n',{attrs:{\"path\":_vm.expired ? 'polls.expired' : 'polls.expires_in'}},[_c('Timeago',{attrs:{\"time\":_vm.expiresAt,\"auto-update\":60,\"now-threshold\":0}})],1)],1)],2)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","const ExtraButtons = {\n props: [ 'status' ],\n methods: {\n deleteStatus () {\n const confirmed = window.confirm(this.$t('status.delete_confirm'))\n if (confirmed) {\n this.$store.dispatch('deleteStatus', { id: this.status.id })\n }\n },\n pinStatus () {\n this.$store.dispatch('pinStatus', this.status.id)\n .then(() => this.$emit('onSuccess'))\n .catch(err => this.$emit('onError', err.error.error))\n },\n unpinStatus () {\n this.$store.dispatch('unpinStatus', this.status.id)\n .then(() => this.$emit('onSuccess'))\n .catch(err => this.$emit('onError', err.error.error))\n },\n muteConversation () {\n this.$store.dispatch('muteConversation', this.status.id)\n .then(() => this.$emit('onSuccess'))\n .catch(err => this.$emit('onError', err.error.error))\n },\n unmuteConversation () {\n this.$store.dispatch('unmuteConversation', this.status.id)\n .then(() => this.$emit('onSuccess'))\n .catch(err => this.$emit('onError', err.error.error))\n }\n },\n computed: {\n currentUser () { return this.$store.state.users.currentUser },\n canDelete () {\n if (!this.currentUser) { return }\n const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin\n return superuser || this.status.user.id === this.currentUser.id\n },\n ownStatus () {\n return this.status.user.id === this.currentUser.id\n },\n canPin () {\n return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')\n },\n canMute () {\n return !!this.currentUser\n }\n }\n}\n\nexport default ExtraButtons\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./extra_buttons.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./extra_buttons.js\"\nimport __vue_script__ from \"!!babel-loader!./extra_buttons.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-0caa0403\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./extra_buttons.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.canDelete || _vm.canMute || _vm.canPin)?_c('v-popover',{staticClass:\"extra-button-popover\",attrs:{\"trigger\":\"click\",\"placement\":\"top\"}},[_c('div',{attrs:{\"slot\":\"popover\"},slot:\"popover\"},[_c('div',{staticClass:\"dropdown-menu\"},[(_vm.canMute && !_vm.status.thread_muted)?_c('button',{staticClass:\"dropdown-item dropdown-item-icon\",on:{\"click\":function($event){$event.preventDefault();return _vm.muteConversation($event)}}},[_c('i',{staticClass:\"icon-eye-off\"}),_c('span',[_vm._v(_vm._s(_vm.$t(\"status.mute_conversation\")))])]):_vm._e(),_vm._v(\" \"),(_vm.canMute && _vm.status.thread_muted)?_c('button',{staticClass:\"dropdown-item dropdown-item-icon\",on:{\"click\":function($event){$event.preventDefault();return _vm.unmuteConversation($event)}}},[_c('i',{staticClass:\"icon-eye-off\"}),_c('span',[_vm._v(_vm._s(_vm.$t(\"status.unmute_conversation\")))])]):_vm._e(),_vm._v(\" \"),(!_vm.status.pinned && _vm.canPin)?_c('button',{directives:[{name:\"close-popover\",rawName:\"v-close-popover\"}],staticClass:\"dropdown-item dropdown-item-icon\",on:{\"click\":function($event){$event.preventDefault();return _vm.pinStatus($event)}}},[_c('i',{staticClass:\"icon-pin\"}),_c('span',[_vm._v(_vm._s(_vm.$t(\"status.pin\")))])]):_vm._e(),_vm._v(\" \"),(_vm.status.pinned && _vm.canPin)?_c('button',{directives:[{name:\"close-popover\",rawName:\"v-close-popover\"}],staticClass:\"dropdown-item dropdown-item-icon\",on:{\"click\":function($event){$event.preventDefault();return _vm.unpinStatus($event)}}},[_c('i',{staticClass:\"icon-pin\"}),_c('span',[_vm._v(_vm._s(_vm.$t(\"status.unpin\")))])]):_vm._e(),_vm._v(\" \"),(_vm.canDelete)?_c('button',{directives:[{name:\"close-popover\",rawName:\"v-close-popover\"}],staticClass:\"dropdown-item dropdown-item-icon\",on:{\"click\":function($event){$event.preventDefault();return _vm.deleteStatus($event)}}},[_c('i',{staticClass:\"icon-cancel\"}),_c('span',[_vm._v(_vm._s(_vm.$t(\"status.delete\")))])]):_vm._e()])]),_vm._v(\" \"),_c('div',{staticClass:\"button-icon\"},[_c('i',{staticClass:\"icon-ellipsis\"})])]):_vm._e()}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import Attachment from '../attachment/attachment.vue'\nimport { chunk, last, dropRight, sumBy } from 'lodash'\n\nconst Gallery = {\n props: [\n 'attachments',\n 'nsfw',\n 'setMedia'\n ],\n data () {\n return {\n sizes: {}\n }\n },\n components: { Attachment },\n computed: {\n rows () {\n if (!this.attachments) {\n return []\n }\n const rows = chunk(this.attachments, 3)\n if (last(rows).length === 1 && rows.length > 1) {\n // if 1 attachment on last row -> add it to the previous row instead\n const lastAttachment = last(rows)[0]\n const allButLastRow = dropRight(rows)\n last(allButLastRow).push(lastAttachment)\n return allButLastRow\n }\n return rows\n },\n useContainFit () {\n return this.$store.getters.mergedConfig.useContainFit\n }\n },\n methods: {\n onNaturalSizeLoad (id, size) {\n this.$set(this.sizes, id, size)\n },\n rowStyle (itemsPerRow) {\n return { 'padding-bottom': `${(100 / (itemsPerRow + 0.6))}%` }\n },\n itemStyle (id, row) {\n const total = sumBy(row, item => this.getAspectRatio(item.id))\n return { flex: `${this.getAspectRatio(id) / total} 1 0%` }\n },\n getAspectRatio (id) {\n const size = this.sizes[id]\n return size ? size.width / size.height : 1\n }\n }\n}\n\nexport default Gallery\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./gallery.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./gallery.js\"\nimport __vue_script__ from \"!!babel-loader!./gallery.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-68a574b8\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./gallery.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{ref:\"galleryContainer\",staticStyle:{\"width\":\"100%\"}},_vm._l((_vm.rows),function(row,index){return _c('div',{key:index,staticClass:\"gallery-row\",class:{ 'contain-fit': _vm.useContainFit, 'cover-fit': !_vm.useContainFit },style:(_vm.rowStyle(row.length))},[_c('div',{staticClass:\"gallery-row-inner\"},_vm._l((row),function(attachment){return _c('attachment',{key:attachment.id,style:(_vm.itemStyle(attachment.id, row)),attrs:{\"set-media\":_vm.setMedia,\"nsfw\":_vm.nsfw,\"attachment\":attachment,\"allow-play\":false,\"natural-size-load\":_vm.onNaturalSizeLoad.bind(null, attachment.id)}})}),1)])}),0)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","const LinkPreview = {\n name: 'LinkPreview',\n props: [\n 'card',\n 'size',\n 'nsfw'\n ],\n data () {\n return {\n imageLoaded: false\n }\n },\n computed: {\n useImage () {\n // Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid\n // as it makes sure to hide the image if somehow NSFW tagged preview can\n // exist.\n return this.card.image && !this.nsfw && this.size !== 'hide'\n },\n useDescription () {\n return this.card.description && /\\S/.test(this.card.description)\n }\n },\n created () {\n if (this.useImage) {\n const newImg = new Image()\n newImg.onload = () => {\n this.imageLoaded = true\n }\n newImg.src = this.card.image\n }\n }\n}\n\nexport default LinkPreview\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./link-preview.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./link-preview.js\"\nimport __vue_script__ from \"!!babel-loader!./link-preview.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-7c8d99ac\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./link-preview.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('a',{staticClass:\"link-preview-card\",attrs:{\"href\":_vm.card.url,\"target\":\"_blank\",\"rel\":\"noopener\"}},[(_vm.useImage && _vm.imageLoaded)?_c('div',{staticClass:\"card-image\",class:{ 'small-image': _vm.size === 'small' }},[_c('img',{attrs:{\"src\":_vm.card.image}})]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"card-content\"},[_c('span',{staticClass:\"card-host faint\"},[_vm._v(_vm._s(_vm.card.provider_name))]),_vm._v(\" \"),_c('h4',{staticClass:\"card-title\"},[_vm._v(_vm._s(_vm.card.title))]),_vm._v(\" \"),(_vm.useDescription)?_c('p',{staticClass:\"card-description\"},[_vm._v(_vm._s(_vm.card.description))]):_vm._e()])])])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import UserAvatar from '../user_avatar/user_avatar.vue'\nimport generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'\n\nconst AvatarList = {\n props: ['users'],\n computed: {\n slicedUsers () {\n return this.users ? this.users.slice(0, 15) : []\n }\n },\n components: {\n UserAvatar\n },\n methods: {\n userProfileLink (user) {\n return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)\n }\n }\n}\n\nexport default AvatarList\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./avatar_list.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./avatar_list.js\"\nimport __vue_script__ from \"!!babel-loader!./avatar_list.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-4cea5bcf\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./avatar_list.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"avatars\"},_vm._l((_vm.slicedUsers),function(user){return _c('router-link',{key:user.id,staticClass:\"avatars-item\",attrs:{\"to\":_vm.userProfileLink(user)}},[_c('UserAvatar',{staticClass:\"avatar-small\",attrs:{\"user\":user}})],1)}),1)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import { find } from 'lodash'\n\nconst StatusPopover = {\n name: 'StatusPopover',\n props: [\n 'statusId'\n ],\n data () {\n return {\n popperOptions: {\n modifiers: {\n preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }\n }\n }\n }\n },\n computed: {\n status () {\n return find(this.$store.state.statuses.allStatuses, { id: this.statusId })\n }\n },\n components: {\n Status: () => import('../status/status.vue')\n },\n methods: {\n enter () {\n if (!this.status) {\n this.$store.dispatch('fetchStatus', this.statusId)\n }\n }\n }\n}\n\nexport default StatusPopover\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./status_popover.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./status_popover.js\"\nimport __vue_script__ from \"!!babel-loader!./status_popover.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-ef621460\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./status_popover.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-popover',{attrs:{\"popover-class\":\"status-popover\",\"placement\":\"top-start\",\"popper-options\":_vm.popperOptions},on:{\"show\":function($event){_vm.enter()}}},[_c('template',{slot:\"popover\"},[(_vm.status)?_c('Status',{attrs:{\"is-preview\":true,\"statusoid\":_vm.status,\"compact\":true}}):_c('div',{staticClass:\"status-preview-loading\"},[_c('i',{staticClass:\"icon-spin4 animate-spin\"})])],1),_vm._v(\" \"),_vm._t(\"default\")],2)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import Attachment from '../attachment/attachment.vue'\nimport FavoriteButton from '../favorite_button/favorite_button.vue'\nimport RetweetButton from '../retweet_button/retweet_button.vue'\nimport Poll from '../poll/poll.vue'\nimport ExtraButtons from '../extra_buttons/extra_buttons.vue'\nimport PostStatusForm from '../post_status_form/post_status_form.vue'\nimport UserCard from '../user_card/user_card.vue'\nimport UserAvatar from '../user_avatar/user_avatar.vue'\nimport Gallery from '../gallery/gallery.vue'\nimport LinkPreview from '../link-preview/link-preview.vue'\nimport AvatarList from '../avatar_list/avatar_list.vue'\nimport Timeago from '../timeago/timeago.vue'\nimport StatusPopover from '../status_popover/status_popover.vue'\nimport generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'\nimport fileType from 'src/services/file_type/file_type.service'\nimport { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'\nimport { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'\nimport { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'\nimport { filter, unescape, uniqBy } from 'lodash'\nimport { mapGetters, mapState } from 'vuex'\n\nconst Status = {\n name: 'Status',\n props: [\n 'statusoid',\n 'expandable',\n 'inConversation',\n 'focused',\n 'highlight',\n 'compact',\n 'replies',\n 'isPreview',\n 'noHeading',\n 'inlineExpanded',\n 'showPinned',\n 'inProfile',\n 'profileUserId'\n ],\n data () {\n return {\n replying: false,\n unmuted: false,\n userExpanded: false,\n showingTall: this.inConversation && this.focused,\n showingLongSubject: false,\n error: null,\n // not as computed because it sets the initial state which will be changed later\n expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject\n }\n },\n computed: {\n localCollapseSubjectDefault () {\n return this.mergedConfig.collapseMessageWithSubject\n },\n muteWords () {\n return this.mergedConfig.muteWords\n },\n repeaterClass () {\n const user = this.statusoid.user\n return highlightClass(user)\n },\n userClass () {\n const user = this.retweet ? (this.statusoid.retweeted_status.user) : this.statusoid.user\n return highlightClass(user)\n },\n deleted () {\n return this.statusoid.deleted\n },\n repeaterStyle () {\n const user = this.statusoid.user\n const highlight = this.mergedConfig.highlight\n return highlightStyle(highlight[user.screen_name])\n },\n userStyle () {\n if (this.noHeading) return\n const user = this.retweet ? (this.statusoid.retweeted_status.user) : this.statusoid.user\n const highlight = this.mergedConfig.highlight\n return highlightStyle(highlight[user.screen_name])\n },\n hideAttachments () {\n return (this.mergedConfig.hideAttachments && !this.inConversation) ||\n (this.mergedConfig.hideAttachmentsInConv && this.inConversation)\n },\n userProfileLink () {\n return this.generateUserProfileLink(this.status.user.id, this.status.user.screen_name)\n },\n replyProfileLink () {\n if (this.isReply) {\n return this.generateUserProfileLink(this.status.in_reply_to_user_id, this.replyToName)\n }\n },\n retweet () { return !!this.statusoid.retweeted_status },\n retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name },\n retweeterHtml () { return this.statusoid.user.name_html },\n retweeterProfileLink () { return this.generateUserProfileLink(this.statusoid.user.id, this.statusoid.user.screen_name) },\n status () {\n if (this.retweet) {\n return this.statusoid.retweeted_status\n } else {\n return this.statusoid\n }\n },\n statusFromGlobalRepository () {\n // NOTE: Consider to replace status with statusFromGlobalRepository\n return this.$store.state.statuses.allStatusesObject[this.status.id]\n },\n loggedIn () {\n return !!this.currentUser\n },\n muteWordHits () {\n const statusText = this.status.text.toLowerCase()\n const statusSummary = this.status.summary.toLowerCase()\n const hits = filter(this.muteWords, (muteWord) => {\n return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())\n })\n\n return hits\n },\n muted () { return !this.unmuted && ((!(this.inProfile && this.status.user.id === this.profileUserId) && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },\n hideFilteredStatuses () {\n return this.mergedConfig.hideFilteredStatuses\n },\n hideStatus () {\n return (this.hideReply || this.deleted) || (this.muted && this.hideFilteredStatuses)\n },\n isFocused () {\n // retweet or root of an expanded conversation\n if (this.focused) {\n return true\n } else if (!this.inConversation) {\n return false\n }\n // use conversation highlight only when in conversation\n return this.status.id === this.highlight\n },\n // This is a bit hacky, but we want to approximate post height before rendering\n // so we count newlines (masto uses

for paragraphs, GS uses
between them)\n // as well as approximate line count by counting characters and approximating ~80\n // per line.\n //\n // Using max-height + overflow: auto for status components resulted in false positives\n // very often with japanese characters, and it was very annoying.\n tallStatus () {\n const lengthScore = this.status.statusnet_html.split(/ 20\n },\n longSubject () {\n return this.status.summary.length > 900\n },\n isReply () {\n return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id)\n },\n replyToName () {\n if (this.status.in_reply_to_screen_name) {\n return this.status.in_reply_to_screen_name\n } else {\n const user = this.$store.getters.findUser(this.status.in_reply_to_user_id)\n return user && user.screen_name\n }\n },\n hideReply () {\n if (this.mergedConfig.replyVisibility === 'all') {\n return false\n }\n if (this.inConversation || !this.isReply) {\n return false\n }\n if (this.status.user.id === this.currentUser.id) {\n return false\n }\n if (this.status.type === 'retweet') {\n return false\n }\n const checkFollowing = this.mergedConfig.replyVisibility === 'following'\n for (var i = 0; i < this.status.attentions.length; ++i) {\n if (this.status.user.id === this.status.attentions[i].id) {\n continue\n }\n const taggedUser = this.$store.getters.findUser(this.status.attentions[i].id)\n if (checkFollowing && taggedUser && taggedUser.following) {\n return false\n }\n if (this.status.attentions[i].id === this.currentUser.id) {\n return false\n }\n }\n return this.status.attentions.length > 0\n },\n hideSubjectStatus () {\n if (this.tallStatus && !this.localCollapseSubjectDefault) {\n return false\n }\n return !this.expandingSubject && this.status.summary\n },\n hideTallStatus () {\n if (this.status.summary && this.localCollapseSubjectDefault) {\n return false\n }\n if (this.showingTall) {\n return false\n }\n return this.tallStatus\n },\n showingMore () {\n return (this.tallStatus && this.showingTall) || (this.status.summary && this.expandingSubject)\n },\n nsfwClickthrough () {\n if (!this.status.nsfw) {\n return false\n }\n if (this.status.summary && this.localCollapseSubjectDefault) {\n return false\n }\n return true\n },\n replySubject () {\n if (!this.status.summary) return ''\n const decodedSummary = unescape(this.status.summary)\n const behavior = this.mergedConfig.subjectLineBehavior\n const startsWithRe = decodedSummary.match(/^re[: ]/i)\n if ((behavior !== 'noop' && startsWithRe) || behavior === 'masto') {\n return decodedSummary\n } else if (behavior === 'email') {\n return 're: '.concat(decodedSummary)\n } else if (behavior === 'noop') {\n return ''\n }\n },\n attachmentSize () {\n if ((this.mergedConfig.hideAttachments && !this.inConversation) ||\n (this.mergedConfig.hideAttachmentsInConv && this.inConversation) ||\n (this.status.attachments.length > this.maxThumbnails)) {\n return 'hide'\n } else if (this.compact) {\n return 'small'\n }\n return 'normal'\n },\n galleryTypes () {\n if (this.attachmentSize === 'hide') {\n return []\n }\n return this.mergedConfig.playVideosInModal\n ? ['image', 'video']\n : ['image']\n },\n galleryAttachments () {\n return this.status.attachments.filter(\n file => fileType.fileMatchesSomeType(this.galleryTypes, file)\n )\n },\n nonGalleryAttachments () {\n return this.status.attachments.filter(\n file => !fileType.fileMatchesSomeType(this.galleryTypes, file)\n )\n },\n maxThumbnails () {\n return this.mergedConfig.maxThumbnails\n },\n postBodyHtml () {\n const html = this.status.statusnet_html\n\n if (this.mergedConfig.greentext) {\n try {\n if (html.includes('>')) {\n // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works\n return processHtml(html, (string) => {\n if (string.includes('>') &&\n string\n .replace(/<[^>]+?>/gi, '') // remove all tags\n .replace(/@\\w+/gi, '') // remove mentions (even failed ones)\n .trim()\n .startsWith('>')) {\n return `${string}`\n } else {\n return string\n }\n })\n } else {\n return html\n }\n } catch (e) {\n console.err('Failed to process status html', e)\n return html\n }\n } else {\n return html\n }\n },\n contentHtml () {\n if (!this.status.summary_html) {\n return this.postBodyHtml\n }\n return this.status.summary_html + '
' + this.postBodyHtml\n },\n combinedFavsAndRepeatsUsers () {\n // Use the status from the global status repository since favs and repeats are saved in it\n const combinedUsers = [].concat(\n this.statusFromGlobalRepository.favoritedBy,\n this.statusFromGlobalRepository.rebloggedBy\n )\n return uniqBy(combinedUsers, 'id')\n },\n ownStatus () {\n return this.status.user.id === this.currentUser.id\n },\n tags () {\n return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')\n },\n hidePostStats () {\n return this.mergedConfig.hidePostStats\n },\n ...mapGetters(['mergedConfig']),\n ...mapState({\n betterShadow: state => state.interface.browserSupport.cssFilter,\n currentUser: state => state.users.currentUser\n })\n },\n components: {\n Attachment,\n FavoriteButton,\n RetweetButton,\n ExtraButtons,\n PostStatusForm,\n Poll,\n UserCard,\n UserAvatar,\n Gallery,\n LinkPreview,\n AvatarList,\n Timeago,\n StatusPopover\n },\n methods: {\n visibilityIcon (visibility) {\n switch (visibility) {\n case 'private':\n return 'icon-lock'\n case 'unlisted':\n return 'icon-lock-open-alt'\n case 'direct':\n return 'icon-mail-alt'\n default:\n return 'icon-globe'\n }\n },\n showError (error) {\n this.error = error\n },\n clearError () {\n this.error = undefined\n },\n linkClicked (event) {\n const target = event.target.closest('.status-content a')\n if (target) {\n if (target.className.match(/mention/)) {\n const href = target.href\n const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href))\n if (attn) {\n event.stopPropagation()\n event.preventDefault()\n const link = this.generateUserProfileLink(attn.id, attn.screen_name)\n this.$router.push(link)\n return\n }\n }\n if (target.rel.match(/(?:^|\\s)tag(?:$|\\s)/) || target.className.match(/hashtag/)) {\n // Extract tag name from link url\n const tag = extractTagFromUrl(target.href)\n if (tag) {\n const link = this.generateTagLink(tag)\n this.$router.push(link)\n return\n }\n }\n window.open(target.href, '_blank')\n }\n },\n toggleReplying () {\n this.replying = !this.replying\n },\n gotoOriginal (id) {\n if (this.inConversation) {\n this.$emit('goto', id)\n }\n },\n toggleExpanded () {\n this.$emit('toggleExpanded')\n },\n toggleMute () {\n this.unmuted = !this.unmuted\n },\n toggleUserExpanded () {\n this.userExpanded = !this.userExpanded\n },\n toggleShowMore () {\n if (this.showingTall) {\n this.showingTall = false\n } else if (this.expandingSubject && this.status.summary) {\n this.expandingSubject = false\n } else if (this.hideTallStatus) {\n this.showingTall = true\n } else if (this.hideSubjectStatus && this.status.summary) {\n this.expandingSubject = true\n }\n },\n generateUserProfileLink (id, name) {\n return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)\n },\n generateTagLink (tag) {\n return `/tag/${tag}`\n },\n setMedia () {\n const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments\n return () => this.$store.dispatch('setMedia', attachments)\n }\n },\n watch: {\n 'highlight': function (id) {\n if (this.status.id === id) {\n let rect = this.$el.getBoundingClientRect()\n if (rect.top < 100) {\n // Post is above screen, match its top to screen top\n window.scrollBy(0, rect.top - 100)\n } else if (rect.height >= (window.innerHeight - 50)) {\n // Post we want to see is taller than screen so match its top to screen top\n window.scrollBy(0, rect.top - 100)\n } else if (rect.bottom > window.innerHeight - 50) {\n // Post is below screen, match its bottom to screen bottom\n window.scrollBy(0, rect.bottom - window.innerHeight + 50)\n }\n }\n },\n 'status.repeat_num': function (num) {\n // refetch repeats when repeat_num is changed in any way\n if (this.isFocused && this.statusFromGlobalRepository.rebloggedBy && this.statusFromGlobalRepository.rebloggedBy.length !== num) {\n this.$store.dispatch('fetchRepeats', this.status.id)\n }\n },\n 'status.fave_num': function (num) {\n // refetch favs when fave_num is changed in any way\n if (this.isFocused && this.statusFromGlobalRepository.favoritedBy && this.statusFromGlobalRepository.favoritedBy.length !== num) {\n this.$store.dispatch('fetchFavs', this.status.id)\n }\n }\n },\n filters: {\n capitalize: function (str) {\n return str.charAt(0).toUpperCase() + str.slice(1)\n }\n }\n}\n\nexport default Status\n","/**\n * This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and\n * allows it to be processed, useful for greentexting, mostly\n *\n * known issue: doesn't handle CDATA so nested CDATA might not work well\n *\n * @param {Object} input - input data\n * @param {(string) => string} processor - function that will be called on every line\n * @return {string} processed html\n */\nexport const processHtml = (html, processor) => {\n const handledTags = new Set(['p', 'br', 'div'])\n const openCloseTags = new Set(['p', 'div'])\n\n let buffer = '' // Current output buffer\n const level = [] // How deep we are in tags and which tags were there\n let textBuffer = '' // Current line content\n let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag\n\n // Extracts tag name from tag, i.e. => span\n const getTagName = (tag) => {\n const result = /(?:<\\/(\\w+)>|<(\\w+)\\s?[^/]*?\\/?>)/gi.exec(tag)\n return result && (result[1] || result[2])\n }\n\n const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer\n if (textBuffer.trim().length > 0) {\n buffer += processor(textBuffer)\n } else {\n buffer += textBuffer\n }\n textBuffer = ''\n }\n\n const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing\n flush()\n buffer += tag\n }\n\n const handleOpen = (tag) => { // handles opening tags\n flush()\n buffer += tag\n level.push(tag)\n }\n\n const handleClose = (tag) => { // handles closing tags\n flush()\n buffer += tag\n if (level[level.length - 1] === tag) {\n level.pop()\n }\n }\n\n for (let i = 0; i < html.length; i++) {\n const char = html[i]\n if (char === '<' && tagBuffer === null) {\n tagBuffer = char\n } else if (char !== '>' && tagBuffer !== null) {\n tagBuffer += char\n } else if (char === '>' && tagBuffer !== null) {\n tagBuffer += char\n const tagFull = tagBuffer\n tagBuffer = null\n const tagName = getTagName(tagFull)\n if (handledTags.has(tagName)) {\n if (tagName === 'br') {\n handleBr(tagFull)\n } else if (openCloseTags.has(tagName)) {\n if (tagFull[1] === '/') {\n handleClose(tagFull)\n } else if (tagFull[tagFull.length - 2] === '/') {\n // self-closing\n handleBr(tagFull)\n } else {\n handleOpen(tagFull)\n }\n }\n } else {\n textBuffer += tagFull\n }\n } else if (char === '\\n') {\n handleBr(char)\n } else {\n textBuffer += char\n }\n }\n if (tagBuffer) {\n textBuffer += tagBuffer\n }\n\n flush()\n\n return buffer\n}\n","export const mentionMatchesUrl = (attention, url) => {\n if (url === attention.statusnet_profile_url) {\n return true\n }\n const [namepart, instancepart] = attention.screen_name.split('@')\n const matchstring = new RegExp('://' + instancepart + '/.*' + namepart + '$', 'g')\n\n return !!url.match(matchstring)\n}\n\n/**\n * Extract tag name from pleroma or mastodon url.\n * i.e https://bikeshed.party/tag/photo or https://quey.org/tags/sky\n * @param {string} url\n */\nexport const extractTagFromUrl = (url) => {\n const regex = /tag[s]*\\/(\\w+)$/g\n const result = regex.exec(url)\n if (!result) {\n return false\n }\n return result[1]\n}\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./status.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./status.js\"\nimport __vue_script__ from \"!!babel-loader!./status.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-f83de8e4\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./status.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (!_vm.hideStatus)?_c('div',{staticClass:\"status-el\",class:[{ 'status-el_focused': _vm.isFocused }, { 'status-conversation': _vm.inlineExpanded }]},[(_vm.error)?_c('div',{staticClass:\"alert error\"},[_vm._v(\"\\n \"+_vm._s(_vm.error)+\"\\n \"),_c('i',{staticClass:\"button-icon icon-cancel\",on:{\"click\":_vm.clearError}})]):_vm._e(),_vm._v(\" \"),(_vm.muted && !_vm.isPreview)?[_c('div',{staticClass:\"media status container muted\"},[_c('small',[_c('router-link',{attrs:{\"to\":_vm.userProfileLink}},[_vm._v(\"\\n \"+_vm._s(_vm.status.user.screen_name)+\"\\n \")])],1),_vm._v(\" \"),_c('small',{staticClass:\"muteWords\"},[_vm._v(_vm._s(_vm.muteWordHits.join(', ')))]),_vm._v(\" \"),_c('a',{staticClass:\"unmute\",attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleMute($event)}}},[_c('i',{staticClass:\"button-icon icon-eye-off\"})])])]:[(_vm.showPinned)?_c('div',{staticClass:\"status-pin\"},[_c('i',{staticClass:\"fa icon-pin faint\"}),_vm._v(\" \"),_c('span',{staticClass:\"faint\"},[_vm._v(_vm._s(_vm.$t('status.pinned')))])]):_vm._e(),_vm._v(\" \"),(_vm.retweet && !_vm.noHeading && !_vm.inConversation)?_c('div',{staticClass:\"media container retweet-info\",class:[_vm.repeaterClass, { highlighted: _vm.repeaterStyle }],style:([_vm.repeaterStyle])},[(_vm.retweet)?_c('UserAvatar',{staticClass:\"media-left\",attrs:{\"better-shadow\":_vm.betterShadow,\"user\":_vm.statusoid.user}}):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"media-body faint\"},[_c('span',{staticClass:\"user-name\"},[(_vm.retweeterHtml)?_c('router-link',{attrs:{\"to\":_vm.retweeterProfileLink},domProps:{\"innerHTML\":_vm._s(_vm.retweeterHtml)}}):_c('router-link',{attrs:{\"to\":_vm.retweeterProfileLink}},[_vm._v(_vm._s(_vm.retweeter))])],1),_vm._v(\" \"),_c('i',{staticClass:\"fa icon-retweet retweeted\",attrs:{\"title\":_vm.$t('tool_tip.repeat')}}),_vm._v(\"\\n \"+_vm._s(_vm.$t('timeline.repeated'))+\"\\n \")])],1):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"media status\",class:[_vm.userClass, { highlighted: _vm.userStyle, 'is-retweet': _vm.retweet && !_vm.inConversation }],style:([ _vm.userStyle ]),attrs:{\"data-tags\":_vm.tags}},[(!_vm.noHeading)?_c('div',{staticClass:\"media-left\"},[_c('router-link',{attrs:{\"to\":_vm.userProfileLink},nativeOn:{\"!click\":function($event){$event.stopPropagation();$event.preventDefault();return _vm.toggleUserExpanded($event)}}},[_c('UserAvatar',{attrs:{\"compact\":_vm.compact,\"better-shadow\":_vm.betterShadow,\"user\":_vm.status.user}})],1)],1):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"status-body\"},[(_vm.userExpanded)?_c('UserCard',{staticClass:\"status-usercard\",attrs:{\"user\":_vm.status.user,\"rounded\":true,\"bordered\":true}}):_vm._e(),_vm._v(\" \"),(!_vm.noHeading)?_c('div',{staticClass:\"media-heading\"},[_c('div',{staticClass:\"heading-name-row\"},[_c('div',{staticClass:\"name-and-account-name\"},[(_vm.status.user.name_html)?_c('h4',{staticClass:\"user-name\",domProps:{\"innerHTML\":_vm._s(_vm.status.user.name_html)}}):_c('h4',{staticClass:\"user-name\"},[_vm._v(\"\\n \"+_vm._s(_vm.status.user.name)+\"\\n \")]),_vm._v(\" \"),_c('router-link',{staticClass:\"account-name\",attrs:{\"to\":_vm.userProfileLink}},[_vm._v(\"\\n \"+_vm._s(_vm.status.user.screen_name)+\"\\n \")])],1),_vm._v(\" \"),_c('span',{staticClass:\"heading-right\"},[_c('router-link',{staticClass:\"timeago faint-link\",attrs:{\"to\":{ name: 'conversation', params: { id: _vm.status.id } }}},[_c('Timeago',{attrs:{\"time\":_vm.status.created_at,\"auto-update\":60}})],1),_vm._v(\" \"),(_vm.status.visibility)?_c('div',{staticClass:\"button-icon visibility-icon\"},[_c('i',{class:_vm.visibilityIcon(_vm.status.visibility),attrs:{\"title\":_vm._f(\"capitalize\")(_vm.status.visibility)}})]):_vm._e(),_vm._v(\" \"),(!_vm.status.is_local && !_vm.isPreview)?_c('a',{staticClass:\"source_url\",attrs:{\"href\":_vm.status.external_url,\"target\":\"_blank\",\"title\":\"Source\"}},[_c('i',{staticClass:\"button-icon icon-link-ext-alt\"})]):_vm._e(),_vm._v(\" \"),(_vm.expandable && !_vm.isPreview)?[_c('a',{attrs:{\"href\":\"#\",\"title\":\"Expand\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleExpanded($event)}}},[_c('i',{staticClass:\"button-icon icon-plus-squared\"})])]:_vm._e(),_vm._v(\" \"),(_vm.unmuted)?_c('a',{attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleMute($event)}}},[_c('i',{staticClass:\"button-icon icon-eye-off\"})]):_vm._e()],2)]),_vm._v(\" \"),_c('div',{staticClass:\"heading-reply-row\"},[(_vm.isReply)?_c('div',{staticClass:\"reply-to-and-accountname\"},[(!_vm.isPreview)?_c('StatusPopover',{attrs:{\"status-id\":_vm.status.in_reply_to_status_id}},[_c('a',{staticClass:\"reply-to\",attrs:{\"href\":\"#\",\"aria-label\":_vm.$t('tool_tip.reply')},on:{\"click\":function($event){$event.preventDefault();_vm.gotoOriginal(_vm.status.in_reply_to_status_id)}}},[_c('i',{staticClass:\"button-icon icon-reply\"}),_vm._v(\" \"),_c('span',{staticClass:\"faint-link reply-to-text\"},[_vm._v(_vm._s(_vm.$t('status.reply_to')))])])]):_c('span',{staticClass:\"reply-to\"},[_c('span',{staticClass:\"reply-to-text\"},[_vm._v(_vm._s(_vm.$t('status.reply_to')))])]),_vm._v(\" \"),_c('router-link',{attrs:{\"to\":_vm.replyProfileLink}},[_vm._v(\"\\n \"+_vm._s(_vm.replyToName)+\"\\n \")]),_vm._v(\" \"),(_vm.replies && _vm.replies.length)?_c('span',{staticClass:\"faint replies-separator\"},[_vm._v(\"\\n -\\n \")]):_vm._e()],1):_vm._e(),_vm._v(\" \"),(_vm.inConversation && !_vm.isPreview && _vm.replies && _vm.replies.length)?_c('div',{staticClass:\"replies\"},[_c('span',{staticClass:\"faint\"},[_vm._v(_vm._s(_vm.$t('status.replies_list')))]),_vm._v(\" \"),_vm._l((_vm.replies),function(reply){return _c('StatusPopover',{key:reply.id,attrs:{\"status-id\":reply.id}},[_c('a',{staticClass:\"reply-link\",attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();_vm.gotoOriginal(reply.id)}}},[_vm._v(_vm._s(reply.name))])])})],2):_vm._e()])]):_vm._e(),_vm._v(\" \"),(_vm.longSubject)?_c('div',{staticClass:\"status-content-wrapper\",class:{ 'tall-status': !_vm.showingLongSubject }},[(!_vm.showingLongSubject)?_c('a',{staticClass:\"tall-status-hider\",class:{ 'tall-status-hider_focused': _vm.isFocused },attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();_vm.showingLongSubject=true}}},[_vm._v(_vm._s(_vm.$t(\"general.show_more\")))]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"status-content media-body\",domProps:{\"innerHTML\":_vm._s(_vm.contentHtml)},on:{\"click\":function($event){$event.preventDefault();return _vm.linkClicked($event)}}}),_vm._v(\" \"),(_vm.showingLongSubject)?_c('a',{staticClass:\"status-unhider\",attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();_vm.showingLongSubject=false}}},[_vm._v(_vm._s(_vm.$t(\"general.show_less\")))]):_vm._e()]):_c('div',{staticClass:\"status-content-wrapper\",class:{'tall-status': _vm.hideTallStatus}},[(_vm.hideTallStatus)?_c('a',{staticClass:\"tall-status-hider\",class:{ 'tall-status-hider_focused': _vm.isFocused },attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleShowMore($event)}}},[_vm._v(_vm._s(_vm.$t(\"general.show_more\")))]):_vm._e(),_vm._v(\" \"),(!_vm.hideSubjectStatus)?_c('div',{staticClass:\"status-content media-body\",domProps:{\"innerHTML\":_vm._s(_vm.contentHtml)},on:{\"click\":function($event){$event.preventDefault();return _vm.linkClicked($event)}}}):_c('div',{staticClass:\"status-content media-body\",domProps:{\"innerHTML\":_vm._s(_vm.status.summary_html)},on:{\"click\":function($event){$event.preventDefault();return _vm.linkClicked($event)}}}),_vm._v(\" \"),(_vm.hideSubjectStatus)?_c('a',{staticClass:\"cw-status-hider\",attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleShowMore($event)}}},[_vm._v(_vm._s(_vm.$t(\"general.show_more\")))]):_vm._e(),_vm._v(\" \"),(_vm.showingMore)?_c('a',{staticClass:\"status-unhider\",attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleShowMore($event)}}},[_vm._v(_vm._s(_vm.$t(\"general.show_less\")))]):_vm._e()]),_vm._v(\" \"),(_vm.status.poll && _vm.status.poll.options)?_c('div',[_c('poll',{attrs:{\"base-poll\":_vm.status.poll}})],1):_vm._e(),_vm._v(\" \"),(_vm.status.attachments && (!_vm.hideSubjectStatus || _vm.showingLongSubject))?_c('div',{staticClass:\"attachments media-body\"},[_vm._l((_vm.nonGalleryAttachments),function(attachment){return _c('attachment',{key:attachment.id,staticClass:\"non-gallery\",attrs:{\"size\":_vm.attachmentSize,\"nsfw\":_vm.nsfwClickthrough,\"attachment\":attachment,\"allow-play\":true,\"set-media\":_vm.setMedia()}})}),_vm._v(\" \"),(_vm.galleryAttachments.length > 0)?_c('gallery',{attrs:{\"nsfw\":_vm.nsfwClickthrough,\"attachments\":_vm.galleryAttachments,\"set-media\":_vm.setMedia()}}):_vm._e()],2):_vm._e(),_vm._v(\" \"),(_vm.status.card && !_vm.hideSubjectStatus && !_vm.noHeading)?_c('div',{staticClass:\"link-preview media-body\"},[_c('link-preview',{attrs:{\"card\":_vm.status.card,\"size\":_vm.attachmentSize,\"nsfw\":_vm.nsfwClickthrough}})],1):_vm._e(),_vm._v(\" \"),_c('transition',{attrs:{\"name\":\"fade\"}},[(!_vm.hidePostStats && _vm.isFocused && _vm.combinedFavsAndRepeatsUsers.length > 0)?_c('div',{staticClass:\"favs-repeated-users\"},[_c('div',{staticClass:\"stats\"},[(_vm.statusFromGlobalRepository.rebloggedBy && _vm.statusFromGlobalRepository.rebloggedBy.length > 0)?_c('div',{staticClass:\"stat-count\"},[_c('a',{staticClass:\"stat-title\"},[_vm._v(_vm._s(_vm.$t('status.repeats')))]),_vm._v(\" \"),_c('div',{staticClass:\"stat-number\"},[_vm._v(\"\\n \"+_vm._s(_vm.statusFromGlobalRepository.rebloggedBy.length)+\"\\n \")])]):_vm._e(),_vm._v(\" \"),(_vm.statusFromGlobalRepository.favoritedBy && _vm.statusFromGlobalRepository.favoritedBy.length > 0)?_c('div',{staticClass:\"stat-count\"},[_c('a',{staticClass:\"stat-title\"},[_vm._v(_vm._s(_vm.$t('status.favorites')))]),_vm._v(\" \"),_c('div',{staticClass:\"stat-number\"},[_vm._v(\"\\n \"+_vm._s(_vm.statusFromGlobalRepository.favoritedBy.length)+\"\\n \")])]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"avatar-row\"},[_c('AvatarList',{attrs:{\"users\":_vm.combinedFavsAndRepeatsUsers}})],1)])]):_vm._e()]),_vm._v(\" \"),(!_vm.noHeading && !_vm.isPreview)?_c('div',{staticClass:\"status-actions media-body\"},[_c('div',[(_vm.loggedIn)?_c('i',{staticClass:\"button-icon icon-reply\",class:{'button-icon-active': _vm.replying},attrs:{\"title\":_vm.$t('tool_tip.reply')},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleReplying($event)}}}):_c('i',{staticClass:\"button-icon button-icon-disabled icon-reply\",attrs:{\"title\":_vm.$t('tool_tip.reply')}}),_vm._v(\" \"),(_vm.status.replies_count > 0)?_c('span',[_vm._v(_vm._s(_vm.status.replies_count))]):_vm._e()]),_vm._v(\" \"),_c('retweet-button',{attrs:{\"visibility\":_vm.status.visibility,\"logged-in\":_vm.loggedIn,\"status\":_vm.status}}),_vm._v(\" \"),_c('favorite-button',{attrs:{\"logged-in\":_vm.loggedIn,\"status\":_vm.status}}),_vm._v(\" \"),_c('extra-buttons',{attrs:{\"status\":_vm.status},on:{\"onError\":_vm.showError,\"onSuccess\":_vm.clearError}})],1):_vm._e()],1)]),_vm._v(\" \"),(_vm.replying)?_c('div',{staticClass:\"container\"},[_c('PostStatusForm',{staticClass:\"reply-body\",attrs:{\"reply-to\":_vm.status.id,\"attentions\":_vm.status.attentions,\"replied-user\":_vm.status.user,\"copy-message-scope\":_vm.status.visibility,\"subject\":_vm.replySubject},on:{\"posted\":_vm.toggleReplying}})],1):_vm._e()]],2):_vm._e()}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import StillImage from '../still-image/still-image.vue'\n\nconst UserAvatar = {\n props: [\n 'user',\n 'betterShadow',\n 'compact'\n ],\n data () {\n return {\n showPlaceholder: false\n }\n },\n components: {\n StillImage\n },\n computed: {\n imgSrc () {\n return this.showPlaceholder ? '/images/avi.png' : this.user.profile_image_url_original\n }\n },\n methods: {\n imageLoadError () {\n this.showPlaceholder = true\n }\n },\n watch: {\n src () {\n this.showPlaceholder = false\n }\n }\n}\n\nexport default UserAvatar\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./user_avatar.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./user_avatar.js\"\nimport __vue_script__ from \"!!babel-loader!./user_avatar.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-056a5e34\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./user_avatar.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('StillImage',{staticClass:\"avatar\",class:{ 'avatar-compact': _vm.compact, 'better-shadow': _vm.betterShadow },attrs:{\"alt\":_vm.user.screen_name,\"title\":_vm.user.screen_name,\"src\":_vm.imgSrc,\"image-load-error\":_vm.imageLoadError}})}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","export const SECOND = 1000\nexport const MINUTE = 60 * SECOND\nexport const HOUR = 60 * MINUTE\nexport const DAY = 24 * HOUR\nexport const WEEK = 7 * DAY\nexport const MONTH = 30 * DAY\nexport const YEAR = 365.25 * DAY\n\nexport const relativeTime = (date, nowThreshold = 1) => {\n if (typeof date === 'string') date = Date.parse(date)\n const round = Date.now() > date ? Math.floor : Math.ceil\n const d = Math.abs(Date.now() - date)\n let r = { num: round(d / YEAR), key: 'time.years' }\n if (d < nowThreshold * SECOND) {\n r.num = 0\n r.key = 'time.now'\n } else if (d < MINUTE) {\n r.num = round(d / SECOND)\n r.key = 'time.seconds'\n } else if (d < HOUR) {\n r.num = round(d / MINUTE)\n r.key = 'time.minutes'\n } else if (d < DAY) {\n r.num = round(d / HOUR)\n r.key = 'time.hours'\n } else if (d < WEEK) {\n r.num = round(d / DAY)\n r.key = 'time.days'\n } else if (d < MONTH) {\n r.num = round(d / WEEK)\n r.key = 'time.weeks'\n } else if (d < YEAR) {\n r.num = round(d / MONTH)\n r.key = 'time.months'\n }\n // Remove plural form when singular\n if (r.num === 1) r.key = r.key.slice(0, -1)\n return r\n}\n\nexport const relativeTimeShort = (date, nowThreshold = 1) => {\n const r = relativeTime(date, nowThreshold)\n r.key += '_short'\n return r\n}\n","import { hex2rgb } from '../color_convert/color_convert.js'\nconst highlightStyle = (prefs) => {\n if (prefs === undefined) return\n const { color, type } = prefs\n if (typeof color !== 'string') return\n const rgb = hex2rgb(color)\n if (rgb == null) return\n const solidColor = `rgb(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)})`\n const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .1)`\n const tintColor2 = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .2)`\n if (type === 'striped') {\n return {\n backgroundImage: [\n 'repeating-linear-gradient(135deg,',\n `${tintColor} ,`,\n `${tintColor} 20px,`,\n `${tintColor2} 20px,`,\n `${tintColor2} 40px`\n ].join(' '),\n backgroundPosition: '0 0'\n }\n } else if (type === 'solid') {\n return {\n backgroundColor: tintColor2\n }\n } else if (type === 'side') {\n return {\n backgroundImage: [\n 'linear-gradient(to right,',\n `${solidColor} ,`,\n `${solidColor} 2px,`,\n `transparent 6px`\n ].join(' '),\n backgroundPosition: '0 0'\n }\n }\n}\n\nconst highlightClass = (user) => {\n return 'USER____' + user.screen_name\n .replace(/\\./g, '_')\n .replace(/@/g, '_AT_')\n}\n\nexport {\n highlightClass,\n highlightStyle\n}\n","import Vue from 'vue'\n\nimport './tab_switcher.scss'\n\nexport default Vue.component('tab-switcher', {\n name: 'TabSwitcher',\n props: {\n renderOnlyFocused: {\n required: false,\n type: Boolean,\n default: false\n },\n onSwitch: {\n required: false,\n type: Function,\n default: undefined\n },\n activeTab: {\n required: false,\n type: String,\n default: undefined\n },\n scrollableTabs: {\n required: false,\n type: Boolean,\n default: false\n }\n },\n data () {\n return {\n active: this.$slots.default.findIndex(_ => _.tag)\n }\n },\n computed: {\n activeIndex () {\n // In case of controlled component\n if (this.activeTab) {\n return this.$slots.default.findIndex(slot => this.activeTab === slot.key)\n } else {\n return this.active\n }\n }\n },\n beforeUpdate () {\n const currentSlot = this.$slots.default[this.active]\n if (!currentSlot.tag) {\n this.active = this.$slots.default.findIndex(_ => _.tag)\n }\n },\n methods: {\n activateTab (index) {\n return (e) => {\n e.preventDefault()\n if (typeof this.onSwitch === 'function') {\n this.onSwitch.call(null, this.$slots.default[index].key)\n }\n this.active = index\n }\n }\n },\n render (h) {\n const tabs = this.$slots.default\n .map((slot, index) => {\n if (!slot.tag) return\n const classesTab = ['tab']\n const classesWrapper = ['tab-wrapper']\n\n if (this.activeIndex === index) {\n classesTab.push('active')\n classesWrapper.push('active')\n }\n if (slot.data.attrs.image) {\n return (\n

\n \n \n {slot.data.attrs.label ? '' : slot.data.attrs.label}\n \n
\n )\n }\n return (\n
\n \n {slot.data.attrs.label}\n
\n )\n })\n\n const contents = this.$slots.default.map((slot, index) => {\n if (!slot.tag) return\n const active = this.activeIndex === index\n if (this.renderOnlyFocused) {\n return active\n ?
{slot}
\n :
\n }\n return
{slot}
\n })\n\n return (\n
\n
\n {tabs}\n
\n
\n {contents}\n
\n
\n )\n }\n})\n","/* eslint-env browser */\nimport statusPosterService from '../../services/status_poster/status_poster.service.js'\nimport fileSizeFormatService from '../../services/file_size_format/file_size_format.js'\n\nconst mediaUpload = {\n data () {\n return {\n uploading: false,\n uploadReady: true\n }\n },\n methods: {\n uploadFile (file) {\n const self = this\n const store = this.$store\n if (file.size > store.state.instance.uploadlimit) {\n const filesize = fileSizeFormatService.fileSizeFormat(file.size)\n const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit)\n self.$emit('upload-failed', 'file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })\n return\n }\n const formData = new FormData()\n formData.append('file', file)\n\n self.$emit('uploading')\n self.uploading = true\n\n statusPosterService.uploadMedia({ store, formData })\n .then((fileData) => {\n self.$emit('uploaded', fileData)\n self.uploading = false\n }, (error) => { // eslint-disable-line handle-callback-err\n self.$emit('upload-failed', 'default')\n self.uploading = false\n })\n },\n fileDrop (e) {\n if (e.dataTransfer.files.length > 0) {\n e.preventDefault() // allow dropping text like before\n this.uploadFile(e.dataTransfer.files[0])\n }\n },\n fileDrag (e) {\n let types = e.dataTransfer.types\n if (types.contains('Files')) {\n e.dataTransfer.dropEffect = 'copy'\n } else {\n e.dataTransfer.dropEffect = 'none'\n }\n },\n clearFile () {\n this.uploadReady = false\n this.$nextTick(() => {\n this.uploadReady = true\n })\n },\n change ({ target }) {\n for (var i = 0; i < target.files.length; i++) {\n let file = target.files[i]\n this.uploadFile(file)\n }\n }\n },\n props: [\n 'dropFiles'\n ],\n watch: {\n 'dropFiles': function (fileInfos) {\n if (!this.uploading) {\n this.uploadFile(fileInfos[0])\n }\n }\n }\n}\n\nexport default mediaUpload\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./media_upload.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./media_upload.js\"\nimport __vue_script__ from \"!!babel-loader!./media_upload.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-74382032\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./media_upload.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"media-upload\",on:{\"drop\":[function($event){$event.preventDefault();},_vm.fileDrop],\"dragover\":function($event){$event.preventDefault();return _vm.fileDrag($event)}}},[_c('label',{staticClass:\"label\",attrs:{\"title\":_vm.$t('tool_tip.media_upload')}},[(_vm.uploading)?_c('i',{staticClass:\"progress-icon icon-spin4 animate-spin\"}):_vm._e(),_vm._v(\" \"),(!_vm.uploading)?_c('i',{staticClass:\"new-icon icon-upload\"}):_vm._e(),_vm._v(\" \"),(_vm.uploadReady)?_c('input',{staticStyle:{\"position\":\"fixed\",\"top\":\"-100em\"},attrs:{\"type\":\"file\",\"multiple\":\"true\"},on:{\"change\":_vm.change}}):_vm._e()])])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import * as DateUtils from 'src/services/date_utils/date_utils.js'\nimport { uniq } from 'lodash'\n\nexport default {\n name: 'PollForm',\n props: ['visible'],\n data: () => ({\n pollType: 'single',\n options: ['', ''],\n expiryAmount: 10,\n expiryUnit: 'minutes'\n }),\n computed: {\n pollLimits () {\n return this.$store.state.instance.pollLimits\n },\n maxOptions () {\n return this.pollLimits.max_options\n },\n maxLength () {\n return this.pollLimits.max_option_chars\n },\n expiryUnits () {\n const allUnits = ['minutes', 'hours', 'days']\n const expiry = this.convertExpiryFromUnit\n return allUnits.filter(\n unit => this.pollLimits.max_expiration >= expiry(unit, 1)\n )\n },\n minExpirationInCurrentUnit () {\n return Math.ceil(\n this.convertExpiryToUnit(\n this.expiryUnit,\n this.pollLimits.min_expiration\n )\n )\n },\n maxExpirationInCurrentUnit () {\n return Math.floor(\n this.convertExpiryToUnit(\n this.expiryUnit,\n this.pollLimits.max_expiration\n )\n )\n }\n },\n methods: {\n clear () {\n this.pollType = 'single'\n this.options = ['', '']\n this.expiryAmount = 10\n this.expiryUnit = 'minutes'\n },\n nextOption (index) {\n const element = this.$el.querySelector(`#poll-${index + 1}`)\n if (element) {\n element.focus()\n } else {\n // Try adding an option and try focusing on it\n const addedOption = this.addOption()\n if (addedOption) {\n this.$nextTick(function () {\n this.nextOption(index)\n })\n }\n }\n },\n addOption () {\n if (this.options.length < this.maxOptions) {\n this.options.push('')\n return true\n }\n return false\n },\n deleteOption (index, event) {\n if (this.options.length > 2) {\n this.options.splice(index, 1)\n }\n },\n convertExpiryToUnit (unit, amount) {\n // Note: we want seconds and not milliseconds\n switch (unit) {\n case 'minutes': return (1000 * amount) / DateUtils.MINUTE\n case 'hours': return (1000 * amount) / DateUtils.HOUR\n case 'days': return (1000 * amount) / DateUtils.DAY\n }\n },\n convertExpiryFromUnit (unit, amount) {\n // Note: we want seconds and not milliseconds\n switch (unit) {\n case 'minutes': return 0.001 * amount * DateUtils.MINUTE\n case 'hours': return 0.001 * amount * DateUtils.HOUR\n case 'days': return 0.001 * amount * DateUtils.DAY\n }\n },\n expiryAmountChange () {\n this.expiryAmount =\n Math.max(this.minExpirationInCurrentUnit, this.expiryAmount)\n this.expiryAmount =\n Math.min(this.maxExpirationInCurrentUnit, this.expiryAmount)\n this.updatePollToParent()\n },\n updatePollToParent () {\n const expiresIn = this.convertExpiryFromUnit(\n this.expiryUnit,\n this.expiryAmount\n )\n\n const options = uniq(this.options.filter(option => option !== ''))\n if (options.length < 2) {\n this.$emit('update-poll', { error: this.$t('polls.not_enough_options') })\n return\n }\n this.$emit('update-poll', {\n options,\n multiple: this.pollType === 'multiple',\n expiresIn\n })\n }\n }\n}\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./poll_form.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./poll_form.js\"\nimport __vue_script__ from \"!!babel-loader!./poll_form.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-1f896331\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./poll_form.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.visible)?_c('div',{staticClass:\"poll-form\"},[_vm._l((_vm.options),function(option,index){return _c('div',{key:index,staticClass:\"poll-option\"},[_c('div',{staticClass:\"input-container\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.options[index]),expression:\"options[index]\"}],staticClass:\"poll-option-input\",attrs:{\"id\":(\"poll-\" + index),\"type\":\"text\",\"placeholder\":_vm.$t('polls.option'),\"maxlength\":_vm.maxLength},domProps:{\"value\":(_vm.options[index])},on:{\"change\":_vm.updatePollToParent,\"keydown\":function($event){if(!('button' in $event)&&_vm._k($event.keyCode,\"enter\",13,$event.key,\"Enter\")){ return null; }$event.stopPropagation();$event.preventDefault();_vm.nextOption(index)},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(_vm.options, index, $event.target.value)}}})]),_vm._v(\" \"),(_vm.options.length > 2)?_c('div',{staticClass:\"icon-container\"},[_c('i',{staticClass:\"icon-cancel\",on:{\"click\":function($event){_vm.deleteOption(index)}}})]):_vm._e()])}),_vm._v(\" \"),(_vm.options.length < _vm.maxOptions)?_c('a',{staticClass:\"add-option faint\",on:{\"click\":_vm.addOption}},[_c('i',{staticClass:\"icon-plus\"}),_vm._v(\"\\n \"+_vm._s(_vm.$t(\"polls.add_option\"))+\"\\n \")]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"poll-type-expiry\"},[_c('div',{staticClass:\"poll-type\",attrs:{\"title\":_vm.$t('polls.type')}},[_c('label',{staticClass:\"select\",attrs:{\"for\":\"poll-type-selector\"}},[_c('select',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.pollType),expression:\"pollType\"}],staticClass:\"select\",on:{\"change\":[function($event){var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = \"_value\" in o ? o._value : o.value;return val}); _vm.pollType=$event.target.multiple ? $$selectedVal : $$selectedVal[0]},_vm.updatePollToParent]}},[_c('option',{attrs:{\"value\":\"single\"}},[_vm._v(_vm._s(_vm.$t('polls.single_choice')))]),_vm._v(\" \"),_c('option',{attrs:{\"value\":\"multiple\"}},[_vm._v(_vm._s(_vm.$t('polls.multiple_choices')))])]),_vm._v(\" \"),_c('i',{staticClass:\"icon-down-open\"})])]),_vm._v(\" \"),_c('div',{staticClass:\"poll-expiry\",attrs:{\"title\":_vm.$t('polls.expiry')}},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.expiryAmount),expression:\"expiryAmount\"}],staticClass:\"expiry-amount hide-number-spinner\",attrs:{\"type\":\"number\",\"min\":_vm.minExpirationInCurrentUnit,\"max\":_vm.maxExpirationInCurrentUnit},domProps:{\"value\":(_vm.expiryAmount)},on:{\"change\":_vm.expiryAmountChange,\"input\":function($event){if($event.target.composing){ return; }_vm.expiryAmount=$event.target.value}}}),_vm._v(\" \"),_c('label',{staticClass:\"expiry-unit select\"},[_c('select',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.expiryUnit),expression:\"expiryUnit\"}],on:{\"change\":[function($event){var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = \"_value\" in o ? o._value : o.value;return val}); _vm.expiryUnit=$event.target.multiple ? $$selectedVal : $$selectedVal[0]},_vm.expiryAmountChange]}},_vm._l((_vm.expiryUnits),function(unit){return _c('option',{key:unit,domProps:{\"value\":unit}},[_vm._v(\"\\n \"+_vm._s(_vm.$t((\"time.\" + unit + \"_short\"), ['']))+\"\\n \")])}),0),_vm._v(\" \"),_c('i',{staticClass:\"icon-down-open\"})])])])],2):_vm._e()}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import statusPoster from '../../services/status_poster/status_poster.service.js'\nimport MediaUpload from '../media_upload/media_upload.vue'\nimport ScopeSelector from '../scope_selector/scope_selector.vue'\nimport EmojiInput from '../emoji_input/emoji_input.vue'\nimport PollForm from '../poll/poll_form.vue'\nimport fileTypeService from '../../services/file_type/file_type.service.js'\nimport { findOffset } from '../../services/offset_finder/offset_finder.service.js'\nimport { reject, map, uniqBy } from 'lodash'\nimport suggestor from '../emoji_input/suggestor.js'\nimport { mapGetters } from 'vuex'\nimport Checkbox from '../checkbox/checkbox.vue'\n\nconst buildMentionsString = ({ user, attentions = [] }, currentUser) => {\n let allAttentions = [...attentions]\n\n allAttentions.unshift(user)\n\n allAttentions = uniqBy(allAttentions, 'id')\n allAttentions = reject(allAttentions, { id: currentUser.id })\n\n let mentions = map(allAttentions, (attention) => {\n return `@${attention.screen_name}`\n })\n\n return mentions.length > 0 ? mentions.join(' ') + ' ' : ''\n}\n\nconst PostStatusForm = {\n props: [\n 'replyTo',\n 'repliedUser',\n 'attentions',\n 'copyMessageScope',\n 'subject'\n ],\n components: {\n MediaUpload,\n EmojiInput,\n PollForm,\n ScopeSelector,\n Checkbox\n },\n mounted () {\n this.resize(this.$refs.textarea)\n const textLength = this.$refs.textarea.value.length\n this.$refs.textarea.setSelectionRange(textLength, textLength)\n\n if (this.replyTo) {\n this.$refs.textarea.focus()\n }\n },\n data () {\n const preset = this.$route.query.message\n let statusText = preset || ''\n\n const { scopeCopy } = this.$store.getters.mergedConfig\n\n if (this.replyTo) {\n const currentUser = this.$store.state.users.currentUser\n statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)\n }\n\n const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct')\n ? this.copyMessageScope\n : this.$store.state.users.currentUser.default_scope\n\n const { postContentType: contentType } = this.$store.getters.mergedConfig\n\n return {\n dropFiles: [],\n submitDisabled: false,\n error: null,\n posting: false,\n highlighted: 0,\n newStatus: {\n spoilerText: this.subject || '',\n status: statusText,\n nsfw: false,\n files: [],\n poll: {},\n visibility: scope,\n contentType\n },\n caret: 0,\n pollFormVisible: false\n }\n },\n computed: {\n users () {\n return this.$store.state.users.users\n },\n userDefaultScope () {\n return this.$store.state.users.currentUser.default_scope\n },\n showAllScopes () {\n return !this.mergedConfig.minimalScopesMode\n },\n emojiUserSuggestor () {\n return suggestor({\n emoji: [\n ...this.$store.state.instance.emoji,\n ...this.$store.state.instance.customEmoji\n ],\n users: this.$store.state.users.users,\n updateUsersList: (input) => this.$store.dispatch('searchUsers', input)\n })\n },\n emojiSuggestor () {\n return suggestor({\n emoji: [\n ...this.$store.state.instance.emoji,\n ...this.$store.state.instance.customEmoji\n ]\n })\n },\n emoji () {\n return this.$store.state.instance.emoji || []\n },\n customEmoji () {\n return this.$store.state.instance.customEmoji || []\n },\n statusLength () {\n return this.newStatus.status.length\n },\n spoilerTextLength () {\n return this.newStatus.spoilerText.length\n },\n statusLengthLimit () {\n return this.$store.state.instance.textlimit\n },\n hasStatusLengthLimit () {\n return this.statusLengthLimit > 0\n },\n charactersLeft () {\n return this.statusLengthLimit - (this.statusLength + this.spoilerTextLength)\n },\n isOverLengthLimit () {\n return this.hasStatusLengthLimit && (this.charactersLeft < 0)\n },\n minimalScopesMode () {\n return this.$store.state.instance.minimalScopesMode\n },\n alwaysShowSubject () {\n return this.mergedConfig.alwaysShowSubjectInput\n },\n postFormats () {\n return this.$store.state.instance.postFormats || []\n },\n safeDMEnabled () {\n return this.$store.state.instance.safeDM\n },\n pollsAvailable () {\n return this.$store.state.instance.pollsAvailable &&\n this.$store.state.instance.pollLimits.max_options >= 2\n },\n hideScopeNotice () {\n return this.$store.getters.mergedConfig.hideScopeNotice\n },\n pollContentError () {\n return this.pollFormVisible &&\n this.newStatus.poll &&\n this.newStatus.poll.error\n },\n ...mapGetters(['mergedConfig'])\n },\n methods: {\n postStatus (newStatus) {\n if (this.posting) { return }\n if (this.submitDisabled) { return }\n\n if (this.newStatus.status === '') {\n if (this.newStatus.files.length === 0) {\n this.error = 'Cannot post an empty status with no files'\n return\n }\n }\n\n const poll = this.pollFormVisible ? this.newStatus.poll : {}\n if (this.pollContentError) {\n this.error = this.pollContentError\n return\n }\n\n this.posting = true\n statusPoster.postStatus({\n status: newStatus.status,\n spoilerText: newStatus.spoilerText || null,\n visibility: newStatus.visibility,\n sensitive: newStatus.nsfw,\n media: newStatus.files,\n store: this.$store,\n inReplyToStatusId: this.replyTo,\n contentType: newStatus.contentType,\n poll\n }).then((data) => {\n if (!data.error) {\n this.newStatus = {\n status: '',\n spoilerText: '',\n files: [],\n visibility: newStatus.visibility,\n contentType: newStatus.contentType,\n poll: {}\n }\n this.pollFormVisible = false\n this.$refs.mediaUpload.clearFile()\n this.clearPollForm()\n this.$emit('posted')\n let el = this.$el.querySelector('textarea')\n el.style.height = 'auto'\n el.style.height = undefined\n this.error = null\n } else {\n this.error = data.error\n }\n this.posting = false\n })\n },\n addMediaFile (fileInfo) {\n this.newStatus.files.push(fileInfo)\n this.enableSubmit()\n },\n removeMediaFile (fileInfo) {\n let index = this.newStatus.files.indexOf(fileInfo)\n this.newStatus.files.splice(index, 1)\n },\n uploadFailed (errString, templateArgs) {\n templateArgs = templateArgs || {}\n this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)\n this.enableSubmit()\n },\n disableSubmit () {\n this.submitDisabled = true\n },\n enableSubmit () {\n this.submitDisabled = false\n },\n type (fileInfo) {\n return fileTypeService.fileType(fileInfo.mimetype)\n },\n paste (e) {\n this.resize(e)\n if (e.clipboardData.files.length > 0) {\n // prevent pasting of file as text\n e.preventDefault()\n // Strangely, files property gets emptied after event propagation\n // Trying to wrap it in array doesn't work. Plus I doubt it's possible\n // to hold more than one file in clipboard.\n this.dropFiles = [e.clipboardData.files[0]]\n }\n },\n fileDrop (e) {\n if (e.dataTransfer.files.length > 0) {\n e.preventDefault() // allow dropping text like before\n this.dropFiles = e.dataTransfer.files\n }\n },\n fileDrag (e) {\n e.dataTransfer.dropEffect = 'copy'\n },\n onEmojiInputInput (e) {\n this.$nextTick(() => {\n this.resize(this.$refs['textarea'])\n })\n },\n resize (e) {\n const target = e.target || e\n if (!(target instanceof window.Element)) { return }\n\n // Reset to default height for empty form, nothing else to do here.\n if (target.value === '') {\n target.style.height = null\n this.$refs['emoji-input'].resize()\n return\n }\n\n const formRef = this.$refs['form']\n const bottomRef = this.$refs['bottom']\n /* Scroller is either `window` (replies in TL), sidebar (main post form,\n * replies in notifs) or mobile post form. Note that getting and setting\n * scroll is different for `Window` and `Element`s\n */\n const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']\n const bottomBottomPadding = Number(bottomBottomPaddingStr.substring(0, bottomBottomPaddingStr.length - 2))\n\n const scrollerRef = this.$el.closest('.sidebar-scroller') ||\n this.$el.closest('.post-form-modal-view') ||\n window\n\n // Getting info about padding we have to account for, removing 'px' part\n const topPaddingStr = window.getComputedStyle(target)['padding-top']\n const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom']\n const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2))\n const bottomPadding = Number(bottomPaddingStr.substring(0, bottomPaddingStr.length - 2))\n const vertPadding = topPadding + bottomPadding\n\n /* Explanation:\n *\n * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight\n * scrollHeight returns element's scrollable content height, i.e. visible\n * element + overscrolled parts of it. We use it to determine when text\n * inside the textarea exceeded its height, so we can set height to prevent\n * overscroll, i.e. make textarea grow with the text. HOWEVER, since we\n * explicitly set new height, scrollHeight won't go below that, so we can't\n * SHRINK the textarea when there's extra space. To workaround that we set\n * height to 'auto' which makes textarea tiny again, so that scrollHeight\n * will match text height again. HOWEVER, shrinking textarea can screw with\n * the scroll since there might be not enough padding around form-bottom to even\n * warrant a scroll, so it will jump to 0 and refuse to move anywhere,\n * so we check current scroll position before shrinking and then restore it\n * with needed delta.\n */\n\n // this part has to be BEFORE the content size update\n const currentScroll = scrollerRef === window\n ? scrollerRef.scrollY\n : scrollerRef.scrollTop\n const scrollerHeight = scrollerRef === window\n ? scrollerRef.innerHeight\n : scrollerRef.offsetHeight\n const scrollerBottomBorder = currentScroll + scrollerHeight\n\n // BEGIN content size update\n target.style.height = 'auto'\n const newHeight = target.scrollHeight - vertPadding\n target.style.height = `${newHeight}px`\n // END content size update\n\n // We check where the bottom border of form-bottom element is, this uses findOffset\n // to find offset relative to scrollable container (scroller)\n const bottomBottomBorder = bottomRef.offsetHeight + findOffset(bottomRef, scrollerRef).top + bottomBottomPadding\n\n const isBottomObstructed = scrollerBottomBorder < bottomBottomBorder\n const isFormBiggerThanScroller = scrollerHeight < formRef.offsetHeight\n const bottomChangeDelta = bottomBottomBorder - scrollerBottomBorder\n // The intention is basically this;\n // Keep form-bottom always visible so that submit button is in view EXCEPT\n // if form element bigger than scroller and caret isn't at the end, so that\n // if you scroll up and edit middle of text you won't get scrolled back to bottom\n const shouldScrollToBottom = isBottomObstructed &&\n !(isFormBiggerThanScroller &&\n this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)\n const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0\n const targetScroll = currentScroll + totalDelta\n\n if (scrollerRef === window) {\n scrollerRef.scroll(0, targetScroll)\n } else {\n scrollerRef.scrollTop = targetScroll\n }\n\n this.$refs['emoji-input'].resize()\n },\n showEmojiPicker () {\n this.$refs['textarea'].focus()\n this.$refs['emoji-input'].triggerShowPicker()\n },\n clearError () {\n this.error = null\n },\n changeVis (visibility) {\n this.newStatus.visibility = visibility\n },\n togglePollForm () {\n this.pollFormVisible = !this.pollFormVisible\n },\n setPoll (poll) {\n this.newStatus.poll = poll\n },\n clearPollForm () {\n if (this.$refs.pollForm) {\n this.$refs.pollForm.clear()\n }\n },\n dismissScopeNotice () {\n this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })\n }\n }\n}\n\nexport default PostStatusForm\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./post_status_form.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./post_status_form.js\"\nimport __vue_script__ from \"!!babel-loader!./post_status_form.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-c2ba770c\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./post_status_form.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{ref:\"form\",staticClass:\"post-status-form\"},[_c('form',{attrs:{\"autocomplete\":\"off\"},on:{\"submit\":function($event){$event.preventDefault();_vm.postStatus(_vm.newStatus)}}},[_c('div',{staticClass:\"form-group\"},[(!_vm.$store.state.users.currentUser.locked && _vm.newStatus.visibility == 'private')?_c('i18n',{staticClass:\"visibility-notice\",attrs:{\"path\":\"post_status.account_not_locked_warning\",\"tag\":\"p\"}},[_c('router-link',{attrs:{\"to\":{ name: 'user-settings' }}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('post_status.account_not_locked_warning_link'))+\"\\n \")])],1):_vm._e(),_vm._v(\" \"),(!_vm.hideScopeNotice && _vm.newStatus.visibility === 'public')?_c('p',{staticClass:\"visibility-notice notice-dismissible\"},[_c('span',[_vm._v(_vm._s(_vm.$t('post_status.scope_notice.public')))]),_vm._v(\" \"),_c('a',{staticClass:\"button-icon dismiss\",on:{\"click\":function($event){$event.preventDefault();_vm.dismissScopeNotice()}}},[_c('i',{staticClass:\"icon-cancel\"})])]):(!_vm.hideScopeNotice && _vm.newStatus.visibility === 'unlisted')?_c('p',{staticClass:\"visibility-notice notice-dismissible\"},[_c('span',[_vm._v(_vm._s(_vm.$t('post_status.scope_notice.unlisted')))]),_vm._v(\" \"),_c('a',{staticClass:\"button-icon dismiss\",on:{\"click\":function($event){$event.preventDefault();_vm.dismissScopeNotice()}}},[_c('i',{staticClass:\"icon-cancel\"})])]):(!_vm.hideScopeNotice && _vm.newStatus.visibility === 'private' && _vm.$store.state.users.currentUser.locked)?_c('p',{staticClass:\"visibility-notice notice-dismissible\"},[_c('span',[_vm._v(_vm._s(_vm.$t('post_status.scope_notice.private')))]),_vm._v(\" \"),_c('a',{staticClass:\"button-icon dismiss\",on:{\"click\":function($event){$event.preventDefault();_vm.dismissScopeNotice()}}},[_c('i',{staticClass:\"icon-cancel\"})])]):(_vm.newStatus.visibility === 'direct')?_c('p',{staticClass:\"visibility-notice\"},[(_vm.safeDMEnabled)?_c('span',[_vm._v(_vm._s(_vm.$t('post_status.direct_warning_to_first_only')))]):_c('span',[_vm._v(_vm._s(_vm.$t('post_status.direct_warning_to_all')))])]):_vm._e(),_vm._v(\" \"),(_vm.newStatus.spoilerText || _vm.alwaysShowSubject)?_c('EmojiInput',{staticClass:\"form-control\",attrs:{\"enable-emoji-picker\":\"\",\"suggest\":_vm.emojiSuggestor},model:{value:(_vm.newStatus.spoilerText),callback:function ($$v) {_vm.$set(_vm.newStatus, \"spoilerText\", $$v)},expression:\"newStatus.spoilerText\"}},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.newStatus.spoilerText),expression:\"newStatus.spoilerText\"}],staticClass:\"form-post-subject\",attrs:{\"type\":\"text\",\"placeholder\":_vm.$t('post_status.content_warning')},domProps:{\"value\":(_vm.newStatus.spoilerText)},on:{\"input\":function($event){if($event.target.composing){ return; }_vm.$set(_vm.newStatus, \"spoilerText\", $event.target.value)}}})]):_vm._e(),_vm._v(\" \"),_c('EmojiInput',{ref:\"emoji-input\",staticClass:\"form-control main-input\",attrs:{\"suggest\":_vm.emojiUserSuggestor,\"enable-emoji-picker\":\"\",\"hide-emoji-button\":\"\",\"enable-sticker-picker\":\"\"},on:{\"input\":_vm.onEmojiInputInput,\"sticker-uploaded\":_vm.addMediaFile,\"sticker-upload-failed\":_vm.uploadFailed},model:{value:(_vm.newStatus.status),callback:function ($$v) {_vm.$set(_vm.newStatus, \"status\", $$v)},expression:\"newStatus.status\"}},[_c('textarea',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.newStatus.status),expression:\"newStatus.status\"}],ref:\"textarea\",staticClass:\"form-post-body\",attrs:{\"placeholder\":_vm.$t('post_status.default'),\"rows\":\"1\",\"disabled\":_vm.posting},domProps:{\"value\":(_vm.newStatus.status)},on:{\"keydown\":function($event){if(!('button' in $event)&&_vm._k($event.keyCode,\"enter\",13,$event.key,\"Enter\")){ return null; }if(!$event.metaKey){ return null; }_vm.postStatus(_vm.newStatus)},\"keyup\":function($event){if(!('button' in $event)&&_vm._k($event.keyCode,\"enter\",13,$event.key,\"Enter\")){ return null; }if(!$event.ctrlKey){ return null; }_vm.postStatus(_vm.newStatus)},\"drop\":_vm.fileDrop,\"dragover\":function($event){$event.preventDefault();return _vm.fileDrag($event)},\"input\":[function($event){if($event.target.composing){ return; }_vm.$set(_vm.newStatus, \"status\", $event.target.value)},_vm.resize],\"compositionupdate\":_vm.resize,\"paste\":_vm.paste}}),_vm._v(\" \"),(_vm.hasStatusLengthLimit)?_c('p',{staticClass:\"character-counter faint\",class:{ error: _vm.isOverLengthLimit }},[_vm._v(\"\\n \"+_vm._s(_vm.charactersLeft)+\"\\n \")]):_vm._e()]),_vm._v(\" \"),_c('div',{staticClass:\"visibility-tray\"},[_c('scope-selector',{attrs:{\"show-all\":_vm.showAllScopes,\"user-default\":_vm.userDefaultScope,\"original-scope\":_vm.copyMessageScope,\"initial-scope\":_vm.newStatus.visibility,\"on-scope-change\":_vm.changeVis}}),_vm._v(\" \"),(_vm.postFormats.length > 1)?_c('div',{staticClass:\"text-format\"},[_c('label',{staticClass:\"select\",attrs:{\"for\":\"post-content-type\"}},[_c('select',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.newStatus.contentType),expression:\"newStatus.contentType\"}],staticClass:\"form-control\",attrs:{\"id\":\"post-content-type\"},on:{\"change\":function($event){var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = \"_value\" in o ? o._value : o.value;return val}); _vm.$set(_vm.newStatus, \"contentType\", $event.target.multiple ? $$selectedVal : $$selectedVal[0])}}},_vm._l((_vm.postFormats),function(postFormat){return _c('option',{key:postFormat,domProps:{\"value\":postFormat}},[_vm._v(\"\\n \"+_vm._s(_vm.$t((\"post_status.content_type[\\\"\" + postFormat + \"\\\"]\")))+\"\\n \")])}),0),_vm._v(\" \"),_c('i',{staticClass:\"icon-down-open\"})])]):_vm._e(),_vm._v(\" \"),(_vm.postFormats.length === 1 && _vm.postFormats[0] !== 'text/plain')?_c('div',{staticClass:\"text-format\"},[_c('span',{staticClass:\"only-format\"},[_vm._v(\"\\n \"+_vm._s(_vm.$t((\"post_status.content_type[\\\"\" + (_vm.postFormats[0]) + \"\\\"]\")))+\"\\n \")])]):_vm._e()],1)],1),_vm._v(\" \"),(_vm.pollsAvailable)?_c('poll-form',{ref:\"pollForm\",attrs:{\"visible\":_vm.pollFormVisible},on:{\"update-poll\":_vm.setPoll}}):_vm._e(),_vm._v(\" \"),_c('div',{ref:\"bottom\",staticClass:\"form-bottom\"},[_c('div',{staticClass:\"form-bottom-left\"},[_c('media-upload',{ref:\"mediaUpload\",staticClass:\"media-upload-icon\",attrs:{\"drop-files\":_vm.dropFiles},on:{\"uploading\":_vm.disableSubmit,\"uploaded\":_vm.addMediaFile,\"upload-failed\":_vm.uploadFailed}}),_vm._v(\" \"),_c('div',{staticClass:\"emoji-icon\"},[_c('i',{staticClass:\"icon-smile btn btn-default\",attrs:{\"title\":_vm.$t('emoji.add_emoji')},on:{\"click\":_vm.showEmojiPicker}})]),_vm._v(\" \"),(_vm.pollsAvailable)?_c('div',{staticClass:\"poll-icon\",class:{ selected: _vm.pollFormVisible }},[_c('i',{staticClass:\"icon-chart-bar btn btn-default\",attrs:{\"title\":_vm.$t('polls.add_poll')},on:{\"click\":_vm.togglePollForm}})]):_vm._e()],1),_vm._v(\" \"),(_vm.posting)?_c('button',{staticClass:\"btn btn-default\",attrs:{\"disabled\":\"\"}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('post_status.posting'))+\"\\n \")]):(_vm.isOverLengthLimit)?_c('button',{staticClass:\"btn btn-default\",attrs:{\"disabled\":\"\"}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('general.submit'))+\"\\n \")]):_c('button',{staticClass:\"btn btn-default\",attrs:{\"disabled\":_vm.submitDisabled,\"type\":\"submit\"}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('general.submit'))+\"\\n \")])]),_vm._v(\" \"),(_vm.error)?_c('div',{staticClass:\"alert error\"},[_vm._v(\"\\n Error: \"+_vm._s(_vm.error)+\"\\n \"),_c('i',{staticClass:\"button-icon icon-cancel\",on:{\"click\":_vm.clearError}})]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"attachments\"},_vm._l((_vm.newStatus.files),function(file){return _c('div',{key:file.url,staticClass:\"media-upload-wrapper\"},[_c('i',{staticClass:\"fa button-icon icon-cancel\",on:{\"click\":function($event){_vm.removeMediaFile(file)}}}),_vm._v(\" \"),_c('div',{staticClass:\"media-upload-container attachment\"},[(_vm.type(file) === 'image')?_c('img',{staticClass:\"thumbnail media-upload\",attrs:{\"src\":file.url}}):_vm._e(),_vm._v(\" \"),(_vm.type(file) === 'video')?_c('video',{attrs:{\"src\":file.url,\"controls\":\"\"}}):_vm._e(),_vm._v(\" \"),(_vm.type(file) === 'audio')?_c('audio',{attrs:{\"src\":file.url,\"controls\":\"\"}}):_vm._e(),_vm._v(\" \"),(_vm.type(file) === 'unknown')?_c('a',{attrs:{\"href\":file.url}},[_vm._v(_vm._s(file.url))]):_vm._e()])])}),0),_vm._v(\" \"),(_vm.newStatus.files.length > 0)?_c('div',{staticClass:\"upload_settings\"},[_c('Checkbox',{model:{value:(_vm.newStatus.nsfw),callback:function ($$v) {_vm.$set(_vm.newStatus, \"nsfw\", $$v)},expression:\"newStatus.nsfw\"}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('post_status.attachments_sensitive'))+\"\\n \")])],1):_vm._e()],1)])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","\n\n\n","/* script */\nexport * from \"!!babel-loader!../../../node_modules/vue-loader/lib/selector?type=script&index=0!./progress_button.vue\"\nimport __vue_script__ from \"!!babel-loader!../../../node_modules/vue-loader/lib/selector?type=script&index=0!./progress_button.vue\"\n/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-9f751ae6\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./progress_button.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = null\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('button',{attrs:{\"disabled\":_vm.progress || _vm.disabled},on:{\"click\":_vm.onClick}},[(_vm.progress && _vm.$slots.progress)?[_vm._t(\"progress\")]:[_vm._t(\"default\")]],2)}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","const StillImage = {\n props: [\n 'src',\n 'referrerpolicy',\n 'mimetype',\n 'imageLoadError',\n 'imageLoadHandler'\n ],\n data () {\n return {\n stopGifs: this.$store.getters.mergedConfig.stopGifs\n }\n },\n computed: {\n animated () {\n return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))\n }\n },\n methods: {\n onLoad () {\n this.imageLoadHandler && this.imageLoadHandler(this.$refs.src)\n const canvas = this.$refs.canvas\n if (!canvas) return\n const width = this.$refs.src.naturalWidth\n const height = this.$refs.src.naturalHeight\n canvas.width = width\n canvas.height = height\n canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height)\n },\n onError () {\n this.imageLoadError && this.imageLoadError()\n }\n }\n}\n\nexport default StillImage\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./still-image.vue\")\n}\n/* script */\nexport * from \"!!babel-loader!./still-image.js\"\nimport __vue_script__ from \"!!babel-loader!./still-image.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-1bc509fc\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./still-image.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"still-image\",class:{ animated: _vm.animated }},[(_vm.animated)?_c('canvas',{ref:\"canvas\"}):_vm._e(),_vm._v(\" \"),_c('img',{key:_vm.src,ref:\"src\",attrs:{\"src\":_vm.src,\"referrerpolicy\":_vm.referrerpolicy},on:{\"load\":_vm.onLoad,\"error\":_vm.onError}})])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","\n\n\n","/* script */\nexport * from \"!!babel-loader!../../../node_modules/vue-loader/lib/selector?type=script&index=0!./timeago.vue\"\nimport __vue_script__ from \"!!babel-loader!../../../node_modules/vue-loader/lib/selector?type=script&index=0!./timeago.vue\"\n/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-ac499830\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./timeago.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = null\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('time',{attrs:{\"datetime\":_vm.time,\"title\":_vm.localeDateString}},[_vm._v(\"\\n \"+_vm._s(_vm.$t(_vm.relativeTime.key, [_vm.relativeTime.num]))+\"\\n\")])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","const fileSizeFormat = (num) => {\n var exponent\n var unit\n var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']\n if (num < 1) {\n return num + ' ' + units[0]\n }\n\n exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1)\n num = (num / Math.pow(1024, exponent)).toFixed(2) * 1\n unit = units[exponent]\n return { num: num, unit: unit }\n}\nconst fileSizeFormatService = {\n fileSizeFormat\n}\nexport default fileSizeFormatService\n","import { debounce } from 'lodash'\n/**\n * suggest - generates a suggestor function to be used by emoji-input\n * data: object providing source information for specific types of suggestions:\n * data.emoji - optional, an array of all emoji available i.e.\n * (state.instance.emoji + state.instance.customEmoji)\n * data.users - optional, an array of all known users\n * updateUsersList - optional, a function to search and append to users\n *\n * Depending on data present one or both (or none) can be present, so if field\n * doesn't support user linking you can just provide only emoji.\n */\n\nconst debounceUserSearch = debounce((data, input) => {\n data.updateUsersList(input)\n}, 500, { leading: true, trailing: false })\n\nexport default data => input => {\n const firstChar = input[0]\n if (firstChar === ':' && data.emoji) {\n return suggestEmoji(data.emoji)(input)\n }\n if (firstChar === '@' && data.users) {\n return suggestUsers(data)(input)\n }\n return []\n}\n\nexport const suggestEmoji = emojis => input => {\n const noPrefix = input.toLowerCase().substr(1)\n return emojis\n .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))\n .sort((a, b) => {\n let aScore = 0\n let bScore = 0\n\n // Make custom emojis a priority\n aScore += a.imageUrl ? 10 : 0\n bScore += b.imageUrl ? 10 : 0\n\n // Sort alphabetically\n const alphabetically = a.displayText > b.displayText ? 1 : -1\n\n return bScore - aScore + alphabetically\n })\n}\n\nexport const suggestUsers = data => input => {\n const noPrefix = input.toLowerCase().substr(1)\n const users = data.users\n\n const newUsers = users.filter(\n user =>\n user.screen_name.toLowerCase().startsWith(noPrefix) ||\n user.name.toLowerCase().startsWith(noPrefix)\n\n /* taking only 20 results so that sorting is a bit cheaper, we display\n * only 5 anyway. could be inaccurate, but we ideally we should query\n * backend anyway\n */\n ).slice(0, 20).sort((a, b) => {\n let aScore = 0\n let bScore = 0\n\n // Matches on screen name (i.e. user@instance) makes a priority\n aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0\n bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0\n\n // Matches on name takes second priority\n aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0\n bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0\n\n const diff = (bScore - aScore) * 10\n\n // Then sort alphabetically\n const nameAlphabetically = a.name > b.name ? 1 : -1\n const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1\n\n return diff + nameAlphabetically + screenNameAlphabetically\n /* eslint-disable camelcase */\n }).map(({ screen_name, name, profile_image_url_original }) => ({\n displayText: screen_name,\n detailText: name,\n imageUrl: profile_image_url_original,\n replacement: '@' + screen_name + ' '\n }))\n\n // BE search users if there are no matches\n if (newUsers.length === 0 && data.updateUsersList) {\n debounceUserSearch(data, noPrefix)\n }\n return newUsers\n /* eslint-enable camelcase */\n}\n","import { map } from 'lodash'\nimport apiService from '../api/api.service.js'\n\nconst postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {\n const mediaIds = map(media, 'id')\n\n return apiService.postStatus({\n credentials: store.state.users.currentUser.credentials,\n status,\n spoilerText,\n visibility,\n sensitive,\n mediaIds,\n inReplyToStatusId,\n contentType,\n poll })\n .then((data) => {\n if (!data.error) {\n store.dispatch('addNewStatuses', {\n statuses: [data],\n timeline: 'friends',\n showImmediately: true,\n noIdUpdate: true // To prevent missing notices on next pull.\n })\n }\n return data\n })\n .catch((err) => {\n return {\n error: err.message\n }\n })\n}\n\nconst uploadMedia = ({ store, formData }) => {\n const credentials = store.state.users.currentUser.credentials\n\n return apiService.uploadMedia({ credentials, formData })\n}\n\nconst statusPosterService = {\n postStatus,\n uploadMedia\n}\n\nexport default statusPosterService\n","export const findOffset = (child, parent, { top = 0, left = 0 } = {}, ignorePadding = true) => {\n const result = {\n top: top + child.offsetTop,\n left: left + child.offsetLeft\n }\n if (!ignorePadding && child !== window) {\n const { topPadding, leftPadding } = findPadding(child)\n result.top += ignorePadding ? 0 : topPadding\n result.left += ignorePadding ? 0 : leftPadding\n }\n\n if (child.offsetParent && (parent === window || parent.contains(child.offsetParent) || parent === child.offsetParent)) {\n return findOffset(child.offsetParent, parent, result, false)\n } else {\n if (parent !== window) {\n const { topPadding, leftPadding } = findPadding(parent)\n result.top += topPadding\n result.left += leftPadding\n }\n return result\n }\n}\n\nconst findPadding = (el) => {\n const topPaddingStr = window.getComputedStyle(el)['padding-top']\n const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2))\n const leftPaddingStr = window.getComputedStyle(el)['padding-left']\n const leftPadding = Number(leftPaddingStr.substring(0, leftPaddingStr.length - 2))\n\n return { topPadding, leftPadding }\n}\n","import { reduce, find } from 'lodash'\n\nexport const replaceWord = (str, toReplace, replacement) => {\n return str.slice(0, toReplace.start) + replacement + str.slice(toReplace.end)\n}\n\nexport const wordAtPosition = (str, pos) => {\n const words = splitIntoWords(str)\n const wordsWithPosition = addPositionToWords(words)\n\n return find(wordsWithPosition, ({ start, end }) => start <= pos && end > pos)\n}\n\nexport const addPositionToWords = (words) => {\n return reduce(words, (result, word) => {\n const data = {\n word,\n start: 0,\n end: word.length\n }\n\n if (result.length > 0) {\n const previous = result.pop()\n\n data.start += previous.end\n data.end += previous.end\n\n result.push(previous)\n }\n\n result.push(data)\n\n return result\n }, [])\n}\n\nexport const splitIntoWords = (str) => {\n // Split at word boundaries\n const regex = /\\b/\n const triggers = /[@#:]+$/\n\n let split = str.split(regex)\n\n // Add trailing @ and # to the following word.\n const words = reduce(split, (result, word) => {\n if (result.length > 0) {\n let previous = result.pop()\n const matches = previous.match(triggers)\n if (matches) {\n previous = previous.replace(triggers, '')\n word = matches[0] + word\n }\n result.push(previous)\n }\n result.push(word)\n\n return result\n }, [])\n\n return words\n}\n\nconst completion = {\n wordAtPosition,\n addPositionToWords,\n splitIntoWords,\n replaceWord\n}\n\nexport default completion\n","import Checkbox from '../checkbox/checkbox.vue'\n\n// At widest, approximately 20 emoji are visible in a row,\n// loading 3 rows, could be overkill for narrow picker\nconst LOAD_EMOJI_BY = 60\n\n// When to start loading new batch emoji, in pixels\nconst LOAD_EMOJI_MARGIN = 64\n\nconst filterByKeyword = (list, keyword = '') => {\n return list.filter(x => x.displayText.includes(keyword))\n}\n\nconst EmojiPicker = {\n props: {\n enableStickerPicker: {\n required: false,\n type: Boolean,\n default: false\n }\n },\n data () {\n return {\n keyword: '',\n activeGroup: 'custom',\n showingStickers: false,\n groupsScrolledClass: 'scrolled-top',\n keepOpen: false,\n customEmojiBufferSlice: LOAD_EMOJI_BY,\n customEmojiTimeout: null,\n customEmojiLoadAllConfirmed: false\n }\n },\n components: {\n StickerPicker: () => import('../sticker_picker/sticker_picker.vue'),\n Checkbox\n },\n methods: {\n onStickerUploaded (e) {\n this.$emit('sticker-uploaded', e)\n },\n onStickerUploadFailed (e) {\n this.$emit('sticker-upload-failed', e)\n },\n onEmoji (emoji) {\n const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement\n this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })\n },\n onScroll (e) {\n const target = (e && e.target) || this.$refs['emoji-groups']\n this.updateScrolledClass(target)\n this.scrolledGroup(target)\n this.triggerLoadMore(target)\n },\n highlight (key) {\n const ref = this.$refs['group-' + key]\n const top = ref[0].offsetTop\n this.setShowStickers(false)\n this.activeGroup = key\n this.$nextTick(() => {\n this.$refs['emoji-groups'].scrollTop = top + 1\n })\n },\n updateScrolledClass (target) {\n if (target.scrollTop <= 5) {\n this.groupsScrolledClass = 'scrolled-top'\n } else if (target.scrollTop >= target.scrollTopMax - 5) {\n this.groupsScrolledClass = 'scrolled-bottom'\n } else {\n this.groupsScrolledClass = 'scrolled-middle'\n }\n },\n triggerLoadMore (target) {\n const ref = this.$refs['group-end-custom'][0]\n if (!ref) return\n const bottom = ref.offsetTop + ref.offsetHeight\n\n const scrollerBottom = target.scrollTop + target.clientHeight\n const scrollerTop = target.scrollTop\n const scrollerMax = target.scrollHeight\n\n // Loads more emoji when they come into view\n const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN\n // Always load when at the very top in case there's no scroll space yet\n const atTop = scrollerTop < 5\n // Don't load when looking at unicode category or at the very bottom\n const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax\n if (!bottomAboveViewport && (approachingBottom || atTop)) {\n this.loadEmoji()\n }\n },\n scrolledGroup (target) {\n const top = target.scrollTop + 5\n this.$nextTick(() => {\n this.emojisView.forEach(group => {\n const ref = this.$refs['group-' + group.id]\n if (ref[0].offsetTop <= top) {\n this.activeGroup = group.id\n }\n })\n })\n },\n loadEmoji () {\n const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length\n\n if (allLoaded) {\n return\n }\n\n this.customEmojiBufferSlice += LOAD_EMOJI_BY\n },\n startEmojiLoad (forceUpdate = false) {\n if (!forceUpdate) {\n this.keyword = ''\n }\n this.$nextTick(() => {\n this.$refs['emoji-groups'].scrollTop = 0\n })\n const bufferSize = this.customEmojiBuffer.length\n const bufferPrefilledAll = bufferSize === this.filteredEmoji.length\n if (bufferPrefilledAll && !forceUpdate) {\n return\n }\n this.customEmojiBufferSlice = LOAD_EMOJI_BY\n },\n toggleStickers () {\n this.showingStickers = !this.showingStickers\n },\n setShowStickers (value) {\n this.showingStickers = value\n }\n },\n watch: {\n keyword () {\n this.customEmojiLoadAllConfirmed = false\n this.onScroll()\n this.startEmojiLoad(true)\n }\n },\n computed: {\n activeGroupView () {\n return this.showingStickers ? '' : this.activeGroup\n },\n stickersAvailable () {\n if (this.$store.state.instance.stickers) {\n return this.$store.state.instance.stickers.length > 0\n }\n return 0\n },\n filteredEmoji () {\n return filterByKeyword(\n this.$store.state.instance.customEmoji || [],\n this.keyword\n )\n },\n customEmojiBuffer () {\n return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)\n },\n emojis () {\n const standardEmojis = this.$store.state.instance.emoji || []\n const customEmojis = this.customEmojiBuffer\n\n return [\n {\n id: 'custom',\n text: this.$t('emoji.custom'),\n icon: 'icon-smile',\n emojis: customEmojis\n },\n {\n id: 'standard',\n text: this.$t('emoji.unicode'),\n icon: 'icon-picture',\n emojis: filterByKeyword(standardEmojis, this.keyword)\n }\n ]\n },\n emojisView () {\n return this.emojis.filter(value => value.emojis.length > 0)\n },\n stickerPickerEnabled () {\n return (this.$store.state.instance.stickers || []).length !== 0\n }\n }\n}\n\nexport default EmojiPicker\n","function injectStyle (context) {\n require(\"!!vue-style-loader!css-loader?minimize!../../../node_modules/vue-loader/lib/style-compiler/index?{\\\"optionsId\\\":\\\"0\\\",\\\"vue\\\":true,\\\"scoped\\\":false,\\\"sourceMap\\\":false}!sass-loader!./emoji_picker.scss\")\n}\n/* script */\nexport * from \"!!babel-loader!./emoji_picker.js\"\nimport __vue_script__ from \"!!babel-loader!./emoji_picker.js\"/* template */\nimport {render as __vue_render__, staticRenderFns as __vue_static_render_fns__} from \"!!../../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-47d21b3b\\\",\\\"hasScoped\\\":false,\\\"optionsId\\\":\\\"0\\\",\\\"buble\\\":{\\\"transforms\\\":{}}}!../../../node_modules/vue-loader/lib/selector?type=template&index=0!./emoji_picker.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nimport normalizeComponent from \"!../../../node_modules/vue-loader/lib/runtime/component-normalizer\"\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_render__,\n __vue_static_render_fns__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"emoji-picker panel panel-default panel-body\"},[_c('div',{staticClass:\"heading\"},[_c('span',{staticClass:\"emoji-tabs\"},_vm._l((_vm.emojis),function(group){return _c('span',{key:group.id,staticClass:\"emoji-tabs-item\",class:{\n active: _vm.activeGroupView === group.id,\n disabled: group.emojis.length === 0\n },attrs:{\"title\":group.text},on:{\"click\":function($event){$event.preventDefault();_vm.highlight(group.id)}}},[_c('i',{class:group.icon})])}),0),_vm._v(\" \"),(_vm.stickerPickerEnabled)?_c('span',{staticClass:\"additional-tabs\"},[_c('span',{staticClass:\"stickers-tab-icon additional-tabs-item\",class:{active: _vm.showingStickers},attrs:{\"title\":_vm.$t('emoji.stickers')},on:{\"click\":function($event){$event.preventDefault();return _vm.toggleStickers($event)}}},[_c('i',{staticClass:\"icon-star\"})])]):_vm._e()]),_vm._v(\" \"),_c('div',{staticClass:\"content\"},[_c('div',{staticClass:\"emoji-content\",class:{hidden: _vm.showingStickers}},[_c('div',{staticClass:\"emoji-search\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.keyword),expression:\"keyword\"}],staticClass:\"form-control\",attrs:{\"type\":\"text\",\"placeholder\":_vm.$t('emoji.search_emoji')},domProps:{\"value\":(_vm.keyword)},on:{\"input\":function($event){if($event.target.composing){ return; }_vm.keyword=$event.target.value}}})]),_vm._v(\" \"),_c('div',{ref:\"emoji-groups\",staticClass:\"emoji-groups\",class:_vm.groupsScrolledClass,on:{\"scroll\":_vm.onScroll}},_vm._l((_vm.emojisView),function(group){return _c('div',{key:group.id,staticClass:\"emoji-group\"},[_c('h6',{ref:'group-' + group.id,refInFor:true,staticClass:\"emoji-group-title\"},[_vm._v(\"\\n \"+_vm._s(group.text)+\"\\n \")]),_vm._v(\" \"),_vm._l((group.emojis),function(emoji){return _c('span',{key:group.id + emoji.displayText,staticClass:\"emoji-item\",attrs:{\"title\":emoji.displayText},on:{\"click\":function($event){$event.stopPropagation();$event.preventDefault();_vm.onEmoji(emoji)}}},[(!emoji.imageUrl)?_c('span',[_vm._v(_vm._s(emoji.replacement))]):_c('img',{attrs:{\"src\":emoji.imageUrl}})])}),_vm._v(\" \"),_c('span',{ref:'group-end-' + group.id,refInFor:true})],2)}),0),_vm._v(\" \"),_c('div',{staticClass:\"keep-open\"},[_c('Checkbox',{model:{value:(_vm.keepOpen),callback:function ($$v) {_vm.keepOpen=$$v},expression:\"keepOpen\"}},[_vm._v(\"\\n \"+_vm._s(_vm.$t('emoji.keep_open'))+\"\\n \")])],1)]),_vm._v(\" \"),(_vm.showingStickers)?_c('div',{staticClass:\"stickers-content\"},[_c('sticker-picker',{on:{\"uploaded\":_vm.onStickerUploaded,\"upload-failed\":_vm.onStickerUploadFailed}})],1):_vm._e()])])}\nvar staticRenderFns = []\nexport { render, staticRenderFns }","import Completion from '../../services/completion/completion.js'\nimport EmojiPicker from '../emoji_picker/emoji_picker.vue'\nimport { take } from 'lodash'\nimport { findOffset } from '../../services/offset_finder/offset_finder.service.js'\n\n/**\n * EmojiInput - augmented inputs for emoji and autocomplete support in inputs\n * without having to give up the comfort of and