Closed Nihilhem closed 1 year ago
What does the card preview show?
I can't reproduce the error with my setup. Did you use the add-on before or is this your first time using it?
Everything seems just fine, except for the error message popping up every time I open Anki. I don't know if it affects functionality in any way.
I have used this add-on before just fine. The error started happening only after I updated Anki to the current, latest version (2.1.65). I don't know which is the version I had prior to this, but it was not too old. It happened both with the previous version of this add-on, and with the new version that was released yesterday.
@Nihilhem Can you show us the current code (best copy the whole thing) on the template (through "manage note types" selecting AllInOne (kprim, mc, sc)
and then cards) and a screenshot of the fields for this note type?
It certainly would help to know what is wrong with the template after the addon had its way with it 😃. The numbers of question fields in the fields section and in the template should match.
Thank you for your report!
@3ter Sure, here you go. Thanks for your interest.
1) Template code:
<script>
// Loading Persistence
// https://github.com/SimonLammer/anki-persistence
// 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")) } }
</script>
{{#Image}}<p>{{Image}}</p>{{/Image}}
<h3 id="myH1"></h3>
{{#Question}}<p>{{Question}}</p>{{/Question}}
<div class="tappable">
<table style="border: 1px solid black" id="qtable"></table>
</div>
<div class="hidden" id="Q_solutions">{{Answers}}</div>
<div class="hidden" id="Card_Type">{{QType (0=kprim,1=mc,2=sc)}}</div>
<div class="hidden" id="Q_1">{{Q_1}}</div>
<div class="hidden" id="Q_2">{{Q_2}}</div>
<div class="hidden" id="Q_3">{{Q_3}}</div>
<div class="hidden" id="Q_4"></div>
<div class="hidden" id="Q_5"></div>
<script>
// Generate the table depending on the type.
function generateTable() {
var type = document.getElementById("Card_Type").innerHTML;
var table = document.createElement("table");
var tbody = document.createElement("tbody");
for (var i = 0; true; i++) {
if (type == 0 && i == 0) {
tbody.innerHTML = tbody.innerHTML + '<tr><th>yes</th><th>no</th><th></th></tr>';
}
if (document.getElementById('Q_' + (i + 1)) != undefined) {
if (document.getElementById('Q_' + (i + 1)).innerHTML != '') {
var html = [];
let answerText = document.getElementById('Q_' + (i + 1)).innerHTML;
let labelTag = (type == 0) ? '' :
'<label for="inputQuestion' + (i + 1) + '">' + answerText + '</label>';
let textAlign = (type == 0) ? 'center' : 'left';
html.push('<tr>');
var maxColumns = ((type == 0) ? 2 : 1);
for (var j = 0; j < maxColumns; j++) {
let inputTag = '<input id="inputQuestion' + (i + 1) +
'" name="ans_' + ((type != 2) ? (i + 1) : 'A') +
'" type="' + ((type == 1) ? 'checkbox' : 'radio') +
// TODO: I don't see how these values are used, please add a comment
'" value="' + ((j == 0) ? 1 : 0) + '">';
html.push(
'<td onInput="onCheck()" style="text-align: ' + textAlign + '">' + inputTag +
labelTag +
'</td>');
}
if (type == 0) {
html.push('<td>' + answerText + '</td>');
}
html.push('</tr>');
tbody.innerHTML = tbody.innerHTML + html.join("");
}
} else {
break;
}
}
table.appendChild(tbody);
document.getElementById('qtable').innerHTML = table.innerHTML;
onShuffle();
}
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
function onShuffle() {
var solutions = document.getElementById("Q_solutions").innerHTML;
solutions = solutions.replace(/(<([^>]+)>)/gi, "").split(" ");
for (var i = 0; i < solutions.length; i++) {
solutions[i] = Number(solutions[i]);
}
var output = document.getElementById("output");
var qrows = document.getElementById("qtable").getElementsByTagName("tr");
var qanda = new Array();
var type = document.getElementById("Card_Type").innerHTML;
for (i = 0; i < ((type == 0) ? qrows.length - 1 : qrows.length); i++) {
qanda[i] = new Object();
qanda[i].question = qrows[(type == 0) ? i + 1 : i].getElementsByTagName("td")[(type == 0) ? 2 : 0].innerHTML;
qanda[i].answer = solutions[i];
}
qanda = shuffle(qanda);
var mc_solutions = new String();
for (i = 0; i < ((type == 0) ? qrows.length - 1 : qrows.length); i++) {
qrows[(type == 0) ? i + 1 : i].getElementsByTagName("td")[(type == 0) ? 2 : 0].innerHTML = qanda[i].question;
solutions[i] = qanda[i].answer;
mc_solutions += qanda[i].answer + " ";
}
mc_solutions = mc_solutions.substring(0, mc_solutions.lastIndexOf(" "));
document.getElementById("Q_solutions").innerHTML = mc_solutions;
document.getElementById("qtable").HTML = qrows;
onCheck();
}
/**
* Returns true if the option box/circle is checked.
*
* In case of kprim the second box is used as reference.
*
* @param {HTMLTableRowElement} optionRow Row containing option boxes/circles.
* @param {number} index Index of the option in question.
*/
function isOptionChecked(optionRow, index) {
return optionRow.getElementsByTagName("td")[index].getElementsByTagName("input")[0].checked
}
function getUserAnswers() {
let type = document.getElementById("Card_Type").innerHTML;
let qrows = document.getElementById("qtable").getElementsByTagName('tbody')[0].getElementsByTagName("tr");
let userAnswers = [];
for (let i = 0; i < qrows.length; i++) {
if (type == 0 && i == 0) {
i++; // to skip the first row containing no checkboxes when type is 'kprim'
}
if (type == 0) {
if (isOptionChecked(qrows[i], 0)) {
userAnswers.push(1);
} else if (isOptionChecked(qrows[i], 1)) {
userAnswers.push(0);
} else {
userAnswers.push(2);
}
} else {
if (isOptionChecked(qrows[i], 0)) {
userAnswers.push(1);
} else {
userAnswers.push(0);
}
}
}
return userAnswers
}
function getCorrectAnswers() {
let solutions = document.getElementById("Q_solutions").innerHTML.split(" ").map(string => Number(string));
return solutions;
}
/**
* On checking an option this collects and stores answers in between front/back of the card.
*
* In case of kprim only the first box is looked at, if it isn't checked the second box has to be.
* By default a '1' in the answers stands for 'yes' which is the first option from the left.
*/
function onCheck() {
// Send question table and encoded answers to Persistence along with the provided solutions
if (Persistence.isAvailable()) {
Persistence.clear();
Persistence.setItem('user_answers', getUserAnswers());
Persistence.setItem('Q_solutions', getCorrectAnswers());
Persistence.setItem('qtable', document.getElementById("qtable").innerHTML);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function tickCheckboxOnNumberKeyDown(event) {
const keyName = event.key;
let tableBody = document.getElementById("qtable").getElementsByTagName('tbody')[0];
var tableRows = tableBody.getElementsByTagName("tr");
if (0 < +keyName && +keyName < 10) {
let tableData = tableRows[+keyName - 1].getElementsByTagName("td")[0];
let tableRow = tableData.getElementsByTagName("input")[0];
tableRow.checked = !tableRow.checked;
onCheck();
}
}
// addCheckboxTickingShortcuts is an easy approach on using only the keyboard to toggle checkboxes in mc/sc.
//
// Naturally the number keys are an intuitive choice here. Unfortunately anki does capture those.
// So the workaround is to hold the (left) 'Alt' key and then type the corresponding number to toggle the row.
function addCheckboxTickingShortcuts() {
document.addEventListener('keydown', tickCheckboxOnNumberKeyDown, false);
}
function isMobile() {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
return true;
} else {
return false;
}
}
function run() {
let DEFAULT_CARD_TYPE = 1; // for previewing the cards in "Manage Note Type..."
if (isNaN(document.getElementById("Card_Type").innerHTML)) {
document.getElementById("Card_Type").innerHTML = DEFAULT_CARD_TYPE;
}
if (document.getElementById("Card_Type").innerHTML != 0 && !isMobile()) {
addCheckboxTickingShortcuts();
}
setTimeout(generateTable(), 1);
}
async function waitForReadyStateAndRun() {
for (let i = 0; i < 100; i++) {
if (document.readyState === "complete") {
run();
break;
}
console.log("Document not yet fully loaded (readyState: " + document.readyState + "). Retry in 0.1s.");
await sleep(100);
}
}
/*
The following block is inspired by Glutanimate's Cloze Overlapper card template.
The Cloze Overlapper card template is licensed under the CC BY-SA 4.0
license (https://creativecommons.org/licenses/by-sa/4.0/).
*/
if (document.readyState === "complete") {
run();
} else {
waitForReadyStateAndRun();
}
</script>
2) Fields screenshot:
There are some changes to the default template (e.g. the title
is missing and you removed the question ids for 4 and 5) but I can't see how the addon should produce an error with this template.
The version proposed in #114 should alleviate this a bit by actually using the default template (which I thought already was the case... but in your case there was an error when the addon tried to save its changes). Unfortunately anki doesn't tell us what the error in the template is and instead wants us to look at the preview... which is impossible, as this is the addon trying to change the template which we never see. This would be very helpful though...
I try to think of something.
EDIT: https://faqs.ankiweb.net/card-template-has-a-problem.html
It was obvious I'm afraid. As you've removed a field which is used in the default template the template can't be changed by the addon (but it wants to, to make sure you're using the most up to date one).
I'm going to think about a sanity check in #114 to make sure fields that have been removed by the user don't show up in the template...
Current workaround for you:
Title
back in (needed only in the fields, at the bottom is fine)This will work until the next update.
It was obvious I'm afraid. As you've removed a field which is used in the default template the template can't be changed by the addon (but it wants to, to make sure you're using the most up to date one).
I'm going to think about a sanity check in #114 to make sure fields that have been removed by the user don't show up in the template...
Current workaround for you:
* Save your changes (already did that above) to note type * Add the field `Title` back in (needed only in the fields, at the bottom is fine) * Now the addon should be able to redo the template after the next restart of anki * Reapply your changes to the note type (both template and fields)
This will work until the next update.
That's strange, because I don't remember deleting any fields. In fact, i didn't even know this was a possibility, and I've never opened the "Note Types" menu before. I think the reason is a deck that I had downloaded, which only had three question fields instead of five.
I've tried adding the Title field back in (only in the fields and at the bottom), saving changes, restarting Anki and saving changes again in both template and fields, but I still get an error. I don't really know what's going on 🤔
Debug info:
Anki 2.1.65 (aa9a734f) Python 3.9.15 Qt 6.4.3 PyQt 6.4.0
Platform: Windows-10-10.0.19045
Flags: frz=True ao=True sv=2
Add-ons, last update check: 2023-07-23 14:30:09
Caught exception:
Traceback (most recent call last):
File "aqt.progress", line 118, in handler
File "aqt.main", line 217, in on_window_init
File "aqt.main", line 264, in setupProfileAfterWebviewsLoaded
File "aqt.main", line 316, in setupProfile
File "aqt.main", line 496, in loadProfile
File "_aqt.hooks", line 3881, in __call__
File "C:\Users\charl\AppData\Roaming\Anki2\addons21\1566095810\template.py", line 240, in manage_multiple_choice_note_type
AddOrUpdateModel()
File "C:\Users\charl\AppData\Roaming\Anki2\addons21\1566095810\template.py", line 229, in AddOrUpdateModel
updateTemplate(mw.col)
File "C:\Users\charl\AppData\Roaming\Anki2\addons21\1566095810\template.py", line 218, in updateTemplate
col.models.save(model)
File "anki.models", line 569, in save
File "anki.models", line 552, in update
File "anki._backend_generated", line 873, in add_or_update_notetype
File "anki._backend", line 156, in _run_command
anki.errors.CardTypeError: Card template 1 in notetype 'AllInOne (kprim, mc, sc)' has a problem.<br>See the preview for more information.
Thanks for the heads up. I've tested it with a pristine anki installation (2.1.65
) and set the note type up exactly as you had. Then I only added a field with the name Title
(like in your screenshot) and with the next restart the error message (that popped up before) was gone for good.
You're still missing Extra 1
by the way. Could that be the culprit? It is used on the back template, so this most probably is what we're looking for.
PS You can leave the Note ID
of course, additional field are no problem.
PPS The next version of the addon I've proposed in #114 adds all those missing fields automatically, so you don't run into that in the future.
Thanks for the heads up. I've tested it with a pristine anki installation (
2.1.65
) and set the note type up exactly as you had. Then I only added a field with the nameTitle
(like in your screenshot) and with the next restart the error message (that popped up before) was gone for good.You're still missing
Extra 1
by the way. Could that be the culprit? It is used on the back template, so this most probably is what we're looking for.PS You can leave the
Note ID
of course, additional field are no problem. PPS The next version of the addon I've proposed in #114 adds all those missing fields automatically, so you don't run into that in the future.
Nice! That solved the problem. Indeed, after adding the Extra 1
field and restarting Anki, no error message is shown anymore. Thank you for your patience, your interest and your comprehensible answers.
Describe the bug
Anki desktop on Windows 10 shows an error message immediately after launch.
If Anki shows an error message, copy and paste it between the backticks below.
To Reproduce
Expected behavior
I expected Anki to open normally without any error.
What have you tried?
I tried disabling add-ons and that got rid of the error. Anki showed the error when I only had this add-on enabled,
Screenshots
-
Information about your set-up
On which platform(s) does the error occur?
Only if the error occurs with the Anki desktop app: Open Anki, go to Help > About and click on "Copy Debug Info". Paste the result between the backticks below.
Additional context
-