все вопросы по Anki задавайте на форуме  reddit.com/r/AnkiRu/ 

• JavaScript

Здесь пойдёт речь не о самом языке,

а лишь о некоторых довольно интересных случаях его применения в Anki.

Чтобы колупаться во всех этих приколюхах, язык желательно уже знать на неплохом уровне.
Литературы, видео, сайтов, курсов, что платных, что бесплатных,
для Знаек, для Незнаек, для Зазнаек, на любой вкус и цвет, в Сети — завались.

Наибольшую пользу лично я получил от learn.javascript.ru/
По времени заняло полгода по 4-5 дней в неделю и на 2-3 часа за вечер.
Это очень примерный ориентир, понятно, что ваша скорость зависит только от вас.

Язык не самый простой для понимания и не самый удачный выбор, если это ваш первый язык.
Поэтому сначала возьмите какой-нибудь курс по основам программирования вообще,
без привязки к конкретному языку. Это же логично.

 

Ограничения JavaScript

В Anki у javascript:
  • внешние файлы <script src="_myFile.js">

    • должны быть в каталоге collection.media

    • и их имена должны начинаться с _подчёркивания,
      чтобы эти файлы экспортировались вместе с колодами.

  • нет отладчика (консоли)

  • document.write не работает на сайте AnkiWeb

  • alert и т.п. функций нет в некоторых мобильных клиентах

  • не поддерживаются localStorage (локальное хранилище HTML5)

  • и куки (document.cookie)

Так что остаётся только работа в элементами DOM в чистом виде.

AnkiWeb без document.write

document.write
document.writeln
НЕ работают на карточках на сайте AnkiWeb.net
и на Anki desktop.

AnkiDroid и предпросмотр на Anki desktop
прекрасно с этими функциями уживаются.

Почему так?

Чтобы жизнь мёдом не казалась.
Просто они каждый раз обновляют сторону карточки целиком.

А если сугубо про тонкости работы кода, то AnkiWeb и Anki desktop при показе очередной карточки не прорисовывают страницу целиком вновь, а просто подменяют содержимое <div id="qa">…​</div>

Что ещё более забавно,

так это то, что в предпросмотре Anki desktop
такого блока нет.
Сей прискорбный факт надо учитывать для понимания того грустного факта, отчего некторые ваши скрипты и стили работают на предпросмотре, но не работают в бою. Или наоборот.

Но это ещё не вся правда.

Раз в 100 (сто) карточек и Anki desktop
прорисовывает страничку целиком.
Да, чтобы не нарываться на исчерпание памяти.

Иногда и не дожидается истечения лимита,
а обновляется досрочно. Это в случае,
если было редактирование типа записей, шаблона карточек и т.п.

AnkiDroid вынужден каждый раз прорисовывать каждую страничку с нуля, чтобы побороть утечки памяти.

Подсказка Чтобы подсмотреть, какой исходный код по факту работает в вашей карточке на Anki desktop, используйте дополнение ' View HTML source with JavaScript and CSS styles 1128123950

Классы тегов html и body

Вместо document.writeln(…​); можно использовать такой код, например:
<!doctype html>
<html class="ko kos kostik">
<head>
  <meta charset="UTF-8">
  <title>Sample Document</title>
</head>
<body class="bla blah">
  <h1>Sample Document</h1>

<script>
  function showClasses(id, html) {
    var sp = document.getElementById(id);
    (sp != null) && (1 != sp.nodeType && (sp = sp.previousSibling),
      (sp != null) && (sp.innerHTML = html.replace(/ /g, '<br>')));
  }
</script>

<div id="htmlClasses" style="border-bottom:dashed 1px silver;">a</div>

<script>
  showClasses('htmlClasses', document.documentElement.className);
</script>

<div id="bodyClasses">b</div>

<script>
  setTimeout(function() {
    showClasses('bodyClasses', document.body.className);
  }, 1);
</script>

</body>
</html>
Примечание
Запуск по таймауту через 1 миллисекунду гарантирует,
что код будет выполнен только тогда,
когда вся страница будет уже готова.

Дело в том, что в момент формирования страницы
классы тегу <body> ещё не назначены, просто ничего не будет показано,
в отличие от классов тега <html>

С помощью этого фрагмента кода вы сможете увидеть на стороне своей карточки те классы, которые Anki назначила тегам HTML и BODY для вашего случая.

Универсальное решение

Вы можете поставить на любую сторону своей карточки функцию say
и использовать её вместо document.write
скажем, для вывода отладочной информации:

<script>
function say() {
  var args = [],
      spacer = ' '; /* '' если надо без пропусков */
  for (var i = 0; i < arguments.length; i++) {
    args[i] = arguments[i];
    if (typeof args[i] == 'object'){
        args[i] = JSON.stringify(args[i]);
    } else {
        args[i] = args[i].toString();
    }
  }
  if (!document.getElementById('say')) {
    var newDIV = document.createElement('div');
    newDIV.id = 'say';
    document.body.appendChild(newDIV);
  }
  document.getElementById('say').innerHTML +=
    spacer + args.join(spacer) + spacer;
}
</script>
<script>
var a = ['-', '&minus;', '+'],
    b = { "π": '3.141592653589793238462643',
         "1st": 3.141592653589793238462643,
         '2nd': 355/113, '3rd': Math.PI };

say('Hello, world!', 5, 7.25, 3e4, true, a[1]);
say('<b>', a.toString(), '</b><br>'); say(a, b);

</script>

Как понимаете, количество параметров произвольное,
их тип — тоже, главное, чтобы у них поддерживался метод .toString()

Значения выводятся через пробел.

Блок для сообщений добавляется в конец всей информации на карточке.

Но если это произошло внутри {{FrontSide}} на оборотной стороне,
то он окажется перед ответом.

Чтобы этого не происходило,

вы можете просто явно указать желаемое место на каждой стороне карточки:

 <div id="say"></div>

Опять же, если блок задан на обоих сторонах и используется {{FrontSide}}
то работать будет тот, который оперделён выше по тексту,
то есть если на оборотной стороне значится:

<div id=say></div>{{FrontSide}}

то вся последующая отладка будет появлятся в самом начале страницы, ага.

Через стили этому блоку можно указать любое желаемое оформление, например:

 div#say { font-style: italic; }
Осторожно! Если вы поставили скрипт на лицевой стороне,
а на оборотной используете {{FrontSide}},
то на оборотной добавлять определение функции не требуется.

Чтобы функция отработала как document.writeln
просто добавьте последним параметром HTML перевод строки:

 say( ... , '<br>');
В общем, играть — не переиграть.

Море простора для буйства фантазии.

 

Фон для метки

Как сделать разный фон для карточек в зависимости от тегов(меток)?

Я понимаю что меток может быть несколько.
Но хотелось бы визуально отличать разные карточки,
так как я использую как правило только по одной метке на карточку.

В деле различения карточек от записей с разными метками возможны два подхода.

Первый:

просто показывать все метки, что есть у записи.

 <div id=Tags>{{Tags}}</div>
 #Tags {
     background-color: #cfc;
     overflow: hidden;
     position: fixed;
     bottom: 0px;
     left: 0px;
     right: 0px;
     border-top: solid 1px green;
     height: 20px;
     color: darkgreen;
     font-size: 14px;
 }
Второй:

создать классы, одноимённые меткам.

Код для вставки на лицевую сторону карточки

  <script>
    setTimeout(function() {
      var klassList = '{{Tags}}'.split(' ');
      for (var i = 0; i < klassList.length; i++) {
        document.body.classList.add(klassList[i]);
      }
    }, 1);
  </script>

Сами классы описать в секции стилей как обычно.

Если на оборотной стороне не используется {{FrontSide}} , то

Код для вставки на оборотную сторону карточки

  <script>
    var klassList = '{{Tags}}'.split(' ');
    for (var i = 0; i < klassList.length; i++) {
      document.body.classList.add(klassList[i]);
    }
  </script>

Да, на оборотной стороне прокатывает работает и без таймаута.

Можно скачать колоду с примером  .zip

SEPARATOR

С иерархическими тегами проблема

В разделителе из двойного двоеточия по умолчанию —
двоеточие в имени класса не допускается.

# Separator used between hierarchies
SEPARATOR = '::'
В настройках дополнения его надо заменить

На _подчёркивание, например.

 # Разделитель, используемый между уровнями
 #SEPARATOR = '::'
 SEPARATOR = '_'

 

Как постепенно открывать пропуски

Как постепенно открывать пропуски на оборотной стороне:

на оборотной стороне

Оказывается, год назад мимо меня проскочил совершенно замечательный скрипт:

Результат его работы заметен,

только если одновременно закрыты два пропуска (и больше): на оборотной стороне открыт будет только первый пропуск, второй и последующие открываются кликами мыши по ним
либо нажатием на пробел на клавиатуре.

    {{cloze:Текст}}<br>
    {{Дополнительно}}
  <script>
    var handled = false,
      clozes = document.getElementsByClassName("cloze"),
      clr = window.getComputedStyle(clozes[0]).color,
      bg = window.getComputedStyle(clozes[0]).background;
    for (i = 1; i < clozes.length; ++i) {
      clozes[i].style.background = clr;
      clozes[i].onclick = function() {
        this.style.background = bg;
        this.style.cursor = 'text';
      }
      clozes[i].style.cursor = 'help';
      clozes[i].style.borderRadius = '5px';
      clozes[i].style.lineHeight = '150%';
      clozes[i].style.boxShadow = '3px 2px 1px 0px #ccc';
    }
    document.onkeypress = function(ev) {
      if (handled) return false;
    }
    document.onkeydown = function() {
      for (i = 1; i < clozes.length; ++i) {
        if (event.which == 32 && clozes[i].style.background != bg) {
          clozes[i].style.background = bg;
          handled = true;
          return false
        }
      }
      handled = false;
      return true;
    }
    document.body.focus();
  </script>

Это если надо на оборотной стороне на сразу все пропуски открыть,
а постепенно кликая по каждому мышкой. Или нажимая пробел.

  • откройте нужную карточку с пропусками в редакторе

  • перейдите в Карточки…​

  • вставьте этот скрипт в шаблон оборотной стороны

 

Подсказка по клику

Карточки с пропусками: показать подсказку по клику

Я сделал карточки с подсказками на пропусках:

Je mange une pomme {{c1::rouge::red}}.

Есть ли здесь путь показывать кнопочку намекни на лицевой стороне моей карточки, типа как обычные карточки с пропусками говорят:

Je mange une pomme [..].

Но чтобы после клика подсказка сообщала:

Je mange une pomme [red].

Изи.

Просто кликая по [..]

Кроме того, можно показывать во всплывающей подсказке простым наведением мыши:

Последующий клик по [red] закроет его обратно в [..].

Достаточно поставить скрипт в шаблон лицевой стороны карточки:

  {{cloze:Текст}}

  <script>
    var clozes = document.getElementsByClassName('cloze'),
      filler = '[..]';
    for (var i = 0; i < clozes.length; i++) {
      if (clozes[i].innerHTML != '[...]') {
        clozes[i].dataset.innerHtml = clozes[i].innerHTML;
        clozes[i].innerHTML = filler;
        clozes[i].onclick = function() {
          if (this.innerHTML != filler) {
            this.innerHTML = filler;
          } else {
            this.innerHTML = this.dataset.innerHtml;
          }
        }
        clozes[i].style.cursor = 'pointer';
        clozes[i].title = clozes[i].dataset.innerHtml;
      }
    }
  </script>

Обратите внимание, что пропуски без подсказок в работу не берутся.

Если требуется, чтобы оформление таких пропусков отличалось от обычных (без подсказки):

    .cloze[data-inner-html] {
        color: red;
    }

Именно кнопка!

Если же нужна именно кнопочка намекни на всё скажем, в левом верхнем углу лицевой стороны карточки, то к ней надо добавить ещё один скрипт:

  <input type="button" value="hint" title="намекни" accesskey="Z" id="hintThemAll"
         style="position:fixed;top:10px;left:20px;" onclick="hintAll();">
 
  <script>
    function hintAll() {
      for (var i = 0; i < clozes.length; i++) {
        if (clozes[i].innerHTML != '[...]') {
          (function(thiz) {
            if (thiz.innerHTML != filler) {
              thiz.innerHTML = filler;
            } else {
              thiz.innerHTML = thiz.dataset.innerHtml;
            }
          }(clozes[i]))
        }
      }
    }
  </script>
Hotkey
Горячая клавиша Alt+Z

работает только если фокус на карточке, а не на кнопке Показать ответ
И опять же при русской раскладке клавиатуры не работает.

Подсказка Если же хочется открывать по нажатию одной-единственной клавиши,
то это уже надо уходить в дополнения.

Можно попытаться использовать готовое:

ankiweb.net/shared/info/2061394997 Refocus Card when Reviewing

но оно бывает глючит.

Disabled

Если на лицевой стороне нет подсказок, подпадающих под обработку,
то кнопку можно запретить ещё одним скриптом:

  <script>
    var done;
    done = 0;
    if (document.getElementById('hintThemAll')) {
      for (var i = 0; i < clozes.length; i++) {
        if (clozes[i].innerHTML != '[...]') ++done;
      }
      if (done === 0) {
        document.getElementById('hintThemAll').setAttribute('disabled', 'disabled');
      } else {
        document.getElementById('hintThemAll').disabled = false;
      }
    }
  </script>
Скрипт сохраняет работоспособность

благодаря проверке if (document.getElementById('hintThemAll'))
даже при отсутствии кнопки на странице :-)

Тем не менее, обратите внимание:

текст скрипта должен быть по тексту ниже кнопки, никак не перед ней.
Иначе он может потерять работоспособность в некоторых условиях,
скажем, в окне предпросмотра.

На оборотной стороне

Если пропадание кнопки на оборотной стороне карточки доставляет дискомфорт,
то можно просто показать без возможности кликнуть по ней:

 <input type="button" value="hint"
        style="position:fixed;top:10px;left:20px;" disabled>

По буквам

После просмотра этого видео How to add hints to Anki cards (by Geert De Deckere) я понял, что надо добавить возможность не сразу раскрывать всю подсказку, а сначала открыть только первую букву, затем первую и последнюю, и лишь затем открывать всё слово. Причём пользователь должен иметь возможность легко включать и выключать этот режим.

В результате первый скрипт должен выглядеть так:

  <script>
 var clozes = document.getElementsByClassName('cloze'),
  filler = '[..]',
  first_last = true; /* false true */
 for (var i = 0; i < clozes.length; i++) {
  if (clozes[i].innerHTML != '[...]') {
    filler = '[' + "_".repeat(clozes[i].innerHTML.length) + ']';
    clozes[i].dataset.innerHtml = clozes[i].innerHTML;
    clozes[i].innerHTML = filler;
    if (first_last) {
      clozes[i].onclick = function() {
        if (this.innerHTML == filler) {
          this.innerHTML = this.dataset.innerHtml.substring(0, 2) + filler.substr(1);
        } else if (this.innerHTML == this.dataset.innerHtml.substring(0, 2) +
          filler.substr(1)) {
          this.innerHTML = this.dataset.innerHtml.substr(0, 2) +
            filler.substr(1, filler.length - 2) + this.dataset.innerHtml.slice(-2);
        } else if (this.innerHTML == this.dataset.innerHtml.substr(0, 2) +
          filler.substr(1, filler.length - 2) + this.dataset.innerHtml.slice(-2)) {
          this.innerHTML = this.dataset.innerHtml;
        } else {
          this.innerHTML = filler;
        }
      }
    } else {
      clozes[i].onclick = function() {
        if (this.innerHTML != filler) {
          this.innerHTML = filler;
        } else {
          this.innerHTML = this.dataset.innerHtml;
        }
        // return false; в onclick так не работает
        // event.preventDefault(); // ???
      }
      clozes[i].title = clozes[i].dataset.innerHtml;
    }
    // clozes[i].style.color = 'red';
    clozes[i].style.cursor = 'pointer';
  }
 }
  </script>

Заметьте — скрипт сохраняет работоспособность даже при отстутствии кнопки в шаблоне лицевой стороны карточки с пропусками.

Переменная first_last определяет, будут ли сначала показываться первая и последняя буквы, прежде чем будет показана вся подсказка.

Обратите внимание, что в режиме появления по буквам подсказка не всплывает при наведении мыши, иначе вы будете видеть всю подсказку уже при попытке открыть первую букву.

Одновременно.рф

Далее заметил, что если на карточке несколько таких полей, и они находятся в разном состоянии (скажем, одно открыто, а другое закрыто), то общая кнопка ведёт себя не совсем так, как ожидается. Те, у которых открыта первая или обе буквы, закрываются. Остальные просто перекидывают своё состояние на противоположное, причём каждая подсказка отдельно.

А ведь когда по общей кнопке кликаешь, то основной смысл данного действия открой их все
И лишь затем, при всех открытых, как опция: закрой их все

Поэтому второй скрипт должен выглядеть так:

  <script>
    function hintAll() {
      var dummy = 0;
      for (var i = 0; i < clozes.length; i++) {
        if (clozes[i].innerHTML != '[...]') {
          if (clozes[i].innerHTML != clozes[i].dataset.innerHtml) ++dummy;
        }
      }
      if (dummy == 0) {
        for (i = 0; i < clozes.length; i++) {
          if (clozes[i].innerHTML != '[...]') {
            clozes[i].innerHTML = filler;
          }
        }
        if (document.getElementById('hintThemAll')) {
          document.getElementById('hintThemAll').value = 'hint';
        }
      } else {
        for (i = 0; i < clozes.length; i++) {
          if (clozes[i].innerHTML != '[...]') {
            clozes[i].innerHTML = clozes[i].dataset.innerHtml;
          }
        }
        if (document.getElementById('hintThemAll')) {
          document.getElementById('hintThemAll').value = 'hide';
        }
      }
    }
  </script>

Подчёркивания

И без скобок
Далее я вернулся к давним идеям:

чтобы при желании можно было

  • убирать громоздкие квадратные скобки (достаточно выделения цветом и болдом)

  • да вместо многоточия на месте пропуска показывать подчёркивания,

    • причём пропорционально длине скрываемого слова,

      • либо точками, но тоже по количеству букв в слове;

  • и один пропуск за другим открывать пробелом, а не только мышкой.

Примечание
При необходимости кнопку можно
вообще убрать из шаблона оборотной стороны,
скрипт сохраняет свою работоспособность.

Допускается использование любых полей в любом количестве с любым форматированием, скрипт работает со стороной карточки, а не непосредственно с полями.

В результате все скрипты вместе выглядят так:

  {{cloze:Text}}

  <input type="button" value="hint" title="намекни" accesskey="Z"
     id="hintThemAll" style="position:fixed;top:10px;left:20px;" onclick="hintAll();">

<script>
var clozes = document.getElementsByClassName('cloze'),
  filler = '[..]',
  a, b, handled = false,
  square_brackets = false,
  underline = true,
  first_last = true,
  underscore = '_'; // true false

var s_b = (square_brackets ? 2 : 1),
  sb = (square_brackets && !underline ? 1 : (!square_brackets && !underline && first_last ? 0 : s_b));

for (var i = 0; i < clozes.length; i++) {
  if (clozes[i].innerHTML != '[...]') {
    clozes[i].dataset.innerHtml = !square_brackets ? clozes[i].innerHTML.slice(1, -1) : clozes[i].innerHTML;
    clozes[i].dataset.filler = underline ? (square_brackets ? '[' +
        Array(clozes[i].dataset.innerHtml.length - 1).join(underscore) + ']' :
        Array(clozes[i].dataset.innerHtml.length + 1).join(underscore)) :
      (!square_brackets ? filler.slice(1, -1) : filler);

    clozes[i].innerHTML = clozes[i].dataset.filler;

    if (first_last) {
      clozes[i].onclick = function() {
        a = this.dataset.innerHtml.substring(0, s_b) + this.dataset.filler.substr(sb);
        b = this.dataset.innerHtml.substring(0, s_b) + (sb ? this.dataset.filler.slice(sb, -sb) :
          this.dataset.filler) + this.dataset.innerHtml.slice(-s_b);
        console.log(a, b);
        if (this.innerHTML == this.dataset.filler) {
          this.innerHTML = a;
        } else if (this.innerHTML == a) {

          this.innerHTML = b;

        } else if (this.innerHTML == b) {
          this.innerHTML = this.dataset.innerHtml;
        } else {
          this.innerHTML = this.dataset.filler;
        }
        if (document.getElementById('hintThemAll')) {
          document.getElementById('hintThemAll').value = 'hint';
        }
      }
    } else {
      clozes[i].onclick = function() {
        if (this.innerHTML != this.dataset.filler) {
          this.innerHTML = this.dataset.filler;
        } else {
          this.innerHTML = this.dataset.innerHtml;
        }
        if (document.getElementById('hintThemAll')) {
          document.getElementById('hintThemAll').value = 'hint';
        }
        // return false; в onclick так не работает
        // event.preventDefault(); // ???
      }
      clozes[i].title = clozes[i].dataset.innerHtml;
    }

    clozes[i].style.cursor = 'pointer';
    // clozes[i].style.color = 'red';
  }
}
document.onkeypress = function(ev) {
  if (handled) return false;
}

document.onkeydown = function() {
  for (i = 0; i < clozes.length; ++i) {
    if (event.which == 32 && clozes[i].innerHTML == clozes[i].dataset.filler) {
      clozes[i].innerHTML = clozes[i].dataset.innerHtml;
      handled = true;
      return false;
    }
  }
  handled = false;
  return true;
}

document.body.focus();

</script>

<script>
function hintAll() {
  var dummy = 0;
  for (var i = 0; i < clozes.length; i++) {
    if (clozes[i].innerHTML != '[...]') {
      if (clozes[i].innerHTML != clozes[i].dataset.innerHtml) ++dummy;
    }
  }
  if (dummy == 0) {
    for (i = 0; i < clozes.length; i++) {
      if (clozes[i].innerHTML != '[...]') {
        clozes[i].innerHTML = clozes[i].dataset.filler;
      }
    }
    if (document.getElementById('hintThemAll')) {
      document.getElementById('hintThemAll').value = 'hint';
    }
  } else {
    for (i = 0; i < clozes.length; i++) {
      if (clozes[i].innerHTML != '[...]') {
        clozes[i].innerHTML = clozes[i].dataset.innerHtml;
      }
    }
    if (document.getElementById('hintThemAll')) {
      document.getElementById('hintThemAll').value = 'hide';
    }
  }
}

</script>

<script>
var done;
done = 0;
if (document.getElementById('hintThemAll')) {
  for (var i = 0; i < clozes.length; i++) {
    if (clozes[i].innerHTML != '[...]') ++done;
  }
  if (done === 0) {
    document.getElementById('hintThemAll').setAttribute('disabled', 'disabled');
  } else {
    document.getElementById('hintThemAll').disabled = false;
  }
}

</script>
Примечание
В итоге уже достаточно громоздкий скрипт получился.
Наверное, имеет смысл вынести его в отдельный файл или дополнение?
Но дополнений нет в AnkiMobile и AnkiDroid,
да и отдельный файл со скриптами подгрузят ли они?
Подсказка Для того, чтобы можно было спокойно открывать пропуски последовательно,
один за одним, просто нажимая пробел на клавиатуре
(без предварительного клика мышкой по полю карточки),
необходимо установить дополнение Refocus Card when Reviewing 2061394997

И на оборотной

На оборотной стороне можно изначально открыть только первый пропуск, остальные открывать последовательно один за одним, просто кликая мышкой либо нажимая пробел на клавиатуре.

Если захочется открыть все пропуски сразу, то можно задействовать кнопку hint, которая всё равно просто так занимает место, реально работая лишь на лицевой стороне.

В то же самое время, при необходимости кнопку можно вообще убрать из шаблона оборотной стороны, скрипт сохраняет свою работоспособность.

Допускается использование любых полей в любом количестве с любым форматированием, скрипт работает со стороной карточки, а не непосредственно с полями.

  {{cloze:Text}}<br>
  {{Extra}}

  <input type="button" value="hint" title="намекни" accesskey="Z"
   id="hintThemAll" style="position:fixed;top:10px;left:20px;"
   onclick="hintAll();">

  <script>
    var handled = false,
      clozes = document.getElementsByClassName("cloze"),
      clr = window.getComputedStyle(clozes[0]).color,
      bg = window.getComputedStyle(clozes[0]).background;
    for (i = 1; i < clozes.length; ++i) {
      clozes[i].style.background = clr;
      clozes[i].onclick = function() {
        this.style.background = bg;
        this.style.cursor = 'text';
      }
      clozes[i].style.cursor = 'help';
      clozes[i].style.borderRadius = '5px';
      clozes[i].style.lineHeight = '150%';
      clozes[i].style.boxShadow = '3px 2px 1px 0px #ccc';
    }
    document.onkeypress = function(ev) {
      if (handled) return false;
    }
    document.onkeydown = function() {
      for (i = 1; i < clozes.length; ++i) {
        if (event.which == 32 && clozes[i].style.background != bg) {
          clozes[i].style.background = bg;
          handled = true;
          return false
        }
      }
      handled = false;
      return true;
    }
  </script>

  <script>
    function hintAll() {
      for (var i = 0; i < clozes.length; i++) {
        clozes[i].style.background = bg;
      }
      if (document.getElementById('hintThemAll')) {
        document.getElementById('hintThemAll').disabled = true;
      }
    }
  </script>

  <script>
    if (document.getElementById('hintThemAll')) {
      if (clozes.length > 1) {
        document.getElementById('hintThemAll').disabled = false;
      } else {
        document.getElementById('hintThemAll').setAttribute('disabled', 'disabled');
      }
    }
  </script>
Подсказка Для того, чтобы можно было спокойно открывать пропуски последовательно,
один за одним, просто нажимая пробел на клавиатуре
(без предварительного клика мышкой по полю карточки),
необходимо установить дополнение
Refocus Card when Reviewing 2061394997
Осторожно!
Иногда налюдается такой глюк:
При открытии первой карточки в колоде
фокус почему-то остаётся на кнопках,
Anki просто не вызывает обработчик,
устанавливаемый дополнением :-(
Важно! Весь этот код собран в одну работающую колоду:
Anki Cloze OneByOne
А также размещён на GitHub’е.

 

Как одновременно показать все пропуски?

<div id="ma_fld" style="display:none;">{{Text}}</div>
<div id="ma_clz">{{cloze:Text}}</div>
<br>
{{cloze:Extra}}

Класс clozure назначается span элементам,
которые обозначают прочие пропуски.

.clozure {
  font-weight: bold;
  color: red;
}

Например, если у вас открыта карточка с первым пропуском {{c1::
а всего в полях записи назначено три пропуска (есть ещё {{c2:: и {{c3::),
то первый будет .cloze и остальные два .clozure

<script>
var d = document,
  clz = d.getElementById('ma_clz').innerHTML.split(''),
  fld = d.getElementById('ma_fld').innerHTML.split(''),
  tmp = '',
  found = true,
  cmn = [''],
  c = -1, // разбег
  rez = [''],
  z = 0,
  j = 0;

for (var i = 0; i < clz.length; i++) {
  if (clz[i] != fld[j]) {
    if (clz[i] == '<') {
      while (clz[i] != '>') {
        rez[z] += clz[i++];
      }
    }
    rez[z] += clz[i];
    if (found) {
      c++;
      cmn[c] = '';
      found = false;
    } else {}
  } else {
    if (clz[i] == '<') {
      while (clz[i] != '>') {
        cmn[c] += clz[i++];
        j++;
      }
    }
    if (c < 0) {
      c = 0;
    }
    cmn[c] += clz[i];
    if (!found) {
      z++;
      rez[z] = '';
      found = true;
    } else {}
    j++;
  }
}

zax = (typeof(rez[i]) != 'undefined' ? rez[i] : '').substr(0, 1) == '<';
if (clz[0] != fld[0]) {
  for (i = 0; i < cmn.length; i++) {
    tmp += (zax ? "" : '<span class="clozure">') +
        (typeof(rez[i]) != 'undefined' ? rez[i] : '') +
        (zax ? "" : '</span>') + (typeof(cmn[i]) != 'undefined' ? cmn[i] : '');
  }
} else {
  for (i = 0; i < cmn.length; i++) {
    tmp += (typeof(cmn[i]) != 'undefined' ? cmn[i] : '') +
        (zax ? "" : '<span class="clozure">') +
        (typeof(rez[i]) != 'undefined' ? rez[i] : '') + (zax ? "" : '</span>');
  }
}

 document.getElementById('ma_clz').innerHTML = tmp;
</script>

Данный код успешно работает как на лицевой, так и на оборотной стороне карточки с пропусками.

 

Как одновременно открыть все поля {{hint:*}}?

На анкивэбе есть аддон Hint-peeking Keyboard Bindings ,однако он работает только на Front Side’е, можно ли сделать так чтобы он открывал все поля подсказок и на Front Side’e и на Back Side’e?

Инструменты → Дополнения → Hint-peeking_Keyboard_Bindings → Редактировать…​
и там 31-я строка выглядит как

    if (self.state == "question"

надо из неё сделать такую строку:

    if ((self.state == "question" or self.state == "answer")

затем СохранитьOK и перезапустить Anki.

А по-русски?

Алоха! Подскажите, есть ли какой-нибудь способ быстро открыть все поля подсказок? Есть неплохой аддон Hint-peeking Keyboard Bindings 2616209911 однако он работает только с лицевой стороной карточки, а мне нужно чтобы он работал и с обратной стороной + насколько я понял этот аддон не работает в русской раскладке.

С русским языком клавиша h не работает как ожидается, надо назначить любую другую свободную, скажем, К.

Строка 22:

    SHOW_HINT_KEY=Qt.Key_K

Строку 32 удалить.

Строку 31 записать как:

        if ( evt.key() == SHOW_HINT_KEY or evt.text().upper() == u'Л' ):

А при разных раскладках?

Прямое указание Л действует как ожидается только в случае, если вы используете, скажем, только стандартные раскладки для английского и русского языков (впрочем, думаю, это делают 997 пользователей из 1000).

Для тех же, кто использует какую-нибудь экзотику типа Дворака или JCUKEN, нужен иной подход.

А на JavaScript?

  <span id="hint-this" class="hint" style="display: none">
  {{Field1}} {{Field2}} {{Field3}}
  </span> <a class="hint" href="#"
  onclick="this.style.display='none';document.getElementById('hint-this').style.display='block';return false;">
  Показать </a>

У предложенного решения тот недостаток, что оно не работает с клавишами, только мышкой кликать (ну или табом гнать текстовый курсор на эту ссылку). Кроме того, оно не позволяет при желании открывать подсказки по отдельности.

Такие дела.

 

Города России (Russian cities)

 { Нельзя просто так взять чужую колоду и начать учить её }
Нельзя просто так взять чужую колоду и начать учить её.

Как открыть Еле́ц в вики (без символа ударения)

  <a style="text-decoration:none;" id="city_name">{{city_name}}</a>
  <script>
    var re = new RegExp(String.fromCharCode(769), 'g');
    document.getElementById('city_name').href =
      'https://ru.wikipedia.org/wiki/{{text:city_name}}'.replace(re, '');
  </script>

Ну а как не открывать-то? А если про город практически ничего не знаешь? Вики, конечно, не ах, но всё лучше, чем ничего. Не просто же название и км запоминать. Должен же и фон какой-то быть под ними.

Можно, конечно, установить соотв. дополнение и просто из контекстного меню искать в вики выделенный текст. Но это уже совсем другая история…​

Как открыть вики, если статья называется иначе, чем город

Достаточно создать поле city_wiki

 {{#city_wiki}}
    <a href="{{city_wiki}}">{{city_name}}</a>
 {{/city_wiki}}
 {{^city_wiki}}
    <a href="https://ru.wikipedia.org/wiki/{{text:city_name}}">{{city_name}}</a>
 {{/city_wiki}}

23 записи, для которых требуется заполнить city_wiki

https://ru.wikipedia.org/wiki/Артём_(город)
https://ru.wikipedia.org/wiki/Владимир_(город)
https://ru.wikipedia.org/wiki/Дзержинск_(Нижегородская_область)
https://ru.wikipedia.org/wiki/Дзержинский_(город)
https://ru.wikipedia.org/wiki/Димитровград_(Россия)
https://ru.wikipedia.org/wiki/Домодедово_(город)
https://ru.wikipedia.org/wiki/Железногорск_(Курская_область)
https://ru.wikipedia.org/wiki/Жуковский_(Московская_область)
https://ru.wikipedia.org/wiki/Истра_(город)
https://ru.wikipedia.org/wiki/Киров_(Кировская_область)
https://ru.wikipedia.org/wiki/Клин_(город)
https://ru.wikipedia.org/wiki/Королёв_(город)
https://ru.wikipedia.org/wiki/Красноармейск_(Московская_область)
https://ru.wikipedia.org/wiki/Краснознаменск_(Московская_область)
https://ru.wikipedia.org/wiki/Курган_(город)
https://ru.wikipedia.org/wiki/Новомосковск_(Тульская_область)
https://ru.wikipedia.org/wiki/Октябрьский_(город)
https://ru.wikipedia.org/wiki/Орёл_(город)
https://ru.wikipedia.org/wiki/Пересвет_(город)
https://ru.wikipedia.org/wiki/Рошаль_(город)
https://ru.wikipedia.org/wiki/Серов_(город)
https://ru.wikipedia.org/wiki/Чехов_(Московская_область)
https://ru.wikipedia.org/wiki/Энгельс_(город)

А если надо открыть лурк?

Нет ничего проще:

  <a style="text-decoration:none;"
     id="city_name">{{city_name}}</a> {{#city_wiki}}
  <script>
    var d = '{{text:city_wiki}}';
  </script>
  {{/city_wiki}} {{^city_wiki}}
  <script>
    var d = 'https://lurkmore.to/{{text:city_name}}';
  </script>
  {{/city_wiki}}
  <script>
    var re = new RegExp(String.fromCharCode(769), 'g');
    document.getElementById('city_name').href = d.replace(re, '');
  </script>

4етвёртое правило Петра Возняка

Зачем запоминать численность города с точностью до человека?
Ведь во времени эти данные всё равно недостоверны.

Минимум информации

Лучше вместо Население: 12 380 664 чел.
написать Население: 12 млн 380 тыс.

  Население: <br><u id="city_population">{{city_population}}</u><br><br>
  <script>
    var d = document,
      p = d.getElementById("city_population"),
      t = p.innerHTML,
      pp = t.split(' ');
    d.getElementById("city_population").innerHTML =
      (pp.length == 3 ? pp[0] : pp[0] + ' <i>млн</i> ' + pp[1]) + ' <i>тыс.</i>';
    d.getElementById("city_population").title = ' ' + t + ' ';
  </script>

На каждой карточке добавлять чел. НЕ надо, и так, надеюсь, понятно,
что говорится о людях, раз речь о населении.

Округляй

Сюда же относится и ситуация с расстоянием до Москвы:

зачем запоминать с точностью до десяти метров?!
Да ещё дистанция указана в немецком формате: 1.234,56 км.
Честно говоря, и необходимость точки после км — под сомнением.

Тут на помощь придут регулярные выражения Python 2 (подробно об их формате см. доку)

Обзор - Выделить все карточки Городов России - Редактирование - Найти и заменить…​

Что найти:

(\d*?)\.?(\d+?)(?:,\d\d|,\d)?(?:&nbsp;|\s)км\.

Заменить на:

&nbsp;<b>\1 \2</b>&nbsp;км

Где искать:

city_distance_to_msk

V да, Трактовать текущий ввод как регулярное выражение

ОК

Новые км — полужирным шрифтом, чтобы глаз легче выхватывал их из сопровождающих вводных слов.

Можно ещё &nbsp;Mаршрут: на <br> Mаршрут: заменить,
чтобы оба километража оказались друг под другом, а не рядом,
но тут уже Трактовать как регулярное выражение НЕ требуется.

Подсказка

Между <br> и Маршрут: пробел нужен обязательно,
иначе в Обозревателе Anki км и Маршрут: склеятся
(не критично, конечно, но некомфортно).

Ещё круглее!

Дальше есть такая идея, что если 29 км запоминается хорошо, то 734 км уже как бы и незачем на таком расстоянии настолько точность ловить, хватило бы и 730 (или 740). А для 1818 км и до 1800 округлить не грех.

  {{#city_distance_to_msk}}
  <small><a href="http://ru.distance.to/Москва/{{text:city_name}}"
  style="text-decoration:none;">Расстояние до Москвы:</a><br>
  <span id="city_distance_to_msk">{{city_distance_to_msk}}</span></small>
  <script>
    var d = document,
      c = d.getElementById('city_distance_to_msk'),
      b = c.getElementsByTagName('b');
    for (i = 0; i < b.length; i++) {
      b[i].title = '= ' + b[i].innerHTML.trim() + ' км';
      b[i].style.cursor = 'help';
      a = b[i].innerHTML.trim().split(' ');
      j = a.length - 1;
      if (a.length == 2) {
        if (a[j].length < 3) {
          aj = a[j]
        } else {
          aj = a[j].substr(0, 1) + '00'
        }
      } else {
        if (a[j].length < 3) {
          aj = a[j]
        } else {
          aj = Math.ceil(parseInt(a[j]) / 10) * 10
        }
      }
      a[j] = aj;
      b[i].innerHTML = a.join(' ');
    }
  </script>
  {{/city_distance_to_msk}}
В чём преимущество JavaScript перед регулярными выражениями Python:

точное значение никуда не пропадает,
так что при желании его всегда можно показать,
например, во всплывающей подсказке.

P.P.S. Мне почему-то упрямо кажется, что если досконально разобраться с подобным примером, что в нём делает буквально каждый символ, то уже не придётся составлять и запоминать карточки по HTML+CSS+JavaScript+Python

Миллион, миллион, млн

Нет, не алых роз.

…​курьеры, курьеры курьеры…​ можете представить себе,
тридцать пять тысяч одних курьеров!

24 млн бит (нет, не деревянных. нет, не рублей) — и не триколор это,
а TrueColor то есть 16 777 216 (256×256×256 == 224) различных цветов.
Миллионы оттенков серого.

Для таких простеньких карт зачем столько?

Достаточно просто пережать в тот же формат .png
но с использованием всего 256 цветов —
размер карт уменьшится более чем вдвое, с 48 до 18Мб.

На глаз при этом заметить какую-то разницу в качестве изображений карт невозможно.

Сжимай (или растягивай)

Давно мечтал сделать такую фишку, чтобы картинка из поля растягивалась
(а ещё лучше, чтобы сжималась) до размеров окна Anki.
Идея пылилась где-то далеко в очереди приоритетов,
пока вчера на реддите в /r/Anki не спросили, а как же это сделать.

Работающий пример для стандартного типа записей Пропуски:

Как использовать фоном карточки первую картинку из поля Дополнительно.

Шаблон лицевой стороны (вопроса):

<div class="msg">{{cloze:Текст}}</div>

<div id="fieldImg" style="display:none;">{{Дополнительно}}</div>

<div id="imgBg"></div>

  <script>
    var d = document;
    d.getElementById('imgBg').style.backgroundImage =
      'url("' + d.getElementById('fieldImg').getElementsByTagName('img')[0].src + '")';
    // .firstChild IMG after br div
  </script>

Таблица стилей:

.card {
    font-family: arial;
    font-size: 20px;
    text-align: center;
    color: black;
    background-color: white;
}
.cloze {
    font-weight: bold;
    color: blue;
}
.msg {
    background-color: rgba(245, 245, 245, .95);
    display: block;
    padding: 1em 2em;
    border-radius: 9px;
}
html,
body,
#qa,
#imgBg {
    width: 100%;
    height: 100%;
    margin: 0px;
    padding: 0px;
}
#qa {
    padding: 1.5em;
}
#imgBg {
    background-repeat: no-repeat;
    opacity: .9;
    position: fixed;
    top: 0px;
    bottom: 0px;
    left: 0px;
    right: 0px;
    z-index: -999999999;
    background-position: center center;
    background-size: contain;
}

Шаблон оборотной стороны:

<div class="msg">{{cloze:Текст}}<br>{{Дополнительно}}</div>

<div id="fieldImg" style="display:none;">{{Дополнительно}}</div>

<div id="imgBg"></div>

  <script>
    var d = document;
    d.getElementById('imgBg').style.backgroundImage =
      'url("' + d.getElementById('fieldImg').getElementsByTagName('img')[0].src + '")';
    // .firstChild IMG after br div
  </script>

Если у кого-то возникает вопрос: "А к чему такие сложности?"
то обратите внимание, что в поле именно сама фоновая картинка,
а не название её файла. Возможны и другие картинки в этом поле,
и текст, и мусорные HTML-теги div br которые остаются после того,
как составитель карточки случайно нажал Enter в этом поле.

Также учитывайте тот факт, что Anki
при показе следующей карточки не перерисовывает всю веб-страничку "с нуля"
она просто подставляет содержимое лицевой или оборотной стороны
внутрь <div id="qa"></div> предварительно подставив в начало стороны
текст секции стилей, обёрнутый в теги <style></style>

Страница прорисовывается полностью раз в сто карточек.
Либо асинхронно какое-то событие типа редактирования полей
может заставить это произойти.

Вы можете использовать дополнение Refresh Media References 162278717
и горячие клавиши Ctrl+Alt+M для очистки кэша карточек.

Кстати говоря, в предпросмотрах вопроса и ответа окна редактирования шаблонов карточек
блок <div id="qa"></div> отсутствует, что порой заметно осложняет отладку
комплексных скриптов и особенно стилей.

 

Крупнейшие острова Земли

Ещё появилась похожая колода Крупнейшие острова Земли, но это уже не так интересно: о каждом из наших городов я хоть раз, да слышал в жизни; названия же большинства островов вижу впервые. Зачем? Да и картинки мелковаты.

Информация бралась с сайта geo.koltyrin.ru GEO: Играй знаниями

Моря

Поинтереснее будет, всё-таки больше знакомых слов. Правда, они все английские.

Есть и по-русски: ankiweb.net/shared/info/1004918254

Только почему-то ея не видно в разделе ankiweb.net/shared/decks/geography А жаль.

Пустыни

Ага, на следующий день она вошла
в Географические объекты на карте мира

Там при первых же кликах начинается веселуха:

части света названы континентами.
Ага, вот такая сейчас география ,Однако

Всё же и некоторый прогресс наблюдается:

указан первоисточник. geo.koltyrin.ru/

AnkiWeb показывает неверные картинки

В описании колоды показываются превьюшки не от тех карточек, точнее говоря.

Примечание
Такое происходит в течение 24 часов максимум после перезаливки колоды,

пока не обновится кэш веб-сервера.

Так понимаю, кэш обновляется раз в сутки по таймеру.
Для новых такого эффекта нет, разумеется:

в кэше-то для них ничего и не было (КО :-)

Букмарклет предпросмотра

Всю жизнь с колодами порой наблюдается эта проблема:
некоторым картинки показываются от других записей.

В результате лишь подтвердилось то, что и так в принципе было понятно: если ты имел несчастье видеть описание колоды до его обновления на сайте AnkiWeb, то картинки у тебя не обновятся никакими усилиями, можно до посинения чистить кэш своего бравзера или запускать его в приватном режиме. Или жаловаться на своего провайдера либо ему самому.

Так что получается Помоги себе сам или Спасение утопающих — дело рук самих утопающих Мне проще всего оказалось забацать на панель закладок броузера ещё один букмарклет, то есть создать новую закладку и в качестве адреса указать:

javascript:(function(){var%20d=document,v=d.getElementsByClassName('imgprev');for(var%20i=0;i<v.length;i++){v[i].src=v[i].src.split('?')[0]+'?'+parseInt(Math.random()*100000);}}());

При этом надо понимать, что если после работы букмарклета вы обновите страницу по F5, то у вас снова будут кривые картинки.

Впрочем,

Достаточно потерпеть недельку, и какие-то сервера (уж не знаю, на хосте ли у AnkiWeb или кэширующие сервера у интернет-провайдера) таки соизволили обновить изображения. И настроение — улучшилось!

Букмарклет списка ссылок

Есть ещё один полезный букмарклет, он подойдёт вообще для любой веб-страницы:

javascript:var%20w=window,d=document,o=w.open(),od=o.document,l=d.links,x,y,z;od.writeln("<title>links%20of%20"d.title"</title><h1>"d.title"%20links</h1><ol>");x=d.location.protocol+"//"d.location.host;y=x.length;for(i=0;i<l.length;i+){z=l[i].href.substr(0,y);if(x!==z){od.writeln('<li><a%20href="'l[i].href'"%20style="padding-right:3em;">'decodeURIComponent(l[i].href)"</a>"+l[i].innerHTML)}};od.close();

Смысл в том, чтобы показать в новом окне в явном виде список всех внешних ссылок, что есть в данном документе. То есть показывается непосредственно адрес, а текст или картинка, под которыми он прячется, просто показывается справа обычным текстом.

Как вариант можно показывать только ссылки внутри сайта,
но исключить оглавление страницы <div id="toc" class="toc2">
а также заголовки, ссылающиеся на себя
<h1 id='its_me'><a href="#its_me">It’s me</a></h1>

javascript:var%20w=window,d=document,o=w.open(),od=o.document,loc=d.location,lh=loc.protocol+"//"loc.host,l=d.links,i,li,len=lh.length,cz,cl;for(od.writeln("<base href='"+lh"' target=_blank><title>links of "d.title"</title><h1>"d.title" links</h1><ol>"),i=0;i<l.length;i+)li=["H1","H2","H3","H4","H5","H6"].indexOf(l[i].parentNode.tagName)>-1,cl=null!=l[i].closest("div#toc"),lh!==(cz=l[i].href.substr(0,len))||cl||li||od.writeln('<li><a href="'+l[i].href'" style="padding-right:3em;">'decodeURIComponent(l[i].href.slice(len))"</a>"+l[i].innerHTML);od.close();

Подсказка
Исключить элементы с определённым классом:
&&!l[i].classList.contains('link')

Онлайн-сервисы для сжатия javascript: - jscompress.com/ - javascriptcompressor.com/ - javascript-minifier.com/

 

evaluateJavaScript(…​)

Попросту говоря, речь идёт о том, как из дополнения (Python) выполнить JavaScript на стороне карточке, странице колоды или на списке колод, а затем получить от него возвращаемое значение.

html = aqt.mw.web.page().mainFrame().evaluateJavaScript("""
    (function(){
         return document.documentElement.outerHTML
     }())
""")
aqt.utils.showText(html, geomKey="ViewHTML")

Попросту говоря, речь пойдёт о том, как из JavaScript на лицевой или оборотной стороне карточки обратиться к какой-нибудь функциональности Python (основной код Anki или поддерживаемый каким-нибудь дополнением).

deckbrowser.py

drag deck

function handleDropEvent(event, ui) {
    var draggedDeckId = ui.draggable.attr('id');
    var ontoDeckId = $(this).attr('id');
    py.link("drag:" + draggedDeckId + "," + ontoDeckId);
}

proc bottom buttons

def _drawButtons(self):
    links = [
        ["", "shared", _("Get Shared")],
        ["", "create", _("Create Deck")],
        ["Ctrl+I", "import", _("Import File")],
    ]
    buf = ""
    for b in links:
        if b[0]:
            b[0] = _("Shortcut key: %s") % shortcut(b[0])
        buf += """
<button title='%s' onclick='py.link(\"%s\");'>%s</button>""" % tuple(b)
    self.bottom.draw(buf)

main.py

b = self.button("refresh", _("Resume Now"), id="resume")
def button(self, link, name, key=None, class_="", id=""):
    class_ = "but "+ class_
    if key:
        key = _("Shortcut key: %s") % key
    else:
        key = ""
    return '''
<button id="%s" class="%s" onclick="py.link('%s');return false;"
      title="%s">%s</button>''' % (
        id, class_, link, key, name)

overview.py

# Bottom area
######################################################################
def _renderBottom(self):
    links = [
        ["O", "opts", _("Options")],
    ]
    if self.mw.col.decks.current()['dyn']:
        links.append(["R", "refresh", _("Rebuild")])
        links.append(["E", "empty", _("Empty")])
    else:
        links.append(["C", "studymore", _("Custom Study")])
        #links.append(["F", "cram", _("Filter/Cram")])
    if self.mw.col.sched.haveBuried():
        links.append(["U", "unbury", _("Unbury")])
    buf = ""
    for b in links:
        if b[0]:
            b[0] = _("Shortcut key: %s") % shortcut(b[0])
        buf += """
<button title="%s" onclick='py.link(\"%s\");'>%s</button>""" % tuple(b)
    self.bottom.draw(buf)
    if isMac:
        size = 28
    else:
        size = 36 + self.mw.fontHeightDelta*3
    self.bottom.web.setFixedHeight(size)
    self.bottom.web.setLinkHandler(self._linkHandler)

reviewer.py

function _getTypedText () {
    if (typeans) {
        py.link("typeans:"+typeans.value);
    }
};
function _typeAnsPress() {
    if (window.event.keyCode === 13) {
        py.link("ansHack");
    }
}
def _showAnswerHack(self):
    # on <qt4.8, calling _showAnswer() directly fails to show images on
    # the answer side. But if we trigger it via the bottom web's python
    # link, it inexplicably works.
    self.bottom.web.eval("py.link('ans');")
<button title="%(editkey)s" onclick="py.link('edit');">%(edit)s</button></td>
<button onclick="py.link('more');">%(more)s %(downArrow)s</button>
<button title="%s" id=ansbut onclick='py.link(\"ans\");'>%s</button>''' % (
self._remaining(), _("Shortcut key: %s") % _("Space"), _("Show Answer"))
<td align=center>%s<button %s title="%s" onclick='py.link("ease%d");'>\
%s</button></td>''' % (due, extra, _("Shortcut key: %s") % i, i, label)

 

Три банана

Задача: На лицевой стороне карточки — вопрос и три варианта ответа.
При каждом показе карточки порядок вариантов ответа должен быть произвольным.

Решение:

 { с полями Вопрос Ответ1 Ответ2 Ответ3 Правильный1 Правильный2 Правильный3 }
Создать новый тип записей, Три банана

Да, вариантов ответа три, а полей под них требуется шесть.
Три — под сами варианты, три — под указание, какой же из них правильный.

 { Правильный3 * (или любой другой текст) — непустое поле как флаг правильного ответа }

Всё равно, что указывать в этом правильном поле, на карточке показываться не будет.

Статические карточки выглядят так:

 { style li color #0c3 }

Обратите внимание на то, что можно указать стили только для для оборотной стороны карточки,
если вынести их из Таблицы стилей в Шаблон оборотной стороны.

 { Дама сдавала в багаж: диван, чемодан, саквояж. }

Если надо выровнять по левому краю, но оставить в центре, то
display:inline-block;text-align:left;

В ночном режиме (дополнение Night Mode, вкл/выкл Ctrl+N)

цвет ответа #00CC33 также виден нормально.
 { чемодан, саквояж, диван перечисляются в случайном порядке }
Ну и придать немного динамики:
Текст скрипта
  <script>
    var list = document.getElementsByTagName('UL')[0],
      lines = list.getElementsByTagName('LI'),
      newLines = [],
      i = 0,
      j = 0;

    function getRandomInt(min, max) {
      return Math.floor(Math.random() * (max - min)) + min;
    }
    for (i = 0; i < lines.length; i++) {
      if (lines[i].innerHTML === '') {
        list.removeChild(lines[i--]);
        continue
      }
      do {
        j = getRandomInt(0, lines.length);
      } while (typeof newLines[j] != "undefined");
      newLines[j] = lines[i];
    }
    for (i = 0; i < newLines.length; i++) {
      if (typeof newLines[i] !== 'undefined') list.appendChild(newLines[i])
    }
  </script>

Скрипт без дополнительных изменений
работает с произвольным количеством строк неупорядоченного списка
и учитывает, что в каждой записи может оказаться разное количество ответов
(тогда последние варианты ответов просто не заполнены):

 { Шрифт в редакторе Arial 8 }

Кому-то подобный вариант следования полей может показаться удобнее.

 { Не вопрос Ответ1 Правильный1 ... Ответ6 Правильный6 }

Обратите внимание, как изменение размера Шрифта в редакторе
сказалось на внешнем облике списка редактируемых полей записи.

Выделение правильного ответа другим цветом
важно сразу по нескольким соображениям:

  1. Оно несёт эмоциональную окраску:
    зелёный — всё хорошо, можно двигаться дальше.
    Именно по этой причине   правильный   ответ
    не надо выделять красным цветом, например.

  2. Оно создаёт визуально легко различимую
    разницу между лицевой и оборотной сторонами.

  3. Использование цвета
    (вместо шрифтовых выделений, например, курсивом)
    подключает правое полушарие мозга,
    снижая тем самым нагрузку на левое
    и повышая общую эффективность запоминания.

 { А. Блок Стих Звонарь Улица Оптика Дочь }
 { А. Блок Стих Улица }

Более того, пропуски могут быть и в середине списка:

 { Ответ4 пропущен, Ответ5 указан }

А правильных ответов может быть несколько:

 { Репка Бабка Тетка Кошка Дочка }

Только результат почему-то сикось-накосья, выкуси :-(

 { Бабка и Кошка на оборотной стороне оказались на одной строке }

Хорошо, что проверил, а то так бы и сдал в эксплуатацию код с ошибкой.
Правится легко:

 { li #Правильный1 Ответ1 /Правильный1 /li }

Так получилось, потому что формулировка задачи никак не оговаривает
количество возможных ответов, и по умолчанию подразумевался один ответ
(как наиболее распространённый вариант), и лишь в ходе написания кода
появилось стремление сделать его как можно более универсальным —
кто знает, что день грядущий нам готовит!

В принципе, можно изначально в шаблоне HTML проверять,
есть что в поле Ответ? или нет,
и выводить только непустые варианты ответов.

 { #Ответ1 li #Правильный1 Ответ1 /Правильный1 /li /Ответ1 }

В принципе, наверное, практически любой код можно совершенствовать до бесконечности.
Но лучшее — враг хорошего!

— Работает? — Работает! — Проверял? — Проверял.
— Толька! Ради Бога, ничего не трогай, ничего не меняй!!!

Пользуйтесь на здоровье! Живите и радуйтесь :-)

По моему скромному мнению, это уловка для маленьких детей.
Взрослый человек легко просечёт фишку и уже с 3-4 раза
для него не будет никакой разницы, в каком порядке написаны слова.

А есть какие-то исследования на эту тему?

 

 

© 2017-06-08
Несмотря на то, что текст опубликован в свободном доступе, все права на него оставляю за собой.
Если вам необходимо где-то использовать весь текст или его часть — напишите мне и мы обсудим.

Свои вопросы и предложения по Anki засылайте  на форум  reddit.com/r/AnkiRu/ 

P.S. Все имена и события вымышлены. Все совпадения случайны.
Все торговые марки принадлежат их владельцам,
случайные упоминания в тексте не являются покушением на их использование.

И вообще, вам это всё привиделось. Было сном.

Немедленно сотрите у себя этот файл и забудьте то, что прочитали :-)

.

.