From 2be1f4c56da3cd4964f590dcfeded85340aa4d74 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 25 Apr 2022 05:42:54 +0200 Subject: [PATCH 1/7] Instead of transforming to {\lt}, transform to < (#1818) * Instead of transforming to {\lt}, transform to < - In Mathjax editor - This way you can also use Mathjax convenience shortcuts like <=> in chemistry mode: \ce{<=>} * Remove unused translateEntitiesToMathjax() (dae) https://github.com/ankitects/anki/pull/1818#discussion_r857238310 --- ts/editable/Mathjax.svelte | 8 ++++++-- ts/editable/mathjax-element.ts | 16 +++------------- ts/editable/mathjax.ts | 11 +++++++++++ ts/editor/mathjax-overlay/MathjaxEditor.svelte | 9 +-------- ts/editor/mathjax-overlay/MathjaxHandle.svelte | 13 +++++++++---- ts/editor/rich-text-input/transform.ts | 7 ++++--- ts/lib/dom.ts | 3 +++ 7 files changed, 37 insertions(+), 30 deletions(-) diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index cc56a1b2c..03687b73a 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -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); diff --git a/ts/editable/mathjax-element.ts b/ts/editable/mathjax-element.ts index f2b24e335..1d7117cf5 100644 --- a/ts/editable/mathjax-element.ts +++ b/ts/editable/mathjax-element.ts @@ -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}`; + return `<${Mathjax.tagName} block="true">${text}`; }) .replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => { - const escaped = translateEntitiesToMathjax(text); - return `<${Mathjax.tagName}>${escaped}`; + return `<${Mathjax.tagName}>${text}`; }); } @@ -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"; diff --git a/ts/editable/mathjax.ts b/ts/editable/mathjax.ts index 6338b3f5d..825f9d7ed 100644 --- a/ts/editable/mathjax.ts +++ b/ts/editable/mathjax.ts @@ -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, ">"); +} + +export function unescapeSomeEntities(value: string): string { + return value.replace(/</g, "<").replace(/>/g, ">"); +} diff --git a/ts/editor/mathjax-overlay/MathjaxEditor.svelte b/ts/editor/mathjax-overlay/MathjaxEditor.svelte index dfd74f6a4..c9ac38e41 100644 --- a/ts/editor/mathjax-overlay/MathjaxEditor.svelte +++ b/ts/editor/mathjax-overlay/MathjaxEditor.svelte @@ -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, "{\\gt}"); - }
@@ -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 />
diff --git a/ts/editor/mathjax-overlay/MathjaxHandle.svelte b/ts/editor/mathjax-overlay/MathjaxHandle.svelte index 6f13d384d..4b58ef1f3 100644 --- a/ts/editor/mathjax-overlay/MathjaxHandle.svelte +++ b/ts/editor/mathjax-overlay/MathjaxHandle.svelte @@ -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); }); } diff --git a/ts/editor/rich-text-input/transform.ts b/ts/editor/rich-text-input/transform.ts index 185992834..33ce0afb8 100644 --- a/ts/editor/rich-text-input/transform.ts +++ b/ts/editor/rich-text-input/transform.ts @@ -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; } diff --git a/ts/lib/dom.ts b/ts/lib/dom.ts index 1cf599050..60b79689a 100644 --- a/ts/lib/dom.ts +++ b/ts/lib/dom.ts @@ -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); From b8eabfd622266232c63971cb71e01c719b26e262 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 25 Apr 2022 05:56:05 +0200 Subject: [PATCH 2/7] Remove onInput call in input handler (#1819) This was implemented by me to prevent seemingly empty fields, which however still contain some HTML (e.g.
) and thus will trigger in the templates. However it only worked in very limited cases, maybe we could solve this better by indicating whether a field has content somehow. --- ts/sveltelib/input-handler.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ts/sveltelib/input-handler.ts b/ts/sveltelib/input-handler.ts index ff0ec573b..8bd0bc2c4 100644 --- a/ts/sveltelib/input-handler.ts +++ b/ts/sveltelib/input-handler.ts @@ -61,21 +61,8 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] { insertText.clear(); } - function onInput(this: HTMLElement, event: InputEvent): void { - // prevent unwanted
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(); From f60de3901596549b9cd6c68e69a6c43aec77a8c5 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 25 Apr 2022 14:14:08 +1000 Subject: [PATCH 3/7] Fix due graph showing wrong date for review cards with resched off Since we are using the original due date instead of the current one, the learning check needs to be based on the card type, not its current queue. https://forums.ankiweb.net/t/anki-2-1-51-release-candidate/18942/22 --- ts/graphs/future-due.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ts/graphs/future-due.ts b/ts/graphs/future-due.ts index 801ac94e7..89801b9f7 100644 --- a/ts/graphs/future-due.ts +++ b/ts/graphs/future-due.ts @@ -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 { From a989e508384a53cb2c8c7e0b2493171bd8e1ec94 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 25 Apr 2022 14:35:35 +1000 Subject: [PATCH 4/7] Update translations --- repos.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repos.bzl b/repos.bzl index c734784fd..6e7e6daba 100644 --- a/repos.bzl +++ b/repos.bzl @@ -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( From 6f0d86b9a97ae425cd09fa86eec67f7b185c13a2 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 27 Apr 2022 19:23:07 +1000 Subject: [PATCH 5/7] Cap tag matches to 10 on macOS/Qt5 to work around Chromium perf issue Closes #1825 --- ts/editor/tag-editor/WithAutocomplete.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/editor/tag-editor/WithAutocomplete.svelte b/ts/editor/tag-editor/WithAutocomplete.svelte index 54112883d..9ffbacc3e 100644 --- a/ts/editor/tag-editor/WithAutocomplete.svelte +++ b/ts/editor/tag-editor/WithAutocomplete.svelte @@ -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; @@ -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; }); From 27f2e39ff91ae166d26807818fedad3cedf6e352 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 27 Apr 2022 19:26:16 +1000 Subject: [PATCH 6/7] Drop default Show Answer/Good highlight (#1820) Feedback welcome. Discussion at https://forums.ankiweb.net/t/2-1-5-border-around-good/19175 Partially reverts 301c9587d1a71fab79c909ab3db5dd4a88c2458c --- qt/aqt/data/web/css/reviewer-bottom.scss | 15 +-------------- qt/aqt/reviewer.py | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/qt/aqt/data/web/css/reviewer-bottom.scss b/qt/aqt/data/web/css/reviewer-bottom.scss index e124ce05f..67081ad45 100644 --- a/qt/aqt/data/web/css/reviewer-bottom.scss +++ b/qt/aqt/data/web/css/reviewer-bottom.scss @@ -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; diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index a6a35a350..e200b41a3 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -763,7 +763,7 @@ time = %(time)d; def _showAnswerButton(self) -> None: middle = """ {}
-""".format( +""".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) From a3d9a25d18428b40fef6f00b33e4c769e1d71916 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 28 Apr 2022 07:09:51 +0300 Subject: [PATCH 7/7] Extends the SearchNode interface to support field and unqualified searches (#1821) * Add field search option to SearchNode * Add unqualified/literal search option to SearchNode * Rename text to literal_text * Interpret field name parameter literally --- proto/anki/search.proto | 8 ++++++++ rslib/src/backend/search/search_node.rs | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/proto/anki/search.proto b/proto/anki/search.proto index 5b9280928..4048d255a 100644 --- a/proto/anki/search.proto +++ b/proto/anki/search.proto @@ -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; } } diff --git a/rslib/src/backend/search/search_node.rs b/rslib/src/backend/search/search_node.rs index 469cc7391..8df1477e7 100644 --- a/rslib/src/backend/search/search_node.rs +++ b/rslib/src/backend/search/search_node.rs @@ -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 for Node { @@ -98,6 +98,15 @@ impl TryFrom 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)