diff --git a/locales/en-US.yml b/locales/en-US.yml index d23c167c4..ec90c0ad9 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 806dc9af7..370049963 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 f9c4c8bbf..4348df93e 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 929f80058..f56ecdadd 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 e0b4ae492..b4e8bf3f8 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 e92d8ac55..d2e1944a8 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 05a695a71..199d2693c 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 01b56422a..a4c12445c 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',