Reviewer and note editor addons
This page will be updated regularly. This repository created for testing pre alpha release of js addon support in AnkiDroid.
This is for pre alpha version of AnkiDroid so report issues, bugs, suggestions and feedback here.
Anki has many amazing addons that make learning, creating and managing notes and decks easier. But in AnkiDroid due to platform restrictions those addons are not available. Anki written in python and rust and easier to write addons also. But in AnkiDroid addons can be developed but those need to install as separate app.
Note: AnkiDroid only download js addons from npmjs and inject or perform action defined in index.js
of addons. But hosting and managing of these addons is depend on addon developer and npmjs. AnkiDroid reads index.js
from addons dir and perform action.
This is implementation of using JavaScript as addons support. There are two implementation in progress.
It adds content to reviewer webview. It can be used to redesign the reviewer UI. These addons have full access to AnkiDroid JS API.
It adds buttons to note editor toolbar. It can be used to create notes easily.
To make it remain for longer time in AnkiDroid there are some specific standard set up. It may change with new ideas and suggestions.
First download latest AnkiDroid version from releases page
Open AnkiDroid advanced settings and select checkbox
AnkiDroid -> Settings -> Advanced -> JavaScript Addons Support
Open Addons Browser in AnkiDroid and select Get Addons
from options menu then download addons.
AnkiDroid -> Addons -> Get Addons
Turn on downloaded addons so it will be visible in reviewer or note editor.
View demo for progress bar
View demo for mini cloze overlapper
The addons get downloaded from npmjs.com and installed in AnkiDroid. For enabled addons the content added to reviewer in Card Template.
View PR #8440 for more
Users will navigate to Addons Browser Activity
There are four one button in options menu
When user click Install Addon
It will download the addons to AnkiDroid/addons
folder.
There are implementation for checking valid npm package
{
"name": "ankidroid-js-addon-progress-bar",
"addonTitle": "Progress Bar",
"version": "1.0.0",
"author": "https://github.com/krmanik",
"homepage": "https://github.com/krmanik/Anki-Custom-Card-Layout",
"ankidroidJsApi": "0.0.1",
"addonType": "reviewer",
"keywords": ["ankidroid-js-addon"]
}
View this file AddonModel.kt #L32-#L46
If above found in package.json on npmjs registry then then it will download the package.json and get tarball url.
Here package with .tgz
extension will be downloaded to cache
folder and extracted then copied to AnkiDroid/addons
folder
The addons will be listed on the Addons Browser screen
Users have option to enable, update and delete addons.
When users enable the addons, the index.js
file of that addons will be added to card during review. The addons have full access to AnkiDroid JS API.
Demo of progress bar
In Note editor buttons added for each and every enabled addons. When user click the buttons the function in Note editor read index.js
in AnkiDroid/addons directory and call function AnkiJSFunction
with data flow from AnkiDroid to JS Addons and JS addons to AnkiDroid. Here js-evaluator-for-android used to call js function inside Note editor.
Data flow from AnkiDroid to JS Addon
send deck name, model name, fields count, fields name and selected text in json format to js addon
Data flow from JS Addon to AnkiDroid
receive json format data with changed text and data that needs to be inserted to fields with index
View PR #8011 for more
AnkiDroid
- addons
- ankidroid-js-addons...
- package
- index.js
- package.json
- collection.media
View AddonBrowser.kt : listAddonsFromDir()
"ankidroidJsApi": "0.0.1",
"addonType": "note-editor",
"keywords": [
"ankidroid-js-addon"
],
"author": "Your Name",
"homepage": "Your project website"
Example of progress bar
{
"name": "ankidroid-js-addon-progress-bar",
"addonTitle": "Progress Bar",
"version": "1.0.9",
"description": "Show progress bar in AnkiDroid, this package may not be used in node_modules. For using this addon view. https://github.com/ankidroid/Anki-Android/pull/9232",
"main": "index.js",
"ankidroidJsApi": "0.0.1",
"addonType": "reviewer",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/krmanik/Anki-Custom-Card-Layout.git"
},
"keywords": [
"ankidroid-js-addon"
],
"author": {"name":"krmanik", "email": "[email protected]", "url":"https://krmanik.github.io"},
"license": "MIT",
"bugs": {
"url": "https://github.com/krmanik/Anki-Custom-Card-Layout/issues"
},
"homepage": "https://github.com/krmanik/Anki-Custom-Card-Layout#readme"
}
index.js
with content that you want to inject to reviewerView NpmUtils.kt : getEnabledAddonsContent()
var progressDiv = document.createElement("div");
progressDiv.id = "h-progress";
progressDiv.className = "progress-1";
progressDiv.innerHTML = '<div class="progress-bar-1" id="h-bar"></div>';
// v0.5.2 - https://github.com/SimonLammer/anki-persistence/blob/62463a7f63e79ce12f7a622a8ca0beb4c1c5d556/script.js
if (void 0 === window.Persistence) { var _persistenceKey = "github.com/SimonLammer/anki-persistence/", _defaultKey = "_default"; if (window.Persistence_sessionStorage = function () { var e = !1; try { "object" == typeof window.sessionStorage && (e = !0, this.clear = function () { for (var e = 0; e < sessionStorage.length; e++) { var t = sessionStorage.key(e); 0 == t.indexOf(_persistenceKey) && (sessionStorage.removeItem(t), e--) } }, this.setItem = function (e, t) { void 0 == t && (t = e, e = _defaultKey), sessionStorage.setItem(_persistenceKey + e, JSON.stringify(t)) }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), JSON.parse(sessionStorage.getItem(_persistenceKey + e)) }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), sessionStorage.removeItem(_persistenceKey + e) }) } catch (e) { } this.isAvailable = function () { return e } }, window.Persistence_windowKey = function (e) { var t = window[e], i = !1; "object" == typeof t && (i = !0, this.clear = function () { t[_persistenceKey] = {} }, this.setItem = function (e, i) { void 0 == i && (i = e, e = _defaultKey), t[_persistenceKey][e] = i }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), t[_persistenceKey][e] || null }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), delete t[_persistenceKey][e] }, void 0 == t[_persistenceKey] && this.clear()), this.isAvailable = function () { return i } }, window.Persistence = new Persistence_sessionStorage, Persistence.isAvailable() || (window.Persistence = new Persistence_windowKey("py")), !Persistence.isAvailable()) { var titleStartIndex = window.location.toString().indexOf("title"), titleContentIndex = window.location.toString().indexOf("main", titleStartIndex); titleStartIndex > 0 && titleContentIndex > 0 && titleContentIndex - titleStartIndex < 10 && (window.Persistence = new Persistence_windowKey("qt")) } }
var UA = navigator.userAgent;
var isMobile = /Android/i.test(UA);
var isAndroidWebview = /wv/i.test(UA);
if (isMobile && isAndroidWebview) {
progressBarInit();
} else {
progressBarHide();
}
function progressBarHide() {
document.getElementById("h-progress").style.display = "none";
document.getElementById("h-bar").style.display = "none";
}
function progressBarInit() {
document.body.insertBefore(progressDiv, document.body.firstChild);
var cardCount = parseInt(AnkiDroidJS.ankiGetNewCardCount()) + parseInt(AnkiDroidJS.ankiGetLrnCardCount()) + parseInt(AnkiDroidJS.ankiGetRevCardCount());
var totalCardCount = 1;
if (Persistence.isAvailable()) {
totalCardCount = Persistence.getItem("total");
if (totalCardCount == null) {
totalCardCount = cardCount; // count set to total card count at first card, it will not change for current session
Persistence.setItem("total", totalCardCount);
}
}
// progress bar percentage
var per = Math.trunc(100 - cardCount * 100 / totalCardCount) + "%";
document.getElementById("h-bar").style.width = per;
}
var progressBarCSS = `
.progress-1 {
width: 100%;
border-radius: 2px;
background-color: #e6e6e6;
}
.progress-bar-1 {
height: 12px;
border-radius: 2px;
background-color: limegreen;
}
`;
var styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = progressBarCSS;
document.head.appendChild(styleSheet);
AnkiDroid
- addons
- ankidroid-js-addons...
- package
- index.js
- package.json
- collection.media
Create package.json file. Note: This is import to have following in package.json to distinguish from other npm package and use inside AnkiDroid
View AddonModel.kt : isValidAnkiDroidAddon()
Change addon type to note-editor
Add icon
with single char as icon for button in note editor
"ankidroidJsApi": "0.0.1",
"addonType": "note-editor",
"icon": "["
"keywords": ["ankidroid-js-addon"]
"author": {"name":"Dev Name", "email": "[email protected]", "url":"https://example.com"},
"homepage": "Your project website"
{
"name": "ankidroid-js-addon-cloze",
"addonTitle": "Cloze",
"icon": "[",
"version": "1.0.3",
"ankidroidJsApi": "0.0.1",
"addonType":"note-editor",
"description": "Create cloze from list",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/krmanik/Anki-Custom-Card-Layout.git"
},
"keywords": [
"ankidroid-js-addon"
],
"author": {"name":"krmanik", "email": "[email protected]", "url":"https://krmanik.github.io"},
"license": "MIT",
"bugs": {
"url": "https://github.com/krmanik/Anki-Custom-Card-Layout/issues"
},
"homepage": "https://github.com/krmanik/Anki-Custom-Card-Layout#readme"
}
index.js
and write function with the name AnkiJSFunction
and single parameter. Note: don't change function name as this is the function that AnkiDroid call.View NoteEditorAddon.kt : runJsCode()
The parameter contains json format data of selected text, fields name, fields count, deck name and note type. Use this json data to change or insert data to Edit fields in Note editor.
AnkiDroid --> JS Addon
when AnkiJSFunction called then deck name, model name, fields count, fields name and selected text in json format passed to js addon
{
"noteType": "Basic",
"deckName": "Default",
"fieldsName": ["Front", "Back"],
"fieldsCount": 2,
"selectedText": "Some selected text..."
}
AnkiDroid <-- JS Addon
Note editor receive json format data with changed text and data that needs to be inserted to fields with index
{
"changedText": "some changed text...",
"addToFields": {
"0": "some text to field one",
"2": "some text to field two"
"3": "some text to field three"
},
"changeOption": "replace"
}
Selected text change option
replace
append
clear
default
View NoteEditorAddon.kt : jsAddonParseResult()
Usage of above implementation in new AnkiDroid Cloze Overlapper Mini JS Addon
a) To test this addon first import this deck as it contains note type matching in index.js
b) Select ocloze-infi type then use this addon
function AnkiJSFunction(data) {
var jsonData = JSON.parse(data);
var selectedText = jsonData['selectedText'];
var newJsonData = {};
var fieldsData = {};
if (jsonData['noteType'] == "ocloze-infi" && jsonData["fieldsCount"] == 26) {
newJsonData["changedText"] = ""; // intentionally empty
var list = selectedText.split("\n");
var fullText = "";
var len = list.length;
var startField = 5;
for (i = 0; i < len; i++) {
var text = "";
fullText += "<div>{{c21::" + list[i] + "}}</div>";
for (j = 0; j < len; j++) {
if (i == j) {
if (j > 0) {
text += "<div>" + list[j - 1] + "</div>";
}
text += "<div>{{c" + (j + 1) + "::" + list[j] + "}}</div>";
} else {
text += "<div>...</div>";
}
}
fieldsData[startField] = text;
startField++;
}
fieldsData[25] = fullText;
newJsonData["addToFields"] = fieldsData;
}
return JSON.stringify(newJsonData);
}
The config is displayed in webview using config.html
and config.json
. ConfigEditor
JS interface added in NoteEditorAdddon.kt
with read
, save
and toast
function.
The read
function read config.json
from addons package directory return to webview in string format. Then JSON.parse
can be used to parse the config.json
.
The save
function write config.json
. The function called from webview.
In Note Editor, on long press addon icon open webview in popup with config.html
as url.
View NoteEditorAddon.kt#L85
View AddonConfigEditor.kt
The config.json
in string format sent to AnkiJSFunction
function from AnkiDroid to Addon.
View NoteEditorAddon.kt #L117
config.json
and enter configuration in json formatconfig.html
and use following JS interface function to save, read and show messageConfigEditor.read()
It return config.json
in string format.
ConfigEditor.save()
It save config.json
and return boolean.
ConfigEditor.toast()
It show toast given message as parameter.
ConfigEditor.close()
It close popup webview.
View Cloze Addon Config View CC-CEDICT Addon Config
View
This is for pre alpha version of AnkiDroid so report issues, bugs, suggestions and feedback here.