diff --git a/CHANGELOG.md b/CHANGELOG.md index 464bdd677f4b..cddd9f1ad7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ - どのアカウントで認証しようとしているのかがわかるように - 認証するアカウントを切り替えられるように - Enhance: Self-XSS防止用の警告を追加 -- Enhance: カタルーニャ語 (ca-ES) に対応 +- Enhance: カタルーニャ語 (ca-ES) に対応 +- Enhance: 個別お知らせページではMetaタグを出力するように - Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正 - Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 4860ef3e12c5..5ebec4ffd070 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -42,13 +42,26 @@ import { MetaEntityService } from '@/core/entities/MetaEntityService.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, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { + AnnouncementsRepository, + ChannelsRepository, + ClipsRepository, + FlashsRepository, + GalleryPostsRepository, + MiMeta, + NotesRepository, + PagesRepository, + ReversiGamesRepository, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; import type Logger from '@/logger.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { RoleService } from '@/core/RoleService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; +import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js'; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; import { ClientLoggerService } from './ClientLoggerService.js'; @@ -103,6 +116,9 @@ export class ClientServerService { @Inject(DI.reversiGamesRepository) private reversiGamesRepository: ReversiGamesRepository, + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, + private flashEntityService: FlashEntityService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, @@ -112,6 +128,7 @@ export class ClientServerService { private clipEntityService: ClipEntityService, private channelEntityService: ChannelEntityService, private reversiGameEntityService: ReversiGameEntityService, + private announcementEntityService: AnnouncementEntityService, private urlPreviewService: UrlPreviewService, private feedService: FeedService, private roleService: RoleService, @@ -776,6 +793,24 @@ export class ClientServerService { return await renderBase(reply); } }); + + // 個別お知らせページ + fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => { + const announcement = await this.announcementsRepository.findOneBy({ + id: request.params.announcementId, + }); + + if (announcement) { + const _announcement = await this.announcementEntityService.pack(announcement); + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('announcement', { + announcement: _announcement, + ...await this.generateCommonPugData(this.meta), + }); + } else { + return await renderBase(reply); + } + }); //#endregion //#region noindex pages diff --git a/packages/backend/src/server/web/views/announcement.pug b/packages/backend/src/server/web/views/announcement.pug new file mode 100644 index 000000000000..7a4052e8a45a --- /dev/null +++ b/packages/backend/src/server/web/views/announcement.pug @@ -0,0 +1,21 @@ +extends ./base + +block vars + - const title = announcement.title; + - const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text; + - const url = `${config.url}/announcements/${announcement.id}`; + +block title + = `${title} | ${instanceName}` + +block desc + meta(name='description' content=description) + +block og + meta(property='og:type' content='article') + meta(property='og:title' content= title) + meta(property='og:description' content= description) + meta(property='og:url' content= url) + if announcement.imageUrl + meta(property='og:image' content=announcement.imageUrl) + meta(property='twitter:card' content='summary_large_image') diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 88714b255641..280a5923c23f 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -2,6 +2,7 @@ block vars block loadClientEntry - const entry = config.frontendEntry; + - const baseUrl = config.url; doctype html @@ -32,7 +33,7 @@ html link(rel='icon' href= icon || '/favicon.ico') link(rel='apple-touch-icon' href= appleTouchIcon || '/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='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${baseUrl}/opensearch.xml`) link(rel='prefetch' href=serverErrorImageUrl) link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue index 01c29cf02d50..56c10fb2928c 100644 --- a/packages/frontend/src/pages/announcement.vue +++ b/packages/frontend/src/pages/announcement.vue @@ -103,7 +103,7 @@ const headerActions = computed(() => []); const headerTabs = computed(() => []); definePageMetadata(() => ({ - title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements, + title: announcement.value ? announcement.value.title : i18n.ts.announcements, icon: 'ti ti-speakerphone', }));