{"id":15950376,"url":"https://github.com/krmanik/create-addon-in-browser-for-anki","last_synced_at":"2026-05-20T14:09:31.836Z","repository":{"id":54120303,"uuid":"345709976","full_name":"krmanik/Create-Addon-in-browser-for-Anki","owner":"krmanik","description":"Create Addon in browser for Anki, AnkiDroid and AnkiMobile","archived":false,"fork":false,"pushed_at":"2021-03-09T14:48:43.000Z","size":9475,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-09T16:11:08.069Z","etag":null,"topics":["anki","pyodide","python"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/krmanik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-08T15:53:40.000Z","updated_at":"2023-11-08T01:03:47.000Z","dependencies_parsed_at":"2022-08-13T07:00:41.080Z","dependency_job_id":null,"html_url":"https://github.com/krmanik/Create-Addon-in-browser-for-Anki","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krmanik%2FCreate-Addon-in-browser-for-Anki","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krmanik%2FCreate-Addon-in-browser-for-Anki/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krmanik%2FCreate-Addon-in-browser-for-Anki/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krmanik%2FCreate-Addon-in-browser-for-Anki/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/krmanik","download_url":"https://codeload.github.com/krmanik/Create-Addon-in-browser-for-Anki/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247120819,"owners_count":20886976,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anki","pyodide","python"],"created_at":"2024-10-07T12:59:10.767Z","updated_at":"2026-05-20T14:09:26.811Z","avatar_url":"https://github.com/krmanik.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tutorial : How to create an Anki-addon for the browser\nThis project will explain how to create a web-app that can be used as an addon for Anki to generate decks. \nThe resulting addon can be then used with Anki, AnkiDroid and AnkiMobile.\n\n## Example addon\n\nLet's start with an example: an app that let's you create basic question-answer cards in the browser.\n\nhttps://infinyte7.github.io/Create-Addon-in-browser-for-Anki\n\n\u003cb\u003eUsage\u003c/b\u003e\n1. Add text for the front and back side of a card\n2. Click the add button to save the node\n3. Click download to export a deck containing the saved notes\n\n# web-app addons\n- [Setup](#setup)\n- [How does it work?](#how-does-it-work)\n- [Advantages](#advantages)\n- [Disadvantages](#disadvantages)\n- [Projects](#projects)\n- [Read more](#read-more)\n- [Tutorial](#tutorial)\n    - [index.html](#indexhtml)\n    - [index.js](#indexjs)\n    - [deck-export.js](#deck-exportjs)\n- [Note](#note)\n\n# Setup\n1. Fork this repository\n2. All the setup-files are in docs folder\n3. ```index.html``` is the front page for sending note data from html/js to pyodide internally\n4. ```index.js``` contains code for object conversion between javaScript and pyodide\n5. ```deck-export.js``` contains code for generating and downloading Anki decks\n\n# How does it work?\n1. Note data is sent from html/js to pyodide in a tab-separated format\n2. When you click the ```add button``` pyodide writes the data to ```output-all-notes.txt``` \n3. When you click the ```download button``` ```genanki``` generates Anki decks from ```output-all-notes.txt``` and downloads it\n\n# Advantages\n- Runs on smartphones as well as on desktop computers\n- You can import the generated decks to any version of Anki\n\n# Disadvantages\n- The loading time is high\n- Some python modules are not available (for example opencv)\n- One needs to understand object conversion between javaScript and python\n\n# Projects \n- [image occlusion in browser](https://github.com/infinyte7/image-occlusion-in-browser)\n- [ocloze: cloze overlapper in browser](https://github.com/infinyte7/ocloze)\n\n# Read more\n- [Anki](https://apps.ankiweb.net/)\n    Anki is a free and open-source flashcard program using spaced repetition, a technique from cognitive science for fast and long-lasting memorization. \n\n- [pyodide](https://github.com/iodide-project/pyodide)\n\n    Pyodide brings the Python 3.8 runtime to the browser via WebAssembly, along with the Python scientific stack including NumPy, Pandas, Matplotlib, SciPy, and scikit-learn. The packages directory lists over 75 packages which are currently available. In addition it's possible to install pure Python wheels from PyPi.\n\n    Pyodide provides transparent conversion of objects between Javascript and Python. When used inside a browser, Python has full access to the Web APIs.\n\n- [genanki](https://github.com/kerrickstaley/genanki)\n\n    ```genanki``` allows you to programmatically generate decks in Python 3 for Anki, a popular spaced-repetition flashcard program.\n\n# Tutorial\n## index.html\n[View index.html](docs/index.html)\n#### 1. Add pyodide\n```html\n  \u003cscript type=\"text/javascript\"\u003e\n    window.languagePluginUrl = 'pyodide/dev/full/';\n  \u003c/script\u003e\n  \u003cscript src=\"pyodide/dev/full/pyodide.js\"\u003e\u003c/script\u003e\n  ...\n  ...\n\n  \u003cscript src=\"js/deck-export.js\"\u003e\u003c/script\u003e\n```\n\n#### 2. Text areas for front and back fields of the card to be added\n```html\n  \u003c!-- Note Form --\u003e\n  \u003c!-- Front Back --\u003e\n  \u003cdiv id=\"add-note\"\u003e\n    \u003cdiv class=\"input-note\" style=\"padding-top: 60px;\"\u003eFront\n      \u003chr class=\"thin\"\u003e\n      \u003ctextarea id=\"noteFront\" class=\"input-add-note\" type=\"text\" placeholder=\"Front...\" required\u003e\u003c/textarea\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"input-note\" style=\"padding-top: 30px;\"\u003eBack\n      \u003chr class=\"thin\"\u003e\n      \u003ctextarea id=\"noteBack\" class=\"input-add-note\" type=\"text\" placeholder=\"Back...\" required\u003e\u003c/textarea\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n```\n#### 3. Buttons for adding note data to a list and for downloading decks\n```html\n    \u003cdiv id=\"done-export-all\" class=\"toolbar-button\" style=\"float: right; display: block;\" onclick=\"exportAll();\"\u003e\u003ci\n        class=\"material-icons\"\u003eget_app\u003c/i\u003e\u003c/div\u003e\n\n    \u003cdiv id=\"done-btn\" class=\"toolbar-button\" style=\"float: right;\" onclick=\"addNoteToList();\"\u003e\u003ci\n        class=\"material-icons\"\u003eadd\u003c/i\u003e\u003c/div\u003e\n```\n\n## index.js\n[View index.js](docs/js/index.js)\n#### 1. Adding note data to pyodide \n```js\n// add note data to pyodide output text file for deck export\nvar textToExport = \"\";\nvar textFileName = \"\";\nfunction addNoteToList() {\n    textFileName = \"output-all-notes.txt\";\n    \n    var container = document.getElementById(\"add-note\");\n\n    // tab separated value for genanki\n    for (i = 0; i \u003c container.childElementCount; i++) {\n        textToExport += container.children[i].children[1].value.trim() + \"\\t\";\n    }\n\n    // remove last space, tab...\n    textToExport = textToExport.trim();\n\n    // write to file using pyodide\n    pyodide.runPython(\"textFileName = js.textFileName\")\n    pyodide.runPython(\"textToExport = js.textToExport\")\n\n    pyodide.runPython(`with open(textFileName, 'a', encoding='utf-8') as f: \n                            f.write(textToExport)`)\n}\n```\n#### 2. Generate and export deck\n```js\n// export and download deck \nfunction exportAll() {\n    document.getElementById('statusMsg').innerHTML = \"Wait, deck generating...\";\n    setTimeout(function () { document.getElementById('statusMsg').innerHTML = \"\"; }, 2000);\n\n    exportDeck();\n    downloadDeck();\n}\n```\n\n## deck-export.js\n[View deck-export.js](docs/js/deck-export.js)\n#### 1. Init pyodide for running python in browser\n```js\nlanguagePluginLoader.then(() =\u003e {\n    return pyodide.loadPackage(['micropip'])\n}).then(() =\u003e {\n    pyodide.runPython(pythonCode);\n\n    document.getElementById(\"loading\").style.display = \"none\";\n    document.getElementById(\"statusMsg\").innerHTML = \"\";\n\n    showSnackbar(\"Ready to import file\");\n})\n\nlanguagePluginLoader.then(function () {\n    console.log('Ready');\n});\n\n```\n#### 2. python code to generate decks from the tsv file ```output-all-notes.txt```\n\n```python\n      import genanki\n\n      # front side\n      front = \"\"\"\n\u003cdiv\u003e{{Front}}\u003c/div\u003e\n      \"\"\"\n\n      # styling\n      style = \"\"\"\n.card {\n  font-family: arial;\n  font-size: 20px;\n  text-align: center;\n  color: black;\n  background-color: white;\n}      \n      \"\"\"\n\n      # back side\n      back = \"\"\"\n\u003cdiv\u003e{{Front}}\u003c/div\u003e\n\u003chr\u003e\n\u003cdiv\u003e{{Back}}\u003c/div\u003e\n      \"\"\"\n\n      t_fields = [{\"name\": \"Front\"}, {\"name\": \"Back\"}]\n      \n      # print(self.fields)\n      anki_model = genanki.Model(\n          model_id,\n          anki_model_name,\n          fields=t_fields,\n          templates=[\n              {\n                  \"name\": \"Card 1\",\n                  \"qfmt\": front,\n                  \"afmt\": back,\n              },\n          ],\n          css=style\n      )\n\n      anki_notes = []\n\n      with open(data_filename, \"r\", encoding=\"utf-8\") as csv_file:\n          csv_reader = csv.reader(csv_file, delimiter=\"\\\\t\")\n          for row in csv_reader:\n              flds = []\n              for i in range(len(row)):\n                  flds.append(row[i])\n\n              anki_note = genanki.Note(\n                  model=anki_model,\n                  fields=flds,\n              )\n              anki_notes.append(anki_note)\n\n      anki_deck = genanki.Deck(model_id, anki_deck_title)\n      anki_package = genanki.Package(anki_deck)\n\n      for anki_note in anki_notes:\n          anki_deck.add_note(anki_note)\n\n      anki_package.write_to_file(deck_filename)\n\n      deck_export_msg = \"Deck generated with {} flashcards\".format(len(anki_deck.notes))\n      js.showSnackbar(deck_export_msg)\n```\n#### 3. Change front, back, style and fields\n\nFront and back should contain fields that are present in ```t_fields```.\n\n```t_fields``` should contain fields in the following manner.\n```python\n{\"name\": \"Field name\"}\n```\n#### 4. Images can also be added to a deck if the tsv file contains ```\u003cimg\u003e``` with a src tag\nSelect image using ```input```\n\n```js\nfunction addImage() {\n    var imgHeight;\n    var imgWidth;\n\n    var selectedFile = event.target.files[0];\n    var reader = new FileReader();\n\n    var imgtag = document.getElementById(\"uploadPreview\");\n    imgtag.title = selectedFile.name;\n    imgtag.type = selectedFile.type;\n\n    reader.onload = function (event) {\n        imgtag.src = event.target.result;\n\n        imgtag.onload = function () {\n            imgHeight = this.height;\n            imgWidth = this.width;\n        };\n    };\n\n    reader.readAsDataURL(selectedFile);\n}\n```\n\nSaving selected image to internal. In this code base64 of image passed and written to file.\n\n```js\nfunction saveSelectedImageToInternal() {\n    pyodide.runPython(\"import os\")\n    pyodide.runPython(\"import js\")\n    pyodide.runPython(\"import base64\")\n\n    pyodide.runPython(\"if not os.path.exists('images'): os.mkdir('images')\")\n\n    // src tag contains base64 of image data\n    pyodide.runPython(\"blob = js.document.getElementById('uploadPreview').getAttribute('src')\")\n    pyodide.runPython(\"name = js.document.getElementById('uploadPreview').getAttribute('title')\")\n\n    // base64 data\n    // data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEECAAAAAAjIz....\n    // split data and write later part\n    // iVBORw0KGgoAAAANSUhEUgAAAZAAAAEECAAAAAAjIz....\n    pyodide.runPython(\"blob = blob.split(',')[1]\")\n    pyodide.runPython(\"imgData = base64.b64decode(blob)\")\n    pyodide.runPython(`with open('images/' + name, 'wb') as f: \n                            f.write(imgData)`)\n}\n```\n\nAppending images to genanki Anki packages.\n\n```python\n# add media\nfiles = []\nfor ext in ('*.gif', '*.png', '*.jpg', '*.jpeg', '*.bmp', '*.svg'):\n    files.extend(glob(join(\"images\", ext)))\n\nanki_package.media_files = files\n```\nSame goes for other media files.\n\u003cbr\u003eView usage in [image occlusion in browser](https://github.com/infinyte7/image-occlusion-in-browser)\n\n#### 5. Import the required python module in pyodide\n```python\nimport micropip\n\n# from GitHub using CDN\nmicropip.install(\"https://cdn.jsdelivr.net/gh/infinyte7/Anki-Export-Deck-tkinter/docs/py-whl/frozendict-1.2-py3-none-any.whl\")\nmicropip.install(\"https://cdn.jsdelivr.net/gh/infinyte7/Anki-Export-Deck-tkinter/docs/py-whl/pystache-0.5.4-py3-none-any.whl\")\nmicropip.install(\"https://cdn.jsdelivr.net/gh/infinyte7/Anki-Export-Deck-tkinter/docs/py-whl/PyYAML-5.3.1-cp38-cp38-win_amd64.whl\")\nmicropip.install(\"https://cdn.jsdelivr.net/gh/infinyte7/Anki-Export-Deck-tkinter/docs/py-whl/cached_property-1.5.2-py2.py3-none-any.whl\")\nmicropip.install(\"https://cdn.jsdelivr.net/gh/infinyte7/Anki-Export-Deck-tkinter/docs/py-whl/genanki-0.8.0-py3-none-any.whl\")\n```\n#### 6. Download generated deck\n```js\nfunction downloadDeck() {\n    let txt = pyodide.runPython(`                  \n    with open('/output.apkg', 'rb') as fh:\n        out = fh.read()\n    out\n    `);\n\n    const blob = new Blob([txt], { type: 'application/zip' });\n    let url = window.URL.createObjectURL(blob);\n\n    var downloadLink = document.createElement(\"a\");\n    downloadLink.href = url;\n    downloadLink.download = \"Export-Deck.apkg\";\n    document.body.appendChild(downloadLink);\n    downloadLink.click();\n}\n```\n#### 7. Download ```output-all-notes.txt``` containing note data\n```js\nfunction exportText() {\n    let txt = pyodide.runPython(`                  \n    with open('/output-all-notes.txt', 'r') as fh:\n        out = fh.read()\n    out\n    `);\n\n    console.log(txt);\n\n    const blob = new Blob([txt], { type: 'text/plain' });\n    let url = window.URL.createObjectURL(blob);\n\n    var downloadLink = document.createElement(\"a\");\n    downloadLink.href = url;\n    downloadLink.download = \"output.txt\";\n    document.body.appendChild(downloadLink);\n    downloadLink.click();\n}\n```\n# Note\n#### 1. Escape back slash in pythonCode\nIf you want to use backslashes \\\\ in ```pythonCode``` you have to escape them with another backslash \\\\\\\\, otherwise it will not work or show an error.\n```js\ncsv_reader = csv.reader(csv_file, delimiter=\"\\t\")\n                                            ^^^^^\ncsv_reader = csv.reader(csv_file, delimiter=\"\\\\t\")\n```\n#### 2. Add constant value for model id\nFor every deck generation, genanki use random model id which leads to creation of multiple ```Note Type```. So to avoid this use constant model id in ```pythonCode``` in deck-export.js\n\n```python\nanki_model_name = \"addon-in-browser\"\n\n# model_id = random.randrange(1 \u003c\u003c 30, 1 \u003c\u003c 31)\n# constant model id for this project\nmodel_id = 1716551648\n```\n\n#### 3. Pass deck name from js to pyodide\nCreate a variable ```deckName``` in js file and access it in pyodide\n\n```python\nimport js\n\nnew_title = js.deckName\nanki_deck_title = \"Addon in browser\"\n\nif new_title != None:\n   anki_deck_title = new_title\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrmanik%2Fcreate-addon-in-browser-for-anki","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkrmanik%2Fcreate-addon-in-browser-for-anki","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrmanik%2Fcreate-addon-in-browser-for-anki/lists"}