Merge branch 'develop' into use-interval

This commit is contained in:
tamaina 2023-06-10 02:09:00 +00:00
commit 5e6f4e5d03
40 changed files with 352 additions and 128 deletions

View File

@ -46,8 +46,10 @@ Please include errors from the developer console and/or server log files if you
<!-- Example: Chrome 113.0.5672.126 --> <!-- Example: Chrome 113.0.5672.126 -->
* Server URL: * Server URL:
<!-- Example: misskey.io --> <!-- Example: misskey.io -->
* Misskey:
13.x.x
### 🛰 Backend (for instance admin) ### 🛰 Backend (for server admin)
<!-- If you are using a managed service, put that after the version. --> <!-- If you are using a managed service, put that after the version. -->
* Installation Method or Hosting Service: <!-- Example: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment --> * Installation Method or Hosting Service: <!-- Example: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment -->

View File

@ -17,6 +17,9 @@
### Client ### Client
- Fix: タブがアクティブな間はstreamが切断されないように - Fix: タブがアクティブな間はstreamが切断されないように
### General
- エラー時や項目が存在しないときなどのアイコン画像をサーバー管理者が設定できるようになりました
### Server ### Server
- Fix: api/metaで`TypeError: JSON5.parse is not a function`エラーが発生する問題を修正 - Fix: api/metaで`TypeError: JSON5.parse is not a function`エラーが発生する問題を修正

1
locales/index.d.ts vendored
View File

@ -1065,6 +1065,7 @@ export interface Locale {
"goToMisskey": string; "goToMisskey": string;
"additionalEmojiDictionary": string; "additionalEmojiDictionary": string;
"installed": string; "installed": string;
"branding": string;
"_initialAccountSetting": { "_initialAccountSetting": {
"accountCreated": string; "accountCreated": string;
"letsStartAccountSetup": string; "letsStartAccountSetup": string;

View File

@ -1062,6 +1062,7 @@ later: "あとで"
goToMisskey: "Misskeyへ" goToMisskey: "Misskeyへ"
additionalEmojiDictionary: "絵文字の追加辞書" additionalEmojiDictionary: "絵文字の追加辞書"
installed: "インストール済み" installed: "インストール済み"
branding: "ブランディング"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!" accountCreated: "アカウントの作成が完了しました!"

View File

@ -0,0 +1,17 @@
export class ErrorImageUrl1685973839966 {
name = 'ErrorImageUrl1685973839966'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "errorImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" ADD "serverErrorImageUrl" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "notFoundImageUrl" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "infoImageUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "infoImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "notFoundImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "serverErrorImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" ADD "errorImageUrl" character varying(1024) DEFAULT 'https://xn--931a.moe/aiart/yubitun.png'`);
}
}

View File

@ -101,13 +101,25 @@ export class Meta {
length: 1024, length: 1024,
nullable: true, nullable: true,
}) })
public errorImageUrl: string | null; public iconUrl: string | null;
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,
nullable: true, nullable: true,
}) })
public iconUrl: string | null; public serverErrorImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public notFoundImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public infoImageUrl: string | null;
@Column('boolean', { @Column('boolean', {
default: true, default: true,

View File

@ -128,26 +128,27 @@ export class StreamingApiServerService {
ev.removeAllListeners(); ev.removeAllListeners();
stream.dispose(); stream.dispose();
this.redisForSub.off('message', onRedisMessage); this.redisForSub.off('message', onRedisMessage);
this.#connections.delete(connection);
if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); if (userUpdateIntervalId) clearInterval(userUpdateIntervalId);
}); });
connection.on('message', async (data) => { connection.on('pong', () => {
this.#connections.set(connection, Date.now()); this.#connections.set(connection, Date.now());
if (data.toString() === 'ping') {
connection.send('pong');
}
}); });
}); });
// 一定期間通信が無いコネクションは実際には切断されている可能性があるため定期的にterminateする
this.#cleanConnectionsIntervalId = setInterval(() => { this.#cleanConnectionsIntervalId = setInterval(() => {
const now = Date.now(); const now = Date.now();
for (const [connection, lastActive] of this.#connections.entries()) { for (const [connection, lastActive] of this.#connections.entries()) {
if (now - lastActive > 1000 * 60 * 5) { if (now - lastActive > 1000 * 60 * 2) {
connection.terminate(); connection.terminate();
this.#connections.delete(connection); this.#connections.delete(connection);
} else {
connection.ping();
} }
} }
}, 1000 * 60 * 5); }, 1000 * 60);
} }
@bindThis @bindThis

View File

@ -61,10 +61,17 @@ export const meta = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
errorImageUrl: { serverErrorImageUrl: {
type: 'string',
optional: false, nullable: true,
},
infoImageUrl: {
type: 'string',
optional: false, nullable: true,
},
notFoundImageUrl: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
default: 'https://xn--931a.moe/aiart/yubitun.png',
}, },
iconUrl: { iconUrl: {
type: 'string', type: 'string',
@ -305,7 +312,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
themeColor: instance.themeColor, themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl, mascotImageUrl: instance.mascotImageUrl,
bannerUrl: instance.bannerUrl, bannerUrl: instance.bannerUrl,
errorImageUrl: instance.errorImageUrl, serverErrorImageUrl: instance.serverErrorImageUrl,
notFoundImageUrl: instance.notFoundImageUrl,
infoImageUrl: instance.infoImageUrl,
iconUrl: instance.iconUrl, iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl, backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl, logoImageUrl: instance.logoImageUrl,

View File

@ -32,7 +32,9 @@ export const paramDef = {
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
mascotImageUrl: { type: 'string', nullable: true }, mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true },
errorImageUrl: { type: 'string', nullable: true }, serverErrorImageUrl: { type: 'string', nullable: true },
infoImageUrl: { type: 'string', nullable: true },
notFoundImageUrl: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true }, iconUrl: { type: 'string', nullable: true },
backgroundImageUrl: { type: 'string', nullable: true }, backgroundImageUrl: { type: 'string', nullable: true },
logoImageUrl: { type: 'string', nullable: true }, logoImageUrl: { type: 'string', nullable: true },
@ -149,6 +151,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.iconUrl = ps.iconUrl; set.iconUrl = ps.iconUrl;
} }
if (ps.serverErrorImageUrl !== undefined) {
set.serverErrorImageUrl = ps.serverErrorImageUrl;
}
if (ps.infoImageUrl !== undefined) {
set.infoImageUrl = ps.infoImageUrl;
}
if (ps.notFoundImageUrl !== undefined) {
set.notFoundImageUrl = ps.notFoundImageUrl;
}
if (ps.backgroundImageUrl !== undefined) { if (ps.backgroundImageUrl !== undefined) {
set.backgroundImageUrl = ps.backgroundImageUrl; set.backgroundImageUrl = ps.backgroundImageUrl;
} }
@ -281,10 +295,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.smtpPass = ps.smtpPass; set.smtpPass = ps.smtpPass;
} }
if (ps.errorImageUrl !== undefined) {
set.errorImageUrl = ps.errorImageUrl;
}
if (ps.enableServiceWorker !== undefined) { if (ps.enableServiceWorker !== undefined) {
set.enableServiceWorker = ps.enableServiceWorker; set.enableServiceWorker = ps.enableServiceWorker;
} }

View File

@ -124,10 +124,17 @@ export const meta = {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
}, },
errorImageUrl: { serverErrorImageUrl: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: true,
default: 'https://xn--931a.moe/aiart/yubitun.png', },
infoImageUrl: {
type: 'string',
optional: false, nullable: true,
},
notFoundImageUrl: {
type: 'string',
optional: false, nullable: true,
}, },
iconUrl: { iconUrl: {
type: 'string', type: 'string',
@ -288,7 +295,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
themeColor: instance.themeColor, themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl, mascotImageUrl: instance.mascotImageUrl,
bannerUrl: instance.bannerUrl, bannerUrl: instance.bannerUrl,
errorImageUrl: instance.errorImageUrl, infoImageUrl: instance.infoImageUrl,
serverErrorImageUrl: instance.serverErrorImageUrl,
notFoundImageUrl: instance.notFoundImageUrl,
iconUrl: instance.iconUrl, iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl, backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl, logoImageUrl: instance.logoImageUrl,

View File

@ -26,7 +26,7 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, Meta, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { deepClone } from '@/misc/clone.js'; import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -117,6 +117,18 @@ export class ClientServerService {
return (res); return (res);
} }
@bindThis
private generateCommonPugData(meta: Meta) {
return {
instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg',
infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
};
}
@bindThis @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.register(fastifyCookie, {}); fastify.register(fastifyCookie, {});
@ -341,12 +353,10 @@ export class ClientServerService {
reply.header('Cache-Control', 'public, max-age=30'); reply.header('Cache-Control', 'public, max-age=30');
return await reply.view('base', { return await reply.view('base', {
img: meta.bannerUrl, img: meta.bannerUrl,
title: meta.name ?? 'Misskey',
instanceName: meta.name ?? 'Misskey',
url: this.config.url, url: this.config.url,
title: meta.name ?? 'Misskey',
desc: meta.description, desc: meta.description,
icon: meta.iconUrl, ...this.generateCommonPugData(meta),
themeColor: meta.themeColor,
}); });
}; };
@ -431,9 +441,7 @@ export class ClientServerService {
user, profile, me, user, profile, me,
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
sub: request.params.sub, sub: request.params.sub,
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
// リモートユーザーなので // リモートユーザーなので
@ -481,9 +489,7 @@ export class ClientServerService {
avatarUrl: _note.user.avatarUrl, avatarUrl: _note.user.avatarUrl,
// TODO: Let locale changeable by instance setting // TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note), summary: getNoteSummary(_note),
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
return await renderBase(reply); return await renderBase(reply);
@ -522,9 +528,7 @@ export class ClientServerService {
page: _page, page: _page,
profile, profile,
avatarUrl: _page.user.avatarUrl, avatarUrl: _page.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
return await renderBase(reply); return await renderBase(reply);
@ -550,9 +554,7 @@ export class ClientServerService {
flash: _flash, flash: _flash,
profile, profile,
avatarUrl: _flash.user.avatarUrl, avatarUrl: _flash.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
return await renderBase(reply); return await renderBase(reply);
@ -578,9 +580,7 @@ export class ClientServerService {
clip: _clip, clip: _clip,
profile, profile,
avatarUrl: _clip.user.avatarUrl, avatarUrl: _clip.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
return await renderBase(reply); return await renderBase(reply);
@ -604,9 +604,7 @@ export class ClientServerService {
post: _post, post: _post,
profile, profile,
avatarUrl: _post.user.avatarUrl, avatarUrl: _post.user.avatarUrl,
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
return await renderBase(reply); return await renderBase(reply);
@ -625,9 +623,7 @@ export class ClientServerService {
reply.header('Cache-Control', 'public, max-age=15'); reply.header('Cache-Control', 'public, max-age=15');
return await reply.view('channel', { return await reply.view('channel', {
channel: _channel, channel: _channel,
instanceName: meta.name ?? 'Misskey', ...this.generateCommonPugData(meta),
icon: meta.iconUrl,
themeColor: meta.themeColor,
}); });
} else { } else {
return await renderBase(reply); return await renderBase(reply);

View File

@ -31,9 +31,9 @@ html
link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png')
link(rel='manifest' href='/manifest.json') link(rel='manifest' href='/manifest.json')
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`) link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`)
link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') link(rel='prefetch' href=serverErrorImageUrl)
link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842 //- https://github.com/misskey-dev/misskey/issues/9842
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0') link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0')
link(rel='modulepreload' href=`/vite/${clientEntry.file}`) link(rel='modulepreload' href=`/vite/${clientEntry.file}`)

View File

@ -2,7 +2,7 @@
<MkPagination :pagination="pagination"> <MkPagination :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.notFound }}</div>
</div> </div>
</template> </template>
@ -17,6 +17,7 @@
import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkChannelPreview from '@/components/MkChannelPreview.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { infoImageUrl } from '@/instance';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
pagination: Paging; pagination: Paging;

View File

@ -2,7 +2,7 @@
<MkPagination ref="pagingComponent" :pagination="pagination"> <MkPagination ref="pagingComponent" :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noNotes }}</div>
</div> </div>
</template> </template>
@ -32,6 +32,7 @@ import MkNote from '@/components/MkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { infoImageUrl } from '@/instance';
const props = defineProps<{ const props = defineProps<{
pagination: Paging; pagination: Paging;

View File

@ -2,7 +2,7 @@
<MkPagination ref="pagingComponent" :pagination="pagination"> <MkPagination ref="pagingComponent" :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noNotifications }}</div>
</div> </div>
</template> </template>
@ -26,6 +26,7 @@ import { useStream } from '@/stream';
import { $i } from '@/account'; import { $i } from '@/account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { notificationTypes } from '@/const'; import { notificationTypes } from '@/const';
import { infoImageUrl } from '@/instance';
const props = defineProps<{ const props = defineProps<{
includeTypes?: typeof notificationTypes[number][]; includeTypes?: typeof notificationTypes[number][];

View File

@ -13,7 +13,7 @@
<div v-else-if="empty" key="_empty_" class="empty"> <div v-else-if="empty" key="_empty_" class="empty">
<slot name="empty"> <slot name="empty">
<div class="_fullinfo"> <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>{{ i18n.ts.nothing }}</div>
</div> </div>
</slot> </slot>
@ -73,6 +73,8 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints>
}; };
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { infoImageUrl } from '@/instance';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
pagination: Paging; pagination: Paging;
disableAutoLoad?: boolean; disableAutoLoad?: boolean;

View File

@ -11,7 +11,7 @@
<MkSpacer :marginMin="20" :marginMax="28"> <MkSpacer :marginMin="20" :marginMax="28">
<div v-if="note" class="_gaps"> <div v-if="note" class="_gaps">
<div v-if="reactions.length === 0" class="_fullinfo"> <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>{{ i18n.ts.nothing }}</div>
</div> </div>
<template v-else> <template v-else>
@ -42,6 +42,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { userPage } from '@/filters/user'; import { userPage } from '@/filters/user';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import * as os from '@/os'; import * as os from '@/os';
import { infoImageUrl } from '@/instance';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'closed'): void, (ev: 'closed'): void,

View File

@ -11,7 +11,7 @@
<MkSpacer :marginMin="20" :marginMax="28"> <MkSpacer :marginMin="20" :marginMax="28">
<div v-if="renotes" class="_gaps"> <div v-if="renotes" class="_gaps">
<div v-if="renotes.length === 0" class="_fullinfo"> <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>{{ i18n.ts.nothing }}</div>
</div> </div>
<template v-else> <template v-else>
@ -35,6 +35,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { userPage } from '@/filters/user'; import { userPage } from '@/filters/user';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import * as os from '@/os'; import * as os from '@/os';
import { infoImageUrl } from '@/instance';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'closed'): void, (ev: 'closed'): void,

View File

@ -2,7 +2,7 @@
<MkPagination :pagination="pagination"> <MkPagination :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noUsers }}</div>
</div> </div>
</template> </template>
@ -19,6 +19,7 @@
import MkUserInfo from '@/components/MkUserInfo.vue'; import MkUserInfo from '@/components/MkUserInfo.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { infoImageUrl } from '@/instance';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
pagination: Paging; pagination: Paging;

View File

@ -1,7 +1,7 @@
<template> <template>
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
<div :class="$style.root"> <div :class="$style.root">
<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> <img :class="$style.img" :src="infoImageUrl" class="_ghost"/>
<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> <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> <MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton>
</div> </div>
@ -12,6 +12,7 @@
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { infoImageUrl } from '@/instance';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'retry'): void; (ev: 'retry'): void;

View File

@ -78,3 +78,7 @@ export const ROLE_POLICIES = [
//export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM'); //export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM');
export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP'; export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM'; 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';

View File

@ -1,7 +1,8 @@
import { reactive } from 'vue'; import { computed, reactive } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { api } from './os'; import { api } from './os';
import { miLocalStorage } from './local-storage'; import { miLocalStorage } from './local-storage';
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
@ -13,6 +14,12 @@ export const instance: Misskey.entities.InstanceMetadata = reactive(cached ? JSO
// TODO: set default values // 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() { export async function fetchInstance() {
const meta = await api('meta', { const meta = await api('meta', {
detail: false, detail: false,

View File

@ -2,7 +2,7 @@
<MkLoading v-if="!loaded"/> <MkLoading v-if="!loaded"/>
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
<div v-show="loaded" :class="$style.root"> <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"> <div class="_gaps">
<p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p> <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> <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 { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage'; import { miLocalStorage } from '@/local-storage';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { serverErrorImageUrl } from '@/instance';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
error?: Error; error?: Error;

View 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>

View File

@ -143,6 +143,11 @@ const menuDef = $computed(() => [{
text: i18n.ts.general, text: i18n.ts.general,
to: '/admin/settings', to: '/admin/settings',
active: currentPage?.route.name === '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', icon: 'ti ti-shield',
text: i18n.ts.moderation, text: i18n.ts.moderation,

View File

@ -23,7 +23,7 @@
<MkPagination :pagination="usersPagination"> <MkPagination :pagination="usersPagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noUsers }}</div>
</div> </div>
</template> </template>
@ -69,6 +69,7 @@ import MkButton from '@/components/MkButton.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { infoImageUrl } from '@/instance';
const router = useRouter(); const router = useRouter();

View File

@ -29,41 +29,6 @@
<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
</MkTextarea> </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> <FormSection>
<template #label>{{ i18n.ts.files }}</template> <template #label>{{ i18n.ts.files }}</template>
@ -145,12 +110,6 @@ let name: string | null = $ref(null);
let description: string | null = $ref(null); let description: string | null = $ref(null);
let maintainerName: string | null = $ref(null); let maintainerName: string | null = $ref(null);
let maintainerEmail: 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 pinnedUsers: string = $ref('');
let cacheRemoteFiles: boolean = $ref(false); let cacheRemoteFiles: boolean = $ref(false);
let enableServiceWorker: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false);
@ -163,12 +122,6 @@ async function init() {
const meta = await os.api('admin/meta'); const meta = await os.api('admin/meta');
name = meta.name; name = meta.name;
description = meta.description; description = meta.description;
iconUrl = meta.iconUrl;
bannerUrl = meta.bannerUrl;
backgroundImageUrl = meta.backgroundImageUrl;
themeColor = meta.themeColor;
defaultLightTheme = meta.defaultLightTheme;
defaultDarkTheme = meta.defaultDarkTheme;
maintainerName = meta.maintainerName; maintainerName = meta.maintainerName;
maintainerEmail = meta.maintainerEmail; maintainerEmail = meta.maintainerEmail;
pinnedUsers = meta.pinnedUsers.join('\n'); pinnedUsers = meta.pinnedUsers.join('\n');
@ -184,12 +137,6 @@ function save() {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
name, name,
description, description,
iconUrl,
bannerUrl,
backgroundImageUrl,
themeColor: themeColor === '' ? null : themeColor,
defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme,
defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme,
maintainerName, maintainerName,
maintainerEmail, maintainerEmail,
pinnedUsers: pinnedUsers.split('\n'), pinnedUsers: pinnedUsers.split('\n'),

View File

@ -5,7 +5,7 @@
<MkPagination :pagination="pagination"> <MkPagination :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noNotes }}</div>
</div> </div>
</template> </template>
@ -26,6 +26,7 @@ import MkNote from '@/components/MkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { infoImageUrl } from '@/instance';
const pagination = { const pagination = {
endpoint: 'i/favorites' as const, endpoint: 'i/favorites' as const,

View File

@ -5,7 +5,7 @@
<MkPagination ref="paginationComponent" :pagination="pagination"> <MkPagination ref="paginationComponent" :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noFollowRequests }}</div>
</div> </div>
</template> </template>
@ -39,6 +39,7 @@ import { userPage, acct } from '@/filters/user';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { infoImageUrl } from '@/instance';
const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();

View File

@ -3,7 +3,7 @@
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
<div :class="$style.root"> <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"> <p :class="$style.text">
<i class="ti ti-alert-triangle"></i> <i class="ti ti-alert-triangle"></i>
{{ i18n.ts.nothing }} {{ i18n.ts.nothing }}
@ -36,6 +36,7 @@ import { i18n } from '@/i18n';
import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { serverErrorImageUrl } from '@/instance';
const props = defineProps<{ const props = defineProps<{
listId: string; listId: string;

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="_fullinfo"> <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>{{ i18n.ts.notFoundDescription }}</div>
</div> </div>
</div> </div>
@ -10,6 +10,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { notFoundImageUrl } from '@/instance';
const headerActions = $computed(() => []); const headerActions = $computed(() => []);

View File

@ -3,7 +3,7 @@
<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template> <template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
<div :class="$style.root"> <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"> <p :class="$style.text">
<i class="ti ti-alert-triangle"></i> <i class="ti ti-alert-triangle"></i>
{{ error }} {{ error }}
@ -30,6 +30,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import MkTimeline from '@/components/MkTimeline.vue'; import MkTimeline from '@/components/MkTimeline.vue';
import { instanceName } from '@/config'; import { instanceName } from '@/config';
import { serverErrorImageUrl } from '@/instance';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
role: string; role: string;

View File

@ -3,7 +3,7 @@
<FormPagination ref="list" :pagination="pagination"> <FormPagination ref="list" :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.nothing }}</div>
</div> </div>
</template> </template>
@ -47,6 +47,7 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { infoImageUrl } from '@/instance';
const list = ref<any>(null); const list = ref<any>(null);

View File

@ -10,7 +10,7 @@
<MkPagination :pagination="renoteMutingPagination"> <MkPagination :pagination="renoteMutingPagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noUsers }}</div>
</div> </div>
</template> </template>
@ -38,7 +38,7 @@
<MkPagination :pagination="mutingPagination"> <MkPagination :pagination="mutingPagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noUsers }}</div>
</div> </div>
</template> </template>
@ -68,7 +68,7 @@
<MkPagination :pagination="blockingPagination"> <MkPagination :pagination="blockingPagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <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>{{ i18n.ts.noUsers }}</div>
</div> </div>
</template> </template>
@ -107,6 +107,7 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue';
import * as os from '@/os'; import * as os from '@/os';
import { infoImageUrl } from '@/instance';
let tab = $ref('renoteMute'); let tab = $ref('renoteMute');

View File

@ -127,7 +127,6 @@ const profile = reactive({
lang: $i.lang, lang: $i.lang,
isBot: $i.isBot, isBot: $i.isBot,
isCat: $i.isCat, isCat: $i.isCat,
showTimelineReplies: $i.showTimelineReplies,
}); });
watch(() => profile, () => { watch(() => profile, () => {
@ -151,7 +150,7 @@ while (fields.value.length < 4) {
addField(); addField();
} }
function deleteField(index: number) { function deleteField(index: number) {
fields.value.splice(index, 1); fields.value.splice(index, 1);
} }
@ -176,7 +175,6 @@ function save() {
lang: profile.lang || null, lang: profile.lang || null,
isBot: !!profile.isBot, isBot: !!profile.isBot,
isCat: !!profile.isCat, isCat: !!profile.isCat,
showTimelineReplies: !!profile.showTimelineReplies,
}); });
claimAchievement('profileFilled'); claimAchievement('profileFilled');
if (profile.name === 'syuilo' || profile.name === 'しゅいろ') { if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {

View File

@ -392,6 +392,10 @@ export const routes = [{
path: '/settings', path: '/settings',
name: 'settings', name: 'settings',
component: page(() => import('./pages/admin/settings.vue')), component: page(() => import('./pages/admin/settings.vue')),
}, {
path: '/branding',
name: 'branding',
component: page(() => import('./pages/admin/branding.vue')),
}, { }, {
path: '/moderation', path: '/moderation',
name: 'moderation', name: 'moderation',

View File

@ -254,6 +254,28 @@ async function deleteProfile() {
} }
</script> </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> <style lang="scss" module>
.transition_menuDrawerBg_enterActive, .transition_menuDrawerBg_enterActive,
.transition_menuDrawerBg_leaveActive { .transition_menuDrawerBg_leaveActive {

View File

@ -215,6 +215,28 @@ watch($$(navFooter), () => {
}); });
</script> </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> <style lang="scss" module>
$ui-font-size: 1em; // TODO: $ui-font-size: 1em; // TODO:
$widgets-hide-threshold: 1090px; $widgets-hide-threshold: 1090px;

View File

@ -7,7 +7,7 @@
<div class="ekmkgxbj"> <div class="ekmkgxbj">
<MkLoading v-if="fetching"/> <MkLoading v-if="fetching"/>
<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo"> <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>{{ i18n.ts.nothing }}</div>
</div> </div>
<div v-else :class="$style.feed"> <div v-else :class="$style.feed">
@ -25,6 +25,7 @@ import MkContainer from '@/components/MkContainer.vue';
import { url as base } from '@/config'; import { url as base } from '@/config';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { useInterval } from '@/scripts/use-interval'; import { useInterval } from '@/scripts/use-interval';
import { infoImageUrl } from '@/instance';
const name = 'rss'; const name = 'rss';

View File

@ -294,7 +294,9 @@ export type LiteInstanceMetadata = {
themeColor: string | null; themeColor: string | null;
mascotImageUrl: string | null; mascotImageUrl: string | null;
bannerUrl: string | null; bannerUrl: string | null;
errorImageUrl: string | null; serverErrorImageUrl: string | null;
infoImageUrl: string | null;
notFoundImageUrl: string | null;
iconUrl: string | null; iconUrl: string | null;
backgroundImageUrl: string | null; backgroundImageUrl: string | null;
logoImageUrl: string | null; logoImageUrl: string | null;