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; repeated SearchNode nodes = 1;
Joiner joiner = 2; Joiner joiner = 2;
} }
message Field {
string field_name = 1;
string text = 2;
bool is_re = 3;
}
oneof filter { oneof filter {
Group group = 1; Group group = 1;
SearchNode negated = 2; SearchNode negated = 2;
@ -88,6 +94,8 @@ message SearchNode {
string tag = 17; string tag = 17;
string note = 18; string note = 18;
uint32 introduced_in_days = 19; uint32 introduced_in_days = 19;
Field field = 20;
string literal_text = 21;
} }
} }

View file

@ -49,12 +49,7 @@ button {
margin-bottom: 1em; margin-bottom: 1em;
} }
/** :focus {
* We use .focus to recreate the highlight on the good button
* while the actual focus is actually in the main webview
*/
:focus,
.focus {
outline: 1px auto var(--focus-color); outline: 1px auto var(--focus-color);
.nightMode & { .nightMode & {
@ -63,14 +58,6 @@ button {
} }
} }
#innertable:focus-within .focus:not(:focus) {
outline: none;
.nightMode & {
box-shadow: none;
}
}
.nobold { .nobold {
font-weight: normal; font-weight: normal;
display: inline-block; display: inline-block;

View file

@ -763,7 +763,7 @@ time = %(time)d;
def _showAnswerButton(self) -> None: def _showAnswerButton(self) -> None:
middle = """ middle = """
<span class=stattxt>{}</span><br> <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(), self._remaining(),
tr.actions_shortcut_key(val=tr.studying_space()), tr.actions_shortcut_key(val=tr.studying_space()),
tr.studying_show_answer(), tr.studying_show_answer(),
@ -851,7 +851,7 @@ time = %(time)d;
def but(i: int, label: str) -> str: def but(i: int, label: str) -> str:
if i == default: if i == default:
extra = """id="defease" class="focus" """ extra = """id="defease" """
else: else:
extra = "" extra = ""
due = self._buttonTime(i, v3_labels=labels) due = self._buttonTime(i, v3_labels=labels)

View file

@ -115,12 +115,12 @@ def register_repos():
################ ################
core_i18n_repo = "anki-core-i18n" core_i18n_repo = "anki-core-i18n"
core_i18n_commit = "3fbbbd277adf6a307aad1f1389105740fb14bb52" core_i18n_commit = "7b86958ed51bcd63ea878cd781936976d6cf26f4"
core_i18n_zip_csum = "18db75ca57b5068c0c9cb926f19b451185cf04a086004a96fcf8f6e734f51b29" core_i18n_zip_csum = "accbb148e56f5b64dcd5981a563889cdd4b84d6ea0f5464914c7844bc5de1e75"
qtftl_i18n_repo = "anki-desktop-ftl" qtftl_i18n_repo = "anki-desktop-ftl"
qtftl_i18n_commit = "a6811a12eaf86ff02d5ce168ac65022f3e442993" qtftl_i18n_commit = "10532f009d01145bc7490423db66375cc2e7dae3"
qtftl_i18n_zip_csum = "03b62eb593e4929e502e6ee6d7fd81492d6fe9164aec50573e30028edbc9bf2e" qtftl_i18n_zip_csum = "268cefd8650b827d3b03a6bdc1ffae8a7309523c2da4416e27afc3c8686920f6"
i18n_build_content = """ i18n_build_content = """
filegroup( filegroup(

View file

@ -9,7 +9,7 @@ use crate::{
search::{ search::{
parse_search, Negated, Node, PropertyKind, RatingKind, SearchNode, StateKind, TemplateKind, 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 { impl TryFrom<pb::SearchNode> for Node {
@ -98,6 +98,15 @@ impl TryFrom<pb::SearchNode> for Node {
Node::Group(nodes) 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 { } else {
Node::Search(SearchNode::WholeCollection) 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 { randomUUID } from "../lib/uuid";
import { pageTheme } from "../sveltelib/theme"; import { pageTheme } from "../sveltelib/theme";
import { convertMathjax } from "./mathjax"; import { convertMathjax, unescapeSomeEntities } from "./mathjax";
export let mathjax: string; export let mathjax: string;
export let block: boolean; export let block: boolean;
export let fontSize: number; export let fontSize: number;
$: [converted, title] = convertMathjax(mathjax, $pageTheme.isDark, fontSize); $: [converted, title] = convertMathjax(
unescapeSomeEntities(mathjax),
$pageTheme.isDark,
fontSize,
);
$: empty = title === "MathJax"; $: empty = title === "MathJax";
$: encoded = encodeURIComponent(converted); $: encoded = encodeURIComponent(converted);

View file

@ -15,14 +15,6 @@ const mathjaxTagPattern =
const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu;
const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/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 export const Mathjax: DecoratedElementConstructor = class Mathjax
extends HTMLElement extends HTMLElement
implements DecoratedElement implements DecoratedElement
@ -45,12 +37,10 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
static toUndecorated(stored: string): string { static toUndecorated(stored: string): string {
return stored return stored
.replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => { .replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => {
const escaped = translateEntitiesToMathjax(text); return `<${Mathjax.tagName} block="true">${text}</${Mathjax.tagName}>`;
return `<${Mathjax.tagName} block="true">${escaped}</${Mathjax.tagName}>`;
}) })
.replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => { .replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => {
const escaped = translateEntitiesToMathjax(text); return `<${Mathjax.tagName}>${text}</${Mathjax.tagName}>`;
return `<${Mathjax.tagName}>${escaped}</${Mathjax.tagName}>`;
}); });
} }
@ -107,7 +97,7 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
return; return;
} }
this.dataset.mathjax = this.innerText; this.dataset.mathjax = this.innerHTML;
this.innerHTML = ""; this.innerHTML = "";
this.style.whiteSpace = "normal"; this.style.whiteSpace = "normal";

View file

@ -65,3 +65,14 @@ export function convertMathjax(
return [svg.outerHTML, title]; 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> </script>
<div class="mathjax-editor"> <div class="mathjax-editor">
@ -101,7 +94,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{code} {code}
{configuration} {configuration}
bind:api={codeMirror} bind:api={codeMirror}
on:change={({ detail }) => code.set(escapeSomeEntities(detail))} on:change={({ detail: mathjaxText }) => code.set(mathjaxText)}
on:blur on:blur
/> />
</div> </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 { writable } from "svelte/store";
import WithDropdown from "../../components/WithDropdown.svelte"; import WithDropdown from "../../components/WithDropdown.svelte";
import { escapeSomeEntities, unescapeSomeEntities } from "../../editable/mathjax";
import { Mathjax } from "../../editable/mathjax-element"; import { Mathjax } from "../../editable/mathjax-element";
import { on } from "../../lib/events"; import { on } from "../../lib/events";
import { noop } from "../../lib/functional"; 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 { container, api } = context.get();
const { editable, preventResubscription } = api; const { editable, preventResubscription } = api;
const code = writable("");
let activeImage: HTMLImageElement | null = null; let activeImage: HTMLImageElement | null = null;
let mathjaxElement: HTMLElement | null = null; let mathjaxElement: HTMLElement | null = null;
let allow = noop; 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 selectAll = false;
let position: CodeMirrorLib.Position | undefined = undefined; 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 { function showHandle(image: HTMLImageElement, pos?: CodeMirrorLib.Position): void {
allow = preventResubscription(); allow = preventResubscription();
position = pos; position = pos;
@ -39,9 +44,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
activeImage = image; activeImage = image;
mathjaxElement = activeImage.closest(Mathjax.tagName)!; mathjaxElement = activeImage.closest(Mathjax.tagName)!;
code.set(mathjaxElement.dataset.mathjax ?? ""); code.set(unescapeSomeEntities(mathjaxElement.dataset.mathjax ?? ""));
unsubscribe = code.subscribe((value: string) => { 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 */ /* We need .createContextualFragment so that customElements are initialized */
const fragment = document const fragment = document
.createRange() .createRange()
.createContextualFragment(createDummyDoc(adjustInputHTML(html))); .createContextualFragment(createDummyDoc(adjustInputHTML(storedHTML)));
adjustInputFragment(fragment); adjustInputFragment(fragment);
return fragment; return fragment;
@ -56,5 +56,6 @@ export function fragmentToStored(fragment: DocumentFragment): string {
const clone = document.importNode(fragment, true); const clone = document.importNode(fragment, true);
adjustOutputFragment(clone); 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 Popover from "../../components/Popover.svelte";
import WithFloating from "../../components/WithFloating.svelte"; import WithFloating from "../../components/WithFloating.svelte";
import { isApplePlatform } from "../../lib/platform";
import AutocompleteItem from "./AutocompleteItem.svelte"; import AutocompleteItem from "./AutocompleteItem.svelte";
export let suggestionsPromise: Promise<string[]>; 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[] = []; let suggestionsItems: string[] = [];
$: suggestionsPromise.then((items) => { $: suggestionsPromise.then((items) => {
show.set(items.length > 0); show.set(items.length > 0);
if (isApplePlatform() && navigator.userAgent.match(/Chrome\/77/)) {
items = items.slice(0, 10);
}
suggestionsItems = items; suggestionsItems = items;
}); });

View file

@ -16,7 +16,7 @@ import {
sum, sum,
} from "d3"; } from "d3";
import { CardQueue } from "../lib/cards"; import { CardType } from "../lib/cards";
import * as tr from "../lib/ftl"; import * as tr from "../lib/ftl";
import { localizedNumber } from "../lib/i18n"; import { localizedNumber } from "../lib/i18n";
import type { Cards, Stats } from "../lib/proto"; import type { Cards, Stats } from "../lib/proto";
@ -31,19 +31,15 @@ export interface GraphData {
} }
export function gatherData(data: Stats.GraphsResponse): GraphData { export function gatherData(data: Stats.GraphsResponse): GraphData {
const isLearning = (card: Cards.Card): boolean => const isIntradayLearning = (card: Cards.Card, due: number): boolean => {
[CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue); return (
[CardType.Learn, CardType.Relearn].includes(card.ctype) &&
due > 1_000_000_000
);
};
let haveBacklog = false; let haveBacklog = false;
const due = (data.cards as Cards.Card[]) const due = (data.cards as Cards.Card[])
.filter((c: Cards.Card) => { .filter((c: Cards.Card) => c.queue >= 0)
// reviews
return (
[CardQueue.Review, CardQueue.DayLearn].includes(c.queue) ||
// or learning cards
isLearning(c)
);
})
.map((c: Cards.Card) => { .map((c: Cards.Card) => {
// - testing just odue fails on day 1 // - testing just odue fails on day 1
// - testing just odid fails on lapsed cards that // - 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; const due = c.originalDeckId && c.originalDue ? c.originalDue : c.due;
let dueDay: number; let dueDay: number;
if (isLearning(c)) { if (isIntradayLearning(c, due)) {
const offset = due - data.nextDayAtSecs; const offset = due - data.nextDayAtSecs;
dueDay = Math.floor(offset / 86_400) + 1; dueDay = Math.floor(offset / 86_400) + 1;
} else { } else {

View file

@ -110,6 +110,9 @@ export function nodeContainsInlineContent(node: Node): boolean {
return true; return true;
} }
/**
* Consumes the input fragment.
*/
export function fragmentToString(fragment: DocumentFragment): string { export function fragmentToString(fragment: DocumentFragment): string {
const fragmentDiv = document.createElement("div"); const fragmentDiv = document.createElement("div");
fragmentDiv.appendChild(fragment); fragmentDiv.appendChild(fragment);

View file

@ -61,21 +61,8 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
insertText.clear(); 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 } { function setupHandler(element: HTMLElement): { destroy(): void } {
const beforeInputOff = on(element, "beforeinput", onBeforeInput); const beforeInputOff = on(element, "beforeinput", onBeforeInput);
const inputOff = on(element, "input" as "beforeinput", onInput);
const blurOff = on(element, "blur", clearInsertText); const blurOff = on(element, "blur", clearInsertText);
const pointerDownOff = on(element, "pointerdown", clearInsertText); const pointerDownOff = on(element, "pointerdown", clearInsertText);
@ -84,7 +71,6 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
return { return {
destroy() { destroy() {
beforeInputOff(); beforeInputOff();
inputOff();
blurOff(); blurOff();
pointerDownOff(); pointerDownOff();
selectionChangeOff(); selectionChangeOff();