Merge remote-tracking branch 'upstream' into apkg

This commit is contained in:
RumovZ 2022-04-30 19:35:17 +02:00
commit 92319116d4
15 changed files with 75 additions and 78 deletions

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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(

View file

@ -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)

View file

@ -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);

View file

@ -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(/&lt;/g, "{\\lt}").replace(/&gt;/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";

View file

@ -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, "&lt;").replace(/>/g, "&gt;");
}
export function unescapeSomeEntities(value: string): string {
return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
}

View file

@ -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>

View file

@ -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);
});
}

View file

@ -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;
}

View file

@ -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;
});

View file

@ -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 {

View file

@ -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);

View file

@ -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();