Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multiple deck feature Fix Issue #14 #27

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Anki💗Notion addon
# Notion Toggles Sync

It's an [Anki](https://apps.ankiweb.net/) addon that loads toggle lists from [Notion](https://notion.so) as notes to
a specified deck.
It's an [Anki](https://apps.ankiweb.net/) addon that loads toggle lists from [Notion](https://notion.so) as notes to specified decks and keeps them syncronized.

[![Supported versions](https://img.shields.io/badge/python-3.8%20%7C%203.9-blue)](https://github.com/9dogs/notion-anki-sync)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Codestyle: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

Expand Down Expand Up @@ -48,15 +46,17 @@ Edit plugin config file from Anki: `Tools ➡ Add-ons ➡ Notion Toggles Loader
"sync_every_minutes": 30,
"anki_target_deck": "Notion Sync",
"notion_token": "<your_notion_token_here>",
"notion_namespace": "<your_notion_username_here",
"notion_namespace": "<your_notion_username_here>",
"notion_pages": [
{
"page_id": "<page_id1>",
"recursive": false
"recursive": false,
"target_deck": "Math"
},
{
"page_id": "<page_id2>",
"recursive": true
"recursive": true,
"target_deck": "Biology"
}
]
}
Expand All @@ -73,16 +73,24 @@ Notion API is used, the addon may break without a warning.
- Some toggle blocks are empty on export which leads to empty Anki notes. The issue is on the Notion side (and they're
aware of it).

## TODO:

- Add option to use headers (H1, H2, H3) as hierarchy to build subdecks
- Allow users to define custom note types in Anki and map different Notion blocks to these custom note types.
- Implement a bi-directional sync that not only pulls data from Notion to Anki but also pushes updates from Anki back to Notion (for hot fixes furing learning).
- Enhance error handling and provide detailed error reports to the user, including suggestions for resolving common issues like when deck names or IDs dont match
- Add option to add tags to headers preceding the toggles that apply to all the toggles undernearth (to avoid having to add them manually)

## Configuration parameters

- `debug`: `bool [default: false]` — enable debug logging to file.
- `sync_every_minutes`: `int [default: 30]` — auto sync interval in minutes. Set to 0 to disable auto sync.
- `anki_target_deck`: `str [default: "Notion Sync"]` — the deck loaded notes will be added to.
- `anki_target_deck`: `str [default: "Notion Sync"]` — the default deck loaded notes will be added to, if not specified in the notion pages.
- `notion_token`: `str [default: None]` — Notion APIv2 token.
- `notion_namespace`: `str [default: None]` — Notion namespace (your username) to form source URLs.
- `notion_pages`: `array [default: [] ]` — List of Notion pages to export notes from.


## Inspiration
Additional Information
This fork is based on the unmaintained [notion-toggles loader](https://github.com/9dogs/notion-anki-sync) plugin. The enhancements are intended to provide added functionality to the original plugin.

This project is inspired by a great [Notion to Anki](https://github.com/alemayhu/Notion-to-Anki).
19 changes: 12 additions & 7 deletions notion_sync_addon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
from aqt.gui_hooks import main_window_did_init
from aqt.utils import showCritical, showInfo
from jsonschema import ValidationError, validate
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QMenu, QMessageBox
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
from PyQt5.QtWidgets import QAction, QMenu, QMessageBox

from .helpers import (
BASE_DIR,
Expand Down Expand Up @@ -197,9 +196,9 @@ def handle_worker_result(self, notes: List[AnkiNote]) -> None:
is_updated = self.notes_manager.update_note(note_id, note)
if is_updated:
self._updated += 1
# Create new note
# Create new note in the target deck
else:
note_id = self.notes_manager.create_note(note)
note_id = self.notes_manager.create_note_in_deck(note, note.target_deck)
self._created += 1
self.synced_note_ids.add(note_id)
except Exception:
Expand Down Expand Up @@ -332,12 +331,14 @@ def _sync(self) -> None:
for page_spec in self.config.get('notion_pages', []):
page_id = page_spec['page_id']
recursive = page_spec.get('recursive', False)
target_deck = page_spec.get('target_deck', self.config.get('anki_target_deck', self.DEFAULT_DECK_NAME))
page_id = normalize_block_id(page_id)
worker = NotesExtractorWorker(
notion_token=self.config['notion_token'],
page_id=page_id,
recursive=recursive,
notion_namespace=self.config.get('notion_namespace', ''),
target_deck=target_deck,
debug=self.debug,
)
worker.signals.result.connect(self.handle_worker_result)
Expand All @@ -360,14 +361,13 @@ class NoteExtractorSignals(QObject):


class NotesExtractorWorker(QRunnable):
"""Notes extractor worker thread."""

def __init__(
self,
notion_token: str,
page_id: str,
recursive: bool,
notion_namespace: str,
target_deck: str,
debug: bool = False,
):
"""Init notes extractor.
Expand All @@ -376,6 +376,7 @@ def __init__(
:param page_id: Notion page id
:param recursive: recursive export
:param notion_namespace: Notion namespace to form source links
:param target_deck: target deck in Anki
:param debug: debug log level
"""
super().__init__()
Expand All @@ -386,6 +387,7 @@ def __init__(
self.page_id = page_id
self.recursive = recursive
self.notion_namespace = notion_namespace
self.target_deck = target_deck

def run(self) -> None:
"""Extract note data from given Notion page.
Expand Down Expand Up @@ -419,6 +421,9 @@ def run(self) -> None:
debug=self.debug,
)
self.logger.info('Notes extracted: count=%s', len(notes))
# Attach target deck to each note
for note in notes:
note.target_deck = self.target_deck
except NotionClientError as exc:
self.logger.error('Error extracting notes', exc_info=exc)
error_msg = f'Cannot export {self.page_id}:\n{exc}'
Expand Down
19 changes: 17 additions & 2 deletions notion_sync_addon/config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
{
"debug": false,
"sync_every_minutes": 30,
"anki_target_deck": "Notion Sync",
"anki_target_deck": "Default deck",
"notion_token": "",
"notion_namespace": "",
"notion_pages": [{"page_id": "ID", "recursive": true}]
"notion_pages": [
{
"page_id": "page-id-1",
"recursive": false,
"target_deck": "deck01"
},
{
"page_id": "page-id-2",
"recursive": true,
"target_deck": "deck02"
},
{
"page_id": "page_id3",
"recursive": true
}
]
}
6 changes: 4 additions & 2 deletions notion_sync_addon/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ The `notion_pages` section should look like that:
"notion_pages": [
{
"page_id": "<page_id1>",
"recursive": false
"recursive": false,
"target_deck": "Math"
},
{
"page_id": "<page_id2>",
"recursive": true
"recursive": true,
"target_deck": "Biology"
}
]
```
Expand Down
1 change: 1 addition & 0 deletions notion_sync_addon/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"config": {"anki_target_deck": "Notion Sync", "debug": false, "notion_namespace": "elliotverstraelen", "notion_pages": [{"page_id": "5fbd73fb944e4929af70faede1c9b5c9", "recursive": true, "target_deck": "Machine-Learning-test"}, {"page_id": "a0e163f763154e048a97998f5c389ca7", "recursive": true, "target_deck": "TMIRI-test"}, {"page_id": "page_id3", "recursive": true}], "notion_token": "v02%3Auser_token_or_cookies%3AyNbL1xVS_Z5g_T5f76sn0nIn-z8eIqaj2lazRlnHS4LCoBuCPx7qaIg6DRVe4WhZMNN7u0qCjy6qf8lfdwF7g5XcKaowTWn3ol5Rzb0AUeYUaJLkC9AcH7Yiny8X_QE-hdBR", "sync_every_minutes": 30}}
24 changes: 17 additions & 7 deletions notion_sync_addon/notes_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def __init__(
self.logger = get_logger(self.__class__.__name__, debug)
self.collection = collection
self.deck_name = deck_name
self.deck = self.get_deck()
self.deck = self.get_deck(self.deck_name) # Pass deck_name to get_deck
self.create_models()

@property
Expand Down Expand Up @@ -139,12 +139,13 @@ def create_models(self) -> None:
cloze_model.pop('css')
self.logger.info(f'Cloze model updated: {cloze_model}')

def get_deck(self) -> int:
def get_deck(self, deck_name: str) -> int:
"""Get or create target deck.

:returns: working deck
:param deck_name: name of the target deck
:returns: deck id
"""
deck_id = self.collection.decks.id(self.deck_name, create=True)
deck_id = self.collection.decks.id(deck_name, create=True)
assert deck_id # mypy
return deck_id

Expand Down Expand Up @@ -186,10 +187,11 @@ def _fill_fields(
target[field_name] = new_value
return updated_data

def create_note(self, note: AnkiNote) -> int:
"""Create new note.
def create_note_in_deck(self, note: AnkiNote, deck_name: str) -> int:
"""Create new note in specified deck.

:param note: note
:param deck_name: target deck name
:returns: id of the note created
"""
# Pick the model
Expand All @@ -199,7 +201,7 @@ def create_note(self, note: AnkiNote) -> int:
model = self.collection.models.by_name(self.MODEL_NAME)
# Create a note and add it to the deck
anki_note = Note(self.collection, model)
deck_id = self.get_deck()
deck_id = self.get_deck(deck_name)
self.collection.add_note(anki_note, deck_id)
# Upload note media
media_manager = self.collection.media
Expand Down Expand Up @@ -229,6 +231,14 @@ def create_note(self, note: AnkiNote) -> int:
)
return note_id

def create_note(self, note: AnkiNote) -> int:
"""Create new note in default deck.

:param note: note
:returns: id of the note created
"""
return self.create_note_in_deck(note, self.deck_name)

def update_note(self, note_id: int, note: AnkiNote) -> bool:
"""Update existing note.

Expand Down