Merge branch '1397-instance-personnalization' into 'main'
Draft: enable instance personnalization Closes #1087 and #1397 See merge request framasoft/mobilizon!1538
This commit is contained in:
commit
37afc27ec7
4
Makefile
4
Makefile
|
@ -19,8 +19,8 @@ stop:
|
||||||
@bash docker/message.sh "Mobilizon is stopped"
|
@bash docker/message.sh "Mobilizon is stopped"
|
||||||
test: stop
|
test: stop
|
||||||
@bash docker/message.sh "Running tests"
|
@bash docker/message.sh "Running tests"
|
||||||
docker compose -f docker compose.yml -f docker compose.test.yml run api mix prepare_test
|
docker compose -f docker-compose.yml -f docker-compose.test.yml run api mix prepare_test
|
||||||
docker compose -f docker compose.yml -f docker compose.test.yml run api mix test $(only)
|
docker compose -f docker-compose.yml -f docker-compose.test.yml run api mix test $(only)
|
||||||
@bash docker/message.sh "Done running tests"
|
@bash docker/message.sh "Done running tests"
|
||||||
format:
|
format:
|
||||||
docker compose run --rm api bash -c "mix format && mix credo --strict"
|
docker compose run --rm api bash -c "mix format && mix credo --strict"
|
||||||
|
|
|
@ -5,11 +5,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||||
|
|
||||||
import Mobilizon.Users.Guards
|
import Mobilizon.Users.Guards
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Admin, Config, Events, Instances, Users}
|
alias Mobilizon.{Actors, Admin, Config, Events, Instances, Media, Users}
|
||||||
alias Mobilizon.Actors.{Actor, Follower}
|
alias Mobilizon.Actors.{Actor, Follower}
|
||||||
alias Mobilizon.Admin.{ActionLog, Setting}
|
alias Mobilizon.Admin.{ActionLog, Setting, SettingMedia}
|
||||||
alias Mobilizon.Cldr.Language
|
alias Mobilizon.Cldr.Language
|
||||||
alias Mobilizon.Config
|
|
||||||
alias Mobilizon.Discussions.Comment
|
alias Mobilizon.Discussions.Comment
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Federation.ActivityPub.{Actions, Relay}
|
alias Mobilizon.Federation.ActivityPub.{Actions, Relay}
|
||||||
|
@ -20,6 +19,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||||
alias Mobilizon.Storage.Page
|
alias Mobilizon.Storage.Page
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
alias Mobilizon.Web.Email
|
alias Mobilizon.Web.Email
|
||||||
|
|
||||||
|
alias Mobilizon.GraphQL.Resolvers.Media, as: MediaResolver
|
||||||
|
|
||||||
import Mobilizon.Web.Gettext
|
import Mobilizon.Web.Gettext
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -268,8 +270,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||||
with {:ok, res} <- Admin.save_settings("instance", args),
|
with {:ok, res} <- Admin.save_settings("instance", args),
|
||||||
res <-
|
res <-
|
||||||
res
|
res
|
||||||
|> Enum.map(fn {key, %Setting{value: value}} ->
|
|> Enum.map(fn {key, val} ->
|
||||||
{key, Admin.get_setting_value(value)}
|
case val do
|
||||||
|
%Setting{value: value} -> {key, Admin.get_setting_value(value)}
|
||||||
|
%SettingMedia{media: media} -> {key, media}
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.into(%{}),
|
|> Enum.into(%{}),
|
||||||
:ok <- eventually_update_instance_actor(res) do
|
:ok <- eventually_update_instance_actor(res) do
|
||||||
|
@ -284,6 +289,38 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||||
dgettext("errors", "You need to be logged-in and an administrator to save admin settings")}
|
dgettext("errors", "You need to be logged-in and an administrator to save admin settings")}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_media_setting(any(), any(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Media.t()} | {:error, String.t()}
|
||||||
|
def get_media_setting(_parent, %{group: group, name: name}, %{
|
||||||
|
context: %{current_user: %User{role: role}}
|
||||||
|
})
|
||||||
|
when is_admin(role) do
|
||||||
|
{:ok, MediaResolver.transform_media(Admin.get_admin_setting_media(group, name, nil))}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_media_setting(_parent, _args, _resolution) do
|
||||||
|
{:error,
|
||||||
|
dgettext("errors", "You need to be logged-in and an administrator to access admin settings")}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_instance_logo(any(), any(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Media.t() | nil} | {:error, String.t()}
|
||||||
|
def get_instance_logo(parent, _args, resolution) do
|
||||||
|
get_media_setting(parent, %{group: "instance", name: "instance_logo"}, resolution)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_instance_favicon(any(), any(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Media.t() | nil} | {:error, String.t()}
|
||||||
|
def get_instance_favicon(parent, _args, resolution) do
|
||||||
|
get_media_setting(parent, %{group: "instance", name: "instance_favicon"}, resolution)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_default_picture(any(), any(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Media.t() | nil} | {:error, String.t()}
|
||||||
|
def get_default_picture(parent, _args, resolution) do
|
||||||
|
get_media_setting(parent, %{group: "instance", name: "default_picture"}, resolution)
|
||||||
|
end
|
||||||
|
|
||||||
@spec update_user(any, map(), Absinthe.Resolution.t()) ::
|
@spec update_user(any, map(), Absinthe.Resolution.t()) ::
|
||||||
{:error, :invalid_argument | :user_not_found | binary | Ecto.Changeset.t()}
|
{:error, :invalid_argument | :user_not_found | binary | Ecto.Changeset.t()}
|
||||||
| {:ok, Mobilizon.Users.User.t()}
|
| {:ok, Mobilizon.Users.User.t()}
|
||||||
|
|
|
@ -5,8 +5,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||||
|
|
||||||
alias Mobilizon.Config
|
alias Mobilizon.Config
|
||||||
alias Mobilizon.Events.Categories
|
alias Mobilizon.Events.Categories
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Service.{AntiSpam, FrontEndAnalytics}
|
alias Mobilizon.Service.{AntiSpam, FrontEndAnalytics}
|
||||||
|
|
||||||
|
alias Mobilizon.GraphQL.Resolvers.Media, as: MediaResolver
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets config.
|
Gets config.
|
||||||
"""
|
"""
|
||||||
|
@ -31,6 +34,16 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec instance_logo(any(), map(), Absinthe.Resolution.t()) :: {:ok, Media.t()}
|
||||||
|
def instance_logo(_parent, _params, _resolution) do
|
||||||
|
{:ok, MediaResolver.transform_media(Config.instance_logo())}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec default_picture(any(), map(), Absinthe.Resolution.t()) :: {:ok, Media.t()}
|
||||||
|
def default_picture(_parent, _params, _resolution) do
|
||||||
|
{:ok, MediaResolver.transform_media(Config.default_picture())}
|
||||||
|
end
|
||||||
|
|
||||||
@spec terms(any(), map(), Absinthe.Resolution.t()) :: {:ok, map()}
|
@spec terms(any(), map(), Absinthe.Resolution.t()) :: {:ok, map()}
|
||||||
def terms(_parent, %{locale: locale}, _resolution) do
|
def terms(_parent, %{locale: locale}, _resolution) do
|
||||||
type = Config.instance_terms_type()
|
type = Config.instance_terms_type()
|
||||||
|
@ -98,6 +111,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||||
long_description: Config.instance_long_description(),
|
long_description: Config.instance_long_description(),
|
||||||
slogan: Config.instance_slogan(),
|
slogan: Config.instance_slogan(),
|
||||||
languages: Config.instance_languages(),
|
languages: Config.instance_languages(),
|
||||||
|
instance_logo: Config.instance_logo(),
|
||||||
|
primary_color: Config.primary_color(),
|
||||||
|
secondary_color: Config.secondary_color(),
|
||||||
|
default_picture: Config.default_picture(),
|
||||||
anonymous: %{
|
anonymous: %{
|
||||||
participation: %{
|
participation: %{
|
||||||
allowed: Config.anonymous_participation?(),
|
allowed: Config.anonymous_participation?(),
|
||||||
|
|
|
@ -18,6 +18,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||||
do_fetch_media(media_id)
|
do_fetch_media(media_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def media(%{media_id: media_id} = _parent, _args, _resolution) do
|
||||||
|
do_fetch_media(media_id)
|
||||||
|
end
|
||||||
|
|
||||||
def media(%{picture: media} = _parent, _args, _resolution), do: {:ok, media}
|
def media(%{picture: media} = _parent, _args, _resolution), do: {:ok, media}
|
||||||
def media(_parent, %{id: media_id}, _resolution), do: do_fetch_media(media_id)
|
def media(_parent, %{id: media_id}, _resolution), do: do_fetch_media(media_id)
|
||||||
def media(_parent, _args, _resolution), do: {:ok, nil}
|
def media(_parent, _args, _resolution), do: {:ok, nil}
|
||||||
|
@ -133,8 +137,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||||
|
|
||||||
def user_size(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
def user_size(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
||||||
|
|
||||||
@spec transform_media(Media.t()) :: map()
|
@spec transform_media(Media.t() | nil) :: map() | nil
|
||||||
defp transform_media(%Media{id: id, file: file, metadata: metadata}) do
|
def transform_media(nil), do: nil
|
||||||
|
|
||||||
|
def transform_media(%Media{id: id, file: file, metadata: metadata}) do
|
||||||
%{
|
%{
|
||||||
name: file.name,
|
name: file.name,
|
||||||
url: file.url,
|
url: file.url,
|
||||||
|
|
|
@ -124,6 +124,24 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
||||||
field(:instance_terms_type, :instance_terms_type, description: "The instance's terms type")
|
field(:instance_terms_type, :instance_terms_type, description: "The instance's terms type")
|
||||||
field(:instance_terms_url, :string, description: "The instance's terms URL")
|
field(:instance_terms_url, :string, description: "The instance's terms URL")
|
||||||
|
|
||||||
|
field(:instance_logo, :media,
|
||||||
|
description: "The instance's logo",
|
||||||
|
resolve: &Admin.get_instance_logo/3
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:instance_favicon, :media,
|
||||||
|
description: "The instance's favicon",
|
||||||
|
resolve: &Admin.get_instance_favicon/3
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:default_picture, :media,
|
||||||
|
description: "The default picture",
|
||||||
|
resolve: &Admin.get_default_picture/3
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:primary_color, :string, description: "The instance's primary color")
|
||||||
|
field(:secondary_color, :string, description: "The instance's secondary color")
|
||||||
|
|
||||||
field(:instance_privacy_policy, :string,
|
field(:instance_privacy_policy, :string,
|
||||||
description: "The instance's privacy policy body text"
|
description: "The instance's privacy policy body text"
|
||||||
)
|
)
|
||||||
|
@ -412,6 +430,25 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
||||||
arg(:instance_long_description, :string, description: "The instance's long description")
|
arg(:instance_long_description, :string, description: "The instance's long description")
|
||||||
arg(:instance_slogan, :string, description: "The instance's slogan")
|
arg(:instance_slogan, :string, description: "The instance's slogan")
|
||||||
arg(:contact, :string, description: "The instance's contact details")
|
arg(:contact, :string, description: "The instance's contact details")
|
||||||
|
|
||||||
|
arg(:instance_logo, :media_input,
|
||||||
|
description:
|
||||||
|
"The instance's logo, either as an object or directly the ID of an existing media"
|
||||||
|
)
|
||||||
|
|
||||||
|
arg(:instance_favicon, :media_input,
|
||||||
|
description:
|
||||||
|
"The instance's favicon, either as an object or directly the ID of an existing media"
|
||||||
|
)
|
||||||
|
|
||||||
|
arg(:default_picture, :media_input,
|
||||||
|
description:
|
||||||
|
"The default picture, either as an object or directly the ID of an existing media"
|
||||||
|
)
|
||||||
|
|
||||||
|
arg(:primary_color, :string, description: "The instance's primary color")
|
||||||
|
arg(:secondary_color, :string, description: "The instance's secondary color")
|
||||||
|
|
||||||
arg(:instance_terms, :string, description: "The instance's terms body text")
|
arg(:instance_terms, :string, description: "The instance's terms body text")
|
||||||
arg(:instance_terms_type, :instance_terms_type, description: "The instance's terms type")
|
arg(:instance_terms_type, :instance_terms_type, description: "The instance's terms type")
|
||||||
arg(:instance_terms_url, :string, description: "The instance's terms URL")
|
arg(:instance_terms_url, :string, description: "The instance's terms URL")
|
||||||
|
|
|
@ -59,6 +59,17 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||||
resolve(&Config.terms/3)
|
resolve(&Config.terms/3)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
field(:instance_logo, :media, description: "The instance's logo") do
|
||||||
|
resolve(&Config.instance_logo/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
field(:default_picture, :media, description: "The default picture") do
|
||||||
|
resolve(&Config.default_picture/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
field(:primary_color, :string, description: "The instance's primary color")
|
||||||
|
field(:secondary_color, :string, description: "The instance's secondary color")
|
||||||
|
|
||||||
field(:privacy, :privacy, description: "The instance's privacy policy") do
|
field(:privacy, :privacy, description: "The instance's privacy policy") do
|
||||||
arg(:locale, :string,
|
arg(:locale, :string,
|
||||||
default_value: "en",
|
default_value: "en",
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule Mobilizon.Admin do
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.{Admin, Users}
|
alias Mobilizon.{Admin, Users}
|
||||||
alias Mobilizon.Admin.ActionLog
|
alias Mobilizon.Admin.ActionLog
|
||||||
alias Mobilizon.Admin.Setting
|
alias Mobilizon.Admin.{Setting, SettingMedia}
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
alias Mobilizon.Storage.{Page, Repo}
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
|
@ -78,9 +79,47 @@ defmodule Mobilizon.Admin do
|
||||||
|
|
||||||
defp stringify_struct(struct), do: struct
|
defp stringify_struct(struct), do: struct
|
||||||
|
|
||||||
@spec get_all_admin_settings :: list(Setting.t())
|
@spec get_all_admin_settings :: map()
|
||||||
def get_all_admin_settings do
|
def get_all_admin_settings do
|
||||||
Repo.all(Setting)
|
medias =
|
||||||
|
SettingMedia
|
||||||
|
|> Repo.all()
|
||||||
|
|> Repo.preload(:media)
|
||||||
|
|> Enum.map(fn %SettingMedia{group: group, name: name, media: media} ->
|
||||||
|
{group, name, media}
|
||||||
|
end)
|
||||||
|
|
||||||
|
values =
|
||||||
|
Setting
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(fn %Setting{group: group, name: name, value: value} ->
|
||||||
|
{group, name, get_setting_value(value)}
|
||||||
|
end)
|
||||||
|
|
||||||
|
all_settings = Enum.concat(values, medias)
|
||||||
|
|
||||||
|
Enum.reduce(
|
||||||
|
all_settings,
|
||||||
|
%{},
|
||||||
|
# For each {group,name,value}
|
||||||
|
fn {group, name, value}, acc ->
|
||||||
|
# We update the %{group: map} in the accumulator
|
||||||
|
{_, new_acc} =
|
||||||
|
Map.get_and_update(
|
||||||
|
acc,
|
||||||
|
group,
|
||||||
|
# We put the %{name: value} into the %{group: map}
|
||||||
|
fn group_map ->
|
||||||
|
{
|
||||||
|
group_map,
|
||||||
|
Map.put(group_map || %{}, name, value)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
new_acc
|
||||||
|
end
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_admin_setting_value(String.t(), String.t(), String.t() | nil) ::
|
@spec get_admin_setting_value(String.t(), String.t(), String.t() | nil) ::
|
||||||
|
@ -119,21 +158,40 @@ defmodule Mobilizon.Admin do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_admin_setting_media(String.t(), String.t(), String.t() | nil) ::
|
||||||
|
{:ok, Media.t()} | {:error, :not_found} | nil
|
||||||
|
def get_admin_setting_media(group, name, fallback \\ nil)
|
||||||
|
when is_binary(group) and is_binary(name) do
|
||||||
|
case SettingMedia
|
||||||
|
|> where(group: ^group)
|
||||||
|
|> where(name: ^name)
|
||||||
|
|> preload(:media)
|
||||||
|
|> Repo.one() do
|
||||||
|
nil ->
|
||||||
|
fallback
|
||||||
|
|
||||||
|
%SettingMedia{media: media} ->
|
||||||
|
media
|
||||||
|
|
||||||
|
%SettingMedia{} ->
|
||||||
|
fallback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec save_settings(String.t(), map()) :: {:ok, any} | {:error, any}
|
@spec save_settings(String.t(), map()) :: {:ok, any} | {:error, any}
|
||||||
def save_settings(group, args) do
|
def save_settings(group, args) do
|
||||||
|
{medias, values} = Map.split(args, [:instance_logo, :instance_favicon, :default_picture])
|
||||||
|
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> do_save_setting(group, args)
|
|> do_save_media_setting(group, medias)
|
||||||
|
|> do_save_value_setting(group, values)
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_settings(group) do
|
@spec do_save_value_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
|
||||||
Setting |> where([s], s.group == ^group) |> Repo.delete_all()
|
defp do_save_value_setting(transaction, _group, args) when args == %{}, do: transaction
|
||||||
end
|
|
||||||
|
|
||||||
@spec do_save_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
|
defp do_save_value_setting(transaction, group, args) do
|
||||||
defp do_save_setting(transaction, _group, args) when args == %{}, do: transaction
|
|
||||||
|
|
||||||
defp do_save_setting(transaction, group, args) do
|
|
||||||
key = hd(Map.keys(args))
|
key = hd(Map.keys(args))
|
||||||
{val, rest} = Map.pop(args, key)
|
{val, rest} = Map.pop(args, key)
|
||||||
|
|
||||||
|
@ -150,7 +208,40 @@ defmodule Mobilizon.Admin do
|
||||||
conflict_target: [:group, :name]
|
conflict_target: [:group, :name]
|
||||||
)
|
)
|
||||||
|
|
||||||
do_save_setting(transaction, group, rest)
|
do_save_value_setting(transaction, group, rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec do_save_media_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
|
||||||
|
defp do_save_media_setting(transaction, _group, args) when args == %{}, do: transaction
|
||||||
|
|
||||||
|
defp do_save_media_setting(transaction, group, args) do
|
||||||
|
key = hd(Map.keys(args))
|
||||||
|
{val, rest} = Map.pop(args, key)
|
||||||
|
|
||||||
|
transaction =
|
||||||
|
case val do
|
||||||
|
val ->
|
||||||
|
Multi.insert(
|
||||||
|
transaction,
|
||||||
|
key,
|
||||||
|
SettingMedia.changeset(%SettingMedia{}, %{
|
||||||
|
group: group,
|
||||||
|
name: Atom.to_string(key),
|
||||||
|
media: val
|
||||||
|
}),
|
||||||
|
on_conflict: :replace_all,
|
||||||
|
conflict_target: [:group, :name]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
do_save_media_setting(transaction, group, rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_settings(group) do
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.delete_all(:settings, Setting |> where([s], s.group == ^group))
|
||||||
|
|> Multi.delete_all(:settings_medias, SettingMedia |> where([s], s.group == ^group))
|
||||||
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec convert_to_string(any()) :: String.t()
|
@spec convert_to_string(any()) :: String.t()
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Mobilizon.Admin.Setting do
|
||||||
"""
|
"""
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias Ecto.Changeset
|
||||||
|
|
||||||
@required_attrs [:group, :name]
|
@required_attrs [:group, :name]
|
||||||
@optional_attrs [:value]
|
@optional_attrs [:value]
|
||||||
|
@ -32,3 +33,93 @@ defmodule Mobilizon.Admin.Setting do
|
||||||
|> unique_constraint(:group, name: :admin_settings_group_name_index)
|
|> unique_constraint(:group, name: :admin_settings_group_name_index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule Mobilizon.Admin.SettingMedia do
|
||||||
|
@moduledoc """
|
||||||
|
A Key-Value settings table for media settings
|
||||||
|
"""
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
alias Ecto.Changeset
|
||||||
|
alias Mobilizon.Federation.ActivityPub.Relay
|
||||||
|
alias Mobilizon.Medias
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
|
||||||
|
@required_attrs [:group, :name]
|
||||||
|
|
||||||
|
@type t :: %{
|
||||||
|
group: String.t(),
|
||||||
|
name: String.t(),
|
||||||
|
media: Media.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
schema "admin_settings_medias" do
|
||||||
|
field(:group, :string)
|
||||||
|
field(:name, :string)
|
||||||
|
belongs_to(:media, Media, on_replace: :delete)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t()
|
||||||
|
def changeset(setting_media, attrs) do
|
||||||
|
setting_media
|
||||||
|
|> Repo.preload(:media)
|
||||||
|
|> cast(attrs, @required_attrs)
|
||||||
|
|> put_media(attrs)
|
||||||
|
|> validate_required(@required_attrs)
|
||||||
|
|> unique_constraint(:group, name: :admin_settings_medias_group_name_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
# # In case the provided media is an existing one
|
||||||
|
@spec put_media(Changeset.t(), map) :: Changeset.t()
|
||||||
|
defp put_media(%Changeset{} = changeset, %{media: %{media_id: id}}) do
|
||||||
|
%Media{} = media = Medias.get_media!(id)
|
||||||
|
put_assoc(changeset, :media, media)
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case it's a new media
|
||||||
|
defp put_media(%Changeset{} = changeset, %{media: %{media: media}}) do
|
||||||
|
{:ok, media} = upload_media(media)
|
||||||
|
put_assoc(changeset, :media, media)
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case there is no media
|
||||||
|
defp put_media(%Changeset{} = changeset, _media) do
|
||||||
|
put_assoc(changeset, :media, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
import Mobilizon.Web.Gettext
|
||||||
|
@spec upload_media(map) :: {:ok, Media.t()} | {:error, any}
|
||||||
|
defp upload_media(%{file: %Plug.Upload{} = file} = args) do
|
||||||
|
with {:ok,
|
||||||
|
%{
|
||||||
|
name: _name,
|
||||||
|
url: url,
|
||||||
|
content_type: content_type,
|
||||||
|
size: size
|
||||||
|
} = uploaded} <-
|
||||||
|
Mobilizon.Web.Upload.store(file),
|
||||||
|
args <-
|
||||||
|
args
|
||||||
|
|> Map.put(:url, url)
|
||||||
|
|> Map.put(:size, size)
|
||||||
|
|> Map.put(:content_type, content_type),
|
||||||
|
{:ok, media = %Media{}} <-
|
||||||
|
Medias.create_media(%{
|
||||||
|
file: args,
|
||||||
|
actor_id: Map.get(args, :actor_id, Relay.get_actor().id),
|
||||||
|
metadata: Map.take(uploaded, [:width, :height, :blurhash])
|
||||||
|
}) do
|
||||||
|
{:ok, media}
|
||||||
|
else
|
||||||
|
{:error, :mime_type_not_allowed} ->
|
||||||
|
{:error, dgettext("errors", "File doesn't have an allowed MIME type.")}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -4,7 +4,8 @@ defmodule Mobilizon.Config do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Admin.Setting
|
alias Mobilizon.Admin
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Service.GitStatus
|
alias Mobilizon.Service.GitStatus
|
||||||
require Logger
|
require Logger
|
||||||
import Mobilizon.Service.Export.Participants.Common, only: [enabled_formats: 0]
|
import Mobilizon.Service.Export.Participants.Common, only: [enabled_formats: 0]
|
||||||
|
@ -29,56 +30,18 @@ defmodule Mobilizon.Config do
|
||||||
@spec instance_config :: mobilizon_config
|
@spec instance_config :: mobilizon_config
|
||||||
def instance_config, do: Application.get_env(:mobilizon, :instance)
|
def instance_config, do: Application.get_env(:mobilizon, :instance)
|
||||||
|
|
||||||
@spec db_instance_config :: list(Setting.t())
|
|
||||||
def db_instance_config, do: Mobilizon.Admin.get_all_admin_settings()
|
|
||||||
|
|
||||||
@spec config_cache :: map()
|
@spec config_cache :: map()
|
||||||
def config_cache do
|
def config_cache do
|
||||||
case Cachex.fetch(:config, :all_db_config, fn _key ->
|
case Cachex.fetch(
|
||||||
value =
|
:config,
|
||||||
Enum.reduce(
|
:all_db_config,
|
||||||
Mobilizon.Admin.get_all_admin_settings(),
|
fn _key -> {:commit, Admin.get_all_admin_settings()} end
|
||||||
%{},
|
) do
|
||||||
&arrange_values/2
|
|
||||||
)
|
|
||||||
|
|
||||||
{:commit, value}
|
|
||||||
end) do
|
|
||||||
{status, value} when status in [:ok, :commit] -> value
|
{status, value} when status in [:ok, :commit] -> value
|
||||||
_err -> %{}
|
_err -> %{}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec arrange_values(Setting.t(), map()) :: map()
|
|
||||||
defp arrange_values(setting, acc) do
|
|
||||||
{_, new_data} =
|
|
||||||
Map.get_and_update(acc, setting.group, fn current_value ->
|
|
||||||
new_value = current_value || %{}
|
|
||||||
|
|
||||||
{current_value, Map.put(new_value, setting.name, process_value(setting.value))}
|
|
||||||
end)
|
|
||||||
|
|
||||||
new_data
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec process_value(String.t() | nil) :: any()
|
|
||||||
defp process_value(nil), do: nil
|
|
||||||
defp process_value(""), do: nil
|
|
||||||
|
|
||||||
defp process_value(value) do
|
|
||||||
case Jason.decode(value) do
|
|
||||||
{:ok, val} ->
|
|
||||||
val
|
|
||||||
|
|
||||||
{:error, _} ->
|
|
||||||
case value do
|
|
||||||
"true" -> true
|
|
||||||
"false" -> false
|
|
||||||
value -> value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec config_cached_value(String.t(), String.t(), String.t()) :: any()
|
@spec config_cached_value(String.t(), String.t(), String.t()) :: any()
|
||||||
def config_cached_value(group, name, fallback \\ nil) do
|
def config_cached_value(group, name, fallback \\ nil) do
|
||||||
config_cache()
|
config_cache()
|
||||||
|
@ -115,10 +78,23 @@ defmodule Mobilizon.Config do
|
||||||
@spec instance_slogan :: String.t() | nil
|
@spec instance_slogan :: String.t() | nil
|
||||||
def instance_slogan, do: config_cached_value("instance", "instance_slogan")
|
def instance_slogan, do: config_cached_value("instance", "instance_slogan")
|
||||||
|
|
||||||
|
@spec instance_logo :: Media.t() | nil
|
||||||
|
def instance_logo, do: config_cached_value("instance", "instance_logo")
|
||||||
|
|
||||||
|
@spec instance_favicon :: Media.t() | nil
|
||||||
|
def instance_favicon, do: config_cached_value("instance", "instance_favicon")
|
||||||
|
|
||||||
|
@spec default_picture :: Media.t() | nil
|
||||||
|
def default_picture, do: config_cached_value("instance", "default_picture")
|
||||||
|
|
||||||
|
@spec primary_color :: Media.t() | nil
|
||||||
|
def primary_color, do: config_cached_value("instance", "primary_color")
|
||||||
|
|
||||||
|
@spec secondary_color :: Media.t() | nil
|
||||||
|
def secondary_color, do: config_cached_value("instance", "secondary_color")
|
||||||
|
|
||||||
@spec contact :: String.t() | nil
|
@spec contact :: String.t() | nil
|
||||||
def contact do
|
def contact, do: config_cached_value("instance", "contact")
|
||||||
config_cached_value("instance", "contact")
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec instance_terms(String.t()) :: String.t()
|
@spec instance_terms(String.t()) :: String.t()
|
||||||
def instance_terms(locale \\ "en") do
|
def instance_terms(locale \\ "en") do
|
||||||
|
@ -470,6 +446,9 @@ defmodule Mobilizon.Config do
|
||||||
instance_slogan: instance_slogan(),
|
instance_slogan: instance_slogan(),
|
||||||
registrations_open: instance_registrations_open?(),
|
registrations_open: instance_registrations_open?(),
|
||||||
contact: contact(),
|
contact: contact(),
|
||||||
|
primary_color: primary_color(),
|
||||||
|
secondary_color: secondary_color(),
|
||||||
|
instance_logo: instance_logo(),
|
||||||
instance_terms: instance_terms(),
|
instance_terms: instance_terms(),
|
||||||
instance_terms_type: instance_terms_type(),
|
instance_terms_type: instance_terms_type(),
|
||||||
instance_terms_url: instance_terms_url(),
|
instance_terms_url: instance_terms_url(),
|
||||||
|
|
|
@ -185,7 +185,8 @@ defmodule Mobilizon.Medias do
|
||||||
[from: "events_medias", param: "media_id"],
|
[from: "events_medias", param: "media_id"],
|
||||||
[from: "posts", param: "picture_id"],
|
[from: "posts", param: "picture_id"],
|
||||||
[from: "posts_medias", param: "media_id"],
|
[from: "posts_medias", param: "media_id"],
|
||||||
[from: "comments_medias", param: "media_id"]
|
[from: "comments_medias", param: "media_id"],
|
||||||
|
[from: "admin_settings_medias", param: "media_id"]
|
||||||
]
|
]
|
||||||
|> Enum.map_join(" UNION ", fn [from: from, param: param] ->
|
|> Enum.map_join(" UNION ", fn [from: from, param: param] ->
|
||||||
"SELECT 1 FROM #{from} WHERE #{from}.#{param} = m0.id"
|
"SELECT 1 FROM #{from} WHERE #{from}.#{param} = m0.id"
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
defmodule Mobilizon.Web.ManifestController do
|
||||||
|
use Mobilizon.Web, :controller
|
||||||
|
|
||||||
|
alias Mobilizon.Config
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
|
|
||||||
|
@spec manifest(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||||
|
def manifest(conn, _params) do
|
||||||
|
favicons =
|
||||||
|
case Config.instance_favicon() do
|
||||||
|
%Media{file: %{url: url}, metadata: metadata} ->
|
||||||
|
[
|
||||||
|
Map.merge(
|
||||||
|
%{
|
||||||
|
src: url
|
||||||
|
},
|
||||||
|
case metadata do
|
||||||
|
%{width: width} -> %{sizes: "#{width}x#{width}"}
|
||||||
|
_ -> %{}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
src: "./img/icons/android-chrome-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
src: "./img/icons/android-chrome-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
json(conn, %{
|
||||||
|
name: Config.instance_name(),
|
||||||
|
start_url: "/",
|
||||||
|
scope: "/",
|
||||||
|
display: "standalone",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
theme_color: "#ffd599",
|
||||||
|
orientation: "portrait-primary",
|
||||||
|
icons: favicons
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec favicon(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||||
|
def favicon(conn, _params) do
|
||||||
|
case Config.instance_favicon() do
|
||||||
|
%Media{file: %{url: url}} -> redirect(conn, external: url)
|
||||||
|
_ -> redirect(conn, to: "/img/icons/favicon.ico")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,8 +18,7 @@ defmodule Mobilizon.Web do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def static_paths,
|
def static_paths,
|
||||||
do:
|
do: ~w(index.html service-worker.js css fonts img js robots.txt assets)
|
||||||
~w(index.html manifest.json manifest.webmanifest service-worker.js css fonts img js favicon.ico robots.txt assets)
|
|
||||||
|
|
||||||
def controller do
|
def controller do
|
||||||
quote do
|
quote do
|
||||||
|
|
|
@ -113,6 +113,12 @@ defmodule Mobilizon.Web.Router do
|
||||||
get("/nodeinfo/:version", NodeInfoController, :nodeinfo)
|
get("/nodeinfo/:version", NodeInfoController, :nodeinfo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/", Mobilizon.Web do
|
||||||
|
get("/manifest.webmanifest", ManifestController, :manifest)
|
||||||
|
get("/manifest.json", ManifestController, :manifest)
|
||||||
|
get("/favicon.ico", ManifestController, :favicon)
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", Mobilizon.Web do
|
scope "/", Mobilizon.Web do
|
||||||
pipe_through(:activity_pub_and_html)
|
pipe_through(:activity_pub_and_html)
|
||||||
pipe_through(:activity_pub_signature)
|
pipe_through(:activity_pub_signature)
|
||||||
|
|
|
@ -4,15 +4,16 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
<link rel="apple-touch-icon" href={favicon_url()} sizes={favicon_sizes()} />
|
||||||
|
<link rel="icon" href={favicon_url()} sizes={favicon_sizes()} />
|
||||||
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<meta name="theme-color" content={theme_color()} />
|
<meta name="theme-color" content={theme_color()} />
|
||||||
<script>
|
<script>
|
||||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add('dark')
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<%= if root?(assigns) do %>
|
<%= if root?(assigns) do %>
|
||||||
|
@ -24,6 +25,7 @@
|
||||||
<%= Vite.vite_client() %>
|
<%= Vite.vite_client() %>
|
||||||
<%= Vite.vite_snippet("src/main.ts") %>
|
<%= Vite.vite_snippet("src/main.ts") %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>
|
<strong>
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Mobilizon.Web.PageView do
|
||||||
use Mobilizon.Web, :view
|
use Mobilizon.Web, :view
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Config
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Posts.Post
|
alias Mobilizon.Posts.Post
|
||||||
|
@ -91,4 +92,27 @@ defmodule Mobilizon.Web.PageView do
|
||||||
def root?(assigns) do
|
def root?(assigns) do
|
||||||
assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/"
|
assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp favicon do
|
||||||
|
case Config.instance_favicon() do
|
||||||
|
%{file: %{url: url}, metadata: metadata} ->
|
||||||
|
%{
|
||||||
|
src: url,
|
||||||
|
sizes:
|
||||||
|
case metadata do
|
||||||
|
%{width: width} -> "#{width}x#{width}"
|
||||||
|
_ -> "any"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
%{
|
||||||
|
src: "/img/icons/apple-touch-icon-152x152.png",
|
||||||
|
sizes: "152x152"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def favicon_url, do: Map.get(favicon(), :src)
|
||||||
|
def favicon_sizes, do: Map.get(favicon(), :sizes)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.CreateAdminSettingsMedias do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:admin_settings_medias) do
|
||||||
|
add(:group, :string)
|
||||||
|
add(:name, :string)
|
||||||
|
add(:media_id, references(:medias, on_delete: :delete_all))
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create(unique_index(:admin_settings_medias, [:group, :name]))
|
||||||
|
end
|
||||||
|
end
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -169,6 +169,18 @@ type Config {
|
||||||
"The instance's slogan"
|
"The instance's slogan"
|
||||||
slogan: String
|
slogan: String
|
||||||
|
|
||||||
|
"The instance's logo"
|
||||||
|
instanceLogo: Media
|
||||||
|
|
||||||
|
"The default picture"
|
||||||
|
defaultPicture: Media
|
||||||
|
|
||||||
|
"The instance's primary color"
|
||||||
|
primaryColor: String
|
||||||
|
|
||||||
|
"The instance's secondary color"
|
||||||
|
secondaryColor: String
|
||||||
|
|
||||||
"The instance's contact details"
|
"The instance's contact details"
|
||||||
contact: String
|
contact: String
|
||||||
|
|
||||||
|
@ -1878,6 +1890,15 @@ type RootMutationType {
|
||||||
"The instance's contact details"
|
"The instance's contact details"
|
||||||
contact: String
|
contact: String
|
||||||
|
|
||||||
|
"The instance's logo"
|
||||||
|
instanceLogo: MediaInput
|
||||||
|
|
||||||
|
"The instance's favicon"
|
||||||
|
instanceFavicon: MediaInput
|
||||||
|
|
||||||
|
"The default picture"
|
||||||
|
defaultPicture: MediaInput
|
||||||
|
|
||||||
"The instance's terms body text"
|
"The instance's terms body text"
|
||||||
instanceTerms: String
|
instanceTerms: String
|
||||||
|
|
||||||
|
@ -3698,6 +3719,15 @@ type AdminSettings {
|
||||||
"The instance's contact details"
|
"The instance's contact details"
|
||||||
contact: String
|
contact: String
|
||||||
|
|
||||||
|
"The instance's logo"
|
||||||
|
instanceLogo: Media
|
||||||
|
|
||||||
|
"The instance's favicon"
|
||||||
|
instanceFavicon: Media
|
||||||
|
|
||||||
|
"The default picture"
|
||||||
|
defaultPicture: Media
|
||||||
|
|
||||||
"The instance's terms body text"
|
"The instance's terms body text"
|
||||||
instanceTerms: String
|
instanceTerms: String
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ import { IAnalyticsConfig, IConfig } from "@/types/config.model";
|
||||||
import { computed, defineAsyncComponent, ref } from "vue";
|
import { computed, defineAsyncComponent, ref } from "vue";
|
||||||
import { useQuery, useQueryLoading } from "@vue/apollo-composable";
|
import { useQuery, useQueryLoading } from "@vue/apollo-composable";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useAnalytics } from "@/composition/apollo/config";
|
import { useAnalytics } from "@/composition/apollo/config";
|
||||||
import { INSTANCE_NAME } from "@/graphql/config";
|
import { INSTANCE_NAME } from "@/graphql/config";
|
||||||
const SentryFeedback = defineAsyncComponent(
|
const SentryFeedback = defineAsyncComponent(
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { useGroup } from "@/composition/apollo/group";
|
||||||
import { displayName } from "@/types/actor";
|
import { displayName } from "@/types/actor";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
preferredUsername: string;
|
preferredUsername: string;
|
||||||
|
|
|
@ -11,8 +11,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { IMedia } from "@/types/media.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
|
import { useDefaultPicture } from "@/composition/apollo/config";
|
||||||
import LazyImage from "../Image/LazyImage.vue";
|
import LazyImage from "../Image/LazyImage.vue";
|
||||||
|
|
||||||
|
const { defaultPicture } = useDefaultPicture();
|
||||||
|
|
||||||
const DEFAULT_CARD_URL = "/img/mobilizon_default_card.png";
|
const DEFAULT_CARD_URL = "/img/mobilizon_default_card.png";
|
||||||
const DEFAULT_BLURHASH = "MCHKI4El-P-U}+={R-WWoes,Iu-P=?R,xD";
|
const DEFAULT_BLURHASH = "MCHKI4El-P-U}+={R-WWoes,Iu-P=?R,xD";
|
||||||
const DEFAULT_WIDTH = 630;
|
const DEFAULT_WIDTH = 630;
|
||||||
|
@ -38,6 +41,9 @@ const props = withDefaults(
|
||||||
|
|
||||||
const pictureOrDefault = computed(() => {
|
const pictureOrDefault = computed(() => {
|
||||||
if (props.picture === null) {
|
if (props.picture === null) {
|
||||||
|
if (defaultPicture?.value?.url) {
|
||||||
|
return defaultPicture.value;
|
||||||
|
}
|
||||||
return DEFAULT_PICTURE;
|
return DEFAULT_PICTURE;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
class="bg-white dark:bg-zinc-900 dark:fill-white"
|
v-if="!instanceLogoUrl"
|
||||||
|
class="bg-white dark:bg-zinc-900 dark:fill-white max-h-12"
|
||||||
:class="{ 'bg-gray-900': invert }"
|
:class="{ 'bg-gray-900': invert }"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 248.16 46.78"
|
viewBox="0 0 248.16 46.78"
|
||||||
|
@ -30,9 +31,14 @@
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
<img v-else alt="" class="max-h-12 w-auto" :src="instanceLogoUrl" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useInstanceLogoUrl } from "@/composition/apollo/config";
|
||||||
|
|
||||||
|
const { instanceLogoUrl } = useInstanceLogoUrl();
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
invert?: boolean;
|
invert?: boolean;
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900"
|
class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900"
|
||||||
id="navbar"
|
id="navbar"
|
||||||
>
|
>
|
||||||
<div
|
<div class="container mx-auto flex flex-wrap items-center gap-2 sm:gap-4">
|
||||||
class="container mx-auto flex flex-wrap items-center mx-auto gap-2 sm:gap-4"
|
|
||||||
>
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.HOME }"
|
:to="{ name: RouteName.HOME }"
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
|
|
@ -45,7 +45,7 @@ import { LEAVE_EVENT } from "../../graphql/event";
|
||||||
import { computed, ref, watchEffect } from "vue";
|
import { computed, ref, watchEffect } from "vue";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { IActor } from "@/types/actor";
|
import { IActor } from "@/types/actor";
|
||||||
import { IEvent } from "@/types/event.model";
|
import { IEvent } from "@/types/event.model";
|
||||||
import { useAnonymousActorId } from "@/composition/apollo/config";
|
import { useAnonymousActorId } from "@/composition/apollo/config";
|
||||||
|
|
|
@ -70,7 +70,7 @@ import { CONFIRM_PARTICIPATION } from "../../graphql/event";
|
||||||
import { computed, ref, watchEffect } from "vue";
|
import { computed, ref, watchEffect } from "vue";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
|
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
|
||||||
import { useFetchEvent } from "@/composition/apollo/event";
|
import { useFetchEvent } from "@/composition/apollo/event";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ import { useFetchEventBasic } from "@/composition/apollo/event";
|
||||||
import { useAnonymousActorId } from "@/composition/apollo/config";
|
import { useAnonymousActorId } from "@/composition/apollo/config";
|
||||||
import { computed, reactive, ref } from "vue";
|
import { computed, reactive, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
|
||||||
const error = ref<boolean | string>(false);
|
const error = ref<boolean | string>(false);
|
||||||
|
|
|
@ -99,7 +99,7 @@ import { useFetchEvent } from "@/composition/apollo/event";
|
||||||
import { useAnonymousParticipationConfig } from "@/composition/apollo/config";
|
import { useAnonymousParticipationConfig } from "@/composition/apollo/config";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = defineProps<{ uuid: string }>();
|
const props = defineProps<{ uuid: string }>();
|
||||||
|
|
|
@ -4,11 +4,14 @@ import {
|
||||||
ANONYMOUS_ACTOR_ID,
|
ANONYMOUS_ACTOR_ID,
|
||||||
ANONYMOUS_PARTICIPATION_CONFIG,
|
ANONYMOUS_PARTICIPATION_CONFIG,
|
||||||
ANONYMOUS_REPORTS_CONFIG,
|
ANONYMOUS_REPORTS_CONFIG,
|
||||||
|
DEFAULT_PICTURE,
|
||||||
DEMO_MODE,
|
DEMO_MODE,
|
||||||
EVENT_CATEGORIES,
|
EVENT_CATEGORIES,
|
||||||
EVENT_PARTICIPANTS,
|
EVENT_PARTICIPANTS,
|
||||||
FEATURES,
|
FEATURES,
|
||||||
GEOCODING_AUTOCOMPLETE,
|
GEOCODING_AUTOCOMPLETE,
|
||||||
|
COLORS,
|
||||||
|
INSTANCE_LOGO,
|
||||||
LOCATION,
|
LOCATION,
|
||||||
MAPS_TILES,
|
MAPS_TILES,
|
||||||
REGISTRATIONS,
|
REGISTRATIONS,
|
||||||
|
@ -76,6 +79,36 @@ export function useInstanceName() {
|
||||||
return { instanceName, error, loading };
|
return { instanceName, error, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useInstanceLogoUrl() {
|
||||||
|
const { result, error, loading } = useQuery<{
|
||||||
|
config: Pick<IConfig, "instanceLogo">;
|
||||||
|
}>(INSTANCE_LOGO);
|
||||||
|
|
||||||
|
const instanceLogoUrl = computed(
|
||||||
|
() => result.value?.config?.instanceLogo?.url
|
||||||
|
);
|
||||||
|
return { instanceLogoUrl, error, loading };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColors() {
|
||||||
|
const { result, error, loading } = useQuery<{
|
||||||
|
config: Pick<IConfig, "primaryColor" | "secondaryColor">;
|
||||||
|
}>(COLORS);
|
||||||
|
|
||||||
|
const primaryColor = computed(() => result.value?.config?.primaryColor);
|
||||||
|
const secondaryColor = computed(() => result.value?.config?.secondaryColor);
|
||||||
|
return { primaryColor, secondaryColor, error, loading };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDefaultPicture() {
|
||||||
|
const { result, error, loading } = useQuery<{
|
||||||
|
config: Pick<IConfig, "defaultPicture">;
|
||||||
|
}>(DEFAULT_PICTURE);
|
||||||
|
|
||||||
|
const defaultPicture = computed(() => result.value?.config?.defaultPicture);
|
||||||
|
return { defaultPicture, error, loading };
|
||||||
|
}
|
||||||
|
|
||||||
export function useAnonymousActorId() {
|
export function useAnonymousActorId() {
|
||||||
const { result, error, loading } = useQuery<{
|
const { result, error, loading } = useQuery<{
|
||||||
config: Pick<IConfig, "anonymous">;
|
config: Pick<IConfig, "anonymous">;
|
||||||
|
|
|
@ -4,6 +4,12 @@ export const useHost = (): string => {
|
||||||
return window.location.hostname;
|
return window.location.hostname;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDefaultMaxSize = (): number | undefined => {
|
||||||
|
const { uploadLimits } = useUploadLimits();
|
||||||
|
|
||||||
|
return uploadLimits.value?.default;
|
||||||
|
};
|
||||||
|
|
||||||
export const useAvatarMaxSize = (): number | undefined => {
|
export const useAvatarMaxSize = (): number | undefined => {
|
||||||
const { uploadLimits } = useUploadLimits();
|
const { uploadLimits } = useUploadLimits();
|
||||||
|
|
||||||
|
|
|
@ -195,6 +195,23 @@ export const ADMIN_SETTINGS_FRAGMENT = gql`
|
||||||
instanceLongDescription
|
instanceLongDescription
|
||||||
instanceSlogan
|
instanceSlogan
|
||||||
contact
|
contact
|
||||||
|
instanceLogo {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
name
|
||||||
|
}
|
||||||
|
instanceFavicon {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
name
|
||||||
|
}
|
||||||
|
defaultPicture {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
name
|
||||||
|
}
|
||||||
|
primaryColor
|
||||||
|
secondaryColor
|
||||||
instanceTerms
|
instanceTerms
|
||||||
instanceTermsType
|
instanceTermsType
|
||||||
instanceTermsUrl
|
instanceTermsUrl
|
||||||
|
@ -223,6 +240,11 @@ export const SAVE_ADMIN_SETTINGS = gql`
|
||||||
$instanceLongDescription: String
|
$instanceLongDescription: String
|
||||||
$instanceSlogan: String
|
$instanceSlogan: String
|
||||||
$contact: String
|
$contact: String
|
||||||
|
$instanceLogo: MediaInput
|
||||||
|
$instanceFavicon: MediaInput
|
||||||
|
$defaultPicture: MediaInput
|
||||||
|
$primaryColor: String
|
||||||
|
$secondaryColor: String
|
||||||
$instanceTerms: String
|
$instanceTerms: String
|
||||||
$instanceTermsType: InstanceTermsType
|
$instanceTermsType: InstanceTermsType
|
||||||
$instanceTermsUrl: String
|
$instanceTermsUrl: String
|
||||||
|
@ -239,6 +261,11 @@ export const SAVE_ADMIN_SETTINGS = gql`
|
||||||
instanceLongDescription: $instanceLongDescription
|
instanceLongDescription: $instanceLongDescription
|
||||||
instanceSlogan: $instanceSlogan
|
instanceSlogan: $instanceSlogan
|
||||||
contact: $contact
|
contact: $contact
|
||||||
|
instanceLogo: $instanceLogo
|
||||||
|
instanceFavicon: $instanceFavicon
|
||||||
|
defaultPicture: $defaultPicture
|
||||||
|
primaryColor: $primaryColor
|
||||||
|
secondaryColor: $secondaryColor
|
||||||
instanceTerms: $instanceTerms
|
instanceTerms: $instanceTerms
|
||||||
instanceTermsType: $instanceTermsType
|
instanceTermsType: $instanceTermsType
|
||||||
instanceTermsUrl: $instanceTermsUrl
|
instanceTermsUrl: $instanceTermsUrl
|
||||||
|
|
|
@ -12,6 +12,21 @@ export const CONFIG = gql`
|
||||||
demoMode
|
demoMode
|
||||||
countryCode
|
countryCode
|
||||||
languages
|
languages
|
||||||
|
primaryColor
|
||||||
|
secondaryColor
|
||||||
|
instanceLogo {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
defaultPicture {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
name
|
||||||
|
metadata {
|
||||||
|
width
|
||||||
|
height
|
||||||
|
blurhash
|
||||||
|
}
|
||||||
|
}
|
||||||
eventCategories {
|
eventCategories {
|
||||||
id
|
id
|
||||||
label
|
label
|
||||||
|
@ -454,6 +469,42 @@ export const SEARCH_CONFIG = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const INSTANCE_LOGO = gql`
|
||||||
|
query InstanceLogo {
|
||||||
|
config {
|
||||||
|
instanceLogo {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const COLORS = gql`
|
||||||
|
query Colors {
|
||||||
|
config {
|
||||||
|
primaryColor
|
||||||
|
secondaryColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DEFAULT_PICTURE = gql`
|
||||||
|
query DefaultPicture {
|
||||||
|
config {
|
||||||
|
defaultPicture {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
name
|
||||||
|
metadata {
|
||||||
|
width
|
||||||
|
height
|
||||||
|
blurhash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const REGISTRATIONS = gql`
|
export const REGISTRATIONS = gql`
|
||||||
query Registrations {
|
query Registrations {
|
||||||
config {
|
config {
|
||||||
|
|
|
@ -416,6 +416,14 @@
|
||||||
"No one is participating|One person participating|{going} people participating": "No one is participating|One person participating|{going} people participating",
|
"No one is participating|One person participating|{going} people participating": "No one is participating|One person participating|{going} people participating",
|
||||||
"Date and time": "Date and time",
|
"Date and time": "Date and time",
|
||||||
"Location": "Location",
|
"Location": "Location",
|
||||||
|
"Logo": "Logo",
|
||||||
|
"Logo of the instance. Defaults to the upstream Mobilizon logo.": "Logo of the instance. Defaults to the upstream Mobilizon logo.",
|
||||||
|
"Favicon": "Favicon",
|
||||||
|
"Browser tab icon and PWA icon of the instance. Defaults to the upstream Mobilizon icon.": "Browser tab icon and PWA icon of the instance. Defaults to the upstream Mobilizon icon.",
|
||||||
|
"Default Picture": "Default Picture",
|
||||||
|
"Default picture when an event or group doesn't have one.": "Default picture when an event or group doesn't have one.",
|
||||||
|
"Primary Color": "Primary Color",
|
||||||
|
"Secondary Color": "Secondary Color",
|
||||||
"No resources selected": "No resources selected|One resources selected|{count} resources selected",
|
"No resources selected": "No resources selected|One resources selected|{count} resources selected",
|
||||||
"You have been invited by {invitedBy} to the following group:": "You have been invited by {invitedBy} to the following group:",
|
"You have been invited by {invitedBy} to the following group:": "You have been invited by {invitedBy} to the following group:",
|
||||||
"Accept": "Accept",
|
"Accept": "Accept",
|
||||||
|
|
|
@ -627,6 +627,14 @@
|
||||||
"Local times ({timezone})": "Heures locales ({timezone})",
|
"Local times ({timezone})": "Heures locales ({timezone})",
|
||||||
"Locality": "Commune",
|
"Locality": "Commune",
|
||||||
"Location": "Lieu",
|
"Location": "Lieu",
|
||||||
|
"Logo": "Logo",
|
||||||
|
"Logo of the instance. Defaults to the upstream Mobilizon logo.": "Logo de l'instance.",
|
||||||
|
"Favicon": "Favicon",
|
||||||
|
"Browser tab icon and PWA icon of the instance. Defaults to the upstream Mobilizon icon.": "Icône de l'onglet du navigateur et de la progressive web app.",
|
||||||
|
"Default Picture": "Image par défaut",
|
||||||
|
"Default picture when an event or group doesn't have one.": "Image par défaut quand un évènement ou groupe n'en a pas.",
|
||||||
|
"Primary Color": "Couleur primaire",
|
||||||
|
"Secondary Color": "Couleur secondaire",
|
||||||
"Log in": "Se connecter",
|
"Log in": "Se connecter",
|
||||||
"Log out": "Se déconnecter",
|
"Log out": "Se déconnecter",
|
||||||
"Login": "Se connecter",
|
"Login": "Se connecter",
|
||||||
|
|
15
src/main.ts
15
src/main.ts
|
@ -57,6 +57,21 @@ apolloClient
|
||||||
})
|
})
|
||||||
.then(({ data: configData }) => {
|
.then(({ data: configData }) => {
|
||||||
instanceName.value = configData.config?.name;
|
instanceName.value = configData.config?.name;
|
||||||
|
|
||||||
|
const primaryColor = configData.config?.primaryColor;
|
||||||
|
if (primaryColor) {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--custom-primary",
|
||||||
|
primaryColor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const secondaryColor = configData.config?.secondaryColor;
|
||||||
|
if (secondaryColor) {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--custom-secondary",
|
||||||
|
secondaryColor
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const head = createHead();
|
const head = createHead();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { IEvent } from "@/types/event.model";
|
import type { IEvent } from "@/types/event.model";
|
||||||
|
import type { IMedia } from "@/types/media.model";
|
||||||
import type { IGroup } from "./actor";
|
import type { IGroup } from "./actor";
|
||||||
import { InstancePrivacyType, InstanceTermsType } from "./enums";
|
import { InstancePrivacyType, InstanceTermsType } from "./enums";
|
||||||
|
|
||||||
|
@ -25,6 +26,10 @@ export interface IAdminSettings {
|
||||||
instanceSlogan: string;
|
instanceSlogan: string;
|
||||||
instanceLongDescription: string;
|
instanceLongDescription: string;
|
||||||
contact: string;
|
contact: string;
|
||||||
|
instanceLogo: IMedia | null;
|
||||||
|
defaultPicture: IMedia | null;
|
||||||
|
primaryColor: string;
|
||||||
|
secondaryColor: string;
|
||||||
instanceTerms: string;
|
instanceTerms: string;
|
||||||
instanceTermsType: InstanceTermsType;
|
instanceTermsType: InstanceTermsType;
|
||||||
instanceTermsUrl: string | null;
|
instanceTermsUrl: string | null;
|
||||||
|
|
|
@ -37,6 +37,10 @@ export interface IConfig {
|
||||||
longDescription: string;
|
longDescription: string;
|
||||||
contact: string;
|
contact: string;
|
||||||
slogan: string;
|
slogan: string;
|
||||||
|
instanceLogo: { url: string };
|
||||||
|
defaultPicture: { url: string };
|
||||||
|
primaryColor: string;
|
||||||
|
secondaryColor: string;
|
||||||
|
|
||||||
registrationsOpen: boolean;
|
registrationsOpen: boolean;
|
||||||
registrationsAllowlist: boolean;
|
registrationsAllowlist: boolean;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { Ref } from "vue";
|
||||||
|
|
||||||
export interface IMedia {
|
export interface IMedia {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -21,3 +23,9 @@ export interface IMediaMetadata {
|
||||||
height?: number;
|
height?: number;
|
||||||
blurhash?: string;
|
blurhash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IModifiableMedia {
|
||||||
|
file: Ref<File | null>;
|
||||||
|
firstHash: string | null;
|
||||||
|
hash: string | null;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { provideApolloClient, useQuery } from "@vue/apollo-composable";
|
||||||
|
import { useHead as unHead } from "@unhead/vue";
|
||||||
|
import { apolloClient } from "@/vue-apollo";
|
||||||
|
import { IConfig } from "@/types/config.model";
|
||||||
|
import { ABOUT } from "@/graphql/config";
|
||||||
|
|
||||||
|
const { result } = provideApolloClient(apolloClient)(() =>
|
||||||
|
useQuery<{ config: Pick<IConfig, "name"> }>(ABOUT)
|
||||||
|
);
|
||||||
|
const instanceName = computed(() => result.value?.config?.name);
|
||||||
|
|
||||||
|
export function useHead(args: any) {
|
||||||
|
return unHead({
|
||||||
|
...args,
|
||||||
|
title: computed(() =>
|
||||||
|
args?.title?.value
|
||||||
|
? `${args.title.value} - ${instanceName.value}`
|
||||||
|
: instanceName.value
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { IMedia } from "@/types/media.model";
|
import { IMedia, IModifiableMedia } from "@/types/media.model";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
|
||||||
export async function buildFileFromIMedia(
|
export async function buildFileFromIMedia(
|
||||||
obj: IMedia | null | undefined
|
obj: IMedia | null | undefined
|
||||||
|
@ -29,18 +30,83 @@ export function buildFileVariable(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readFileAsync(
|
export function readFileAsync(file: File): Promise<ArrayBuffer | null> {
|
||||||
file: File
|
|
||||||
): Promise<string | ArrayBuffer | null> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
resolve(reader.result);
|
resolve(reader.result as ArrayBuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
|
|
||||||
reader.readAsBinaryString(file);
|
reader.readAsArrayBuffer(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fileHash(file: File): Promise<string | null> {
|
||||||
|
const data = await readFileAsync(file);
|
||||||
|
if (data === null) return null;
|
||||||
|
const hash = await crypto.subtle.digest("SHA-1", data);
|
||||||
|
const b64Hash = btoa(
|
||||||
|
Array.from(new Uint8Array(hash))
|
||||||
|
.map((b) => String.fromCharCode(b))
|
||||||
|
.join("")
|
||||||
|
);
|
||||||
|
return b64Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initWrappedMedia(): IModifiableMedia {
|
||||||
|
return {
|
||||||
|
file: ref<File | null>(null),
|
||||||
|
firstHash: null,
|
||||||
|
hash: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadWrappedMedia(
|
||||||
|
modifiableMedia: IModifiableMedia,
|
||||||
|
media: IMedia | null
|
||||||
|
) {
|
||||||
|
watch(modifiableMedia.file, async () => {
|
||||||
|
if (modifiableMedia.file.value) {
|
||||||
|
modifiableMedia.hash = await fileHash(modifiableMedia.file.value);
|
||||||
|
} else {
|
||||||
|
modifiableMedia.hash = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
modifiableMedia.file.value = await buildFileFromIMedia(media);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("catched error while building media", e);
|
||||||
|
}
|
||||||
|
if (modifiableMedia.file.value) {
|
||||||
|
modifiableMedia.firstHash = await fileHash(modifiableMedia.file.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asMediaInput(
|
||||||
|
mmedia: IModifiableMedia,
|
||||||
|
name: string,
|
||||||
|
fallbackId: number
|
||||||
|
): any {
|
||||||
|
const ret = {
|
||||||
|
[name]: {},
|
||||||
|
};
|
||||||
|
if (mmedia.file.value) {
|
||||||
|
if (mmedia.firstHash != mmedia.hash) {
|
||||||
|
ret[name] = {
|
||||||
|
media: {
|
||||||
|
name: mmedia.file.value?.name,
|
||||||
|
alt: "",
|
||||||
|
file: mmedia.file.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
ret[name] = {
|
||||||
|
mediaId: fallbackId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ import { IStatistics } from "../../types/statistics.model";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const { result: configResult } = useQuery<{ config: IConfig }>(ABOUT);
|
const { result: configResult } = useQuery<{ config: IConfig }>(ABOUT);
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { ABOUT } from "../../graphql/config";
|
import { ABOUT } from "../../graphql/config";
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { PRIVACY } from "@/graphql/config";
|
||||||
import { IConfig } from "@/types/config.model";
|
import { IConfig } from "@/types/config.model";
|
||||||
import { InstancePrivacyType } from "@/types/enums";
|
import { InstancePrivacyType } from "@/types/enums";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, watch } from "vue";
|
import { computed, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
import { RULES } from "@/graphql/config";
|
import { RULES } from "@/graphql/config";
|
||||||
import { IConfig } from "@/types/config.model";
|
import { IConfig } from "@/types/config.model";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { TERMS } from "@/graphql/config";
|
||||||
import { IConfig } from "@/types/config.model";
|
import { IConfig } from "@/types/config.model";
|
||||||
import { InstanceTermsType } from "@/types/enums";
|
import { InstanceTermsType } from "@/types/enums";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, watch } from "vue";
|
import { computed, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ import { useQuery } from "@vue/apollo-composable";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useCurrentUserClient } from "@/composition/apollo/user";
|
import { useCurrentUserClient } from "@/composition/apollo/user";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const { currentUser } = useCurrentUserClient();
|
const { currentUser } = useCurrentUserClient();
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ import { useRouter } from "vue-router";
|
||||||
import { registerAccount } from "@/composition/apollo/user";
|
import { registerAccount } from "@/composition/apollo/user";
|
||||||
import { convertToUsername } from "@/utils/username";
|
import { convertToUsername } from "@/utils/username";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { getValueFromMeta } from "@/utils/html";
|
import { getValueFromMeta } from "@/utils/html";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
|
|
@ -224,7 +224,7 @@ import { Dialog } from "@/plugins/dialog";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { AbsintheGraphQLErrors } from "@/types/errors.model";
|
import { AbsintheGraphQLErrors } from "@/types/errors.model";
|
||||||
import { ICurrentUser } from "@/types/current-user.model";
|
import { ICurrentUser } from "@/types/current-user.model";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
|
@ -336,7 +336,7 @@ import EmptyContent from "../../components/Utils/EmptyContent.vue";
|
||||||
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { computed, inject } from "vue";
|
import { computed, inject } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -319,7 +319,7 @@ import { MemberRole } from "@/types/enums";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -327,7 +327,7 @@ import { ADMIN_UPDATE_USER, LANGUAGES_CODES } from "@/graphql/admin";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { ILanguage } from "@/types/admin.model";
|
import { ILanguage } from "@/types/admin.model";
|
||||||
import { computed, inject, reactive, ref, watch } from "vue";
|
import { computed, inject, reactive, ref, watch } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { formatDateTimeString } from "@/filters/datetime";
|
import { formatDateTimeString } from "@/filters/datetime";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
|
@ -90,7 +90,7 @@ import RouteName from "@/router/name";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import NumberDashboardTile from "@/components/Dashboard/NumberDashboardTile.vue";
|
import NumberDashboardTile from "@/components/Dashboard/NumberDashboardTile.vue";
|
||||||
import LinkedNumberDashboardTile from "@/components/Dashboard/LinkedNumberDashboardTile.vue";
|
import LinkedNumberDashboardTile from "@/components/Dashboard/LinkedNumberDashboardTile.vue";
|
||||||
import { InstanceFilterFollowStatus } from "@/types/enums";
|
import { InstanceFilterFollowStatus } from "@/types/enums";
|
||||||
|
|
|
@ -119,7 +119,7 @@ import {
|
||||||
useRouteQuery,
|
useRouteQuery,
|
||||||
} from "vue-use-route-query";
|
} from "vue-use-route-query";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { Paginate } from "@/types/paginate";
|
import { Paginate } from "@/types/paginate";
|
||||||
import { IGroup } from "@/types/actor";
|
import { IGroup } from "@/types/actor";
|
||||||
|
|
|
@ -235,7 +235,7 @@ import {
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { computed, inject, ref, watch } from "vue";
|
import { computed, inject, ref, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import CloudQuestion from "../../../node_modules/vue-material-design-icons/CloudQuestion.vue";
|
import CloudQuestion from "../../../node_modules/vue-material-design-icons/CloudQuestion.vue";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import MastodonLogo from "@/components/Share/MastodonLogo.vue";
|
import MastodonLogo from "@/components/Share/MastodonLogo.vue";
|
||||||
|
|
|
@ -103,7 +103,7 @@ import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import {
|
import {
|
||||||
useRouteQuery,
|
useRouteQuery,
|
||||||
booleanTransformer,
|
booleanTransformer,
|
||||||
|
|
|
@ -58,6 +58,68 @@
|
||||||
</small>
|
</small>
|
||||||
<o-input v-model="settingsToWrite.contact" id="instance-contact" />
|
<o-input v-model="settingsToWrite.contact" id="instance-contact" />
|
||||||
</div>
|
</div>
|
||||||
|
<label class="field flex flex-col">
|
||||||
|
<p>{{ t("Logo") }}</p>
|
||||||
|
<small>
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"Logo of the instance. Defaults to the upstream Mobilizon logo."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</small>
|
||||||
|
<picture-upload
|
||||||
|
v-model:modelValue="instanceLogoFile"
|
||||||
|
:defaultImage="settingsToWrite.instanceLogo"
|
||||||
|
:textFallback="t('Logo')"
|
||||||
|
:maxSize="maxSize"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field flex flex-col">
|
||||||
|
<p>{{ t("Favicon") }}</p>
|
||||||
|
<small>
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"Browser tab icon and PWA icon of the instance. Defaults to the upstream Mobilizon icon."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</small>
|
||||||
|
<picture-upload
|
||||||
|
v-model:modelValue="instanceFaviconFile"
|
||||||
|
:defaultImage="settingsToWrite.instanceFavicon"
|
||||||
|
:textFallback="t('Favicon')"
|
||||||
|
:maxSize="maxSize"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field flex flex-col">
|
||||||
|
<p>{{ t("Default Picture") }}</p>
|
||||||
|
<small>
|
||||||
|
{{ t("Default picture when an event or group doesn't have one.") }}
|
||||||
|
</small>
|
||||||
|
<picture-upload
|
||||||
|
v-model:modelValue="defaultPictureFile"
|
||||||
|
:defaultImage="settingsToWrite.defaultPicture"
|
||||||
|
:textFallback="t('Default Picture')"
|
||||||
|
:maxSize="maxSize"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div class="field flex flex-col">
|
||||||
|
<label class="" for="primary-color">{{ t("Primary Color") }}</label>
|
||||||
|
<o-input
|
||||||
|
type="color"
|
||||||
|
v-model="settingsToWrite.primaryColor"
|
||||||
|
id="primary-color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field flex flex-col">
|
||||||
|
<label class="" for="secondary-color">{{
|
||||||
|
t("Secondary Color")
|
||||||
|
}}</label>
|
||||||
|
<o-input
|
||||||
|
type="color"
|
||||||
|
v-model="settingsToWrite.secondaryColor"
|
||||||
|
id="secondary-color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<o-field :label="t('Allow registrations')">
|
<o-field :label="t('Allow registrations')">
|
||||||
<o-switch v-model="settingsToWrite.registrationsOpen">
|
<o-switch v-model="settingsToWrite.registrationsOpen">
|
||||||
<p
|
<p
|
||||||
|
@ -389,15 +451,29 @@ import RouteName from "@/router/name";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { ref, computed, watch, inject } from "vue";
|
import { ref, computed, watch, inject } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import type { Notifier } from "@/plugins/notifier";
|
import type { Notifier } from "@/plugins/notifier";
|
||||||
|
|
||||||
|
// Media upload related
|
||||||
|
import PictureUpload from "@/components/PictureUpload.vue";
|
||||||
|
import {
|
||||||
|
initWrappedMedia,
|
||||||
|
loadWrappedMedia,
|
||||||
|
asMediaInput,
|
||||||
|
} from "@/utils/image";
|
||||||
|
import { useDefaultMaxSize } from "@/composition/config";
|
||||||
|
|
||||||
const defaultAdminSettings: IAdminSettings = {
|
const defaultAdminSettings: IAdminSettings = {
|
||||||
instanceName: "",
|
instanceName: "",
|
||||||
instanceDescription: "",
|
instanceDescription: "",
|
||||||
instanceSlogan: "",
|
instanceSlogan: "",
|
||||||
instanceLongDescription: "",
|
instanceLongDescription: "",
|
||||||
contact: "",
|
contact: "",
|
||||||
|
instanceLogo: null,
|
||||||
|
instanceFavicon: null,
|
||||||
|
defaultPicture: null,
|
||||||
|
primaryColor: "",
|
||||||
|
secondaryColor: "",
|
||||||
instanceTerms: "",
|
instanceTerms: "",
|
||||||
instanceTermsType: InstanceTermsType.DEFAULT,
|
instanceTermsType: InstanceTermsType.DEFAULT,
|
||||||
instanceTermsUrl: null,
|
instanceTermsUrl: null,
|
||||||
|
@ -409,12 +485,30 @@ const defaultAdminSettings: IAdminSettings = {
|
||||||
instanceLanguages: [],
|
instanceLanguages: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const { result: adminSettingsResult } = useQuery<{
|
const { onResult: onAdminSettingsResult } = useQuery<{
|
||||||
adminSettings: IAdminSettings;
|
adminSettings: IAdminSettings;
|
||||||
}>(ADMIN_SETTINGS);
|
}>(ADMIN_SETTINGS);
|
||||||
const adminSettings = computed(
|
|
||||||
() => adminSettingsResult.value?.adminSettings ?? defaultAdminSettings
|
const adminSettings = ref<IAdminSettings>();
|
||||||
);
|
|
||||||
|
onAdminSettingsResult(async ({ data }) => {
|
||||||
|
if (!data) return;
|
||||||
|
adminSettings.value =
|
||||||
|
{
|
||||||
|
...data.adminSettings,
|
||||||
|
} ?? defaultAdminSettings;
|
||||||
|
|
||||||
|
loadWrappedMedia(instanceLogo, adminSettings.value.instanceLogo);
|
||||||
|
loadWrappedMedia(instanceFavicon, adminSettings.value.instanceFavicon);
|
||||||
|
loadWrappedMedia(defaultPicture, adminSettings.value.defaultPicture);
|
||||||
|
});
|
||||||
|
|
||||||
|
const instanceLogo = initWrappedMedia();
|
||||||
|
const { file: instanceLogoFile } = instanceLogo;
|
||||||
|
const instanceFavicon = initWrappedMedia();
|
||||||
|
const { file: instanceFaviconFile } = instanceFavicon;
|
||||||
|
const defaultPicture = initWrappedMedia();
|
||||||
|
const { file: defaultPictureFile } = defaultPicture;
|
||||||
|
|
||||||
const { result: languageResult } = useQuery<{ languages: ILanguage[] }>(
|
const { result: languageResult } = useQuery<{ languages: ILanguage[] }>(
|
||||||
LANGUAGES
|
LANGUAGES
|
||||||
|
@ -463,6 +557,9 @@ const {
|
||||||
} = useMutation(SAVE_ADMIN_SETTINGS);
|
} = useMutation(SAVE_ADMIN_SETTINGS);
|
||||||
|
|
||||||
saveAdminSettingsDone(() => {
|
saveAdminSettingsDone(() => {
|
||||||
|
instanceLogo.firstHash = instanceLogo.hash;
|
||||||
|
instanceFavicon.firstHash = instanceFavicon.hash;
|
||||||
|
defaultPicture.firstHash = defaultPicture.hash;
|
||||||
notifier?.success(t("Admin settings successfully saved.") as string);
|
notifier?.success(t("Admin settings successfully saved.") as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -472,11 +569,29 @@ saveAdminSettingsError((e) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSettings = async (): Promise<void> => {
|
const updateSettings = async (): Promise<void> => {
|
||||||
const variables = { ...settingsToWrite.value };
|
const variables = {
|
||||||
console.debug("updating settings with variables", variables);
|
...settingsToWrite.value,
|
||||||
|
...asMediaInput(
|
||||||
|
instanceLogo,
|
||||||
|
"instanceLogo",
|
||||||
|
adminSettings.value?.instanceLogo?.id
|
||||||
|
),
|
||||||
|
...asMediaInput(
|
||||||
|
instanceFavicon,
|
||||||
|
"instanceFavicon",
|
||||||
|
adminSettings.value?.instanceFavicon?.id
|
||||||
|
),
|
||||||
|
...asMediaInput(
|
||||||
|
defaultPicture,
|
||||||
|
"defaultPicture",
|
||||||
|
adminSettings.value?.defaultPicture?.id
|
||||||
|
),
|
||||||
|
};
|
||||||
saveAdminSettings(variables);
|
saveAdminSettings(variables);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const maxSize = useDefaultMaxSize();
|
||||||
|
|
||||||
const getFilteredLanguages = (text: string): void => {
|
const getFilteredLanguages = (text: string): void => {
|
||||||
filteredLanguages.value = languages.value
|
filteredLanguages.value = languages.value
|
||||||
? languages.value
|
? languages.value
|
||||||
|
|
|
@ -110,7 +110,7 @@ import { useQuery } from "@vue/apollo-composable";
|
||||||
import { ILanguage } from "@/types/admin.model";
|
import { ILanguage } from "@/types/admin.model";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { formatDateTimeString } from "@/filters/datetime";
|
import { formatDateTimeString } from "@/filters/datetime";
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ import {
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useEventCategories } from "@/composition/apollo/config";
|
import { useEventCategories } from "@/composition/apollo/config";
|
||||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ import {
|
||||||
import { PROFILE_CONVERSATIONS } from "@/graphql/event";
|
import { PROFILE_CONVERSATIONS } from "@/graphql/event";
|
||||||
import ConversationListItem from "../../components/Conversations/ConversationListItem.vue";
|
import ConversationListItem from "../../components/Conversations/ConversationListItem.vue";
|
||||||
import EmptyContent from "../../components/Utils/EmptyContent.vue";
|
import EmptyContent from "../../components/Utils/EmptyContent.vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { IPerson } from "@/types/actor";
|
import { IPerson } from "@/types/actor";
|
||||||
import { useOruga } from "@oruga-ui/oruga-next";
|
import { useOruga } from "@oruga-ui/oruga-next";
|
||||||
import { arrayTransformer } from "@/utils/route";
|
import { arrayTransformer } from "@/utils/route";
|
||||||
|
|
|
@ -189,7 +189,7 @@ import {
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useCurrentActorClient } from "../../composition/apollo/actor";
|
import { useCurrentActorClient } from "../../composition/apollo/actor";
|
||||||
import { AbsintheGraphQLError } from "../../types/errors.model";
|
import { AbsintheGraphQLError } from "../../types/errors.model";
|
||||||
|
|
|
@ -70,7 +70,7 @@ import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { IDiscussion } from "@/types/discussions";
|
import { IDiscussion } from "@/types/discussions";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { AbsintheGraphQLError } from "@/types/errors.model";
|
import { AbsintheGraphQLError } from "@/types/errors.model";
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ import {
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||||
import { AbsintheGraphQLError } from "@/types/errors.model";
|
import { AbsintheGraphQLError } from "@/types/errors.model";
|
||||||
|
|
|
@ -83,7 +83,7 @@ import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||||
import { usePersonStatusGroup } from "@/composition/apollo/actor";
|
import { usePersonStatusGroup } from "@/composition/apollo/actor";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouteQuery, integerTransformer } from "vue-use-route-query";
|
import { useRouteQuery, integerTransformer } from "vue-use-route-query";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
const page = useRouteQuery("page", 1, integerTransformer);
|
const page = useRouteQuery("page", 1, integerTransformer);
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ErrorCode } from "@/types/enums";
|
import { ErrorCode } from "@/types/enums";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouteQuery } from "vue-use-route-query";
|
import { useRouteQuery } from "vue-use-route-query";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import EventConversations from "../../components/Conversations/EventConversations.vue";
|
import EventConversations from "../../components/Conversations/EventConversations.vue";
|
||||||
import NewPrivateMessage from "../../components/Participation/NewPrivateMessage.vue";
|
import NewPrivateMessage from "../../components/Participation/NewPrivateMessage.vue";
|
||||||
import { useFetchEvent } from "@/composition/apollo/event";
|
import { useFetchEvent } from "@/composition/apollo/event";
|
||||||
|
|
|
@ -635,7 +635,7 @@ import {
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { Dialog } from "@/plugins/dialog";
|
import { Dialog } from "@/plugins/dialog";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useOruga } from "@oruga-ui/oruga-next";
|
import { useOruga } from "@oruga-ui/oruga-next";
|
||||||
import type { Locale } from "date-fns";
|
import type { Locale } from "date-fns";
|
||||||
import sortBy from "lodash/sortBy";
|
import sortBy from "lodash/sortBy";
|
||||||
|
|
|
@ -326,7 +326,7 @@ import {
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { AbsintheGraphQLErrors } from "@/types/errors.model";
|
import { AbsintheGraphQLErrors } from "@/types/errors.model";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const IntegrationTwitch = defineAsyncComponent(
|
const IntegrationTwitch = defineAsyncComponent(
|
||||||
() => import("@/components/Event/Integrations/TwitchIntegration.vue")
|
() => import("@/components/Event/Integrations/TwitchIntegration.vue")
|
||||||
|
|
|
@ -116,7 +116,7 @@ import {
|
||||||
useRouteQuery,
|
useRouteQuery,
|
||||||
} from "vue-use-route-query";
|
} from "vue-use-route-query";
|
||||||
import { MemberRole } from "@/types/enums";
|
import { MemberRole } from "@/types/enums";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const EVENTS_PAGE_LIMIT = 10;
|
const EVENTS_PAGE_LIMIT = 10;
|
||||||
|
|
|
@ -237,7 +237,7 @@ import {
|
||||||
import { Locale } from "date-fns";
|
import { Locale } from "date-fns";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRestrictions } from "@/composition/apollo/config";
|
import { useRestrictions } from "@/composition/apollo/config";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const EventParticipationCard = defineAsyncComponent(
|
const EventParticipationCard = defineAsyncComponent(
|
||||||
() => import("@/components/Event/EventParticipationCard.vue")
|
() => import("@/components/Event/EventParticipationCard.vue")
|
||||||
|
|
|
@ -284,7 +284,7 @@ import Incognito from "vue-material-design-icons/Incognito.vue";
|
||||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import Tag from "@/components/TagElement.vue";
|
import Tag from "@/components/TagElement.vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const PARTICIPANTS_PER_PAGE = 10;
|
const PARTICIPANTS_PER_PAGE = 10;
|
||||||
const MESSAGE_ELLIPSIS_LENGTH = 130;
|
const MESSAGE_ELLIPSIS_LENGTH = 130;
|
||||||
|
|
|
@ -231,7 +231,7 @@ import {
|
||||||
useHost,
|
useHost,
|
||||||
} from "@/composition/config";
|
} from "@/composition/config";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { Openness, GroupVisibility } from "@/types/enums";
|
import { Openness, GroupVisibility } from "@/types/enums";
|
||||||
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ import {
|
||||||
useRouteQuery,
|
useRouteQuery,
|
||||||
} from "vue-use-route-query";
|
} from "vue-use-route-query";
|
||||||
import { computed, inject } from "vue";
|
import { computed, inject } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { usePersonStatusGroup } from "@/composition/apollo/actor";
|
import { usePersonStatusGroup } from "@/composition/apollo/actor";
|
||||||
import { MemberRole } from "@/types/enums";
|
import { MemberRole } from "@/types/enums";
|
||||||
|
|
|
@ -250,7 +250,7 @@ import {
|
||||||
} from "@/graphql/member";
|
} from "@/graphql/member";
|
||||||
import { usernameWithDomain, displayName, IGroup } from "@/types/actor";
|
import { usernameWithDomain, displayName, IGroup } from "@/types/actor";
|
||||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
|
|
|
@ -208,7 +208,7 @@ import { DELETE_GROUP } from "@/graphql/group";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Dialog } from "@/plugins/dialog";
|
import { Dialog } from "@/plugins/dialog";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
|
|
||||||
const Editor = defineAsyncComponent(
|
const Editor = defineAsyncComponent(
|
||||||
|
|
|
@ -706,7 +706,7 @@ import AccountMultiplePlus from "vue-material-design-icons/AccountMultiplePlus.v
|
||||||
import Earth from "vue-material-design-icons/Earth.vue";
|
import Earth from "vue-material-design-icons/Earth.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useCreateReport } from "@/composition/apollo/report";
|
import { useCreateReport } from "@/composition/apollo/report";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import Discussions from "@/components/Group/Sections/DiscussionsSection.vue";
|
import Discussions from "@/components/Group/Sections/DiscussionsSection.vue";
|
||||||
import Resources from "@/components/Group/Sections/ResourcesSection.vue";
|
import Resources from "@/components/Group/Sections/ResourcesSection.vue";
|
||||||
import Posts from "@/components/Group/Sections/PostsSection.vue";
|
import Posts from "@/components/Group/Sections/PostsSection.vue";
|
||||||
|
|
|
@ -92,7 +92,7 @@ import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { IUser } from "@/types/current-user.model";
|
import { IUser } from "@/types/current-user.model";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { computed, inject } from "vue";
|
import { computed, inject } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
import SettingMenuSection from "@/components/Settings/SettingMenuSection.vue";
|
import SettingMenuSection from "@/components/Settings/SettingMenuSection.vue";
|
||||||
import SettingMenuItem from "@/components/Settings/SettingMenuItem.vue";
|
import SettingMenuItem from "@/components/Settings/SettingMenuItem.vue";
|
||||||
|
|
|
@ -166,7 +166,7 @@ import SkeletonActivityItem from "../../components/Activity/SkeletonActivityItem
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import TimelineText from "vue-material-design-icons/TimelineText.vue";
|
import TimelineText from "vue-material-design-icons/TimelineText.vue";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { enumTransformer, useRouteQuery } from "vue-use-route-query";
|
import { enumTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { computed, defineAsyncComponent, ref } from "vue";
|
import { computed, defineAsyncComponent, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
|
@ -38,7 +38,7 @@ import { computed, reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouteQuery } from "vue-use-route-query";
|
import { useRouteQuery } from "vue-use-route-query";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
|
@ -442,7 +442,7 @@ import { displayNameAndUsername, displayName } from "../../types/actor";
|
||||||
import { Paginate } from "@/types/paginate";
|
import { Paginate } from "@/types/paginate";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { formatDateTimeString } from "@/filters/datetime";
|
import { formatDateTimeString } from "@/filters/datetime";
|
||||||
|
|
|
@ -95,7 +95,7 @@ import { Paginate } from "@/types/paginate";
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import {
|
import {
|
||||||
enumTransformer,
|
enumTransformer,
|
||||||
|
|
|
@ -414,7 +414,7 @@ import { GraphQLError } from "graphql";
|
||||||
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
||||||
import { useLazyQuery, useMutation, useQuery } from "@vue/apollo-composable";
|
import { useLazyQuery, useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { ref, computed, inject } from "vue";
|
import { ref, computed, inject } from "vue";
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouteQuery } from "vue-use-route-query";
|
import { useRouteQuery } from "vue-use-route-query";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DEVICE_ACTIVATION } from "@/graphql/application";
|
import { DEVICE_ACTIVATION } from "@/graphql/application";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, reactive, ref, watch } from "vue";
|
import { computed, reactive, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import AuthorizeApplication from "@/components/OAuth/AuthorizeApplication.vue";
|
import AuthorizeApplication from "@/components/OAuth/AuthorizeApplication.vue";
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
|
@ -154,7 +154,7 @@ import {
|
||||||
useCurrentActorClient,
|
useCurrentActorClient,
|
||||||
usePersonStatusGroup,
|
usePersonStatusGroup,
|
||||||
} from "@/composition/apollo/actor";
|
} from "@/composition/apollo/actor";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { computed, inject, onMounted, ref, watch } from "vue";
|
import { computed, inject, onMounted, ref, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
|
@ -83,7 +83,7 @@ import MultiPostListItem from "../../components/Post/MultiPostListItem.vue";
|
||||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { MemberRole } from "@/types/enums";
|
import { MemberRole } from "@/types/enums";
|
||||||
|
|
|
@ -268,7 +268,7 @@ import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { IPost } from "@/types/post.model";
|
import { IPost } from "@/types/post.model";
|
||||||
import { DELETE_POST, FETCH_POST } from "@/graphql/post";
|
import { DELETE_POST, FETCH_POST } from "@/graphql/post";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { formatDateTimeString } from "@/filters/datetime";
|
import { formatDateTimeString } from "@/filters/datetime";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useCreateReport } from "@/composition/apollo/report";
|
import { useCreateReport } from "@/composition/apollo/report";
|
||||||
|
|
|
@ -245,7 +245,7 @@ import { computed, nextTick, reactive, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useResourceProviders } from "@/composition/apollo/config";
|
import { useResourceProviders } from "@/composition/apollo/config";
|
||||||
import Folder from "vue-material-design-icons/Folder.vue";
|
import Folder from "vue-material-design-icons/Folder.vue";
|
||||||
import Link from "vue-material-design-icons/Link.vue";
|
import Link from "vue-material-design-icons/Link.vue";
|
||||||
|
|
|
@ -770,7 +770,7 @@ import Calendar from "vue-material-design-icons/Calendar.vue";
|
||||||
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
|
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
|
||||||
import Magnify from "vue-material-design-icons/Magnify.vue";
|
import Magnify from "vue-material-design-icons/Magnify.vue";
|
||||||
|
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import type { Locale } from "date-fns";
|
import type { Locale } from "date-fns";
|
||||||
import FilterSection from "@/components/Search/filters/FilterSection.vue";
|
import FilterSection from "@/components/Search/filters/FilterSection.vue";
|
||||||
import { listShortDisjunctionFormatter } from "@/utils/listFormat";
|
import { listShortDisjunctionFormatter } from "@/utils/listFormat";
|
||||||
|
|
|
@ -235,7 +235,7 @@ import { useLoggedUser } from "@/composition/apollo/user";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import { IAuthProvider } from "@/types/enums";
|
import { IAuthProvider } from "@/types/enums";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { GraphQLError } from "graphql/error/GraphQLError";
|
import { GraphQLError } from "graphql/error/GraphQLError";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
|
@ -82,7 +82,7 @@ import {
|
||||||
REVOKED_AUTHORIZED_APPLICATION,
|
REVOKED_AUTHORIZED_APPLICATION,
|
||||||
} from "@/graphql/application";
|
} from "@/graphql/application";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, inject } from "vue";
|
import { computed, inject } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
|
|
|
@ -339,7 +339,7 @@ import {
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { IConfig } from "@/types/config.model";
|
import { IConfig } from "@/types/config.model";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { Dialog } from "@/plugins/dialog";
|
import { Dialog } from "@/plugins/dialog";
|
||||||
|
|
||||||
type NotificationSubType = { label: string; id: string };
|
type NotificationSubType = { label: string; id: string };
|
||||||
|
|
|
@ -148,7 +148,7 @@ import { AddressSearchType } from "@/types/enums";
|
||||||
import { Address, IAddress } from "@/types/address.model";
|
import { Address, IAddress } from "@/types/address.model";
|
||||||
import { useTimezones } from "@/composition/apollo/config";
|
import { useTimezones } from "@/composition/apollo/config";
|
||||||
import { useUserSettings, updateLocale } from "@/composition/apollo/user";
|
import { useUserSettings, updateLocale } from "@/composition/apollo/user";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, defineAsyncComponent, ref, watch } from "vue";
|
import { computed, defineAsyncComponent, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
import SettingsMenu from "../components/Settings/SettingsMenu.vue";
|
import SettingsMenu from "../components/Settings/SettingsMenu.vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ import RouteName from "../../router/name";
|
||||||
import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
|
import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{ id: string }>();
|
const props = defineProps<{ id: string }>();
|
||||||
|
|
|
@ -70,7 +70,7 @@ import { ITodoList } from "@/types/todolist";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import { useGroup } from "@/composition/apollo/group";
|
import { useGroup } from "@/composition/apollo/group";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import FullTodo from "@/components/Todo/FullTodo.vue";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import { displayName, usernameWithDomain } from "@/types/actor";
|
import { displayName, usernameWithDomain } from "@/types/actor";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{ todoId: string }>();
|
const props = defineProps<{ todoId: string }>();
|
||||||
|
|
|
@ -143,7 +143,7 @@ import AuthProviders from "@/components/User/AuthProviders.vue";
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
import { LoginError, LoginErrorCode } from "@/types/enums";
|
import { LoginError, LoginErrorCode } from "@/types/enums";
|
||||||
import { useCurrentUserClient } from "@/composition/apollo/user";
|
import { useCurrentUserClient } from "@/composition/apollo/user";
|
||||||
import { useHead } from "@unhead/vue";
|
import { useHead } from "@/utils/head";
|
||||||
import { enumTransformer, useRouteQuery } from "vue-use-route-query";
|
import { enumTransformer, useRouteQuery } from "vue-use-route-query";
|
||||||
import { useLazyCurrentUserIdentities } from "@/composition/apollo/actor";
|
import { useLazyCurrentUserIdentities } from "@/composition/apollo/actor";
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue