mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge remote-tracking branch 'upstream' into apkg
This commit is contained in:
commit
92319116d4
15 changed files with 75 additions and 78 deletions
|
@ -68,6 +68,12 @@ message SearchNode {
|
|||
repeated SearchNode nodes = 1;
|
||||
Joiner joiner = 2;
|
||||
}
|
||||
message Field {
|
||||
string field_name = 1;
|
||||
string text = 2;
|
||||
bool is_re = 3;
|
||||
}
|
||||
|
||||
oneof filter {
|
||||
Group group = 1;
|
||||
SearchNode negated = 2;
|
||||
|
@ -88,6 +94,8 @@ message SearchNode {
|
|||
string tag = 17;
|
||||
string note = 18;
|
||||
uint32 introduced_in_days = 19;
|
||||
Field field = 20;
|
||||
string literal_text = 21;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,12 +49,7 @@ button {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/**
|
||||
* We use .focus to recreate the highlight on the good button
|
||||
* while the actual focus is actually in the main webview
|
||||
*/
|
||||
:focus,
|
||||
.focus {
|
||||
:focus {
|
||||
outline: 1px auto var(--focus-color);
|
||||
|
||||
.nightMode & {
|
||||
|
@ -63,14 +58,6 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
#innertable:focus-within .focus:not(:focus) {
|
||||
outline: none;
|
||||
|
||||
.nightMode & {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nobold {
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
|
|
|
@ -763,7 +763,7 @@ time = %(time)d;
|
|||
def _showAnswerButton(self) -> None:
|
||||
middle = """
|
||||
<span class=stattxt>{}</span><br>
|
||||
<button title="{}" id="ansbut" class="focus" onclick='pycmd("ans");'>{}</button>""".format(
|
||||
<button title="{}" id="ansbut" onclick='pycmd("ans");'>{}</button>""".format(
|
||||
self._remaining(),
|
||||
tr.actions_shortcut_key(val=tr.studying_space()),
|
||||
tr.studying_show_answer(),
|
||||
|
@ -851,7 +851,7 @@ time = %(time)d;
|
|||
|
||||
def but(i: int, label: str) -> str:
|
||||
if i == default:
|
||||
extra = """id="defease" class="focus" """
|
||||
extra = """id="defease" """
|
||||
else:
|
||||
extra = ""
|
||||
due = self._buttonTime(i, v3_labels=labels)
|
||||
|
|
|
@ -115,12 +115,12 @@ def register_repos():
|
|||
################
|
||||
|
||||
core_i18n_repo = "anki-core-i18n"
|
||||
core_i18n_commit = "3fbbbd277adf6a307aad1f1389105740fb14bb52"
|
||||
core_i18n_zip_csum = "18db75ca57b5068c0c9cb926f19b451185cf04a086004a96fcf8f6e734f51b29"
|
||||
core_i18n_commit = "7b86958ed51bcd63ea878cd781936976d6cf26f4"
|
||||
core_i18n_zip_csum = "accbb148e56f5b64dcd5981a563889cdd4b84d6ea0f5464914c7844bc5de1e75"
|
||||
|
||||
qtftl_i18n_repo = "anki-desktop-ftl"
|
||||
qtftl_i18n_commit = "a6811a12eaf86ff02d5ce168ac65022f3e442993"
|
||||
qtftl_i18n_zip_csum = "03b62eb593e4929e502e6ee6d7fd81492d6fe9164aec50573e30028edbc9bf2e"
|
||||
qtftl_i18n_commit = "10532f009d01145bc7490423db66375cc2e7dae3"
|
||||
qtftl_i18n_zip_csum = "268cefd8650b827d3b03a6bdc1ffae8a7309523c2da4416e27afc3c8686920f6"
|
||||
|
||||
i18n_build_content = """
|
||||
filegroup(
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
search::{
|
||||
parse_search, Negated, Node, PropertyKind, RatingKind, SearchNode, StateKind, TemplateKind,
|
||||
},
|
||||
text::escape_anki_wildcards_for_search_node,
|
||||
text::{escape_anki_wildcards, escape_anki_wildcards_for_search_node},
|
||||
};
|
||||
|
||||
impl TryFrom<pb::SearchNode> for Node {
|
||||
|
@ -98,6 +98,15 @@ impl TryFrom<pb::SearchNode> for Node {
|
|||
Node::Group(nodes)
|
||||
}
|
||||
}
|
||||
Filter::Field(field) => Node::Search(SearchNode::SingleField {
|
||||
field: escape_anki_wildcards(&field.field_name),
|
||||
text: escape_anki_wildcards(&field.text),
|
||||
is_re: field.is_re,
|
||||
}),
|
||||
Filter::LiteralText(text) => {
|
||||
let text = escape_anki_wildcards(&text);
|
||||
Node::Search(SearchNode::UnqualifiedText(text))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Node::Search(SearchNode::WholeCollection)
|
||||
|
|
|
@ -23,13 +23,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
import { randomUUID } from "../lib/uuid";
|
||||
import { pageTheme } from "../sveltelib/theme";
|
||||
import { convertMathjax } from "./mathjax";
|
||||
import { convertMathjax, unescapeSomeEntities } from "./mathjax";
|
||||
|
||||
export let mathjax: string;
|
||||
export let block: boolean;
|
||||
export let fontSize: number;
|
||||
|
||||
$: [converted, title] = convertMathjax(mathjax, $pageTheme.isDark, fontSize);
|
||||
$: [converted, title] = convertMathjax(
|
||||
unescapeSomeEntities(mathjax),
|
||||
$pageTheme.isDark,
|
||||
fontSize,
|
||||
);
|
||||
$: empty = title === "MathJax";
|
||||
$: encoded = encodeURIComponent(converted);
|
||||
|
||||
|
|
|
@ -15,14 +15,6 @@ const mathjaxTagPattern =
|
|||
const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu;
|
||||
const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu;
|
||||
|
||||
/**
|
||||
* If the user enters the Mathjax with delimiters, "<" and ">" will
|
||||
* be first translated to entities.
|
||||
*/
|
||||
function translateEntitiesToMathjax(value: string) {
|
||||
return value.replace(/</g, "{\\lt}").replace(/>/g, "{\\gt}");
|
||||
}
|
||||
|
||||
export const Mathjax: DecoratedElementConstructor = class Mathjax
|
||||
extends HTMLElement
|
||||
implements DecoratedElement
|
||||
|
@ -45,12 +37,10 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
|
|||
static toUndecorated(stored: string): string {
|
||||
return stored
|
||||
.replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => {
|
||||
const escaped = translateEntitiesToMathjax(text);
|
||||
return `<${Mathjax.tagName} block="true">${escaped}</${Mathjax.tagName}>`;
|
||||
return `<${Mathjax.tagName} block="true">${text}</${Mathjax.tagName}>`;
|
||||
})
|
||||
.replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => {
|
||||
const escaped = translateEntitiesToMathjax(text);
|
||||
return `<${Mathjax.tagName}>${escaped}</${Mathjax.tagName}>`;
|
||||
return `<${Mathjax.tagName}>${text}</${Mathjax.tagName}>`;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -107,7 +97,7 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
|
|||
return;
|
||||
}
|
||||
|
||||
this.dataset.mathjax = this.innerText;
|
||||
this.dataset.mathjax = this.innerHTML;
|
||||
this.innerHTML = "";
|
||||
this.style.whiteSpace = "normal";
|
||||
|
||||
|
|
|
@ -65,3 +65,14 @@ export function convertMathjax(
|
|||
|
||||
return [svg.outerHTML, title];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape characters which are technically legal in Mathjax, but confuse HTML.
|
||||
*/
|
||||
export function escapeSomeEntities(value: string): string {
|
||||
return value.replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
export function unescapeSomeEntities(value: string): string {
|
||||
return value.replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
|
|
@ -87,13 +87,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Escape characters which are technically legal in Mathjax, but confuse HTML.
|
||||
*/
|
||||
export function escapeSomeEntities(value: string): string {
|
||||
return value.replace(/</g, "{\\lt}").replace(/>/g, "{\\gt}");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mathjax-editor">
|
||||
|
@ -101,7 +94,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
{code}
|
||||
{configuration}
|
||||
bind:api={codeMirror}
|
||||
on:change={({ detail }) => code.set(escapeSomeEntities(detail))}
|
||||
on:change={({ detail: mathjaxText }) => code.set(mathjaxText)}
|
||||
on:blur
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
import WithDropdown from "../../components/WithDropdown.svelte";
|
||||
import { escapeSomeEntities, unescapeSomeEntities } from "../../editable/mathjax";
|
||||
import { Mathjax } from "../../editable/mathjax-element";
|
||||
import { on } from "../../lib/events";
|
||||
import { noop } from "../../lib/functional";
|
||||
|
@ -20,8 +21,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
const { container, api } = context.get();
|
||||
const { editable, preventResubscription } = api;
|
||||
|
||||
const code = writable("");
|
||||
|
||||
let activeImage: HTMLImageElement | null = null;
|
||||
let mathjaxElement: HTMLElement | null = null;
|
||||
let allow = noop;
|
||||
|
@ -30,6 +29,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
let selectAll = false;
|
||||
let position: CodeMirrorLib.Position | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Will contain the Mathjax text with unescaped entities.
|
||||
* This is the text displayed in the actual editor window.
|
||||
*/
|
||||
const code = writable("");
|
||||
|
||||
function showHandle(image: HTMLImageElement, pos?: CodeMirrorLib.Position): void {
|
||||
allow = preventResubscription();
|
||||
position = pos;
|
||||
|
@ -39,9 +44,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
activeImage = image;
|
||||
mathjaxElement = activeImage.closest(Mathjax.tagName)!;
|
||||
|
||||
code.set(mathjaxElement.dataset.mathjax ?? "");
|
||||
code.set(unescapeSomeEntities(mathjaxElement.dataset.mathjax ?? ""));
|
||||
unsubscribe = code.subscribe((value: string) => {
|
||||
mathjaxElement!.dataset.mathjax = value;
|
||||
mathjaxElement!.dataset.mathjax = escapeSomeEntities(value);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ function adjustInputFragment(fragment: DocumentFragment): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function storedToFragment(html: string): DocumentFragment {
|
||||
export function storedToFragment(storedHTML: string): DocumentFragment {
|
||||
/* We need .createContextualFragment so that customElements are initialized */
|
||||
const fragment = document
|
||||
.createRange()
|
||||
.createContextualFragment(createDummyDoc(adjustInputHTML(html)));
|
||||
.createContextualFragment(createDummyDoc(adjustInputHTML(storedHTML)));
|
||||
|
||||
adjustInputFragment(fragment);
|
||||
return fragment;
|
||||
|
@ -56,5 +56,6 @@ export function fragmentToStored(fragment: DocumentFragment): string {
|
|||
const clone = document.importNode(fragment, true);
|
||||
adjustOutputFragment(clone);
|
||||
|
||||
return adjustOutputHTML(fragmentToString(clone));
|
||||
const storedHTML = adjustOutputHTML(fragmentToString(clone));
|
||||
return storedHTML;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
import Popover from "../../components/Popover.svelte";
|
||||
import WithFloating from "../../components/WithFloating.svelte";
|
||||
import { isApplePlatform } from "../../lib/platform";
|
||||
import AutocompleteItem from "./AutocompleteItem.svelte";
|
||||
|
||||
export let suggestionsPromise: Promise<string[]>;
|
||||
|
@ -16,6 +17,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
let suggestionsItems: string[] = [];
|
||||
$: suggestionsPromise.then((items) => {
|
||||
show.set(items.length > 0);
|
||||
if (isApplePlatform() && navigator.userAgent.match(/Chrome\/77/)) {
|
||||
items = items.slice(0, 10);
|
||||
}
|
||||
suggestionsItems = items;
|
||||
});
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
sum,
|
||||
} from "d3";
|
||||
|
||||
import { CardQueue } from "../lib/cards";
|
||||
import { CardType } from "../lib/cards";
|
||||
import * as tr from "../lib/ftl";
|
||||
import { localizedNumber } from "../lib/i18n";
|
||||
import type { Cards, Stats } from "../lib/proto";
|
||||
|
@ -31,19 +31,15 @@ export interface GraphData {
|
|||
}
|
||||
|
||||
export function gatherData(data: Stats.GraphsResponse): GraphData {
|
||||
const isLearning = (card: Cards.Card): boolean =>
|
||||
[CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue);
|
||||
|
||||
const isIntradayLearning = (card: Cards.Card, due: number): boolean => {
|
||||
return (
|
||||
[CardType.Learn, CardType.Relearn].includes(card.ctype) &&
|
||||
due > 1_000_000_000
|
||||
);
|
||||
};
|
||||
let haveBacklog = false;
|
||||
const due = (data.cards as Cards.Card[])
|
||||
.filter((c: Cards.Card) => {
|
||||
// reviews
|
||||
return (
|
||||
[CardQueue.Review, CardQueue.DayLearn].includes(c.queue) ||
|
||||
// or learning cards
|
||||
isLearning(c)
|
||||
);
|
||||
})
|
||||
.filter((c: Cards.Card) => c.queue >= 0)
|
||||
.map((c: Cards.Card) => {
|
||||
// - testing just odue fails on day 1
|
||||
// - testing just odid fails on lapsed cards that
|
||||
|
@ -51,7 +47,7 @@ export function gatherData(data: Stats.GraphsResponse): GraphData {
|
|||
const due = c.originalDeckId && c.originalDue ? c.originalDue : c.due;
|
||||
|
||||
let dueDay: number;
|
||||
if (isLearning(c)) {
|
||||
if (isIntradayLearning(c, due)) {
|
||||
const offset = due - data.nextDayAtSecs;
|
||||
dueDay = Math.floor(offset / 86_400) + 1;
|
||||
} else {
|
||||
|
|
|
@ -110,6 +110,9 @@ export function nodeContainsInlineContent(node: Node): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the input fragment.
|
||||
*/
|
||||
export function fragmentToString(fragment: DocumentFragment): string {
|
||||
const fragmentDiv = document.createElement("div");
|
||||
fragmentDiv.appendChild(fragment);
|
||||
|
|
|
@ -61,21 +61,8 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
|
|||
insertText.clear();
|
||||
}
|
||||
|
||||
function onInput(this: HTMLElement, event: InputEvent): void {
|
||||
// prevent unwanted <div> from being left behind when clearing field contents
|
||||
if (
|
||||
!event.data &&
|
||||
this.children.length === 1 &&
|
||||
this.children.item(0) instanceof HTMLDivElement &&
|
||||
/^\n?$/.test(this.innerText)
|
||||
) {
|
||||
this.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
function setupHandler(element: HTMLElement): { destroy(): void } {
|
||||
const beforeInputOff = on(element, "beforeinput", onBeforeInput);
|
||||
const inputOff = on(element, "input" as "beforeinput", onInput);
|
||||
|
||||
const blurOff = on(element, "blur", clearInsertText);
|
||||
const pointerDownOff = on(element, "pointerdown", clearInsertText);
|
||||
|
@ -84,7 +71,6 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
|
|||
return {
|
||||
destroy() {
|
||||
beforeInputOff();
|
||||
inputOff();
|
||||
blurOff();
|
||||
pointerDownOff();
|
||||
selectionChangeOff();
|
||||
|
|
Loading…
Reference in a new issue