Skip to content

Instantly share code, notes, and snippets.

@redoPop
Created February 7, 2015 19:22
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save redoPop/10381ad5d576b9d73745 to your computer and use it in GitHub Desktop.
Save redoPop/10381ad5d576b9d73745 to your computer and use it in GitHub Desktop.
HTML used by AnkiMobile to render card templates; a reference for creating advanced Anki card templates with special HTML/CSS. When used by AnkiMobile, the card's HTML replaces the `<!-- (Card contents) -->` comment in this gist, with the shared styles dropped inline inside a `<style>` tag.
<!doctype html>
<html class=" webkit safari mobile iphone js">
<head>
<meta name="viewport" content="width=device-width;">
<style id="ss"></style>
<style>
body {
text-align: center;
font-size: 1em;
-webkit-transform: translate3d(0,0,0);
-webkit-font-smoothing:antialiased;
}
img {
max-width: 95%;
}
#typeans {
width: 95%;
margin-top: 5px;
}
.invis { visibility:hidden; }
.card { margin: 20px; }
</style>
<script type="text/javascript">
var typeans;
function setupType() {
typeans = document.getElementById("typeans");
if (typeans) {
typeans.focus();
}
$('.typeans').focus();
}
function runScripts() {
var d = document.getElementById('qa').getElementsByTagName("script");
var t = d.length;
for (var x = 0; x < t; x++) {
var newScript = document.createElement('script');
newScript.type = "text/javascript";
newScript.text = d[x].text;
document.getElementById('qa').appendChild(newScript);
}
}
// hack for ios7 to try make sure it refreshes the screen
function _refresh() {
setTimeout(function() {
var e = document.createElement('iframe');
e.setAttribute('src', 'ankiinternal://foo');
e.className = "invis";
document.documentElement.appendChild(e);
e.parentNode.removeChild(e);
e = null;
//console.log('update');}
}, 0);
}
function _getTypedText() {
if (typeans) {
return typeans.value;
}
}
function _resizeImages(shrink) {
var elems = document.getElementsByTagName("img");
for (var i=0; i<elems.length; i++) {
var e = elems[i];
var w = e.style.maxWidth;
if (!w) {
// assume it's default 95%
w = "95%";
}
w = parseInt(w, 10);
w += 30 * (shrink ? -1 : 1);
w = Math.max(30, w);
e.style.maxWidth = w+"%";
}
}
// make console.log work
var console = {
log: function(msg) {
var iframe = document.createElement('IFRAME');
iframe.setAttribute('src', 'ankilog://dummy/' + encodeURIComponent(msg));
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
};
// tap handling
//////////////////////////////////////////////////////////
var _startX, _startY, _winX, _winY;
var _lastTap = 0;
var _tapStart;
var _tapReady = function(threshold) {
var now = new Date().getTime();
var elapsed = now - _lastTap;
if (elapsed > threshold) {
_lastTap = now;
return true;
}
return false;
}
document.ontouchstart = function (e) {
_startX = e.touches[0].clientX;
_startY = e.touches[0].clientY;
_winX = _startX - window.pageXOffset;
_winY = _startY - window.pageYOffset;
_tapStart = new Date().getTime();
};
// this is run after the event bubbles up to the document
document.ontouchend = function (e) {
if (!_tapReady(300)) {
return;
} else if (_isLink(e)) {
return;
} else if ((new Date().getTime() - _tapStart) > 500) {
// long tap (eg copy), so ignore
return;
}
// if tap has moved, treat it as scrolling
var x = e.changedTouches[0].clientX;
var y = e.changedTouches[0].clientY;
var deltaX = Math.abs(x-_startX);
var deltaY = Math.abs(y-_startY);
var threshold = 30;
if (deltaX > threshold || deltaY > threshold) {
// if it was scrolling, we shouldn't use a touch delay
_lastTap = 0;
return;
}
// determine the section we're in
var tap;
var h = window.innerHeight / 3.0;
var w = window.innerWidth / 3.0;
x = Math.floor(x / w);
y = Math.floor(y / h);
if (x < 0 || x > 2 || y < 0 || y > 2) {
return;
}
x = ["Left", "Center", "Right"][x];
y = ["top", "mid", "bottom"][y];
tap = y+x;
// send tap to js
var iframe = document.createElement('IFRAME');
iframe.setAttribute('src', 'ankitap://' + tap);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
};
// this is run before any click events have a chance to fire
var _onclick = function (e) {
// if the target is not clickable, don't debounce
if (!_isLink(e)) {
return;
}
if (!_tapReady(1000)) {
e.stopPropagation();
e.preventDefault();
}
}
document.addEventListener("click", _onclick, true);
var _isLink = function(e) {
// we need to look at the source element and all parent elements
// except the document
var node = e.srcElement;
while (node && node != document) {
var res = (node.nodeName == "A" || node.onclick);
if (res) {
return true;
}
node = node.parentNode;
}
return res;
};
// JS callback to indicate page fully loaded
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = function DOMReady() {
var iframe = document.createElement('IFRAME');
iframe.setAttribute('src', 'ankiinternal://DOMIsReady');
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
};
function addScript() {
document.getElementsByTagName('head')[0].appendChild(script);
document.addEventListener('DOMContentLoaded', DOMReady, false);
}
addScript();
/* browsersel.js 0.4.0 */
function css_browser_selector(u) {
var ua = u.toLowerCase(),
is = function(t) {
return ua.indexOf(t) > -1
},
g = 'gecko',
w = 'webkit',
s = 'safari',
o = 'opera',
m = 'mobile',
h = document.documentElement,
b = [(!(/opera|webtv/i.test(ua)) && /msie\s(\d)/.test(ua)) ? ('ie ie' + RegExp.$1) : is('firefox/2') ? g + ' ff2' : is('firefox/3.5') ? g + ' ff3 ff3_5' : is('firefox/3.6') ? g + ' ff3 ff3_6' : is('firefox/3') ? g + ' ff3' : is('gecko/') ? g : is('opera') ? o + (/version\/(\d+)/.test(ua) ? ' ' + o + RegExp.$1 : (/opera(\s|\/)(\d+)/.test(ua) ? ' ' + o + RegExp.$2 : '')) : is('konqueror') ? 'konqueror' : is('blackberry') ? m + ' blackberry' : is('android') ? m + ' android' : is('chrome') ? w + ' chrome' : is('iron') ? w + ' iron' : is('applewebkit/') ? w + ' ' + s + (/version\/(\d+)/.test(ua) ? ' ' + s + RegExp.$1 : '') : is('mozilla/') ? g : '', is('j2me') ? m + ' j2me' : is('iphone') ? m + ' iphone' : is('ipod') ? m + ' ipod' : is('ipad') ? m + ' ipad' : is('mac') ? 'mac' : is('darwin') ? 'mac' : is('webtv') ? 'webtv' : is('win') ? 'win' + (is('windows nt 6.0') ? ' vista' : '') : is('freebsd') ? 'freebsd' : (is('x11') || is('linux')) ? 'linux' : '', 'js'];
c = b.join(' ');
h.className += ' ' + c;
return c;
};
css_browser_selector(navigator.userAgent);
/* fastclick 0.5.0 */
function FastClick(e) {
"use strict";
var t, n = this;
this.trackingClick = false;
this.trackingClickStart = 0;
this.targetElement = null;
this.touchStartX = 0;
this.touchStartY = 0;
this.lastTouchIdentifier = 0;
this.layer = e;
if (!e || !e.nodeType) {
throw new TypeError("Layer must be a document node")
}
this.onClick = function() {
FastClick.prototype.onClick.apply(n, arguments)
};
this.onTouchStart = function() {
FastClick.prototype.onTouchStart.apply(n, arguments)
};
this.onTouchMove = function() {
FastClick.prototype.onTouchMove.apply(n, arguments)
};
this.onTouchEnd = function() {
FastClick.prototype.onTouchEnd.apply(n, arguments)
};
this.onTouchCancel = function() {
FastClick.prototype.onTouchCancel.apply(n, arguments)
};
if (typeof window.ontouchstart === "undefined") {
return
}
e.addEventListener("click", this.onClick, true);
e.addEventListener("touchstart", this.onTouchStart, false);
e.addEventListener("touchmove", this.onTouchMove, false);
e.addEventListener("touchend", this.onTouchEnd, false);
e.addEventListener("touchcancel", this.onTouchCancel, false);
if (!Event.prototype.stopImmediatePropagation) {
e.removeEventListener = function(t, n, r) {
var i = Node.prototype.removeEventListener;
if (t === "click") {
i.call(e, t, n.hijacked || n, r)
} else {
i.call(e, t, n, r)
}
};
e.addEventListener = function(t, n, r) {
var i = Node.prototype.addEventListener;
if (t === "click") {
i.call(e, t, n.hijacked || (n.hijacked = function(e) {
if (!e.propagationStopped) {
n(e)
}
}), r)
} else {
i.call(e, t, n, r)
}
}
}
if (typeof e.onclick === "function") {
t = e.onclick;
e.addEventListener("click", function(e) {
t(e)
}, false);
e.onclick = null
}
}
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf("Android") > 0;
FastClick.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent);
FastClick.prototype.deviceIsIOS4 = FastClick.prototype.deviceIsIOS && /OS 4_\d(_\d)?/.test(navigator.userAgent);
FastClick.prototype.needsClick = function(e) {
"use strict";
switch (e.nodeName.toLowerCase()) {
case "label":
case "video":
return true;
default:
return /\bneedsclick\b/.test(e.className)
}
};
FastClick.prototype.needsFocus = function(e) {
"use strict";
switch (e.nodeName.toLowerCase()) {
case "textarea":
case "select":
return true;
case "input":
switch (e.type) {
case "button":
case "checkbox":
case "file":
case "image":
case "radio":
case "submit":
return false
}
return true;
default:
return /\bneedsfocus\b/.test(e.className)
}
};
FastClick.prototype.sendClick = function(e, t) {
"use strict";
var n, r;
if (document.activeElement && document.activeElement !== e) {
document.activeElement.blur()
}
r = t.changedTouches[0];
n = document.createEvent("MouseEvents");
n.initMouseEvent("click", true, true, window, 1, r.screenX, r.screenY, r.clientX, r.clientY, false, false, false, false, 0, null);
n.forwardedTouchEvent = true;
e.dispatchEvent(n)
};
FastClick.prototype.focus = function(e) {
"use strict";
var t;
if (this.deviceIsIOS && e.setSelectionRange) {
t = e.value.length;
e.setSelectionRange(t, t)
} else {
e.focus()
}
};
FastClick.prototype.updateScrollParent = function(e) {
"use strict";
var t, n;
t = e.fastClickScrollParent;
if (!t || !t.contains(e)) {
n = e;
do {
if (n.scrollHeight > n.offsetHeight) {
t = n;
e.fastClickScrollParent = n;
break
}
n = n.parentElement
} while (n)
}
if (t) {
t.fastClickLastScrollTop = t.scrollTop
}
};
FastClick.prototype.onTouchStart = function(e) {
"use strict";
var t, n;
t = e.target;
n = e.targetTouches[0];
if (this.deviceIsIOS) {
if (window.getSelection().rangeCount) {
return true
}
if (!this.deviceIsIOS4) {
if (n.identifier === this.lastTouchIdentifier) {
e.preventDefault();
return false
}
this.lastTouchIdentifier = n.identifier;
this.updateScrollParent(t)
}
}
this.trackingClick = true;
this.trackingClickStart = e.timeStamp;
this.targetElement = t;
this.touchStartX = n.pageX;
this.touchStartY = n.pageY;
if (e.timeStamp - this.lastClickTime < 200) {
e.preventDefault()
}
return true
};
FastClick.prototype.touchHasMoved = function(e) {
"use strict";
var t = e.targetTouches[0];
if (Math.abs(t.pageX - this.touchStartX) > 10 || Math.abs(t.pageY - this.touchStartY) > 10) {
return true
}
return false
};
FastClick.prototype.onTouchMove = function(e) {
"use strict";
if (!this.trackingClick) {
return true
}
if (this.targetElement !== e.target || this.touchHasMoved(e)) {
this.trackingClick = false;
this.targetElement = null
}
return true
};
FastClick.prototype.findControl = function(e) {
"use strict";
if (e.control !== undefined) {
return e.control
}
if (e.htmlFor) {
return document.getElementById(e.htmlFor)
}
return e.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")
};
FastClick.prototype.onTouchEnd = function(e) {
"use strict";
var t, n, r, i, s = this.targetElement;
if (!this.trackingClick) {
return true
}
if (e.timeStamp - this.lastClickTime < 200) {
this.cancelNextClick = true;
return true
}
this.lastClickTime = e.timeStamp;
n = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
r = s.tagName.toLowerCase();
if (r === "label") {
t = this.findControl(s);
if (t) {
this.focus(s);
if (this.deviceIsAndroid) {
return false
}
s = t
}
} else if (this.needsFocus(s)) {
if (e.timeStamp - n > 100 || this.deviceIsIOS && window.top !== window && r === "input") {
this.targetElement = null;
return false
}
this.focus(s);
if (!this.deviceIsIOS4 || r !== "select") {
this.targetElement = null;
e.preventDefault()
}
return false
}
if (this.deviceIsIOS && !this.deviceIsIOS4) {
i = s.fastClickScrollParent;
if (i && i.fastClickLastScrollTop !== i.scrollTop) {
return true
}
}
if (!this.needsClick(s)) {
e.preventDefault();
this.sendClick(s, e)
}
return false
};
FastClick.prototype.onTouchCancel = function() {
"use strict";
this.trackingClick = false;
this.targetElement = null
};
FastClick.prototype.onClick = function(e) {
"use strict";
var t;
if (!this.targetElement) {
return true
}
if (e.forwardedTouchEvent) {
return true
}
t = this.targetElement;
this.targetElement = null;
if (this.trackingClick) {
this.trackingClick = false;
return true
}
if (!e.cancelable) {
return true
}
if (e.target.type === "submit" && e.detail === 0) {
return true
}
if (!this.needsClick(t) || this.cancelNextClick) {
this.cancelNextClick = false;
if (e.stopImmediatePropagation) {
e.stopImmediatePropagation()
} else {
e.propagationStopped = true
}
e.stopPropagation();
e.preventDefault();
return false
}
return true
};
FastClick.prototype.destroy = function() {
"use strict";
var e = this.layer;
e.removeEventListener("click", this.onClick, true);
e.removeEventListener("touchstart", this.onTouchStart, false);
e.removeEventListener("touchmove", this.onTouchMove, false);
e.removeEventListener("touchend", this.onTouchEnd, false);
e.removeEventListener("touchcancel", this.onTouchCancel, false)
};
if (typeof define !== "undefined" && define.amd) {
define(function() {
"use strict";
return FastClick
})
}
if (typeof module !== "undefined" && module.exports) {
module.exports = function(e) {
"use strict";
return new FastClick(e)
};
module.exports.FastClick = FastClick
}
window.addEventListener('load', function() {
new FastClick(document.body);
}, false);
</script>
<script type="text/javascript">
function DOMReady() {
var iframe = document.createElement('IFRAME');
iframe.setAttribute('src', 'ankiinternal://DOMIsReady');
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
</script>
<title></title>
</head>
<body class="card card1">
<div id="qa">
<!--
(Card contents)
-->
</div>
</body>
</html>
@redoPop
Copy link
Author

redoPop commented Feb 7, 2015

Extracted from AnkiMobile for iOS, version 2.0.17 (the Jan 4, 2015 update).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment