Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ repos:
args: ["--diff"]

- repo: https://github.com/pycqa/flake8
rev: 4.0.1
rev: 6.1.0
hooks:
- id: flake8

Expand All @@ -29,7 +29,7 @@ repos:
- id: pylint
language: python
types: [python]
additional_dependencies: [aqt]
additional_dependencies: [aqt, pytest]
args:
[
"-rn", # Only display messages
Expand Down
2 changes: 1 addition & 1 deletion ankiweb_description.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@

2024-01-19: Fix bug in Sketchy-Cloze note type front template

2024-04-08: Add the AnKingAnkisthesia note type - Add a reset button for general settings - Fix cloze color when using b/u/i styling - Fix extra color when using italics styling - Add night mode tables to AnKingMCAT
2024-04-08: Add the AnKingAnkisthesia note type - Add a reset button for general settings - Fix cloze color when using b/u/i styling - Fix extra color when using italics styling - Add night mode tables to AnKing MCAT

2024-04-18: Revert img max height/width for IO one by one to 100% - Change min-width of images in Extra field to 0%

Expand Down
45 changes: 35 additions & 10 deletions src/anking_notetypes/gui/config_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@

from ..ankiaddonconfig import ConfigManager, ConfigWindow
from ..ankiaddonconfig.window import ConfigLayout
from ..constants import ANKIHUB_NOTETYPE_RE, NOTETYPE_COPY_RE
from ..notetype_renames import (
canonical_notetype_name,
legacy_notetype_names,
matching_notetype_names,
)
from ..notetype_setting import NotetypeSetting, NotetypeSettingException
from ..notetype_setting_definitions import (
anking_notetype_model,
anking_notetype_names,
configurable_fields_for_notetype,
general_settings,
general_settings_defaults_dict,
setting_configs,
is_ankihub_notetype_version,
is_notetype_copy,
notetype_base_name,
setting_configs,
)
from ..utils import update_notetype_to_newest_version
from .anking_widgets import AnkingIconsLayout, GithubLinkLayout
Expand Down Expand Up @@ -390,7 +396,7 @@ def _reset_notetype_and_reload_ui(self, model: "NotetypeDict"):
return

nt_base_name = notetype_base_name(model["name"])
for model_version in _note_type_versions(model["name"]):
for model_version in _note_type_versions(nt_base_name):
update_notetype_to_newest_version(model_version, nt_base_name)
mw.col.models.update_dict(model_version) # type: ignore

Expand Down Expand Up @@ -631,26 +637,45 @@ def _note_type_versions(nt_base_name: str) -> List["NotetypeDict"]:
"""Returns a list of all notetype versions of the notetype in the collection.
Version of a note type are created by the AnkiHub add-on and by copying
the base AnKing note types or importing them from different sources."""
matching_names = matching_notetype_names(canonical_notetype_name(nt_base_name))
models = [
mw.col.models.get(x.id) # type: ignore
for x in mw.col.models.all_names_and_ids()
if x.name == nt_base_name
or re.match(ANKIHUB_NOTETYPE_RE.format(notetype_base_name=nt_base_name), x.name)
or re.match(NOTETYPE_COPY_RE.format(notetype_base_name=nt_base_name), x.name)
for matching_name in matching_names
if _matches_notetype_version(x.name, matching_name)
]
return models


def _matches_notetype_version(model_name: str, base_name: str) -> bool:
return (
model_name == base_name
or is_ankihub_notetype_version(model_name, base_name)
or is_notetype_copy(model_name, base_name)
)


def _most_basic_notetype_version(nt_base_name: str) -> Optional["NotetypeDict"]:
"""Returns the most basic version of a note type, that is the version with the shortest name."""
"""Returns the most basic version of a note type.

Prefers an exact match on the canonical name, then an exact legacy name,
then falls back to the shortest name. Without the canonical/legacy
preference, a legacy main like ``AnKingMCAT`` would beat ``AnKing MCAT``
on name length alone.
"""
canonical = canonical_notetype_name(nt_base_name)
model_versions = _note_type_versions(nt_base_name)
result = min(
versions_by_name = {model["name"]: model for model in model_versions}

for preferred in [canonical, *legacy_notetype_names(canonical)]:
if preferred in versions_by_name:
return versions_by_name[preferred]

return min(
model_versions,
# sort by length of name and then alphabetically
key=lambda model: (len(model["name"]), model["name"]),
default=None,
)
return result


def _names_of_all_supported_note_types() -> List[str]:
Expand Down
93 changes: 76 additions & 17 deletions src/anking_notetypes/gui/extra_notetype_versions.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,50 @@
import re
from concurrent.futures import Future
from copy import deepcopy
from typing import Dict, List
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple

from aqt import mw
from aqt.utils import askUser, tooltip

from ..constants import NOTETYPE_COPY_RE
from ..notetype_setting_definitions import anking_notetype_names
from ..notetype_renames import legacy_notetype_names, matching_notetype_names
from ..notetype_setting_definitions import anking_notetype_names, is_notetype_copy
from ..utils import adjust_fields, create_backup

if TYPE_CHECKING:
from anki.models import NotetypeDict


def handle_extra_notetype_versions() -> None:
# mids of copies of the AnKing notetype identified by its name
# mids of copies of the AnKing notetype, keyed by canonical base name
copy_mids_by_notetype_base_name: Dict[str, List[int]] = dict()
# (legacy_name, canonical_name) pairs for mains that will be renamed during conversion
legacy_mains_to_rename: List[Tuple[str, str]] = []
all_models = list(mw.col.models.all_names_and_ids())
for notetype_base_name in anking_notetype_names():
if mw.col.models.by_name(notetype_base_name) is None:
matching_names = matching_notetype_names(notetype_base_name)
if _first_existing_notetype_name(matching_names) is None:
continue

notetype_copy_mids = [
x.id
for x in mw.col.models.all_names_and_ids()
if re.match(
NOTETYPE_COPY_RE.format(notetype_base_name=notetype_base_name), x.name
)
for x in all_models
for matching_name in matching_names
if is_notetype_copy(x.name, matching_name)
]
if notetype_copy_mids:
copy_mids_by_notetype_base_name[notetype_base_name] = notetype_copy_mids
if not notetype_copy_mids:
continue

copy_mids_by_notetype_base_name[notetype_base_name] = notetype_copy_mids
if mw.col.models.by_name(notetype_base_name) is None:
for legacy_name in legacy_notetype_names(notetype_base_name):
if mw.col.models.by_name(legacy_name) is not None:
legacy_mains_to_rename.append((legacy_name, notetype_base_name))
break

if not copy_mids_by_notetype_base_name:
return

if not askUser(
"There are extra copies of AnKing note types. Do you want to convert all note types with names like "
'for example "AnKingOverhaul-1dgs0" to "AnKingOverhaul" respectively?\n\n'
"This will delete the extra note types and require a full upload of the collection "
"the next time you sync with AnkiWeb. A backup will be created before the changes are applied.\n\n"
"No matter what you chose the AnKing Note Types window will open after you select an option.",
_build_confirmation_message(legacy_mains_to_rename),
title="Extra copies of AnKing note types",
):
return
Expand All @@ -63,6 +71,8 @@ def convert_extra_notetypes(

for notetype_base_name, copy_mids in copy_mids_by_notetype_base_name.items():
model = mw.col.models.by_name(notetype_base_name)
if model is None:
model = _rename_legacy_main_to_canonical(notetype_base_name)
for copy_mid in copy_mids:
model_copy = mw.col.models.get(copy_mid) # type: ignore

Expand Down Expand Up @@ -92,3 +102,52 @@ def convert_extra_notetypes(

mw.reset()
tooltip("Note types were converted successfully.")


def _build_confirmation_message(
legacy_mains_to_rename: List[Tuple[str, str]],
) -> str:
message = (
"There are extra copies of AnKing note types. Do you want to convert all "
"note types with names like "
'for example "AnKingOverhaul-1dgs0" to "AnKingOverhaul" respectively?\n\n'
"This will delete the extra note types and require a full upload of the "
"collection the next time you sync with AnkiWeb. A backup will be created "
"before the changes are applied.\n\n"
)
if legacy_mains_to_rename:
renames = "\n".join(
f' - "{legacy}" → "{canonical}"'
for legacy, canonical in legacy_mains_to_rename
)
message += (
"The following note types will also be renamed to their current "
f"names:\n{renames}\n\n"
)
message += (
"No matter what you chose the AnKing Note Types window will open after "
"you select an option."
)
return message


def _first_existing_notetype_name(notetype_names: List[str]) -> Optional[str]:
return next(
(
notetype_name
for notetype_name in notetype_names
if mw.col.models.by_name(notetype_name) is not None
),
None,
)


def _rename_legacy_main_to_canonical(canonical_name: str) -> Optional["NotetypeDict"]:
for legacy_name in legacy_notetype_names(canonical_name):
legacy_model = mw.col.models.by_name(legacy_name)
if legacy_model is None:
continue
legacy_model["name"] = canonical_name
mw.col.models.update_dict(legacy_model)
return legacy_model
return None
4 changes: 2 additions & 2 deletions src/anking_notetypes/note_types/AnKingMCAT/AnKingMCAT.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": 1610414929688,
"name": "AnKingMCAT",
"name": "AnKing MCAT",
"type": 1,
"mod": 1638144691,
"usn": -1,
Expand Down Expand Up @@ -117,4 +117,4 @@
],
"tags": [],
"vers": []
}
}
2 changes: 1 addition & 1 deletion src/anking_notetypes/note_types/AnKingMCAT/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### Features Unique to this Note Type
- <b>AnKingMed alternate styling available</b> <a href="/Note Types/Cloze-AnKingMCAT/AnKing Custom Styling.css">here</a>
- <b>AnKingMed alternate styling available</b> <a href="AnKing Custom Styling.css">here</a>
<details><summary>Replace the customizable portion with the contents of the link above <i>(in styling)</i></summary>
<p>

Expand Down
37 changes: 37 additions & 0 deletions src/anking_notetypes/notetype_renames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import re
from typing import Dict, List


# Add note type renames here as "old bundled name": "new bundled name".
# The old name is still used to find existing note types in users' collections.
NOTETYPE_RENAMES: Dict[str, str] = {
"AnKingMCAT": "AnKing MCAT",
}


def canonical_notetype_name(notetype_name: str) -> str:
return NOTETYPE_RENAMES.get(notetype_name, notetype_name)


def legacy_notetype_names(canonical_name: str) -> List[str]:
return [
old_name
for old_name, new_name in NOTETYPE_RENAMES.items()
if new_name == canonical_name
]


def matching_notetype_names(canonical_name: str) -> List[str]:
return [canonical_name, *legacy_notetype_names(canonical_name)]


def renamed_notetype_name(model_name: str) -> str:
# Match bare name or copy-suffixed form only. AnkiHub-qualified forms
# like "AnKingMCAT (AnKing-MCAT / AnKingMed)" are owned by the AnkiHub
# add-on — we don't rename them here because the deck portion may or
# may not be renamed upstream, and we can't know.
for old_name, new_name in NOTETYPE_RENAMES.items():
match = re.match(rf"({re.escape(old_name)})(?=$|-)", model_name)
if match:
return new_name + model_name[match.end() :]
return model_name
Loading
Loading