mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Add URL scheme whitelist (#3994)
* Add experimental Cursor rules * Add the ability to customize URL schemes Closes #3965
This commit is contained in:
parent
f7cdf4eb9e
commit
86c89907e7
9 changed files with 121 additions and 26 deletions
7
.cursor/rules/i18n.md
Normal file
7
.cursor/rules/i18n.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
- We use the fluent system+code generation for translation.
|
||||||
|
- New strings should be added to rslib/core/. Ask for the appropriate file if you're not sure.
|
||||||
|
- Assuming a string addons-you-have-count has been added to addons.ftl, that string is accessible in our different languages as follows:
|
||||||
|
- Python: from aqt.utils import tr; msg = tr.addons_you_have_count(count=3)
|
||||||
|
- TypeScript: import * as tr from "@generated/ftl"; tr.addonsYouHaveCount({count: 3})
|
||||||
|
- Rust: collection.tr.addons_you_have_count(3)
|
||||||
|
- In Qt .ui files, strings that are marked as translatable will automatically use the registered ftl strings. So a QLabel with a title 'addons_you_have_count' that is marked as translatable will automatically use the translation defined in our addons.ftl file.
|
2
.cursor/rules/testing.md
Normal file
2
.cursor/rules/testing.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
- To build and check the project, use ./check(.bat)
|
||||||
|
- This will format files, then run lints and unit tests.
|
|
@ -83,6 +83,13 @@ preferences-ankiweb-intro = AnkiWeb is a free service that lets you keep your fl
|
||||||
preferences-ankihub-intro = AnkiHub provides collaborative deck editing and additional study tools. A paid subscription is required to access certain features.
|
preferences-ankihub-intro = AnkiHub provides collaborative deck editing and additional study tools. A paid subscription is required to access certain features.
|
||||||
preferences-third-party-description = Third-party services are unaffiliated with and not endorsed by Anki. Use of these services may require payment.
|
preferences-third-party-description = Third-party services are unaffiliated with and not endorsed by Anki. Use of these services may require payment.
|
||||||
|
|
||||||
|
## URL scheme related
|
||||||
|
preferences-url-schemes = URL Schemes
|
||||||
|
preferences-url-scheme-prompt = Allowed { preferences-url-schemes } (space-separated):
|
||||||
|
preferences-url-scheme-warning = Blocked attempt to open `{ $link }`, which may be a security issue.
|
||||||
|
|
||||||
|
If you trust the deck author and wish to proceed, you can add `{ $scheme }` to your allowed { preferences-url-schemes }.
|
||||||
|
|
||||||
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.
|
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.
|
||||||
|
|
||||||
preferences-basic = Basic
|
preferences-basic = Basic
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::StrongFocus</enum>
|
<enum>Qt::FocusPolicy::StrongFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
<property name="sizeAdjustPolicy">
|
||||||
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -260,7 +260,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_9">
|
<spacer name="verticalSpacer_9">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -451,6 +451,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="url_schemes">
|
||||||
|
<property name="text">
|
||||||
|
<string>preferences_url_schemes</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -466,7 +473,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_12">
|
<spacer name="verticalSpacer_12">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -518,10 +525,10 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_7">
|
<spacer name="verticalSpacer_7">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<property name="sizeType">
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -614,10 +621,10 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_3">
|
<spacer name="verticalSpacer_3">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<property name="sizeType">
|
||||||
<enum>QSizePolicy::Expanding</enum>
|
<enum>QSizePolicy::Policy::Expanding</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -739,7 +746,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -827,7 +834,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -840,7 +847,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -918,10 +925,10 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_5">
|
<spacer name="verticalSpacer_5">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<property name="sizeType">
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -953,7 +960,7 @@
|
||||||
<item row="1" column="3">
|
<item row="1" column="3">
|
||||||
<spacer name="horizontalSpacer_2">
|
<spacer name="horizontalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -1020,7 +1027,7 @@
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<spacer name="horizontalSpacer_3">
|
<spacer name="horizontalSpacer_3">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -1035,10 +1042,10 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_6">
|
<spacer name="verticalSpacer_6">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<property name="sizeType">
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -1080,7 +1087,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_4">
|
<spacer name="verticalSpacer_4">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -1128,10 +1135,10 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<property name="sizeType">
|
||||||
<enum>QSizePolicy::Maximum</enum>
|
<enum>QSizePolicy::Policy::Maximum</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -1207,7 +1214,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalspacer_13">
|
<spacer name="verticalspacer_13">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -1227,17 +1234,17 @@
|
||||||
<string>preferences_some_settings_will_take_effect_after</string>
|
<string>preferences_some_settings_will_take_effect_after</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignCenter</set>
|
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="standardButtons">
|
<property name="standardButtons">
|
||||||
<set>QDialogButtonBox::Close|QDialogButtonBox::Help</set>
|
<set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::Help</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -1266,6 +1273,7 @@
|
||||||
<tabstop>showEstimates</tabstop>
|
<tabstop>showEstimates</tabstop>
|
||||||
<tabstop>spacebar_rates_card</tabstop>
|
<tabstop>spacebar_rates_card</tabstop>
|
||||||
<tabstop>render_latex</tabstop>
|
<tabstop>render_latex</tabstop>
|
||||||
|
<tabstop>url_schemes</tabstop>
|
||||||
<tabstop>pastePNG</tabstop>
|
<tabstop>pastePNG</tabstop>
|
||||||
<tabstop>paste_strips_formatting</tabstop>
|
<tabstop>paste_strips_formatting</tabstop>
|
||||||
<tabstop>useCurrent</tabstop>
|
<tabstop>useCurrent</tabstop>
|
||||||
|
|
|
@ -20,9 +20,11 @@ from aqt.profiles import VideoDriver
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sync import sync_login
|
from aqt.sync import sync_login
|
||||||
from aqt.theme import Theme
|
from aqt.theme import Theme
|
||||||
|
from aqt.url_schemes import show_url_schemes_dialog
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
HelpPage,
|
HelpPage,
|
||||||
add_close_shortcut,
|
add_close_shortcut,
|
||||||
|
add_ellipsis_to_action_label,
|
||||||
askUser,
|
askUser,
|
||||||
disable_help_button,
|
disable_help_button,
|
||||||
is_win,
|
is_win,
|
||||||
|
@ -152,6 +154,9 @@ class Preferences(QDialog):
|
||||||
form.monthly_backups.setValue(self.prefs.backups.monthly)
|
form.monthly_backups.setValue(self.prefs.backups.monthly)
|
||||||
form.minutes_between_backups.setValue(self.prefs.backups.minimum_interval_mins)
|
form.minutes_between_backups.setValue(self.prefs.backups.minimum_interval_mins)
|
||||||
|
|
||||||
|
add_ellipsis_to_action_label(self.form.url_schemes)
|
||||||
|
qconnect(self.form.url_schemes.clicked, show_url_schemes_dialog)
|
||||||
|
|
||||||
def update_collection(self, on_done: Callable[[], None]) -> None:
|
def update_collection(self, on_done: Callable[[], None]) -> None:
|
||||||
form = self.form
|
form = self.form
|
||||||
|
|
||||||
|
|
|
@ -744,3 +744,9 @@ create table if not exists profiles
|
||||||
|
|
||||||
def ankihub_username(self) -> str | None:
|
def ankihub_username(self) -> str | None:
|
||||||
return self.profile.get("thirdPartyAnkiHubUsername")
|
return self.profile.get("thirdPartyAnkiHubUsername")
|
||||||
|
|
||||||
|
def allowed_url_schemes(self) -> list[str]:
|
||||||
|
return self.profile.get("allowedUrlSchemes", [])
|
||||||
|
|
||||||
|
def set_allowed_url_schemes(self, schemes: list[str]) -> None:
|
||||||
|
self.profile["allowedUrlSchemes"] = schemes
|
||||||
|
|
58
qt/aqt/url_schemes.py
Normal file
58
qt/aqt/url_schemes.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from markdown import markdown
|
||||||
|
|
||||||
|
from aqt.qt import Qt, QUrl
|
||||||
|
from aqt.utils import ask_user_dialog, getText, openLink, tr
|
||||||
|
|
||||||
|
|
||||||
|
def show_url_schemes_dialog() -> None:
|
||||||
|
from aqt import mw
|
||||||
|
|
||||||
|
default = " ".join(mw.pm.allowed_url_schemes())
|
||||||
|
schemes, ok = getText(
|
||||||
|
prompt=tr.preferences_url_scheme_prompt(),
|
||||||
|
title=tr.preferences_url_schemes(),
|
||||||
|
default=default,
|
||||||
|
)
|
||||||
|
if ok:
|
||||||
|
mw.pm.set_allowed_url_schemes(schemes.split(" "))
|
||||||
|
mw.pm.save()
|
||||||
|
|
||||||
|
|
||||||
|
def is_supported_scheme(url: QUrl) -> bool:
|
||||||
|
from aqt import mw
|
||||||
|
|
||||||
|
scheme = url.scheme().lower()
|
||||||
|
allowed_schemes = mw.pm.allowed_url_schemes()
|
||||||
|
|
||||||
|
return scheme in allowed_schemes or scheme in ["http", "https"]
|
||||||
|
|
||||||
|
|
||||||
|
def open_url_if_supported_scheme(url: QUrl) -> None:
|
||||||
|
from aqt import mw
|
||||||
|
|
||||||
|
if is_supported_scheme(url):
|
||||||
|
openLink(url)
|
||||||
|
else:
|
||||||
|
|
||||||
|
def on_button(idx: int) -> None:
|
||||||
|
if idx == 0:
|
||||||
|
show_url_schemes_dialog()
|
||||||
|
|
||||||
|
msg = markdown(
|
||||||
|
tr.preferences_url_scheme_warning(link=url.toString(), scheme=url.scheme())
|
||||||
|
)
|
||||||
|
ask_user_dialog(
|
||||||
|
msg,
|
||||||
|
buttons=[
|
||||||
|
tr.actions_with_ellipsis(action=tr.preferences_url_schemes()),
|
||||||
|
tr.actions_close(),
|
||||||
|
],
|
||||||
|
parent=mw,
|
||||||
|
callback=on_button,
|
||||||
|
textFormat=Qt.TextFormat.RichText,
|
||||||
|
)
|
|
@ -1188,7 +1188,7 @@ def disallow_full_screen() -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_ellipsis_to_action_label(*actions: QAction) -> None:
|
def add_ellipsis_to_action_label(*actions: QAction | QPushButton) -> None:
|
||||||
"""Pass actions to add '...' to their labels, indicating that more input is
|
"""Pass actions to add '...' to their labels, indicating that more input is
|
||||||
required before they can be performed.
|
required before they can be performed.
|
||||||
|
|
||||||
|
|
|
@ -266,7 +266,9 @@ class AnkiWebPage(QWebEnginePage):
|
||||||
print("onclick handler needs to return false")
|
print("onclick handler needs to return false")
|
||||||
return False
|
return False
|
||||||
# load all other links in browser
|
# load all other links in browser
|
||||||
openLink(url)
|
from aqt.url_schemes import open_url_if_supported_scheme
|
||||||
|
|
||||||
|
open_url_if_supported_scheme(url)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _onCmd(self, str: str) -> Any:
|
def _onCmd(self, str: str) -> Any:
|
||||||
|
|
Loading…
Reference in a new issue