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

feat: add AnkiConnect plugin support #29

Open
wants to merge 3 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Support for continuous integration with GitHub Actions. Backend and frontend will now build with every push to the `main` branch or to the opened PR. (@MarkoSagadin)
- Automated release process with GitHub Actions. A new release can now be manually triggered by providing the next version tag under the _Actions_ tab in the GitHub Web UI. (@MarkoSagadin)
- Added information on how to build the backend project for developers.
- Added first configuration option for frontmatter metadata blocks in the input markdown files! [Frontmatter blocks] allow you to configure options per single markdown file. The first added property is called `no_tabs`. When set to `True`, it disables tabs in generated cards. (@MarkoSagadin)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing notes on the other frontmatter options and ankiconnect support


[Frontmatter blocks]: https://dev.to/dailydevtips1/what-exactly-is-frontmatter-123g

### Fixed
- When reviewing card styles in Anki from its editor view, night mode now triggers the night-mode styles as expected. (@MarkoSagadin)
Expand Down
67 changes: 49 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ PyPi - https://pypi.org/project/markdown2anki
- **Support for clozes** including those in code blocks.
- **Support for images** with automatic importing: the program can find the images you mention in your obsidian notes and copy them to your Anki's media folder.
- **Support for Obsidian links and images**: using `[[Note.md|my Note]]` and `[[my_image.jpg]]`-like markdown.
- **Automatic upload of cards to Anki**: the program uses the [AnkiConnect plugin] to upload the cards to Anki automatically.
- **Accessible config file** that can self-heal (using [Type-Config](https://pypi.org/project/type-config/)): retaining as many custom configurations as possible even if the file is corrupted. This also ensures that if options are added with updates, your custom configuration will be retained.
- **Helpful error messages and feedback**.
- **Backup files of your inputs**, to help you retry in case something goes wrong.
Expand All @@ -65,6 +66,8 @@ PyPi - https://pypi.org/project/markdown2anki
- **Mobile first responsive CSS**: to ensure it's fully responsive on all devices.
- **High-level dev documentation** to make it easier for contributions and maintenance.

[AnkiConnect plugin]: https://foosoft.net/projects/anki-connect/

## Images

### CLI
Expand Down Expand Up @@ -130,6 +133,13 @@ There will also be a `markdown2anki.apkg` file which contains the anki note type
If you have Anki installed on your system, you should be able to just double-click the file to import it, and it will create a new deck with some template cards and the note types.
You can learn more on `.apkg` files here: https://docs.ankiweb.net/exporting.html#deck-apkg

### Installing the AnkiConnect plugin

If you want the `md2anki` to automatically uploaded the generated cards to Anki, you will need to install the [AnkiConnect plugin](https://ankiweb.net/shared/info/2055492159).
As when installing any other Anki plugin, navigate to the `Tools>Add-ons` menu, click on the `Get Add-ons` button, and paste the code `2055492159` in the `Code` field and click `OK`.

If you don't want to install the AnkiConnect plugin, you can use the [legacy CSV file] output, which you can then import manually.

### Using fill the blanks

If you would like to have "type-in" clozes, you can use this addon: [fill the blanks addon](https://ankiweb.net/shared/info/1933645497).
Expand Down Expand Up @@ -197,7 +207,7 @@ is the same as

#### Example
Here are two example cards to illustrate the formatting requirements:
```markdown
`````markdown
## - [Question]
# A great addition to humanity
What is the **name** of this funny cat?
Expand All @@ -219,34 +229,55 @@ my_list = [3,5,2]
sorted_list = {{C1::sorted}}(my_list, {{C1::True}})
# In place
my_list.{{C1::sort}}(True)
```

```
`````

The result:
![Usage demo image 2](https://raw.githubusercontent.com/Mochitto/Markdown2Anki/master/docs/Usage_demo2.webp)
![Usage demo image 3](https://raw.githubusercontent.com/Mochitto/Markdown2Anki/master/docs/Usage_demo3.webp)
(This is using the [fill the blanks addon](https://ankiweb.net/shared/info/1933645497))
![Usage demo image 1](https://raw.githubusercontent.com/Mochitto/Markdown2Anki/master/docs/Usage_demo1.webp)

### Importing your cards
Once you have processed your cards, they will be divided in cards with clozes and cards without clozes.
Those will become two `.csv` files: `basic_anki_cards.csv` and `clozed_anki_cards.csv`.
To import these, you have to open up Anki and press the "Import File" in the lower side of the main menu, or, if you prefer, you can use "File>Import" from the menu in the top-left of the Anki app.
After selecting the `.csv` file, you have to let anki know that the separator used is `Comma`, select the right `note type` and the deck you wish the cards to be imported in.
Also make sure to allow HTML in the cards, as they need it to work correctly.
### Frontmatter and possible options

Markdown2Anki uses [Frontmatter] metadata to add file specific metadata to your cards.
Frontmatter is essentially a YAML block that is enclosed by three dashes `---`.
It must be placed at the beginning of your markdown file, before the first card.
Frontmatter contains mandatory and optional metadata fields.

Here is a basic structure of a Frontmatter block with mandatory fields:
```yaml
---
deck_name: "My deck"
note_type_basic: "Markdown2Anki - Basic"
note_type_cloze: "Markdown2Anki - Cloze"
---
```

Below is a list of the available options that can be set in the frontmatter block:

Mandatory fields:
- `deck_name` - The name of the deck where the cards will be imported.
- `note_type_basic` - The name of the note type for basic cards.
- `note_type_clode` - The name of the note type for cards with clozes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo


Optional fields:
- `tags` - A list of tags to be added to the cards.
- `no_tabs: True` - Disables tabs in generated cards. If set then:
- `L`, `R`, `-`, `+` tab flags are ignored, only `F` and `B` matter.
- Tab labels are ignored.

[Frontmatter]: https://dev.to/dailydevtips1/what-exactly-is-frontmatter-123g

### Importing your card via the AnkiConnect pluging

This is a screenshot of how this could look in your Anki (Anki's UI can change depending on the OS it is running on):
![Image of Anki's import screen](https://raw.githubusercontent.com/Mochitto/Markdown2Anki/master/docs/Anki_import_example.webp)
After the cards are generated they are automatically imported into Anki via the AnkiConnect plugin.
Besides having the plugin installed in you Anki and having Anki open in the background, you don't need to do anything else.

You can find more information on importing to Anki here: https://docs.ankiweb.net/importing.html
If you don't want to use AnkiConnect plugin you can use the [legacy CSV file] output, which you can then import manually.

### Importing your images
You can import images automatically if you add the path to your [Anki media folder](https://docs.ankiweb.net/files.html#file-locations) in the config file.
Images that are already present won't be added twice and will be skipped (based on filename).
If you prefer checking the images before importing them manually, you can point to another folder or leave the default one.
**Notice:** when images are copied, they lose their metadata: this is due to security, as others' could read your images metadata if you were to share your cards, and for how the python library that handles the copying process is implemented.
[legacy CSV file]: ./docs/legacy_importing_cards_with_csv.md
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can turn this into a markdown link [Legacy CSV imports]( ./docs/legacy_importing_cards_with_csv.md) and put them in-line instead of as a foot-note


Hopefully, in the near future, the importing part will be made automatic by the addition of `AnkiConnect` support.

### Errors and Bad cards
When there are errors in formatting, the app will let you know what went wrong and create a `Bad_cards.md` file in your program folder.
Expand Down
38 changes: 38 additions & 0 deletions docs/legacy_importing_cards_with_csv.md
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just great :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Importing your cards via CSV - Legacy feature

<!-- prettier-ignore -->
> ![NOTE]
> This feature is considered legacy. Currently, the recommended way to import
> cards is via AnkiConnect plugin. To use this feature, you need to set `"use legacy CVS output?"`
> option in .ini config file to `True`.

### Importing your cards

Once you have processed your cards, they will be divided in cards with clozes
and cards without clozes. Those will become two `.csv` files:
`basic_anki_cards.csv` and `clozed_anki_cards.csv`. To import these, you have to
open up Anki and press the "Import File" in the lower side of the main menu, or,
if you prefer, you can use "File>Import" from the menu in the top-left of the
Anki app. After selecting the `.csv` file, you have to let anki know that the
separator used is `Comma`, select the right `note type` and the deck you wish
the cards to be imported in. Also make sure to allow HTML in the cards, as they
need it to work correctly.

This is a screenshot of how this could look in your Anki (Anki's UI can change
depending on the OS it is running on):
![Image of Anki's import screen](https://raw.githubusercontent.com/Mochitto/Markdown2Anki/master/docs/Anki_import_example.webp)

You can find more information on importing to Anki here:
https://docs.ankiweb.net/importing.html

### Importing your images

You can import images automatically if you add the path to your
[Anki media folder](https://docs.ankiweb.net/files.html#file-locations) in the
config file. Images that are already present won't be added twice and will be
skipped (based on filename). If you prefer checking the images before importing
them manually, you can point to another folder or leave the default one.
**Notice:** when images are copied, they lose their metadata: this is due to
security, as others' could read your images metadata if you were to share your
cards, and for how the python library that handles the copying process is
implemented.
32 changes: 32 additions & 0 deletions frontend/main_test_no_tabs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Anki code</title>
<link rel="stylesheet" href="dist/style/main.css" />
</head>
<body style="overflow: hidden">
<!-- Needed to avoid scrollbars on absolute elements-->
<main class="l-row-flex l-viewport">
<div class="body">
<div class="body__content">
<h1>This is a test to understand how to style the cards</h1>
<p>This is some text, my plans are:</p>
<ul>
<li>Get this code to look as expected</li>
<li>Continue</li>
</ul>
<section class="highlight highlight--linenos">
<span class="highlight__language">JavaScript</span>
<div class="highlight__code highlight--linenos">
<pre><span></span><span class='highlight__line'><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cloze">workspace</span><span class="p">,</span><span class="w"> </span><span class="nx">ConfigurationChangeEvent</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s2">&quot;vscode&quot;</span><span class="p">;</span><span class="w"></span></span><span class='highlight__line'><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cloze">getThemePaths</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s2">&quot;./helpers&quot;</span><span class="p">;</span><span class="w"></span></span><span class='highlight__line'><span class="k">import</span><span class="w"> </span><span class="nx">utils</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">UpdateTrigger</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s2">&quot;./utils&quot;</span><span class="p">;</span><span class="w"></span></span><span class='highlight__line'></span><span class='highlight__line'><span class="k">export</span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">activate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">paths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">getThemePaths</span><span class="p">();</span><span class="w"></span></span><span class='highlight__line'></span><span class='highlight__line'><span class="w"> </span><span class="c1">// regenerate on a fresh install if non-default config is set</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">utils</span><span class="p">.</span><span class="nx">isFreshInstall</span><span class="p">()</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">!</span><span class="nx">utils</span><span class="p">.</span><span class="nx">isDefaultConfig</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">utils</span><span class="p">.</span><span class="nx">updateThemes</span><span class="p">(</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">utils</span><span class="p">.</span><span class="nx">getConfiguration</span><span class="p">(),</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">paths</span><span class="p">,</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">UpdateTrigger</span><span class="p">.</span><span class="nx">FRESH_INSTALL</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="p">);</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="p">}</span><span class="w"></span></span><span class='highlight__line'></span><span class='highlight__line'><span class="w"> </span><span class="c1">// regenerate the theme files when the config changes</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">workspace</span><span class="p">.</span><span class="nx">onDidChangeConfiguration</span><span class="p">((</span><span class="nx">event</span><span class="o">:</span><span class="w"> </span><span class="nx">ConfigurationChangeEvent</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">affectsConfiguration</span><span class="p">(</span><span class="s2">&quot;catppuccin&quot;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">utils</span><span class="p">.</span><span class="nx">updateThemes</span><span class="p">(</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">utils</span><span class="p">.</span><span class="nx">getConfiguration</span><span class="p">(),</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">paths</span><span class="p">,</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="nx">UpdateTrigger</span><span class="p">.</span><span class="nx">CONFIG_CHANGE</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="p">);</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="p">}</span><span class="w"></span></span><span class='highlight__line'><span class="w"> </span><span class="p">});</span><span class="w"></span></span><span class='highlight__line'><span class="p">};</span><span class="w"></span></span><span class='highlight__line'></span><span class='highlight__line'><span class="k">export</span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">deactivate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{};</span><span class="w"></span></span></pre>
</div>
</section>
</div>
</div>
</main>
<script src="dist/main.js"></script>
</body>
</html>
34 changes: 18 additions & 16 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ if (nightMode) {
}


const tab_groups = assertQuerySelectorAll(".tab_group")
// tabs, tabs_labels are defined on event call since anki
// reuses the same html if the note type is the same; it just
// changes the differences.
Expand All @@ -24,22 +23,25 @@ let tabs_labels: NodeList
let tab_to_restore: Element
let tab_that_went_fullscreen: Element

for (let tab_group of tab_groups) {
tab_group.addEventListener("click", handle_clicks)
}

// Add a keydown event listener to the window object
window.addEventListener("keydown", function(event) {
// This handles short-keys, by activating the tab
// That has a matching number as the one pressed
tabs_labels = assertQuerySelectorAll(".tab__label")
if (event.altKey && /^[0-9]$/.test(event.key)) {
let index = parseInt(event.key) - 1
let button = tabs_labels[index]
if (button instanceof HTMLButtonElement) {
button.click()}
const tab_groups = document.querySelector(".tab_group");
if (tab_groups) {
for (let tab_group of tab_groups) {
tab_group.addEventListener("click", handle_clicks);
}
});
// Add a keydown event listener to the window object
window.addEventListener("keydown", function (event) {
// This handles short-keys, by activating the tab
// That has a matching number as the one pressed
tabs_labels = assertQuerySelectorAll(".tab__label");
if (event.altKey && /^[0-9]$/.test(event.key)) {
let index = parseInt(event.key) - 1;
let button = tabs_labels[index];
if (button instanceof HTMLButtonElement) {
button.click();
}
}
});
}

function handle_clicks(event: Event): void {
event.preventDefault()
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/style/modules/tab/tab.sass
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,34 @@
@media (min-aspect-ratio: 1/1)
max-width: 70vw
padding: 35px 25px

.card-body
display: flex
background: var(--main-background)
color: var(--main-color)
overflow-y: scroll
width: 100%
height: 100%
@media (min-aspect-ratio: 1/1)
height: calc( 100% - 35px )
width: 100%
&:focus-visible
outline: 3px solid var(--accent-color)
outline-offset: -3px

&::-webkit-scrollbar
width: 10px
border-left: 1px solid var(--secondary-color)
background: var(--secondary-background)
&::-webkit-scrollbar-thumb
background: var(--secondary-color)
border-left: 1px solid var(--secondary-color)

.card-body__content
max-width: 100vw
min-width: calc(50vw - 14px)
padding: 15px 15px
margin: 0 auto
@media (min-aspect-ratio: 1/1)
max-width: 70vw
padding: 35px 25px
Loading
Loading