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/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) 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( 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) 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/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; }); 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 { 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); 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();