diff --git a/locales/en-US.yml b/locales/en-US.yml index d23c167c47..ec90c0ad9e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1218,6 +1218,7 @@ wellKnownWebsites: "Well-known websites" wellKnownWebsitesDescription: "Separate with spaces for AND, new lines for OR. Surround with slashes for regex. Domain names only will match the end of the domain of the URL. If matched, the warning of external links will not be displayed." 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}" +warningRedirectingExternalWebsiteTrustThisSite: "I trust this site" 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 806dc9af79..370049963b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4887,6 +4887,10 @@ export interface Locale extends ILocale { * {url} */ "warningRedirectingExternalWebsiteDescription": ParameterizedString<"url">; + /** + * このサイトを信頼する + */ + "warningRedirectingExternalWebsiteTrustThisSite": string; /** * サムネイルの表示を制限するURL */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f9c4c8bbfb..4348df93ec 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1217,6 +1217,7 @@ wellKnownWebsites: "よく知られたウェブサイト" wellKnownWebsitesDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。ドメイン名だけ書くと後方一致になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。" warningRedirectingExternalWebsiteTitle: "外部サイトへ移動します" warningRedirectingExternalWebsiteDescription: "別のサイトにジャンプしようとしています。\nリンク先の安全性を十分に確認した上で進んでください。\n\n{url}" +warningRedirectingExternalWebsiteTrustThisSite: "このサイトを信頼する" urlPreviewDenyList: "サムネイルの表示を制限するURL" urlPreviewDenyListDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、サムネイルがぼかされて表示されます。" code: "コード" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 929f800588..f56ecdadd8 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1215,6 +1215,7 @@ wellKnownWebsites: "잘 알려진 웹사이트" wellKnownWebsitesDescription: "공백으로 구분하면 AND 지정이 되며, 개행으로 구분하면 OR 지정이 됩니다. 슬래시로 둘러싸면 정규 표현식이 됩니다. 도메인명만 쓰면 후방 일치가 됩니다. 일치하는 경우 외부 사이트로의 경고를 생략할 수 있습니다." warningRedirectingExternalWebsiteTitle: "외부 사이트로 이동합니다" warningRedirectingExternalWebsiteDescription: "다른 사이트로 이동하려고 합니다.\n링크가 안전한지 충분히 확인한 후 이동해주세요.\n\n{url}" +warningRedirectingExternalWebsiteTrustThisSite: "이 사이트를 신뢰합니다" code: "문자열" reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다." remainingN: "나머지: {n}" diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index e0b4ae4924..b4e8bf3f81 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -245,7 +245,7 @@ export function confirm(props: { okWaitInitiate?: 'dialog' | 'input' | 'switch'; okWaitDuration?: number; cancelText?: string; -}): Promise<{ canceled: boolean }> { +}): Promise<{ canceled: boolean, result?: string | number | true | null, toggle?: boolean }> { return new Promise(resolve => { popup(MkDialog, { ...props, diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index e92d8ac558..d2e1944a88 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -113,6 +113,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'defaultWithReplies', 'disableStreamingTimeline', 'useGroupedNotifications', + 'trustedExternalWebsites', 'sound_masterVolume', 'sound_note', 'sound_noteMy', diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts index 05a695a716..199d2693ce 100644 --- a/packages/frontend/src/scripts/warning-external-website.ts +++ b/packages/frontend/src/scripts/warning-external-website.ts @@ -1,4 +1,5 @@ import { url as local } from '@/config.js'; +import { defaultStore } from '@/store.js'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; @@ -16,8 +17,9 @@ export async function warningExternalWebsite(ev: MouseEvent, url: string) { } else if (expression.includes(' ')) return expression.split(' ').every(keyword => url.includes(keyword)); else return domain.endsWith(expression); }); + const isTrusted = defaultStore.reactiveState.trustedExternalWebsites.value.includes(domain); - if (!self && !isWellKnownWebsite) { + if (!self && !isWellKnownWebsite && !isTrusted) { ev.preventDefault(); ev.stopPropagation(); @@ -25,10 +27,15 @@ export async function warningExternalWebsite(ev: MouseEvent, url: string) { type: 'warning', title: i18n.ts.warningRedirectingExternalWebsiteTitle, text: i18n.tsx.warningRedirectingExternalWebsiteDescription({ url: `\`\`\`\n${url}\n\`\`\`` }), + switchLabel: i18n.ts.warningRedirectingExternalWebsiteTrustThisSite, }); if (confirm.canceled) return false; + if (confirm.toggle) { + await defaultStore.set('trustedExternalWebsites', [...defaultStore.reactiveState.trustedExternalWebsites.value, domain]); + } + window.open(url, '_blank', 'noopener'); } diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 01b56422a5..a4c12445c9 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -454,6 +454,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: true, }, + trustedExternalWebsites: { + where: 'device', + default: [] as string[], + }, sound_masterVolume: { where: 'device',