Merge pull request #1292 from hgiesel/reviewerplus
Move reviewer to ts/reviewer v2
This commit is contained in:
commit
ce5f9bb9b0
@ -124,17 +124,14 @@ class Previewer(QDialog):
|
|||||||
self._close_callback()
|
self._close_callback()
|
||||||
|
|
||||||
def _setup_web_view(self) -> None:
|
def _setup_web_view(self) -> None:
|
||||||
jsinc = [
|
|
||||||
"js/vendor/jquery.min.js",
|
|
||||||
"js/vendor/css_browser_selector.min.js",
|
|
||||||
"js/mathjax.js",
|
|
||||||
"js/vendor/mathjax/tex-chtml.js",
|
|
||||||
"js/reviewer.js",
|
|
||||||
]
|
|
||||||
self._web.stdHtml(
|
self._web.stdHtml(
|
||||||
self.mw.reviewer.revHtml(),
|
self.mw.reviewer.revHtml(),
|
||||||
css=["css/reviewer.css"],
|
css=["css/reviewer.css"],
|
||||||
js=jsinc,
|
js=[
|
||||||
|
"js/mathjax.js",
|
||||||
|
"js/vendor/mathjax/tex-chtml.js",
|
||||||
|
"js/reviewer.js",
|
||||||
|
],
|
||||||
context=self,
|
context=self,
|
||||||
)
|
)
|
||||||
self._web.set_bridge_command(self._on_bridge_cmd, self)
|
self._web.set_bridge_command(self._on_bridge_cmd, self)
|
||||||
|
@ -330,17 +330,14 @@ class CardLayout(QDialog):
|
|||||||
)
|
)
|
||||||
qconnect(pform.preview_settings.clicked, self.on_preview_settings)
|
qconnect(pform.preview_settings.clicked, self.on_preview_settings)
|
||||||
|
|
||||||
jsinc = [
|
|
||||||
"js/vendor/jquery.min.js",
|
|
||||||
"js/vendor/css_browser_selector.min.js",
|
|
||||||
"js/mathjax.js",
|
|
||||||
"js/vendor/mathjax/tex-chtml.js",
|
|
||||||
"js/reviewer.js",
|
|
||||||
]
|
|
||||||
self.preview_web.stdHtml(
|
self.preview_web.stdHtml(
|
||||||
self.mw.reviewer.revHtml(),
|
self.mw.reviewer.revHtml(),
|
||||||
css=["css/reviewer.css"],
|
css=["css/reviewer.css"],
|
||||||
js=jsinc,
|
js=[
|
||||||
|
"js/mathjax.js",
|
||||||
|
"js/vendor/mathjax/tex-chtml.js",
|
||||||
|
"js/reviewer.js",
|
||||||
|
],
|
||||||
context=self,
|
context=self,
|
||||||
)
|
)
|
||||||
self.preview_web.set_bridge_command(self._on_bridge_cmd, self)
|
self.preview_web.set_bridge_command(self._on_bridge_cmd, self)
|
||||||
|
@ -26,11 +26,20 @@ copy_files_into_group(
|
|||||||
package = "//ts/editor",
|
package = "//ts/editor",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
copy_files_into_group(
|
||||||
|
name = "reviewer",
|
||||||
|
srcs = [
|
||||||
|
"reviewer.css",
|
||||||
|
],
|
||||||
|
package = "//ts/reviewer",
|
||||||
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "css",
|
name = "css",
|
||||||
srcs = [
|
srcs = [
|
||||||
"css_local",
|
"css_local",
|
||||||
"editor",
|
"editor",
|
||||||
|
"reviewer",
|
||||||
],
|
],
|
||||||
visibility = ["//qt:__subpackages__"],
|
visibility = ["//qt:__subpackages__"],
|
||||||
)
|
)
|
||||||
|
@ -38,9 +38,9 @@ copy_files_into_group(
|
|||||||
)
|
)
|
||||||
|
|
||||||
copy_files_into_group(
|
copy_files_into_group(
|
||||||
name = "reviewer_extras",
|
name = "reviewer",
|
||||||
srcs = [
|
srcs = [
|
||||||
"reviewer_extras.js",
|
"reviewer.js",
|
||||||
],
|
],
|
||||||
package = "//ts/reviewer",
|
package = "//ts/reviewer",
|
||||||
)
|
)
|
||||||
@ -50,8 +50,8 @@ filegroup(
|
|||||||
srcs = [
|
srcs = [
|
||||||
"aqt_es5",
|
"aqt_es5",
|
||||||
"editor",
|
"editor",
|
||||||
|
"reviewer",
|
||||||
"mathjax.js",
|
"mathjax.js",
|
||||||
"reviewer_extras",
|
|
||||||
"//qt/aqt/data/web/js/vendor",
|
"//qt/aqt/data/web/js/vendor",
|
||||||
],
|
],
|
||||||
visibility = ["//qt:__subpackages__"],
|
visibility = ["//qt:__subpackages__"],
|
||||||
|
@ -1,240 +0,0 @@
|
|||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
|
||||||
|
|
||||||
declare var MathJax: any;
|
|
||||||
|
|
||||||
type Callback = () => void | Promise<void>;
|
|
||||||
|
|
||||||
var ankiPlatform = "desktop";
|
|
||||||
var typeans: HTMLElement | undefined;
|
|
||||||
var _updatingQueue: Promise<void> = Promise.resolve();
|
|
||||||
|
|
||||||
var onUpdateHook: Array<Callback>;
|
|
||||||
var onShownHook: Array<Callback>;
|
|
||||||
|
|
||||||
function _runHook(arr: Array<Callback>): Promise<void[]> {
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
promises.push(arr[i]());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _queueAction(action: Callback): void {
|
|
||||||
_updatingQueue = _updatingQueue.then(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setInnerHTML(element: Element, html: string): void {
|
|
||||||
for (const oldVideo of element.getElementsByTagName("video")) {
|
|
||||||
oldVideo.pause();
|
|
||||||
|
|
||||||
while (oldVideo.firstChild) {
|
|
||||||
oldVideo.removeChild(oldVideo.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldVideo.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
element.innerHTML = html;
|
|
||||||
|
|
||||||
for (const oldScript of element.getElementsByTagName("script")) {
|
|
||||||
const newScript = document.createElement("script");
|
|
||||||
|
|
||||||
for (const attribute of oldScript.attributes) {
|
|
||||||
newScript.setAttribute(attribute.name, attribute.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
newScript.appendChild(document.createTextNode(oldScript.innerHTML));
|
|
||||||
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _updateQA(
|
|
||||||
html: string,
|
|
||||||
_unusused: unknown,
|
|
||||||
onupdate: Callback,
|
|
||||||
onshown: Callback
|
|
||||||
): Promise<void> {
|
|
||||||
onUpdateHook = [onupdate];
|
|
||||||
onShownHook = [onshown];
|
|
||||||
|
|
||||||
const qa = document.getElementById("qa")!;
|
|
||||||
const renderError =
|
|
||||||
(kind: string) =>
|
|
||||||
(error: Error): void => {
|
|
||||||
const errorMessage = String(error).substring(0, 2000);
|
|
||||||
const errorStack = String(error.stack).substring(0, 2000);
|
|
||||||
qa.innerHTML =
|
|
||||||
`Invalid ${kind} on card: ${errorMessage}\n${errorStack}`.replace(
|
|
||||||
/\n/g,
|
|
||||||
"<br>"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// hide current card
|
|
||||||
qa.style.opacity = "0";
|
|
||||||
|
|
||||||
// update card
|
|
||||||
try {
|
|
||||||
setInnerHTML(qa, html);
|
|
||||||
} catch (error) {
|
|
||||||
renderError("HTML")(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _runHook(onUpdateHook);
|
|
||||||
|
|
||||||
// wait for mathjax to ready
|
|
||||||
await MathJax.startup.promise
|
|
||||||
.then(() => {
|
|
||||||
// clear MathJax buffers from previous typesets
|
|
||||||
MathJax.typesetClear();
|
|
||||||
|
|
||||||
return MathJax.typesetPromise([qa]);
|
|
||||||
})
|
|
||||||
.catch(renderError("MathJax"));
|
|
||||||
|
|
||||||
// and reveal card when processing is done
|
|
||||||
qa.style.opacity = "1";
|
|
||||||
await _runHook(onShownHook);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _showQuestion(q: string, a: string, bodyclass: string): void {
|
|
||||||
_queueAction(() =>
|
|
||||||
_updateQA(
|
|
||||||
q,
|
|
||||||
null,
|
|
||||||
function () {
|
|
||||||
// return to top of window
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
|
|
||||||
document.body.className = bodyclass;
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
// focus typing area if visible
|
|
||||||
typeans = document.getElementById("typeans");
|
|
||||||
if (typeans) {
|
|
||||||
typeans.focus();
|
|
||||||
}
|
|
||||||
// preload images
|
|
||||||
allImagesLoaded().then(() => preloadAnswerImages(q, a));
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _showAnswer(a: string, bodyclass: string): void {
|
|
||||||
_queueAction(() =>
|
|
||||||
_updateQA(
|
|
||||||
a,
|
|
||||||
null,
|
|
||||||
function () {
|
|
||||||
if (bodyclass) {
|
|
||||||
// when previewing
|
|
||||||
document.body.className = bodyclass;
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid scrolling to the answer until images load
|
|
||||||
allImagesLoaded().then(scrollToAnswer);
|
|
||||||
},
|
|
||||||
function () {}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const _flagColours = {
|
|
||||||
1: "#e25252",
|
|
||||||
2: "#ffb347",
|
|
||||||
3: "#54c414",
|
|
||||||
4: "#578cff",
|
|
||||||
5: "#ff82ee",
|
|
||||||
6: "#00d1b5",
|
|
||||||
7: "#9649dd",
|
|
||||||
};
|
|
||||||
|
|
||||||
function _drawFlag(flag: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7): void {
|
|
||||||
const elem = document.getElementById("_flag");
|
|
||||||
if (flag === 0) {
|
|
||||||
elem.setAttribute("hidden", "");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
elem.removeAttribute("hidden");
|
|
||||||
elem.style.color = _flagColours[flag];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _drawMark(mark: boolean): void {
|
|
||||||
const elem = document.getElementById("_mark");
|
|
||||||
if (!mark) {
|
|
||||||
elem.setAttribute("hidden", "");
|
|
||||||
} else {
|
|
||||||
elem.removeAttribute("hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _typeAnsPress(): void {
|
|
||||||
const code = (window.event as KeyboardEvent).code;
|
|
||||||
if (["Enter", "NumpadEnter"].includes(code)) {
|
|
||||||
pycmd("ans");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _emulateMobile(enabled: boolean): void {
|
|
||||||
const list = document.documentElement.classList;
|
|
||||||
if (enabled) {
|
|
||||||
list.add("mobile");
|
|
||||||
} else {
|
|
||||||
list.remove("mobile");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function allImagesLoaded(): Promise<void[]> {
|
|
||||||
return Promise.all(
|
|
||||||
Array.from(document.getElementsByTagName("img")).map(imageLoaded)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function imageLoaded(img: HTMLImageElement): Promise<void> {
|
|
||||||
return img.complete
|
|
||||||
? Promise.resolve()
|
|
||||||
: new Promise((resolve) => {
|
|
||||||
img.addEventListener("load", () => resolve());
|
|
||||||
img.addEventListener("error", () => resolve());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToAnswer(): void {
|
|
||||||
document.getElementById("answer")?.scrollIntoView();
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectPreloadLink(href: string, as: string): void {
|
|
||||||
const link = document.createElement("link");
|
|
||||||
link.rel = "preload";
|
|
||||||
link.href = href;
|
|
||||||
link.as = as;
|
|
||||||
document.head.appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearPreloadLinks(): void {
|
|
||||||
document.head
|
|
||||||
.querySelectorAll("link[rel='preload']")
|
|
||||||
.forEach((link) => link.remove());
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractImageSrcs(html: string): string[] {
|
|
||||||
const fragment = document.createRange().createContextualFragment(html);
|
|
||||||
const srcs = [...fragment.querySelectorAll("img[src]")].map(
|
|
||||||
(img) => (img as HTMLImageElement).src
|
|
||||||
);
|
|
||||||
return srcs;
|
|
||||||
}
|
|
||||||
|
|
||||||
function preloadAnswerImages(qHtml: string, aHtml: string): void {
|
|
||||||
clearPreloadLinks();
|
|
||||||
const aSrcs = extractImageSrcs(aHtml);
|
|
||||||
if (aSrcs.length) {
|
|
||||||
const qSrcs = extractImageSrcs(qHtml);
|
|
||||||
const diff = aSrcs.filter((src) => !qSrcs.includes(src));
|
|
||||||
diff.forEach((src) => injectPreloadLink(src, "image"));
|
|
||||||
}
|
|
||||||
}
|
|
@ -288,12 +288,9 @@ class Reviewer:
|
|||||||
self.revHtml(),
|
self.revHtml(),
|
||||||
css=["css/reviewer.css"],
|
css=["css/reviewer.css"],
|
||||||
js=[
|
js=[
|
||||||
"js/vendor/jquery.min.js",
|
|
||||||
"js/vendor/css_browser_selector.min.js",
|
|
||||||
"js/mathjax.js",
|
"js/mathjax.js",
|
||||||
"js/vendor/mathjax/tex-chtml.js",
|
"js/vendor/mathjax/tex-chtml.js",
|
||||||
"js/reviewer.js",
|
"js/reviewer.js",
|
||||||
"js/reviewer_extras.js",
|
|
||||||
],
|
],
|
||||||
context=self,
|
context=self,
|
||||||
)
|
)
|
||||||
@ -699,7 +696,7 @@ class Reviewer:
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
def _getTypedAnswer(self) -> None:
|
def _getTypedAnswer(self) -> None:
|
||||||
self.web.evalWithCallback("typeans ? typeans.value : null", self._onTypedAnswer)
|
self.web.evalWithCallback("getTypedAnswer();", self._onTypedAnswer)
|
||||||
|
|
||||||
def _onTypedAnswer(self, val: None) -> None:
|
def _onTypedAnswer(self, val: None) -> None:
|
||||||
self.typedAnswer = val or ""
|
self.typedAnswer = val or ""
|
||||||
|
@ -33,6 +33,7 @@ compile_sass(
|
|||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//ts/sass:scrollbar_lib",
|
"//ts/sass:scrollbar_lib",
|
||||||
|
"//ts/sass/codemirror",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -132,10 +133,10 @@ esbuild(
|
|||||||
output_css = "editor.css",
|
output_css = "editor.css",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"base_css",
|
|
||||||
"bootstrap-icons",
|
|
||||||
"editor_ts",
|
"editor_ts",
|
||||||
|
"base_css",
|
||||||
"local_css",
|
"local_css",
|
||||||
|
"bootstrap-icons",
|
||||||
"mdi-icons",
|
"mdi-icons",
|
||||||
"svelte_components",
|
"svelte_components",
|
||||||
"//ts/components",
|
"//ts/components",
|
||||||
|
@ -5,23 +5,39 @@ load("//ts/svelte:svelte.bzl", "svelte", "svelte_check")
|
|||||||
load("//ts:esbuild.bzl", "esbuild")
|
load("//ts:esbuild.bzl", "esbuild")
|
||||||
load("//ts:compile_sass.bzl", "compile_sass")
|
load("//ts:compile_sass.bzl", "compile_sass")
|
||||||
|
|
||||||
|
compile_sass(
|
||||||
|
srcs = [
|
||||||
|
"reviewer.scss",
|
||||||
|
],
|
||||||
|
group = "reviewer_scss",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ts_library(
|
ts_library(
|
||||||
name = "lib",
|
name = "reviewer_ts",
|
||||||
srcs = glob(["*.ts"]),
|
srcs = glob(["*.ts"]),
|
||||||
deps = ["//ts/lib"],
|
deps = [
|
||||||
|
"//ts/lib",
|
||||||
|
"//ts/lib:backend_proto",
|
||||||
|
"@npm//css-browser-selector",
|
||||||
|
"@npm//jquery",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
esbuild(
|
esbuild(
|
||||||
name = "reviewer_extras",
|
name = "reviewer",
|
||||||
args = [
|
args = [
|
||||||
"--resolve-extensions=.mjs,.js",
|
"--resolve-extensions=.mjs,.js",
|
||||||
"--log-level=warning",
|
"--log-level=warning",
|
||||||
],
|
],
|
||||||
entry_point = "index.ts",
|
entry_point = "index_wrapper.ts",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
":lib",
|
":reviewer_ts",
|
||||||
"//ts/lib",
|
"//ts/lib",
|
||||||
|
"//ts/lib:backend_proto",
|
||||||
"@npm//protobufjs",
|
"@npm//protobufjs",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
49
ts/reviewer/images.ts
Normal file
49
ts/reviewer/images.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
function injectPreloadLink(href: string, as: string): void {
|
||||||
|
const link = document.createElement("link");
|
||||||
|
link.rel = "preload";
|
||||||
|
link.href = href;
|
||||||
|
link.as = as;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allImagesLoaded(): Promise<void[]> {
|
||||||
|
return Promise.all(
|
||||||
|
Array.from(document.getElementsByTagName("img")).map(imageLoaded)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageLoaded(img: HTMLImageElement): Promise<void> {
|
||||||
|
return img.complete
|
||||||
|
? Promise.resolve()
|
||||||
|
: new Promise((resolve) => {
|
||||||
|
img.addEventListener("load", () => resolve());
|
||||||
|
img.addEventListener("error", () => resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPreloadLinks(): void {
|
||||||
|
document.head
|
||||||
|
.querySelectorAll("link[rel='preload']")
|
||||||
|
.forEach((link) => link.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractImageSrcs(html: string): string[] {
|
||||||
|
const fragment = document.createRange().createContextualFragment(html);
|
||||||
|
const srcs = [...fragment.querySelectorAll("img[src]")].map(
|
||||||
|
(img) => (img as HTMLImageElement).src
|
||||||
|
);
|
||||||
|
return srcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function preloadAnswerImages(qHtml: string, aHtml: string): void {
|
||||||
|
clearPreloadLinks();
|
||||||
|
const aSrcs = extractImageSrcs(aHtml);
|
||||||
|
if (aSrcs.length) {
|
||||||
|
const qSrcs = extractImageSrcs(qHtml);
|
||||||
|
const diff = aSrcs.filter((src) => !qSrcs.includes(src));
|
||||||
|
diff.forEach((src) => injectPreloadLink(src, "image"));
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,189 @@
|
|||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
// This is a temporary extra file we load separately from reviewer.ts. Once
|
/* eslint
|
||||||
// reviewer.ts has been migrated into ts/, the code here can be merged into
|
@typescript-eslint/no-non-null-assertion: "off",
|
||||||
// it.
|
@typescript-eslint/no-explicit-any: "off",
|
||||||
|
*/
|
||||||
|
|
||||||
import { mutateNextCardStates } from "./answering";
|
import "css-browser-selector/css_browser_selector";
|
||||||
globalThis.anki = { ...globalThis.anki, mutateNextCardStates };
|
import "jquery/dist/jquery";
|
||||||
|
|
||||||
|
export { mutateNextCardStates } from "./answering";
|
||||||
|
|
||||||
|
import { bridgeCommand } from "lib/bridgecommand";
|
||||||
|
import { allImagesLoaded, preloadAnswerImages } from "./images";
|
||||||
|
declare const MathJax: any;
|
||||||
|
|
||||||
|
type Callback = () => void | Promise<void>;
|
||||||
|
|
||||||
|
export const onUpdateHook: Array<Callback> = [];
|
||||||
|
export const onShownHook: Array<Callback> = [];
|
||||||
|
|
||||||
|
export const ankiPlatform = "desktop";
|
||||||
|
let typeans: HTMLInputElement | undefined;
|
||||||
|
|
||||||
|
export function getTypedAnswer(): string | null {
|
||||||
|
return typeans?.value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _runHook(arr: Array<Callback>): Promise<void[]> {
|
||||||
|
const promises: (Promise<void> | void)[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
promises.push(arr[i]());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _updatingQueue: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
|
function _queueAction(action: Callback): void {
|
||||||
|
_updatingQueue = _updatingQueue.then(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInnerHTML(element: Element, html: string): void {
|
||||||
|
for (const oldVideo of element.getElementsByTagName("video")) {
|
||||||
|
oldVideo.pause();
|
||||||
|
|
||||||
|
while (oldVideo.firstChild) {
|
||||||
|
oldVideo.removeChild(oldVideo.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
oldVideo.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = html;
|
||||||
|
|
||||||
|
for (const oldScript of element.getElementsByTagName("script")) {
|
||||||
|
const newScript = document.createElement("script");
|
||||||
|
|
||||||
|
for (const attribute of oldScript.attributes) {
|
||||||
|
newScript.setAttribute(attribute.name, attribute.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
newScript.appendChild(document.createTextNode(oldScript.innerHTML));
|
||||||
|
oldScript.parentNode!.replaceChild(newScript, oldScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderError =
|
||||||
|
(type: string) =>
|
||||||
|
(error: Error): string => {
|
||||||
|
const errorMessage = String(error).substring(0, 2000);
|
||||||
|
const errorStack = String(error.stack).substring(0, 2000);
|
||||||
|
return `<div>Invalid ${type} on card: ${errorMessage}\n${errorStack}</div>`.replace(
|
||||||
|
/\n/g,
|
||||||
|
"<br>"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function _updateQA(
|
||||||
|
html: string,
|
||||||
|
_unusused: unknown,
|
||||||
|
onupdate: Callback,
|
||||||
|
onshown: Callback
|
||||||
|
): Promise<void> {
|
||||||
|
onUpdateHook.length = 0;
|
||||||
|
onUpdateHook.push(onupdate);
|
||||||
|
|
||||||
|
onShownHook.length = 0;
|
||||||
|
onShownHook.push(onshown);
|
||||||
|
|
||||||
|
const qa = document.getElementById("qa")!;
|
||||||
|
|
||||||
|
qa.style.opacity = "0";
|
||||||
|
|
||||||
|
try {
|
||||||
|
setInnerHTML(qa, html);
|
||||||
|
} catch (error) {
|
||||||
|
setInnerHTML(qa, renderError("html")(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
await _runHook(onUpdateHook);
|
||||||
|
|
||||||
|
// wait for mathjax to ready
|
||||||
|
await MathJax.startup.promise
|
||||||
|
.then(() => {
|
||||||
|
// clear MathJax buffers from previous typesets
|
||||||
|
MathJax.typesetClear();
|
||||||
|
|
||||||
|
return MathJax.typesetPromise([qa]);
|
||||||
|
})
|
||||||
|
.catch(renderError("MathJax"));
|
||||||
|
|
||||||
|
qa.style.opacity = "1";
|
||||||
|
|
||||||
|
await _runHook(onShownHook);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _showQuestion(q: string, a: string, bodyclass: string): void {
|
||||||
|
_queueAction(() =>
|
||||||
|
_updateQA(
|
||||||
|
q,
|
||||||
|
null,
|
||||||
|
function () {
|
||||||
|
// return to top of window
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
|
document.body.className = bodyclass;
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
// focus typing area if visible
|
||||||
|
typeans = document.getElementById("typeans") as HTMLInputElement;
|
||||||
|
if (typeans) {
|
||||||
|
typeans.focus();
|
||||||
|
}
|
||||||
|
// preload images
|
||||||
|
allImagesLoaded().then(() => preloadAnswerImages(q, a));
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToAnswer(): void {
|
||||||
|
document.getElementById("answer")?.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _showAnswer(a: string, bodyclass: string): void {
|
||||||
|
_queueAction(() =>
|
||||||
|
_updateQA(
|
||||||
|
a,
|
||||||
|
null,
|
||||||
|
function () {
|
||||||
|
if (bodyclass) {
|
||||||
|
// when previewing
|
||||||
|
document.body.className = bodyclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid scrolling to the answer until images load
|
||||||
|
allImagesLoaded().then(scrollToAnswer);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _drawFlag(flag: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7): void {
|
||||||
|
const elem = document.getElementById("_flag")!;
|
||||||
|
elem.toggleAttribute("hidden", flag === 0);
|
||||||
|
elem.style.color = `var(--flag${flag}-fg)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _drawMark(mark: boolean): void {
|
||||||
|
document.getElementById("_mark")!.toggleAttribute("hidden", !mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _typeAnsPress(): void {
|
||||||
|
const code = (window.event as KeyboardEvent).code;
|
||||||
|
if (["Enter", "NumpadEnter"].includes(code)) {
|
||||||
|
bridgeCommand("ans");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _emulateMobile(enabled: boolean): void {
|
||||||
|
document.documentElement.classList.toggle("mobile", enabled);
|
||||||
|
}
|
||||||
|
8
ts/reviewer/index_wrapper.ts
Normal file
8
ts/reviewer/index_wrapper.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
// extend the global namespace with our exports - not sure if there's a better way with esbuild
|
||||||
|
import * as globals from "./index";
|
||||||
|
for (const key in globals) {
|
||||||
|
window[key] = globals[key];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user