From 814e28459ed70665b63a15f1d6e8152976cd6412 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 16 Sep 2023 17:05:17 +0900 Subject: [PATCH] enhance(backend): improve server icon setting Resolve #11481 Resolve #10901 --- CHANGELOG.md | 2 + locales/index.d.ts | 9 +- locales/ja-JP.yml | 9 +- ...1694850832075-server-icons-and-manifest.js | 15 +++ packages/backend/src/models/entities/Meta.ts | 18 ++++ .../src/server/api/endpoints/admin/meta.ts | 15 +++ .../server/api/endpoints/admin/update-meta.ts | 15 +++ .../src/server/web/ClientServerService.ts | 96 ++++++++++--------- .../backend/src/server/web/views/base.pug | 2 +- .../frontend/src/pages/admin/branding.vue | 41 +++++++- packages/misskey-js/src/entities.ts | 3 + 11 files changed, 175 insertions(+), 50 deletions(-) create mode 100644 packages/backend/migration/1694850832075-server-icons-and-manifest.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f34a8a98..6ad785d19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ - 二要素認証のバックアップコードが生成されるようになりました ref. https://github.com/MisskeyIO/misskey/pull/121 - 二要素認証でパスキーをサポートするようになりました - 通知をテストできるようになりました +- PWAのアイコンが設定できるようになりました +- manifest.jsonをオーバーライド可能に ### Client - プロフィールにその人が作ったPlayの一覧出せるように diff --git a/locales/index.d.ts b/locales/index.d.ts index 152e2ae81..94d9657ac 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -359,7 +359,6 @@ export interface Locale { "driveCapacityPerLocalAccount": string; "driveCapacityPerRemoteAccount": string; "inMb": string; - "iconUrl": string; "bannerUrl": string; "backgroundImageUrl": string; "basicInfo": string; @@ -1140,6 +1139,14 @@ export interface Locale { "_serverRules": { "description": string; }; + "_serverSettings": { + "iconUrl": string; + "appIconDescription": string; + "appIconUsageExample": string; + "appIconStyleRecommendation": string; + "appIconResolutionMustBe": string; + "manifestJsonOverride": string; + }; "_accountMigration": { "moveFrom": string; "moveFromSub": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1a9a7a3ec..eb0590a72 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -356,7 +356,6 @@ invite: "招待" driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量" driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量" inMb: "メガバイト単位" -iconUrl: "アイコン画像のURL (faviconなど)" bannerUrl: "バナー画像のURL" backgroundImageUrl: "背景画像のURL" basicInfo: "基本情報" @@ -1138,6 +1137,14 @@ _initialAccountSetting: _serverRules: description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。" +_serverSettings: + iconUrl: "アイコン画像のURL" + appIconDescription: "{host}がアプリとして表示される際のアイコンを指定します。" + appIconUsageExample: "例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など" + appIconStyleRecommendation: "画像は透過部分が無く、塗りつぶされた余白がある背景を持つことが推奨されます。" + appIconResolutionMustBe: "解像度は必ず{resolution}である必要があります。" + manifestJsonOverride: "manifest.jsonのオーバーライド" + _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" moveFromSub: "別のアカウントへエイリアスを作成" diff --git a/packages/backend/migration/1694850832075-server-icons-and-manifest.js b/packages/backend/migration/1694850832075-server-icons-and-manifest.js new file mode 100644 index 000000000..cbbb07d39 --- /dev/null +++ b/packages/backend/migration/1694850832075-server-icons-and-manifest.js @@ -0,0 +1,15 @@ +export class ServerIconsAndManifest1694850832075 { + name = 'ServerIconsAndManifest1694850832075' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "app192IconUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "app512IconUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "manifestJsonOverride" character varying(8192) NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "manifestJsonOverride"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app512IconUrl"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app192IconUrl"`); + } +} diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index 5a9c875a7..4bb77b792 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -107,6 +107,18 @@ export class MiMeta { }) public iconUrl: string | null; + @Column('varchar', { + length: 1024, + nullable: true, + }) + public app192IconUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public app512IconUrl: string | null; + @Column('varchar', { length: 1024, nullable: true, @@ -444,6 +456,12 @@ export class MiMeta { }) public serverRules: string[]; + @Column('varchar', { + length: 8192, + default: '{}', + }) + public manifestJsonOverride: string; + @Column('varchar', { length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', }) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 9b6dee296..fe9c134d8 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -85,6 +85,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + app192IconUrl: { + type: 'string', + optional: false, nullable: true, + }, + app512IconUrl: { + type: 'string', + optional: false, nullable: true, + }, enableEmail: { type: 'boolean', optional: false, nullable: false, @@ -278,6 +286,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + manifestJsonOverride: { + type: 'string', + optional: true, nullable: false, + }, policies: { type: 'object', optional: false, nullable: false, @@ -331,6 +343,8 @@ export default class extends Endpoint { // eslint- notFoundImageUrl: instance.notFoundImageUrl, infoImageUrl: instance.infoImageUrl, iconUrl: instance.iconUrl, + app192IconUrl: instance.app192IconUrl, + app512IconUrl: instance.app512IconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, defaultLightTheme: instance.defaultLightTheme, @@ -383,6 +397,7 @@ export default class extends Endpoint { // eslint- enableServerMachineStats: instance.enableServerMachineStats, enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, + manifestJsonOverride: instance.manifestJsonOverride, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 17d5a1f91..65b7736d1 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -39,6 +39,8 @@ export const paramDef = { infoImageUrl: { type: 'string', nullable: true }, notFoundImageUrl: { type: 'string', nullable: true }, iconUrl: { type: 'string', nullable: true }, + app192IconUrl: { type: 'string', nullable: true }, + app512IconUrl: { type: 'string', nullable: true }, backgroundImageUrl: { type: 'string', nullable: true }, logoImageUrl: { type: 'string', nullable: true }, name: { type: 'string', nullable: true }, @@ -104,6 +106,7 @@ export const paramDef = { enableIdenticonGeneration: { type: 'boolean' }, serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, + manifestJsonOverride: { type: 'string' }, }, required: [], } as const; @@ -153,6 +156,14 @@ export default class extends Endpoint { // eslint- set.iconUrl = ps.iconUrl; } + if (ps.app192IconUrl !== undefined) { + set.app192IconUrl = ps.app192IconUrl; + } + + if (ps.app512IconUrl !== undefined) { + set.app512IconUrl = ps.app512IconUrl; + } + if (ps.serverErrorImageUrl !== undefined) { set.serverErrorImageUrl = ps.serverErrorImageUrl; } @@ -421,6 +432,10 @@ export default class extends Endpoint { // eslint- set.preservedUsernames = ps.preservedUsernames; } + if (ps.manifestJsonOverride !== undefined) { + set.manifestJsonOverride = ps.manifestJsonOverride; + } + await this.metaService.update(set); this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index a8c420e57..7b1dd92d7 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -51,45 +51,6 @@ const assets = `${_dirname}/../../../../../built/_frontend_dist_/`; const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; const viteOut = `${_dirname}/../../../../../built/_vite_/`; -const manifest = { - 'short_name': 'Misskey', - 'name': 'Misskey', - 'start_url': '/', - 'display': 'standalone', - 'background_color': '#313a42', - 'theme_color': '#86b300', - 'icons': [ - { - 'src': '/static-assets/icons/192.png', - 'sizes': '192x192', - 'type': 'image/png', - 'purpose': 'maskable', - }, - { - 'src': '/static-assets/icons/512.png', - 'sizes': '512x512', - 'type': 'image/png', - 'purpose': 'maskable', - }, - { - 'src': '/static-assets/splash.png', - 'sizes': '300x300', - 'type': 'image/png', - 'purpose': 'any', - }, - ], - 'share_target': { - 'action': '/share/', - 'method': 'GET', - 'enctype': 'application/x-www-form-urlencoded', - 'params': { - 'title': 'title', - 'text': 'text', - 'url': 'url', - }, - }, -}; - @Injectable() export class ClientServerService { private logger: Logger; @@ -148,16 +109,60 @@ export class ClientServerService { @bindThis private async manifestHandler(reply: FastifyReply) { - const res = deepClone(manifest); - const instance = await this.metaService.fetch(true); - res.short_name = instance.name ?? 'Misskey'; - res.name = instance.name ?? 'Misskey'; - if (instance.themeColor) res.theme_color = instance.themeColor; + let manifest = { + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'short_name': instance.name || 'Misskey', + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'name': instance.name || 'Misskey', + 'start_url': '/', + 'display': 'standalone', + 'background_color': '#313a42', + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'theme_color': instance.themeColor || '#86b300', + 'icons': [{ + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'src': instance.app192IconUrl || '/static-assets/icons/192.png', + 'sizes': '192x192', + 'type': 'image/png', + 'purpose': 'maskable', + }, { + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'src': instance.app512IconUrl || '/static-assets/icons/512.png', + 'sizes': '512x512', + 'type': 'image/png', + 'purpose': 'maskable', + }, { + 'src': '/static-assets/splash.png', + 'sizes': '300x300', + 'type': 'image/png', + 'purpose': 'any', + }], + 'share_target': { + 'action': '/share/', + 'method': 'GET', + 'enctype': 'application/x-www-form-urlencoded', + 'params': { + 'title': 'title', + 'text': 'text', + 'url': 'url', + }, + }, + }; + + manifest = { + ...manifest, + ...JSON.parse(instance.manifestJsonOverride === '' ? '{}' : instance.manifestJsonOverride), + }; reply.header('Cache-Control', 'max-age=300'); - return (res); + return (manifest); } @bindThis @@ -165,6 +170,7 @@ export class ClientServerService { return { instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, + appleTouchIcon: meta.app512IconUrl, themeColor: meta.themeColor, serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg', infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg', diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index c2053517d..ab74a1f7f 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -28,7 +28,7 @@ html meta(property='og:site_name' content= instanceName || 'Misskey') meta(name='viewport' content='width=device-width, initial-scale=1') link(rel='icon' href= icon || '/favicon.ico') - link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') + 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='prefetch' href=serverErrorImageUrl) diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index cc331ebe0..2f369380f 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -12,7 +12,29 @@ SPDX-License-Identifier: AGPL-3.0-only
- + + + + + + + + + + + + + @@ -53,6 +75,10 @@ SPDX-License-Identifier: AGPL-3.0-only + + + +
@@ -69,6 +95,7 @@ SPDX-License-Identifier: AGPL-3.0-only