エラー画像URLを設定可能に (#10959)
* エラー画像URLを設定可能に * Update CHANGELOG.md * 設定したエラーアイコンをprefetchするようにbase.pugを変更 * 不足していたデータを追加 * enhance(frontend): デザイン調整
This commit is contained in:
		
							parent
							
								
									3941c73db0
								
							
						
					
					
						commit
						34a32a8334
					
				|  | @ -17,6 +17,9 @@ | |||
| ### Client | ||||
| - Fix: タブがアクティブな間はstreamが切断されないように | ||||
| 
 | ||||
| ### General | ||||
| - エラー時や項目が存在しないときなどのアイコン画像をサーバー管理者が設定できるようになりました | ||||
| 
 | ||||
| ### Server | ||||
| - Fix: api/metaで`TypeError: JSON5.parse is not a function`エラーが発生する問題を修正 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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'`); | ||||
|     } | ||||
| } | ||||
|  | @ -101,13 +101,25 @@ export class Meta { | |||
| 		length: 1024, | ||||
| 		nullable: true, | ||||
| 	}) | ||||
| 	public errorImageUrl: string | null; | ||||
| 	public iconUrl: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 1024, | ||||
| 		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', { | ||||
| 		default: true, | ||||
|  |  | |||
|  | @ -61,10 +61,17 @@ export const meta = { | |||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			errorImageUrl: { | ||||
| 			serverErrorImageUrl: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			infoImageUrl: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			notFoundImageUrl: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 				default: 'https://xn--931a.moe/aiart/yubitun.png', | ||||
| 			}, | ||||
| 			iconUrl: { | ||||
| 				type: 'string', | ||||
|  | @ -305,7 +312,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				themeColor: instance.themeColor, | ||||
| 				mascotImageUrl: instance.mascotImageUrl, | ||||
| 				bannerUrl: instance.bannerUrl, | ||||
| 				errorImageUrl: instance.errorImageUrl, | ||||
| 				serverErrorImageUrl: instance.serverErrorImageUrl, | ||||
| 				notFoundImageUrl: instance.notFoundImageUrl, | ||||
| 				infoImageUrl: instance.infoImageUrl, | ||||
| 				iconUrl: instance.iconUrl, | ||||
| 				backgroundImageUrl: instance.backgroundImageUrl, | ||||
| 				logoImageUrl: instance.logoImageUrl, | ||||
|  |  | |||
|  | @ -32,7 +32,9 @@ export const paramDef = { | |||
| 		themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, | ||||
| 		mascotImageUrl: { 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 }, | ||||
| 		backgroundImageUrl: { 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; | ||||
| 			} | ||||
| 
 | ||||
| 			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) { | ||||
| 				set.backgroundImageUrl = ps.backgroundImageUrl; | ||||
| 			} | ||||
|  | @ -281,10 +295,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				set.smtpPass = ps.smtpPass; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.errorImageUrl !== undefined) { | ||||
| 				set.errorImageUrl = ps.errorImageUrl; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.enableServiceWorker !== undefined) { | ||||
| 				set.enableServiceWorker = ps.enableServiceWorker; | ||||
| 			} | ||||
|  |  | |||
|  | @ -124,10 +124,17 @@ export const meta = { | |||
| 				type: 'string', | ||||
| 				optional: false, nullable: false, | ||||
| 			}, | ||||
| 			errorImageUrl: { | ||||
| 			serverErrorImageUrl: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: false, | ||||
| 				default: 'https://xn--931a.moe/aiart/yubitun.png', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			infoImageUrl: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			notFoundImageUrl: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			iconUrl: { | ||||
| 				type: 'string', | ||||
|  | @ -288,7 +295,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				themeColor: instance.themeColor, | ||||
| 				mascotImageUrl: instance.mascotImageUrl, | ||||
| 				bannerUrl: instance.bannerUrl, | ||||
| 				errorImageUrl: instance.errorImageUrl, | ||||
| 				infoImageUrl: instance.infoImageUrl, | ||||
| 				serverErrorImageUrl: instance.serverErrorImageUrl, | ||||
| 				notFoundImageUrl: instance.notFoundImageUrl, | ||||
| 				iconUrl: instance.iconUrl, | ||||
| 				backgroundImageUrl: instance.backgroundImageUrl, | ||||
| 				logoImageUrl: instance.logoImageUrl, | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js'; | |||
| import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; | ||||
| import { ClipEntityService } from '@/core/entities/ClipEntityService.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 { deepClone } from '@/misc/clone.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
|  | @ -117,6 +117,18 @@ export class ClientServerService { | |||
| 		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 | ||||
| 	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { | ||||
| 		fastify.register(fastifyCookie, {}); | ||||
|  | @ -341,12 +353,10 @@ export class ClientServerService { | |||
| 			reply.header('Cache-Control', 'public, max-age=30'); | ||||
| 			return await reply.view('base', { | ||||
| 				img: meta.bannerUrl, | ||||
| 				title: meta.name ?? 'Misskey', | ||||
| 				instanceName: meta.name ?? 'Misskey', | ||||
| 				url: this.config.url, | ||||
| 				title: meta.name ?? 'Misskey', | ||||
| 				desc: meta.description, | ||||
| 				icon: meta.iconUrl, | ||||
| 				themeColor: meta.themeColor, | ||||
| 				...this.generateCommonPugData(meta), | ||||
| 			}); | ||||
| 		}; | ||||
| 
 | ||||
|  | @ -431,9 +441,7 @@ export class ClientServerService { | |||
| 					user, profile, me, | ||||
| 					avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), | ||||
| 					sub: request.params.sub, | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				// リモートユーザーなので
 | ||||
|  | @ -481,9 +489,7 @@ export class ClientServerService { | |||
| 					avatarUrl: _note.user.avatarUrl, | ||||
| 					// TODO: Let locale changeable by instance setting
 | ||||
| 					summary: getNoteSummary(_note), | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return await renderBase(reply); | ||||
|  | @ -522,9 +528,7 @@ export class ClientServerService { | |||
| 					page: _page, | ||||
| 					profile, | ||||
| 					avatarUrl: _page.user.avatarUrl, | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return await renderBase(reply); | ||||
|  | @ -550,9 +554,7 @@ export class ClientServerService { | |||
| 					flash: _flash, | ||||
| 					profile, | ||||
| 					avatarUrl: _flash.user.avatarUrl, | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return await renderBase(reply); | ||||
|  | @ -578,9 +580,7 @@ export class ClientServerService { | |||
| 					clip: _clip, | ||||
| 					profile, | ||||
| 					avatarUrl: _clip.user.avatarUrl, | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return await renderBase(reply); | ||||
|  | @ -604,9 +604,7 @@ export class ClientServerService { | |||
| 					post: _post, | ||||
| 					profile, | ||||
| 					avatarUrl: _post.user.avatarUrl, | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return await renderBase(reply); | ||||
|  | @ -625,9 +623,7 @@ export class ClientServerService { | |||
| 				reply.header('Cache-Control', 'public, max-age=15'); | ||||
| 				return await reply.view('channel', { | ||||
| 					channel: _channel, | ||||
| 					instanceName: meta.name ?? 'Misskey', | ||||
| 					icon: meta.iconUrl, | ||||
| 					themeColor: meta.themeColor, | ||||
| 					...this.generateCommonPugData(meta), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return await renderBase(reply); | ||||
|  |  | |||
|  | @ -31,9 +31,9 @@ html | |||
| 		link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') | ||||
| 		link(rel='manifest' href='/manifest.json') | ||||
| 		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='https://xn--931a.moe/assets/not-found.jpg') | ||||
| 		link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') | ||||
| 		link(rel='prefetch' href=serverErrorImageUrl) | ||||
| 		link(rel='prefetch' href=infoImageUrl) | ||||
| 		link(rel='prefetch' href=notFoundImageUrl) | ||||
| 		//- https://github.com/misskey-dev/misskey/issues/9842 | ||||
| 		link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0') | ||||
| 		link(rel='modulepreload' href=`/vite/${clientEntry.file}`) | ||||
|  |  | |||
|  | @ -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 '@/const'; | ||||
| 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; | ||||
|  |  | |||
|  | @ -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="infoImageUrl" 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 { infoImageUrl } from '@/instance'; | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'retry'): void; | ||||
|  |  | |||
|  | @ -78,3 +78,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; | ||||
|  |  | |||
|  | @ -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(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -48,6 +48,21 @@ | |||
| 								<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> | ||||
|  | @ -151,6 +166,9 @@ 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); | ||||
| let pinnedUsers: string = $ref(''); | ||||
| let cacheRemoteFiles: boolean = $ref(false); | ||||
| let enableServiceWorker: boolean = $ref(false); | ||||
|  | @ -169,6 +187,9 @@ async function init() { | |||
| 	themeColor = meta.themeColor; | ||||
| 	defaultLightTheme = meta.defaultLightTheme; | ||||
| 	defaultDarkTheme = meta.defaultDarkTheme; | ||||
| 	serverErrorImageUrl = meta.serverErrorImageUrl; | ||||
| 	infoImageUrl = meta.infoImageUrl; | ||||
| 	notFoundImageUrl = meta.notFoundImageUrl; | ||||
| 	maintainerName = meta.maintainerName; | ||||
| 	maintainerEmail = meta.maintainerEmail; | ||||
| 	pinnedUsers = meta.pinnedUsers.join('\n'); | ||||
|  | @ -190,6 +211,9 @@ function save() { | |||
| 		themeColor: themeColor === '' ? null : themeColor, | ||||
| 		defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme, | ||||
| 		defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme, | ||||
| 		infoImageUrl, | ||||
| 		notFoundImageUrl, | ||||
| 		serverErrorImageUrl, | ||||
| 		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; | ||||
|  |  | |||
|  | @ -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'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -294,7 +294,9 @@ export type LiteInstanceMetadata = { | |||
| 	themeColor: string | null; | ||||
| 	mascotImageUrl: string | null; | ||||
| 	bannerUrl: string | null; | ||||
| 	errorImageUrl: string | null; | ||||
| 	serverErrorImageUrl: string | null; | ||||
| 	infoImageUrl: string | null; | ||||
| 	notFoundImageUrl: string | null; | ||||
| 	iconUrl: string | null; | ||||
| 	backgroundImageUrl: string | null; | ||||
| 	logoImageUrl: string | null; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue