[Anki] カードを表示するhtml全体のソース

カテゴリ: Anki

フラッシュカードアプリのAnki(Android版はAnkiDroid)は、カードをhtmlで表示します。

ユーザが指定するのはhtmlのbody内のみですが、Anki側が提供しているhtml全体を調べたところ、以下のような形になっていました。

Mac版Anki: htmlの全体

まずはMac版のAnkiです。

<!doctype html>
<html><head>
<title>main webview</title>

<style>
body { 
    zoom: 1; background: #ececec; font-size:15px;font-family:"Helvetica"; 
}

button { 
    -webkit-appearance: none; background: #fff; border: 1px solid #ccc;
border-radius:5px; font-family: Helvetica 
}
</style>

<base href="http://127.0.0.1:65400/">
<link rel="stylesheet" type="text/css" href="http://127.0.0.1:65400/_anki/webview.css">
<link rel="stylesheet" type="text/css" href="http://127.0.0.1:65400/_anki/reviewer.css">
<script src="http://127.0.0.1:65400/_anki/webview.js"></script>
<script src="http://127.0.0.1:65400/_anki/jquery.js"></script>
<script src="http://127.0.0.1:65400/_anki/browsersel.js"></script>
<script src="http://127.0.0.1:65400/_anki/mathjax/conf.js"></script>
<script src="http://127.0.0.1:65400/_anki/mathjax/MathJax.js"></script>
<script src="http://127.0.0.1:65400/_anki/reviewer.js"></script>
</head>

<body class="isMac">
<div id=_mark>&#x2605;</div>
<div id=_flag>&#x2691;</div>

<div id=qa></div>

</body>
</html>

jqueryのjsが読み込まれていますが、バージョンはv1.12.4でした。

/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */

AnkiDroid: htmlの全体

次にAndroid版のAnkiDroidアプリです。

この調査は、githubの rev.dbd63ff69fを元にしています(Committer Date: Wed Jan 26 2022 08:02:26 GMT+0900)。AnkiDroidのバージョンだとv2.15辺りの実装です。

カードのテンプレート全体

html全体は以下のような形になっています。

<!doctype html>
<html class="mobile android linux js">
    <head>
        <title>AnkiDroid Flashcard</title>
        <meta charset="utf-8">
        <link rel="stylesheet" type="text/css" href="file:///android_asset/flashcard.css">
        <link rel="stylesheet" type="text/css" href="file:///android_asset/chess.css">
        <link rel="stylesheet" type="text/css" href="file:///android_asset/mathjax/mathjax.css">
        <style>
        ::style::
        </style>
        ::script::
        <script src="file:///android_asset/jquery.min.js"> </script>
        <script src="file:///android_asset/scripts/card.js" type="text/javascript"> </script>
    </head>
    <body class="::class::">
        <div id="content">
        ::content::
        </div>
    </body>
</html>

htmlの中の::の部分は、置換がかかります。

css

flashcard.css

body {
  margin: 0px;
  padding: 0px;
}

.nightMode .latex {
     filter: invert(100%);
}


body.night_mode {
  color: white;
  background-color: black;
}

body.ankidroid_dark_mode {
  background-color: #303030;
}

#content {
  margin: 0.5em;
}

/* For the typed/correct answer comparison. The typePrompt and typeOff
  classes are AnkiDroid specific, and used only for older (before
  4.0) Android versions . typePrompt is a placeholder where the input
  box should be. typeOff is added when the user switched off entering
  the answer. */
.typeGood {
  background-color: #0f0;
}

.typeBad {
  background-color: #f00;
}

.typeMissed {
  background-color: #ccc;
}

#typeans {
  width: 100%;
}

.typePrompt {
  color: magenta;
}

.typePrompt.typeOff {
  display: none;
}

.night_mode input#typeans {
  color: white;
  background-color: black;
}

.night_mode .typeGood {
  background-color: #508040;
}

.night_mode .typeBad {
  background-color: #905050;
}

.ankidroid_dark_mode input#typeans {
  background-color: #303030;
}

.replaybutton {
  text-decoration: none;
}


/*
Use hard-coded max dimensions if using Chrome back-end. Chrome is able to
zoom into images correctly even with max dimensions specified, so this way is
preferred over using JavaScript.
*/
.chrome img {
  max-width: 100%;
  max-height: 90%;
}

.vertically_centered {
  position: absolute;
  width: 100%;
  height: 100%;
  display: -webkit-box;
  -webkit-box-align: stretch;
  -webkit-box-pack: center;
  -webkit-box-orient: vertical;
}


/* Make sure the replay buttons look black or white unless explicitly
changed. Use some fancy CSS to make the button align vertically, and
scale up, but not down.  */

.replaybutton span {
  display: inline-block;
  vertical-align: middle;
  padding: 5px;
}

.replaybutton span svg {
  stroke: none;
  fill: black;
  display: inline;
  height: 1em;
  width: 1em;
  min-width: 32px;
  min-height: 32px;
}

.replaybutton span img {
  display: inline;
  height: 1em;
  width: 1em;
  min-width: 32px;
  min-height: 32px;
}
.night_mode .replaybutton svg {
  fill: white;
}

js

card.js

var resizeDone = false;

/*
  Handle image resizing if the image exceeds the window dimensions.
  If we are using the Chrome engine, add the "chrome" class to the
  body and defer image resizing to pure CSS. The Chrome engine can
  handle image resizing on its own, but for older versions of WebView,
  we do it here.
  If we are resizing with JavasSript, we also account for the CSS zoom
  level applied to the image. If an image is scaled with CSS zoom, the
  dimensions given to us by the browser will not be scaled
  accordingly, giving us only the original dimensions. We have to
  fetch the zoom value and scale the dimensions with it before
  checking if the image exceeds the window bounds.
  If the WebView loads too early on Android <= 2.3 (which happens on
  the first card or regularly with WebView switching enabled), then
  the window dimensions returned to us are 0x0. In this case, we skip
  image resizing and try again after we know the window has fully
  loaded with a method call initiated from Java (onPageFinished).
*/
var resizeImages = function() {
    if (navigator.userAgent.indexOf("Chrome") > -1) {
        document.body.className = document.body.className + " chrome";
    } else {
        if (window.innerWidth === 0 || window.innerHeight === 0) {
            return;
        }
        var maxWidth = window.innerWidth * 0.90;
        var maxHeight = window.innerHeight * 0.90;
        var ratio = 0;
        var images = document.getElementsByTagName('img');
        for (var i = 0; i < images.length; i++) {
            var img = images[i];
            var scale = 1;
            var zoom = window.getComputedStyle(img).getPropertyValue("zoom");
            if (!isNaN(zoom)) {
                scale = zoom;
            }
            var width = img.width * scale;
            var height = img.height * scale;
            if (width > maxWidth) {
                img.width = maxWidth;
                img.height = height * (maxWidth / width);
                width = img.width;
                height = img.height;
                img.style.zoom = 1;
            }
            if (height > maxHeight) {
                img.width = width * (maxHeight / height);
                img.height = maxHeight;
                img.style.zoom = 1;
            }
        }
    }
    resizeDone = true;
};

/* Tell the app that we no longer want to focus the WebView and should instead return keyboard
 * focus to a native answer input method.
 * Naming subject to change.
 */
function _relinquishFocus() {
    // Clicking on a hint set the Android mouse cursor to a text entry bar, even after navigating
    // away. This fixes the issue.
    document.body.style.cursor = "default";
    window.location.href = "signal:relinquishFocus";
}

/* Tell the app that the input box got focus. See also
 * AbstractFlashcardViewer and CompatV15 */
function taFocus() {
    window.location.href = "signal:typefocus";
}

/*  Call displayCardAnswer() and answerCard() from anki deck template using javascript
 *  See also AbstractFlashcardViewer.
 */
function showAnswer() {
    window.location.href = "signal:show_answer";
}
function buttonAnswerEase1() {
    window.location.href = "signal:answer_ease1";
}
function buttonAnswerEase2() {
    window.location.href = "signal:answer_ease2";
}
function buttonAnswerEase3() {
    window.location.href = "signal:answer_ease3";
}
function buttonAnswerEase4() {
    window.location.href = "signal:answer_ease4";
}
// Show options menu
function ankiShowOptionsMenu() {
    window.location.href = "signal:anki_show_options_menu";
}

// Show Navigation Drawer
function ankiShowNavDrawer() {
    window.location.href = "signal:anki_show_navigation_drawer";
}

/* Reload card.html */
function reloadPage() {
    window.location.href = "signal:reload_card_html";
}

// Mark current card
function ankiMarkCard() {
    window.location.href = "signal:mark_current_card";
}

/* Toggle flag on card from AnkiDroid Webview using JavaScript
    Possible values: "none", "red", "orange", "green", "blue"
    See AnkiDroid Manual for Usage
*/
function ankiToggleFlag(flag) {
    var flagVal = Number.isInteger(flag);

    if (flagVal) {
        switch (flag) {
            case 0: window.location.href = "signal:flag_none"; break;
            case 1: window.location.href = "signal:flag_red"; break;
            case 2: window.location.href = "signal:flag_orange"; break;
            case 3: window.location.href = "signal:flag_green"; break;
            case 4: window.location.href = "signal:flag_blue"; break;
            default: console.log('No Flag Found'); break;
        }
    } else {
        window.location.href = "signal:flag_" + flag;
    }
}

// Show toast using js
function ankiShowToast(message) {
    var msg = encodeURI(message);
    window.location.href = "signal:anki_show_toast:" + msg;
}

/* Tell the app the text in the input box when it loses focus */
function taBlur(itag) {
    //#5944 - percent wasn't encoded, but Mandarin was.
    var encodedVal = encodeURI(itag.value);
    window.location.href = "typeblurtext:" + encodedVal;
}

/* Look at the text entered into the input box and send the text on a return */
function taKey(itag, e) {
    var keycode;
    if (window.event) {
        keycode = window.event.keyCode;
    } else if (e) {
        keycode = e.which;
    } else {
        return true;
    }

    if (keycode == 13) {
        //#5944 - percent wasn't encoded, but Mandarin was.
        var encodedVal = encodeURI(itag.value);
        window.location.href = "typeentertext:" + encodedVal;
        return false;
    } else {
        return true;
    }
}

window.onload = function() {
    /* If the WebView loads too early on Android <= 4.3 (which happens
       on the first card or regularly with WebView switching enabled),
       the window dimensions returned to us will be default built-in
       values. In this case, issuing a scroll event will force the
       browser to recalculate the dimensions and give us the correct
       values, so we do this every time. This lets us resize images
       correctly. */
    window.scrollTo(0,0);
    resizeImages();
    window.location.href = "#answer";
};

function _runHook(arr) {
    var promises = [];

    for (var i = 0; i < arr.length; i++) {
        promises.push(arr[i]());
    }

    return Promise.all(promises);
}

var onUpdateHook = [];
var onShownHook = [];

var onPageFinished = function() {
    if (!resizeDone) {
        resizeImages();
        /* Re-anchor to answer after image resize since the point changes */
        window.location.href = "#answer";
    }

    var card = document.querySelector('.card');

    _runHook(onUpdateHook)
        .then(() => {
            if (window.MathJax != null) {
                /* Anki-Android adds mathjax-needs-to-render" as a class to the card when
                   it detects both \( and \) or \[ and \].
                   This does not control *loading* MathJax, but rather controls whether or not MathJax
                   renders content.  We hide all the content until MathJax renders, because otherwise
                   the content loads, and has to reflow after MathJax renders, and it's unsightly.
                   However, if we hide all the content every time, folks don't like the repainting after
                   every question or answer.  This is a middleground, where there is no repainting due to
                   MathJax on non-MathJax cards, and on MathJax cards, there is a small flicker, but there's
                   no reflowing because the content only shows after MathJax has rendered. */

                if (card.classList.contains("mathjax-needs-to-render"))
                {
                    return MathJax.startup.promise
                        .then(() => MathJax.typesetPromise([card]))
                        .then(() => card.classList.remove("mathjax-needs-to-render"));
                }
            }
        })
        .then(() => card.classList.add("mathjax-rendered"))
        .then(_runHook(onShownHook))
}

jQueryのバージョン

jQuery v3.5.1でした。

/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */

Amazonでおトクに買い物する方法
AmazonチャージでポイントGET


Amazonは買いもの前にAmazonギフト券をチャージしてポイントをゲットしないと損!

こちらもおススメ
カテゴリ: Anki

コメントを残す

メールアドレスが公開されることはありません。