format & lint
This commit is contained in:
parent
9c694c6aac
commit
c4550354c3
|
@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
|||
|
||||
import Mobilizon.Users.Guards
|
||||
|
||||
alias Mobilizon.{Actors, Admin, Config, Events, Instances, Users, Media}
|
||||
alias Mobilizon.{Actors, Admin, Config, Events, Instances, Media, Users}
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mobilizon.Admin.{ActionLog, Setting, SettingMedia}
|
||||
alias Mobilizon.Cldr.Language
|
||||
|
|
|
@ -128,10 +128,12 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||
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
|
||||
|
@ -433,10 +435,12 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
|||
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"
|
||||
|
|
|
@ -79,19 +79,19 @@ defmodule Mobilizon.Config do
|
|||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
def secondary_color, do: config_cached_value("instance", "secondary_color")
|
||||
|
||||
@spec contact :: String.t() | nil
|
||||
def contact, do: config_cached_value("instance", "contact")
|
||||
|
|
|
@ -6,30 +6,36 @@ defmodule Mobilizon.Web.ManifestController do
|
|||
|
||||
@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
|
||||
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: "/",
|
||||
|
|
|
@ -18,8 +18,7 @@ defmodule Mobilizon.Web do
|
|||
"""
|
||||
|
||||
def static_paths,
|
||||
do:
|
||||
~w(index.html service-worker.js css fonts img js robots.txt assets)
|
||||
do: ~w(index.html service-worker.js css fonts img js robots.txt assets)
|
||||
|
||||
def controller do
|
||||
quote do
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang={Map.get(assigns, :locale, "en" )} dir={language_direction(assigns)}>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="theme-color" content={theme_color()} />
|
||||
<script>
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
<%= if root?(assigns) do %>
|
||||
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
||||
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
||||
<link rel="preload" href="/img/shape-3.svg" as="image" />
|
||||
<% end %>
|
||||
<%= tags(assigns) || assigns.tags %>
|
||||
<%= Vite.vite_client() %>
|
||||
<%= Vite.vite_snippet("src/main.ts") %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>
|
||||
We're sorry but Mobilizon doesn't work properly without JavaScript enabled. Please enable it to continue.
|
||||
</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
<html lang={Map.get(assigns, :locale, "en")} dir={language_direction(assigns)}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="theme-color" content={theme_color()} />
|
||||
<script>
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
<%= if root?(assigns) do %>
|
||||
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
||||
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
||||
<link rel="preload" href="/img/shape-3.svg" as="image" />
|
||||
<% end %>
|
||||
<%= tags(assigns) || assigns.tags %>
|
||||
<%= Vite.vite_client() %>
|
||||
<%= Vite.vite_snippet("src/main.ts") %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>
|
||||
We're sorry but Mobilizon doesn't work properly without JavaScript enabled. Please enable it to continue.
|
||||
</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -95,17 +95,21 @@ defmodule Mobilizon.Web.PageView do
|
|||
|
||||
defp favicon do
|
||||
case Config.instance_favicon() do
|
||||
%{file: %{url: url}, metadata: metadata} -> %{
|
||||
%{file: %{url: url}, metadata: metadata} ->
|
||||
%{
|
||||
src: url,
|
||||
sizes: case metadata do
|
||||
%{width: width} -> "#{width}x#{width}"
|
||||
_ -> "any"
|
||||
end
|
||||
}
|
||||
_ -> %{
|
||||
sizes:
|
||||
case metadata do
|
||||
%{width: width} -> "#{width}x#{width}"
|
||||
_ -> "any"
|
||||
end
|
||||
}
|
||||
|
||||
_ ->
|
||||
%{
|
||||
src: "/img/icons/apple-touch-icon-152x152.png",
|
||||
sizes: "152x152"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -31,16 +31,10 @@
|
|||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<img
|
||||
v-else
|
||||
alt=""
|
||||
class="max-h-12 w-auto"
|
||||
:src="instanceLogoUrl"
|
||||
/>
|
||||
<img v-else alt="" class="max-h-12 w-auto" :src="instanceLogoUrl" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from "vue";
|
||||
import { useInstanceLogoUrl } from "@/composition/apollo/config";
|
||||
|
||||
const { instanceLogoUrl } = useInstanceLogoUrl();
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900"
|
||||
id="navbar"
|
||||
>
|
||||
<div
|
||||
class="container mx-auto flex flex-wrap items-center gap-2 sm:gap-4"
|
||||
>
|
||||
<div class="container mx-auto flex flex-wrap items-center gap-2 sm:gap-4">
|
||||
<router-link
|
||||
:to="{ name: RouteName.HOME }"
|
||||
class="flex items-center"
|
||||
|
|
|
@ -95,12 +95,8 @@ export function useColors() {
|
|||
config: Pick<IConfig, "primaryColor" | "secondaryColor">;
|
||||
}>(COLORS);
|
||||
|
||||
const primaryColor = computed(
|
||||
() => result.value?.config?.primaryColor
|
||||
);
|
||||
const secondaryColor = computed(
|
||||
() => result.value?.config?.secondaryColor
|
||||
);
|
||||
const primaryColor = computed(() => result.value?.config?.primaryColor);
|
||||
const secondaryColor = computed(() => result.value?.config?.secondaryColor);
|
||||
return { primaryColor, secondaryColor, error, loading };
|
||||
}
|
||||
|
||||
|
@ -109,9 +105,7 @@ export function useDefaultPicture() {
|
|||
config: Pick<IConfig, "defaultPicture">;
|
||||
}>(DEFAULT_PICTURE);
|
||||
|
||||
const defaultPicture = computed(
|
||||
() => result.value?.config?.defaultPicture
|
||||
);
|
||||
const defaultPicture = computed(() => result.value?.config?.defaultPicture);
|
||||
return { defaultPicture, error, loading };
|
||||
}
|
||||
|
||||
|
|
10
src/main.ts
10
src/main.ts
|
@ -60,11 +60,17 @@ apolloClient
|
|||
|
||||
const primaryColor = configData.config?.primaryColor;
|
||||
if (primaryColor) {
|
||||
document.documentElement.style.setProperty('--custom-primary', primaryColor);
|
||||
document.documentElement.style.setProperty(
|
||||
"--custom-primary",
|
||||
primaryColor
|
||||
);
|
||||
}
|
||||
const secondaryColor = configData.config?.secondaryColor;
|
||||
if (secondaryColor) {
|
||||
document.documentElement.style.setProperty('--custom-secondary', secondaryColor);
|
||||
document.documentElement.style.setProperty(
|
||||
"--custom-secondary",
|
||||
secondaryColor
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface IMediaMetadata {
|
|||
}
|
||||
|
||||
export interface IModifiableMedia {
|
||||
file: Ref<File | null>
|
||||
firstHash: string | null
|
||||
hash: string | null
|
||||
file: Ref<File | null>;
|
||||
firstHash: string | null;
|
||||
hash: string | null;
|
||||
}
|
||||
|
|
|
@ -5,15 +5,18 @@ 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 { 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)
|
||||
title: computed(() =>
|
||||
args?.title?.value
|
||||
? `${args.title.value} - ${instanceName.value}`
|
||||
: instanceName.value
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -30,9 +30,7 @@ export function buildFileVariable(
|
|||
};
|
||||
}
|
||||
|
||||
export function readFileAsync(
|
||||
file: File
|
||||
): Promise<ArrayBuffer | null> {
|
||||
export function readFileAsync(file: File): Promise<ArrayBuffer | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
|
@ -50,7 +48,11 @@ 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(''))
|
||||
const b64Hash = btoa(
|
||||
Array.from(new Uint8Array(hash))
|
||||
.map((b) => String.fromCharCode(b))
|
||||
.join("")
|
||||
);
|
||||
return b64Hash;
|
||||
}
|
||||
|
||||
|
@ -62,7 +64,10 @@ export function initWrappedMedia(): IModifiableMedia {
|
|||
};
|
||||
}
|
||||
|
||||
export async function loadWrappedMedia(modifiableMedia: IModifiableMedia, media: IMedia | 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);
|
||||
|
@ -80,8 +85,12 @@ export async function loadWrappedMedia(modifiableMedia: IModifiableMedia, media:
|
|||
}
|
||||
}
|
||||
|
||||
export function asMediaInput(mmedia: IModifiableMedia, name: string, fallbackId: number): any {
|
||||
let ret = {
|
||||
export function asMediaInput(
|
||||
mmedia: IModifiableMedia,
|
||||
name: string,
|
||||
fallbackId: number
|
||||
): any {
|
||||
const ret = {
|
||||
[name]: {},
|
||||
};
|
||||
if (mmedia.file.value) {
|
||||
|
@ -95,7 +104,7 @@ export function asMediaInput(mmedia: IModifiableMedia, name: string, fallbackId:
|
|||
};
|
||||
} else {
|
||||
ret[name] = {
|
||||
mediaId: fallbackId
|
||||
mediaId: fallbackId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,11 @@
|
|||
<label class="field flex flex-col">
|
||||
<p>{{ t("Logo") }}</p>
|
||||
<small>
|
||||
{{ t("Logo of the instance. Defaults to the upstream Mobilizon logo.") }}
|
||||
{{
|
||||
t(
|
||||
"Logo of the instance. Defaults to the upstream Mobilizon logo."
|
||||
)
|
||||
}}
|
||||
</small>
|
||||
<picture-upload
|
||||
v-model:modelValue="instanceLogoFile"
|
||||
|
@ -73,7 +77,11 @@
|
|||
<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.") }}
|
||||
{{
|
||||
t(
|
||||
"Browser tab icon and PWA icon of the instance. Defaults to the upstream Mobilizon icon."
|
||||
)
|
||||
}}
|
||||
</small>
|
||||
<picture-upload
|
||||
v-model:modelValue="instanceFaviconFile"
|
||||
|
@ -95,9 +103,7 @@
|
|||
/>
|
||||
</label>
|
||||
<div class="field flex flex-col">
|
||||
<label class="" for="primary-color">{{
|
||||
t("Primary Color")
|
||||
}}</label>
|
||||
<label class="" for="primary-color">{{ t("Primary Color") }}</label>
|
||||
<o-input
|
||||
type="color"
|
||||
v-model="settingsToWrite.primaryColor"
|
||||
|
@ -450,7 +456,11 @@ import type { Notifier } from "@/plugins/notifier";
|
|||
|
||||
// Media upload related
|
||||
import PictureUpload from "@/components/PictureUpload.vue";
|
||||
import { initWrappedMedia, loadWrappedMedia, asMediaInput } from "@/utils/image";
|
||||
import {
|
||||
initWrappedMedia,
|
||||
loadWrappedMedia,
|
||||
asMediaInput,
|
||||
} from "@/utils/image";
|
||||
import { useDefaultMaxSize } from "@/composition/config";
|
||||
|
||||
const defaultAdminSettings: IAdminSettings = {
|
||||
|
@ -475,10 +485,7 @@ const defaultAdminSettings: IAdminSettings = {
|
|||
instanceLanguages: [],
|
||||
};
|
||||
|
||||
const {
|
||||
result: adminSettingsResult,
|
||||
onResult: onAdminSettingsResult
|
||||
} = useQuery<{
|
||||
const { onResult: onAdminSettingsResult } = useQuery<{
|
||||
adminSettings: IAdminSettings;
|
||||
}>(ADMIN_SETTINGS);
|
||||
|
||||
|
@ -486,9 +493,10 @@ const adminSettings = ref<IAdminSettings>();
|
|||
|
||||
onAdminSettingsResult(async ({ data }) => {
|
||||
if (!data) return;
|
||||
adminSettings.value = {
|
||||
...data.adminSettings,
|
||||
} ?? defaultAdminSettings;
|
||||
adminSettings.value =
|
||||
{
|
||||
...data.adminSettings,
|
||||
} ?? defaultAdminSettings;
|
||||
|
||||
loadWrappedMedia(instanceLogo, adminSettings.value.instanceLogo);
|
||||
loadWrappedMedia(instanceFavicon, adminSettings.value.instanceFavicon);
|
||||
|
@ -563,9 +571,21 @@ saveAdminSettingsError((e) => {
|
|||
const updateSettings = async (): Promise<void> => {
|
||||
const variables = {
|
||||
...settingsToWrite.value,
|
||||
...asMediaInput(instanceLogo, "instanceLogo", adminSettings.value?.instanceLogo?.id),
|
||||
...asMediaInput(instanceFavicon, "instanceFavicon", adminSettings.value?.instanceFavicon?.id),
|
||||
...asMediaInput(defaultPicture, "defaultPicture", adminSettings.value?.defaultPicture?.id),
|
||||
...asMediaInput(
|
||||
instanceLogo,
|
||||
"instanceLogo",
|
||||
adminSettings.value?.instanceLogo?.id
|
||||
),
|
||||
...asMediaInput(
|
||||
instanceFavicon,
|
||||
"instanceFavicon",
|
||||
adminSettings.value?.instanceFavicon?.id
|
||||
),
|
||||
...asMediaInput(
|
||||
defaultPicture,
|
||||
"defaultPicture",
|
||||
adminSettings.value?.defaultPicture?.id
|
||||
),
|
||||
};
|
||||
saveAdminSettings(variables);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue