Merge branch 'develop' into mkjs-n
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.notFound }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -17,6 +17,7 @@
|
||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
pagination: Paging;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noNotes }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -32,6 +32,7 @@ import MkNote from '@/components/MkNote.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const props = defineProps<{
|
||||
pagination: Paging;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noNotifications }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,6 +26,7 @@ import { useStream } from '@/stream';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const props = defineProps<{
|
||||
includeTypes?: typeof notificationTypes[number][];
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<div v-else-if="empty" key="_empty_" class="empty">
|
||||
<slot name="empty">
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
@@ -73,6 +73,8 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints>
|
||||
};
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
pagination: Paging;
|
||||
disableAutoLoad?: boolean;
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<MkSpacer :marginMin="20" :marginMax="28">
|
||||
<div v-if="note" class="_gaps">
|
||||
<div v-if="reactions.length === 0" class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
@@ -42,6 +42,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import { userPage } from '@/filters/user';
|
||||
import { i18n } from '@/i18n';
|
||||
import * as os from '@/os';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void,
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<MkSpacer :marginMin="20" :marginMax="28">
|
||||
<div v-if="renotes" class="_gaps">
|
||||
<div v-if="renotes.length === 0" class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
@@ -35,6 +35,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import { userPage } from '@/filters/user';
|
||||
import { i18n } from '@/i18n';
|
||||
import * as os from '@/os';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void,
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,6 +19,7 @@
|
||||
import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
pagination: Paging;
|
||||
|
@@ -22,7 +22,7 @@
|
||||
<div :class="$style.username"><MkAcct :user="user"/></div>
|
||||
</div>
|
||||
<div :class="$style.description">
|
||||
<Mfm v-if="user.description" :text="user.description" :author="user" :i="$i"/>
|
||||
<Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user" :i="$i"/>
|
||||
<div v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</div>
|
||||
</div>
|
||||
<div :class="$style.status">
|
||||
@@ -192,6 +192,13 @@ onMounted(() => {
|
||||
border-bottom: solid 1px var(--divider);
|
||||
}
|
||||
|
||||
.mfm {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 16px 26px 16px 26px;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
|
||||
<MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true"/>
|
||||
<img :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true"/>
|
||||
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
|
||||
<div v-if="user.isCat" :class="[$style.ears]">
|
||||
<div :class="$style.earLeft">
|
||||
@@ -24,7 +24,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
|
||||
import MkA from './MkA.vue';
|
||||
import { getStaticImageUrl } from '@/scripts/media-proxy';
|
||||
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
|
||||
|
@@ -13,13 +13,20 @@ interface Props {
|
||||
|
||||
const contentSymbol = Symbol();
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const results: {
|
||||
container: HTMLSpanElement;
|
||||
transform: string;
|
||||
}[] = [];
|
||||
for (const entry of entries) {
|
||||
const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement;
|
||||
const props: Required<Props> = content[contentSymbol];
|
||||
const container = content.parentElement as HTMLSpanElement;
|
||||
const contentWidth = content.getBoundingClientRect().width;
|
||||
const containerWidth = container.getBoundingClientRect().width;
|
||||
container.style.transform = `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})`;
|
||||
results.push({ container, transform: `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})` });
|
||||
}
|
||||
for (const result of results) {
|
||||
result.container.style.transform = result.transform;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
|
||||
<MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton>
|
||||
</div>
|
||||
@@ -12,6 +12,7 @@
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
import { serverErrorImageUrl } from '@/instance';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'retry'): void;
|
||||
|
@@ -75,3 +75,7 @@ export const ROLE_POLICIES = [
|
||||
//export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM');
|
||||
export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
|
||||
export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM';
|
||||
|
||||
export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg';
|
||||
export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg';
|
||||
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { reactive } from 'vue';
|
||||
import { computed, reactive } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { api } from './os';
|
||||
import { miLocalStorage } from './local-storage';
|
||||
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const';
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
||||
@@ -13,6 +14,12 @@ export const instance: Misskey.entities.InstanceMetadata = reactive(cached ? JSO
|
||||
// TODO: set default values
|
||||
});
|
||||
|
||||
export const serverErrorImageUrl = computed(() => instance.serverErrorImageUrl ?? DEFAULT_SERVER_ERROR_IMAGE_URL);
|
||||
|
||||
export const infoImageUrl = computed(() => instance.infoImageUrl ?? DEFAULT_INFO_IMAGE_URL);
|
||||
|
||||
export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
|
||||
|
||||
export async function fetchInstance() {
|
||||
const meta = await api('meta', {
|
||||
detail: false,
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<MkLoading v-if="!loaded"/>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<div v-show="loaded" :class="$style.root">
|
||||
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost" :class="$style.img"/>
|
||||
<img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/>
|
||||
<div class="_gaps">
|
||||
<p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p>
|
||||
<p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p>
|
||||
@@ -30,6 +30,7 @@ import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
import { defaultStore } from '@/store';
|
||||
import { serverErrorImageUrl } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
error?: Error;
|
||||
|
133
packages/frontend/src/pages/admin/branding.vue
Normal file
133
packages/frontend/src/pages/admin/branding.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<MkStickyContainer>
|
||||
<template #header><XHeader :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="iconUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.iconUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="bannerUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.bannerUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="backgroundImageUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.backgroundImageUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="notFoundImageUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.notFoundDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="infoImageUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.nothing }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="serverErrorImageUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.somethingHappened }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkColorInput v-model="themeColor">
|
||||
<template #label>{{ i18n.ts.themeColor }}</template>
|
||||
</MkColorInput>
|
||||
|
||||
<MkTextarea v-model="defaultLightTheme">
|
||||
<template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkTextarea v-model="defaultDarkTheme">
|
||||
<template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
|
||||
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
</template>
|
||||
</MkStickyContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import XHeader from './_header_.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkColorInput from '@/components/MkColorInput.vue';
|
||||
|
||||
let iconUrl: string | null = $ref(null);
|
||||
let bannerUrl: string | null = $ref(null);
|
||||
let backgroundImageUrl: string | null = $ref(null);
|
||||
let themeColor: any = $ref(null);
|
||||
let defaultLightTheme: any = $ref(null);
|
||||
let defaultDarkTheme: any = $ref(null);
|
||||
let serverErrorImageUrl: string | null = $ref(null);
|
||||
let infoImageUrl: string | null = $ref(null);
|
||||
let notFoundImageUrl: string | null = $ref(null);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
iconUrl = meta.iconUrl;
|
||||
bannerUrl = meta.bannerUrl;
|
||||
backgroundImageUrl = meta.backgroundImageUrl;
|
||||
themeColor = meta.themeColor;
|
||||
defaultLightTheme = meta.defaultLightTheme;
|
||||
defaultDarkTheme = meta.defaultDarkTheme;
|
||||
serverErrorImageUrl = meta.serverErrorImageUrl;
|
||||
infoImageUrl = meta.infoImageUrl;
|
||||
notFoundImageUrl = meta.notFoundImageUrl;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
iconUrl,
|
||||
bannerUrl,
|
||||
backgroundImageUrl,
|
||||
themeColor: themeColor === '' ? null : themeColor,
|
||||
defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme,
|
||||
defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme,
|
||||
infoImageUrl,
|
||||
notFoundImageUrl,
|
||||
serverErrorImageUrl,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.branding,
|
||||
icon: 'ti ti-paint',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.footer {
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
}
|
||||
</style>
|
@@ -143,6 +143,11 @@ const menuDef = $computed(() => [{
|
||||
text: i18n.ts.general,
|
||||
to: '/admin/settings',
|
||||
active: currentPage?.route.name === 'settings',
|
||||
}, {
|
||||
icon: 'ti ti-paint',
|
||||
text: i18n.ts.branding,
|
||||
to: '/admin/branding',
|
||||
active: currentPage?.route.name === 'branding',
|
||||
}, {
|
||||
icon: 'ti ti-shield',
|
||||
text: i18n.ts.moderation,
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<MkPagination :pagination="usersPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -69,6 +69,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@@ -29,41 +29,6 @@
|
||||
<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.theme }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="iconUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.iconUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="bannerUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.bannerUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="backgroundImageUrl">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.backgroundImageUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkColorInput v-model="themeColor">
|
||||
<template #label>{{ i18n.ts.themeColor }}</template>
|
||||
</MkColorInput>
|
||||
|
||||
<MkTextarea v-model="defaultLightTheme">
|
||||
<template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkTextarea v-model="defaultDarkTheme">
|
||||
<template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.files }}</template>
|
||||
|
||||
@@ -145,12 +110,6 @@ let name: string | null = $ref(null);
|
||||
let description: string | null = $ref(null);
|
||||
let maintainerName: string | null = $ref(null);
|
||||
let maintainerEmail: string | null = $ref(null);
|
||||
let iconUrl: string | null = $ref(null);
|
||||
let bannerUrl: string | null = $ref(null);
|
||||
let backgroundImageUrl: string | null = $ref(null);
|
||||
let themeColor: any = $ref(null);
|
||||
let defaultLightTheme: any = $ref(null);
|
||||
let defaultDarkTheme: any = $ref(null);
|
||||
let pinnedUsers: string = $ref('');
|
||||
let cacheRemoteFiles: boolean = $ref(false);
|
||||
let enableServiceWorker: boolean = $ref(false);
|
||||
@@ -163,12 +122,6 @@ async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
name = meta.name;
|
||||
description = meta.description;
|
||||
iconUrl = meta.iconUrl;
|
||||
bannerUrl = meta.bannerUrl;
|
||||
backgroundImageUrl = meta.backgroundImageUrl;
|
||||
themeColor = meta.themeColor;
|
||||
defaultLightTheme = meta.defaultLightTheme;
|
||||
defaultDarkTheme = meta.defaultDarkTheme;
|
||||
maintainerName = meta.maintainerName;
|
||||
maintainerEmail = meta.maintainerEmail;
|
||||
pinnedUsers = meta.pinnedUsers.join('\n');
|
||||
@@ -184,12 +137,6 @@ function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
name,
|
||||
description,
|
||||
iconUrl,
|
||||
bannerUrl,
|
||||
backgroundImageUrl,
|
||||
themeColor: themeColor === '' ? null : themeColor,
|
||||
defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme,
|
||||
defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme,
|
||||
maintainerName,
|
||||
maintainerEmail,
|
||||
pinnedUsers: pinnedUsers.split('\n'),
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noNotes }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,6 +26,7 @@ import MkNote from '@/components/MkNote.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'i/favorites' as const,
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<MkPagination ref="paginationComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noFollowRequests }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,6 +39,7 @@ import { userPage, acct } from '@/filters/user';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<p :class="$style.text">
|
||||
<i class="ti ti-alert-triangle"></i>
|
||||
{{ i18n.ts.nothing }}
|
||||
@@ -36,6 +36,7 @@ import { i18n } from '@/i18n';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { serverErrorImageUrl } from '@/instance';
|
||||
|
||||
const props = defineProps<{
|
||||
listId: string;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/not-found.jpg" class="_ghost"/>
|
||||
<img :src="notFoundImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.notFoundDescription }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,6 +10,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { notFoundImageUrl } from '@/instance';
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
|
||||
<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<p :class="$style.text">
|
||||
<i class="ti ti-alert-triangle"></i>
|
||||
{{ error }}
|
||||
@@ -30,6 +30,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import { instanceName } from '@/config';
|
||||
import { serverErrorImageUrl } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
role: string;
|
||||
|
@@ -1,146 +0,0 @@
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormSection v-if="stats" first>
|
||||
<template #label>{{ i18n.ts.statistics }}</template>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.notesCount }}</template>
|
||||
<template #value>{{ number(stats.notesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.repliesCount }}</template>
|
||||
<template #value>{{ number(stats.repliesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.renotesCount }}</template>
|
||||
<template #value>{{ number(stats.renotesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.repliedCount }}</template>
|
||||
<template #value>{{ number(stats.repliedCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.renotedCount }}</template>
|
||||
<template #value>{{ number(stats.renotedCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.pollVotesCount }}</template>
|
||||
<template #value>{{ number(stats.pollVotesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.pollVotedCount }}</template>
|
||||
<template #value>{{ number(stats.pollVotedCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.sentReactionsCount }}</template>
|
||||
<template #value>{{ number(stats.sentReactionsCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.receivedReactionsCount }}</template>
|
||||
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.noteFavoritesCount }}</template>
|
||||
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.followingCount }}</template>
|
||||
<template #value>{{ number(stats.followingCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template>
|
||||
<template #value>{{ number(stats.localFollowingCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template>
|
||||
<template #value>{{ number(stats.remoteFollowingCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.followersCount }}</template>
|
||||
<template #value>{{ number(stats.followersCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template>
|
||||
<template #value>{{ number(stats.localFollowersCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template>
|
||||
<template #value>{{ number(stats.remoteFollowersCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.pageLikesCount }}</template>
|
||||
<template #value>{{ number(stats.pageLikesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.pageLikedCount }}</template>
|
||||
<template #value>{{ number(stats.pageLikedCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.driveFilesCount }}</template>
|
||||
<template #value>{{ number(stats.driveFilesCount) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.driveUsage }}</template>
|
||||
<template #value>{{ bytes(stats.driveUsage) }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.other }}</template>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>emailVerified</template>
|
||||
<template #value>{{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>twoFactorEnabled</template>
|
||||
<template #value>{{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>securityKeys</template>
|
||||
<template #value>{{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>usePasswordLessLogin</template>
|
||||
<template #value>{{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>isModerator</template>
|
||||
<template #value>{{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>isAdmin</template>
|
||||
<template #value>{{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import bytes from '@/filters/bytes';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const stats = ref<any>({});
|
||||
|
||||
onMounted(() => {
|
||||
os.api('users/stats', {
|
||||
userId: $i!.id,
|
||||
}).then(response => {
|
||||
stats.value = response;
|
||||
});
|
||||
});
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.accountInfo,
|
||||
icon: 'ti ti-info-circle',
|
||||
});
|
||||
</script>
|
@@ -3,7 +3,7 @@
|
||||
<FormPagination ref="list" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,6 +47,7 @@ import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const list = ref<any>(null);
|
||||
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<MkPagination :pagination="renoteMutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,7 +38,7 @@
|
||||
<MkPagination :pagination="mutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -68,7 +68,7 @@
|
||||
<MkPagination :pagination="blockingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -107,6 +107,7 @@ import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import * as os from '@/os';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
let tab = $ref('renoteMute');
|
||||
|
||||
|
@@ -26,8 +26,6 @@
|
||||
<template #key>{{ i18n.ts.registeredDate }}</template>
|
||||
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
|
||||
</MkKeyValue>
|
||||
|
||||
<FormLink to="/settings/account-stats"><template #icon><i class="ti ti-info-circle"></i></template>{{ i18n.ts.statistics }}</FormLink>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
|
@@ -127,7 +127,6 @@ const profile = reactive({
|
||||
lang: $i.lang,
|
||||
isBot: $i.isBot,
|
||||
isCat: $i.isCat,
|
||||
showTimelineReplies: $i.showTimelineReplies,
|
||||
});
|
||||
|
||||
watch(() => profile, () => {
|
||||
@@ -151,7 +150,7 @@ while (fields.value.length < 4) {
|
||||
addField();
|
||||
}
|
||||
|
||||
function deleteField(index: number) {
|
||||
function deleteField(index: number) {
|
||||
fields.value.splice(index, 1);
|
||||
}
|
||||
|
||||
@@ -176,7 +175,6 @@ function save() {
|
||||
lang: profile.lang || null,
|
||||
isBot: !!profile.isBot,
|
||||
isCat: !!profile.isCat,
|
||||
showTimelineReplies: !!profile.showTimelineReplies,
|
||||
});
|
||||
claimAchievement('profileFilled');
|
||||
if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
|
||||
|
@@ -177,10 +177,6 @@ export const routes = [{
|
||||
path: '/accounts',
|
||||
name: 'profile',
|
||||
component: page(() => import('./pages/settings/accounts.vue')),
|
||||
}, {
|
||||
path: '/account-stats',
|
||||
name: 'other',
|
||||
component: page(() => import('./pages/settings/account-stats.vue')),
|
||||
}, {
|
||||
path: '/other',
|
||||
name: 'other',
|
||||
@@ -392,6 +388,10 @@ export const routes = [{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: page(() => import('./pages/admin/settings.vue')),
|
||||
}, {
|
||||
path: '/branding',
|
||||
name: 'branding',
|
||||
component: page(() => import('./pages/admin/branding.vue')),
|
||||
}, {
|
||||
path: '/moderation',
|
||||
name: 'moderation',
|
||||
|
@@ -12,5 +12,14 @@ export function useStream(): Misskey.Stream {
|
||||
token: $i.token,
|
||||
} : null));
|
||||
|
||||
window.setTimeout(heartbeat, 1000 * 60);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
function heartbeat(): void {
|
||||
if (stream != null && document.visibilityState === 'visible') {
|
||||
stream.heartbeat();
|
||||
}
|
||||
window.setTimeout(heartbeat, 1000 * 60);
|
||||
}
|
||||
|
@@ -254,6 +254,28 @@ async function deleteProfile() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#misskey_app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
.transition_menuDrawerBg_enterActive,
|
||||
.transition_menuDrawerBg_leaveActive {
|
||||
|
@@ -313,6 +313,7 @@ function onDrop(ev) {
|
||||
|
||||
> .body {
|
||||
background: var(--bg) !important;
|
||||
overflow-y: scroll !important;
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: inherit;
|
||||
|
@@ -215,6 +215,28 @@ watch($$(navFooter), () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#misskey_app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$widgets-hide-threshold: 1090px;
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<div class="ekmkgxbj">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
<div v-else :class="$style.feed">
|
||||
@@ -25,6 +25,7 @@ import MkContainer from '@/components/MkContainer.vue';
|
||||
import { url as base } from '@/config';
|
||||
import { i18n } from '@/i18n';
|
||||
import { useInterval } from '@/scripts/use-interval';
|
||||
import { infoImageUrl } from '@/instance';
|
||||
|
||||
const name = 'rss';
|
||||
|
||||
|
Reference in New Issue
Block a user