diff --git a/locales/en-US.yml b/locales/en-US.yml index 1fb581d8df..527beb4714 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1212,6 +1212,10 @@ useGroupedNotifications: "Display grouped notifications" signupPendingError: "There was a problem verifying the email address. The link may have expired." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." doReaction: "Add reaction" +wellKnownWebsites: "Well-known websites" +wellKnownWebsitesDescription: "Separate with spaces for AND, new lines for OR. Surround with slashes for regular expressions. Matching will allow redirection to external sites without a warning." +warningRedirectingExternalWebsiteTitle: "You are leaving our site!" +warningRedirectingExternalWebsiteDescription: "You are about to jump to another site.\nPlease make sure this link is reliable before proceeding.\n\n{url}" code: "Code" reloadRequiredToApplySettings: "Reloading is required to apply the settings." remainingN: "Remaining: {n}" diff --git a/locales/index.d.ts b/locales/index.d.ts index 44b24d83ce..bcca401dab 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4860,6 +4860,25 @@ export interface Locale extends ILocale { * リアクションする */ "doReaction": string; + /** + * よく知られたウェブサイト + */ + "wellKnownWebsites": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。 + */ + "wellKnownWebsitesDescription": string; + /** + * 外部サイトへ移動します + */ + "warningRedirectingExternalWebsiteTitle": string; + /** + * 別のサイトにジャンプしようとしています。 + * リンク先の安全性を十分に確認した上で進んでください。 + * + * {url} + */ + "warningRedirectingExternalWebsiteDescription": ParameterizedString<"url">; /** * サムネイルの表示を制限するURL */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fd84e0318d..d30ded3a52 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1211,6 +1211,10 @@ useGroupedNotifications: "通知をグルーピングして表示する" signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" +wellKnownWebsites: "よく知られたウェブサイト" +wellKnownWebsitesDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。" +warningRedirectingExternalWebsiteTitle: "外部サイトへ移動します" +warningRedirectingExternalWebsiteDescription: "別のサイトにジャンプしようとしています。\nリンク先の安全性を十分に確認した上で進んでください。\n\n{url}" urlPreviewDenyList: "サムネイルの表示を制限するURL" urlPreviewDenyListDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、サムネイルがぼかされて表示されます。" code: "コード" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 5b3d5538a0..e105a0df1e 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1209,6 +1209,10 @@ useGroupedNotifications: "알림을 그룹화하고 표시" signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." doReaction: "리액션 추가" +wellKnownWebsites: "잘 알려진 웹사이트" +wellKnownWebsitesDescription: "공백으로 구분하면 AND 지정이 되며, 개행으로 구분하면 OR 지정이 됩니다. 슬래시로 둘러싸면 정규 표현식이 됩니다. 일치하는 경우, 외부 사이트로의 리다이렉트 경고를 생략할 수 있습니다." +warningRedirectingExternalWebsiteTitle: "외부 사이트로 이동합니다" +warningRedirectingExternalWebsiteDescription: "다른 사이트로 이동하려고 합니다.\n링크가 안전한지 충분히 확인한 후 이동해주세요.\n\n{url}" code: "문자열" reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다." remainingN: "나머지: {n}" diff --git a/packages/backend/migration/1711008460816-external-website-warn.js b/packages/backend/migration/1711008460816-external-website-warn.js new file mode 100644 index 0000000000..6e6ae75ef6 --- /dev/null +++ b/packages/backend/migration/1711008460816-external-website-warn.js @@ -0,0 +1,11 @@ +export class ExternalWebsiteWarn1711008460816 { + name = 'ExternalWebsiteWarn1711008460816' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "wellKnownWebsites" character varying(3072) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "wellKnownWebsites"`); + } +} diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index b21185183d..ad35a440d5 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -99,6 +99,7 @@ export class MetaEntityService { imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, })), + wellKnownWebsites: instance.wellKnownWebsites, notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 3255117d88..08451cff3a 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -594,6 +594,11 @@ export class MiMeta { }) public notesPerOneAd: number; + @Column('varchar', { + length: 3072, array: true, default: '{}', + }) + public wellKnownWebsites: string[]; + @Column('varchar', { length: 3072, array: true, default: '{}', }) diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 150b94a18f..dd440b4b18 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -183,6 +183,14 @@ export const packedMetaLiteSchema = { }, }, }, + wellKnownWebsites: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, notesPerOneAd: { type: 'number', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 107608d791..2d533b2557 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -381,9 +381,17 @@ export const meta = { type: 'number', optional: false, nullable: false, }, + wellKnownWebsites: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, urlPreviewDenyList: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', optional: false, nullable: false, @@ -602,6 +610,7 @@ export default class extends Endpoint { // eslint- perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, + wellKnownWebsites: instance.wellKnownWebsites, notesPerOneAd: instance.notesPerOneAd, urlPreviewDenyList: instance.urlPreviewDenyList, featuredGameChannels: instance.featuredGameChannels, 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 439116a1a7..baad5eff7a 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -155,6 +155,11 @@ export const paramDef = { type: 'string', }, }, + wellKnownWebsites: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, urlPreviewDenyList: { type: 'array', nullable: true, items: { type: 'string', @@ -220,6 +225,10 @@ export default class extends Endpoint { // eslint- }).map(x => x.toLowerCase()); } + if (Array.isArray(ps.wellKnownWebsites)) { + set.wellKnownWebsites = ps.wellKnownWebsites.filter(Boolean); + } + if (Array.isArray(ps.urlPreviewDenyList)) { set.urlPreviewDenyList = ps.urlPreviewDenyList.filter(Boolean); } diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 3f7aba2fe4..3ff5fcc752 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -5,8 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only