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
6 changes: 2 additions & 4 deletions ankihub/main/deck_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import re
import typing
import uuid
from copy import deepcopy
from dataclasses import dataclass
from typing import Dict, List

Expand All @@ -23,7 +22,7 @@
change_note_types_of_notes,
create_backup,
get_note_types_in_deck,
modify_note_type,
modified_note_type,
)


Expand Down Expand Up @@ -88,8 +87,7 @@ def _create_note_types_for_deck(deck_id: DeckId) -> Dict[NotetypeId, NotetypeId]
result: Dict[NotetypeId, NotetypeId] = {}
model_ids = get_note_types_in_deck(deck_id)
for mid in model_ids:
new_model = deepcopy(aqt.mw.col.models.get(mid))
modify_note_type(new_model)
new_model = modified_note_type(aqt.mw.col.models.get(mid))
name_without_modifications = _note_type_name_without_ankihub_modifications(
new_model["name"]
)
Expand Down
9 changes: 3 additions & 6 deletions ankihub/main/importing.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
get_unique_ankihub_deck_name,
is_tag_in_list,
lowest_level_common_ancestor_did,
note_type_with_updated_templates,
note_type_with_updated_templates_and_css,
truncated_list,
)

Expand Down Expand Up @@ -782,13 +782,10 @@ def _update_templates_and_css(
)
)

updated_note_type = note_type_with_updated_templates(
updated_note_type = note_type_with_updated_templates_and_css(
old_note_type=local_note_type,
new_note_type=remote_note_type,
use_new_templates=use_new_templates_and_css,
new_note_type=remote_note_type if use_new_templates_and_css else None,
)
if use_new_templates_and_css:
updated_note_type["css"] = remote_note_type["css"]

aqt.mw.col.models.update_dict(updated_note_type)

Expand Down
173 changes: 81 additions & 92 deletions ankihub/main/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
from ..settings import (
ANKI_INT_VERSION,
ANKI_VERSION_23_10_00,
ANKIHUB_CSS_END_COMMENT,
ANKIHUB_HTML_END_COMMENT,
ANKIHUB_NOTE_TYPE_FIELD_NAME,
ANKIHUB_NOTE_TYPE_MODIFICATION_STRING,
ANKIHUB_TEMPLATE_END_COMMENT,
url_mh_integrations_preview,
url_view_note,
)
Expand All @@ -35,8 +36,11 @@
# Pattern for the AnkiHub end comment in card templates.
# The end comment is used to allow users to add their own content below it without it being overwritten
# when the template is updated.
ANKIHUB_END_COMMENT_PATTERN = re.compile(
rf"{ANKIHUB_TEMPLATE_END_COMMENT}(?P<html_to_migrate>[\w\W]*)"
ANKIHUB_HTML_END_COMMENT_PATTERN = re.compile(
rf"{re.escape(ANKIHUB_HTML_END_COMMENT)}(?P<text_to_migrate>[\w\W]*)"
)
ANKIHUB_CSS_END_COMMENT_PATTERN = re.compile(
rf"{re.escape(ANKIHUB_CSS_END_COMMENT)}(?P<text_to_migrate>[\w\W]*)"
)

# decks
Expand Down Expand Up @@ -298,30 +302,20 @@ def get_anki_nid_to_mid_dict(nids: Collection[NoteId]) -> Dict[NoteId, NotetypeI
)


def modify_note_type(note_type: NotetypeDict) -> None:
"""Adds the AnkiHub ID Field to the Note Type and modifies the card templates."""
LOGGER.info(
"Modifying note type...",
note_type_name=note_type["name"],
note_type_id=note_type["id"],
)

modify_fields(note_type)

templates = note_type["tmpls"]
for template in templates:
modify_template(template)
def modified_note_type(note_type: NotetypeDict) -> NotetypeDict:
"""Returns a modified version of the note type with the AnkiHub field added and
the card templates updated."""
note_type = copy.deepcopy(note_type)

_modify_fields(note_type)

def modify_note_type_templates(note_type_ids: Iterable[NotetypeId]) -> None:
for mid in note_type_ids:
note_type = aqt.mw.col.models.get(mid)
for template in note_type["tmpls"]:
modify_template(template)
aqt.mw.col.models.update_dict(note_type)
return note_type_with_updated_templates_and_css(
old_note_type=note_type,
new_note_type=None,
)


def modify_fields(note_type: Dict) -> None:
def _modify_fields(note_type: Dict) -> None:
fields = note_type["flds"]
field_names = [field["name"] for field in fields]
if settings.ANKIHUB_NOTE_TYPE_FIELD_NAME in field_names:
Expand All @@ -337,10 +331,14 @@ def modify_fields(note_type: Dict) -> None:
note_type["flds"].append(ankihub_field)


def modify_template(template: Dict) -> None:
# the order is important here, the end comment must be added last
template["afmt"] = _template_side_with_view_on_ankihub_snippet(template["afmt"])
_add_ankihub_end_comment_to_template(template)
def modify_note_type_templates(note_type_ids: Iterable[NotetypeId]) -> None:
for mid in note_type_ids:
note_type = aqt.mw.col.models.get(mid)
note_type = note_type_with_updated_templates_and_css(
old_note_type=note_type,
new_note_type=None,
)
aqt.mw.col.models.update_dict(note_type)


def _template_side_with_view_on_ankihub_snippet(template_side: str) -> str:
Expand Down Expand Up @@ -423,110 +421,101 @@ def _template_side_with_view_on_ankihub_snippet(template_side: str) -> str:
)


def _add_ankihub_end_comment_to_template(template: Dict) -> None:
"""Add the AnkiHub end comment to the template if it is not already present.
The purpose of the AnkiHub end comment is to allow users to add their own content below it without it being
overwritten when the note type is updated."""
for key in ["qfmt", "afmt"]:
cur_side = template[key]
if re.search(ANKIHUB_TEMPLATE_END_COMMENT, cur_side):
continue

template[key] = (
template[key].rstrip("\n ") + "\n\n" + ANKIHUB_TEMPLATE_END_COMMENT + "\n\n"
)
LOGGER.info(
"Added ANKIHUB_TEMPLATE_END_COMMENT to template.",
template=template["name"],
key=key,
)


def note_type_with_updated_templates(
old_note_type: NotetypeDict, new_note_type: NotetypeDict, use_new_templates: bool
def note_type_with_updated_templates_and_css(
old_note_type: NotetypeDict,
new_note_type: Optional[NotetypeDict],
) -> NotetypeDict:
"""Returns the new note type with modifications applied to the card templates.
The new templates are used as the base if use_new_templates is True.
"""Returns the updated note type with modifications applied to the card templates and css.
The templates and css of the new note type are used as the base if it is provided.

The modifications are as follows:
- The View on AnkiHub button is added to the back side of each template.
- Contents below the AnkiHub end comments are preserved when the template is updated.
- Contents below the AnkiHub end comments are preserved when the template/css is updated.

Args:
old_note_type (NotetypeDict): The old note tpye. The contents below the AnkiHub end comments is preserved
when the template is updated.
new_note_type (NotetypeDict): The new note type.
use_new_templates (bool): If True, the templates from the new_note_type are used as the base for
the updated templates, otherwise the old templates are used. Then this function just refreshes
the modifications or adds them if they are not present.
old_note_type (NotetypeDict): The old note tpye. The contents below the AnkiHub end comments are preserved.
new_note_type (Optional[NotetypeDict]): The new note type. If provided, the templates and css are updated
based on this.

Returns:
NotetypeDict: The updated note type.
"""

updated_templates = []
for new_template, old_template in zip(
new_note_type["tmpls"], old_note_type["tmpls"]
):
if use_new_templates:
for template_idx, old_template in enumerate(old_note_type["tmpls"]):
if new_note_type is not None:
new_template = new_note_type["tmpls"][template_idx]
updated_template = copy.deepcopy(new_template)
else:
updated_template = copy.deepcopy(old_template)

# Update template sides
for template_side_name in ["qfmt", "afmt"]:
updated_template[template_side_name] = _updated_template_side(
new_template_side=new_template[template_side_name],
old_template_side=old_template[template_side_name],
template_side_name=template_side_name,
use_new_template=use_new_templates,
updated_template[template_side_name] = _updated_note_type_content(
old_content=old_template[template_side_name],
new_content=(
new_template[template_side_name]
if new_note_type is not None
else None
),
add_view_on_ankihub_snippet=template_side_name == "afmt",
content_type="html",
)
updated_templates.append(updated_template)

result = copy.deepcopy(old_note_type)
result["tmpls"] = updated_templates

result["css"] = _updated_note_type_content(
old_content=old_note_type["css"],
new_content=new_note_type["css"] if new_note_type is not None else None,
add_view_on_ankihub_snippet=False,
content_type="css",
)

return result


def _updated_template_side(
new_template_side: str,
old_template_side: str,
template_side_name: str,
use_new_template: bool,
def _updated_note_type_content(
old_content: str,
new_content: Optional[str],
add_view_on_ankihub_snippet: bool,
content_type: str,
) -> str:
"""
Args:
template_side_name (str): The name of the template side ("qfmt" or "afmt").
use_new_template (bool): If True, the new template side is used as the base for
the updated template side, otherwise the old template side is used.
"""Returns updated content with preserved content below ankihub end comment.

Returns:
str: The updated template side with the AnkiHub modifications applied.
Args:
old_content: Original content to preserve custom additions from
new_content: New base content to use, or None to use old_content
add_view_on_ankihub_snippet: Whether to add AnkiHub view button
content_type: Either "html" or "css" to determine comment style
"""
m = re.search(ANKIHUB_END_COMMENT_PATTERN, old_template_side)
html_to_migrate = m.group("html_to_migrate") if m else ""
if content_type == "html":
end_comment = ANKIHUB_HTML_END_COMMENT
end_comment_pattern = ANKIHUB_HTML_END_COMMENT_PATTERN
else:
end_comment = ANKIHUB_CSS_END_COMMENT
end_comment_pattern = ANKIHUB_CSS_END_COMMENT_PATTERN

m = re.search(end_comment_pattern, old_content)
text_to_migrate = m.group("text_to_migrate") if m else ""

# Choose the base for the result
if use_new_template:
result = new_template_side
else:
result = old_template_side
result = new_content if new_content is not None else old_content

# Remove end comment and content below it from the template side.
# Remove end comment and content below it.
# It will be added back below.
result = re.sub(ANKIHUB_END_COMMENT_PATTERN, "", result)
result = re.sub(end_comment_pattern, "", result)

if template_side_name == "afmt":
# The view on AnkiHub button is added to the back side of the template.
if add_view_on_ankihub_snippet:
result = _template_side_with_view_on_ankihub_snippet(result)

# Add the AnkiHub end comment and the content below it back to the template side.
# Add the AnkiHub end comment and the content below it back.
return (
result.rstrip("\n ")
+ "\n\n"
+ ANKIHUB_TEMPLATE_END_COMMENT
+ end_comment
+ "\n"
+ html_to_migrate
+ text_to_migrate.strip("\n ")
)


Expand Down
9 changes: 8 additions & 1 deletion ankihub/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,13 +876,20 @@ def _send_logs_to_datadog(self, records: List[logging.LogRecord]) -> None:

ANKIHUB_NOTE_TYPE_FIELD_NAME = "ankihub_id"
ANKIHUB_NOTE_TYPE_MODIFICATION_STRING = "ANKIHUB MODFICATIONS"
ANKIHUB_TEMPLATE_END_COMMENT = (
ANKIHUB_HTML_END_COMMENT = (
"<!--\n"
"ANKIHUB_END\n"
"Text below this comment will not be modified by AnkiHub or AnKing add-ons.\n"
"Do not edit or remove this comment if you want to protect the content below.\n"
"-->"
)
ANKIHUB_CSS_END_COMMENT = (
"/*\n"
"ANKIHUB_END\n"
"Text below this comment will not be modified by AnkiHub or AnKing add-ons.\n"
"Do not edit or remove this comment if you want to protect the content below.\n"
"*/"
)
ADDON_PACKAGE = __name__.split(".")[0]
ICONS_PATH = ADDON_PATH / "gui/icons"

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
old content

/*
ANKIHUB_END
Text below this comment will not be modified by AnkiHub or AnKing add-ons.
Do not edit or remove this comment if you want to protect the content below.
*/
Loading
Loading