Added: TypeAnswer replacement

This commit is contained in:
Luc Mcgrady 2025-10-31 12:06:58 +00:00
parent cf9c265570
commit bbf575e491
No known key found for this signature in database
GPG key ID: 4F3D7A0B17CC3D9C
4 changed files with 50 additions and 3 deletions

View file

@ -306,6 +306,7 @@ message NextCardDataResponse {
string css = 5;
string body_class = 6;
bool autoplay = 7;
optional string typed_answer = 12;
repeated card_rendering.AVTag question_av_tags = 8;
repeated card_rendering.AVTag answer_av_tags = 9;

View file

@ -4,6 +4,8 @@
mod answering;
mod states;
use std::sync::LazyLock;
use anki_proto::cards;
use anki_proto::generic;
use anki_proto::scheduler;
@ -25,6 +27,7 @@ use fsrs::ComputeParametersInput;
use fsrs::FSRSItem;
use fsrs::FSRSReview;
use fsrs::FSRS;
use regex::Regex;
use crate::backend::Backend;
use crate::card_rendering::service::rendered_nodes_to_proto;
@ -34,7 +37,9 @@ use crate::scheduler::new::NewCardDueOrder;
use crate::scheduler::states::CardState;
use crate::scheduler::states::SchedulingStates;
use crate::search::SortMode;
use crate::services::NotesService;
use crate::stats::studied_today;
use crate::template::RenderedNode;
impl crate::services::SchedulerService for Collection {
/// This behaves like _updateCutoff() in older code - it also unburies at
@ -411,16 +416,48 @@ impl crate::services::SchedulerService for Collection {
let config = self.deck_config_for_card(&next_card.card)?;
// Typed answer replacements
static ANSWER_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[\[type:(.+?:)?(.+?)\]\]").unwrap());
const ANSWER_HTML: &str = "<center>
<input type=text id=typeans onkeypress=\"_typeAnsPress();\"
style=\"font-family: '{self.typeFont}'; font-size: {self.typeSize}px;\">
</center>";
let mut q_nodes = render.qnodes;
let typed_answer_parent_node = q_nodes.iter_mut().find_map(|node| {
if let RenderedNode::Text { text } = node {
let mut out = None;
*text = ANSWER_REGEX
.replace(text, |cap: &regex::Captures<'_>| {
out = Some(cap[2].to_string());
ANSWER_HTML
})
.to_string();
out
} else {
None
}
});
let typed_answer = typed_answer_parent_node.map(|field| {
let note = self.get_note(next_card.card.note_id.into()).unwrap();
let notetype = self.get_notetype(note.notetype_id.into()).unwrap().unwrap();
note.fields[notetype.get_field_ord(&field).unwrap()].clone()
});
Ok(NextCardDataResponse {
next_card: Some(NextCardData {
queue: Some(queue.into()),
css: render.css.clone(),
partial_front: rendered_nodes_to_proto(render.qnodes),
partial_front: rendered_nodes_to_proto(q_nodes),
partial_back: rendered_nodes_to_proto(render.anodes),
answer_buttons,
autoplay: !config.inner.disable_autoplay,
typed_answer,
// Filled by python
front: "".to_string(),

View file

@ -27,6 +27,7 @@ export function updateNightMode() {
export class ReviewerState {
answerHtml = "";
currentTypedAnswer = "";
_cardData: NextCardDataResponse_NextCardData | undefined = undefined;
beginAnsweringMs = Date.now();
readonly cardClass = writable("");
@ -42,13 +43,16 @@ export class ReviewerState {
addEventListener("message", this.onMessage.bind(this));
}
onMessage(e: MessageEvent<ReviewerRequest>) {
async onMessage(e: MessageEvent<ReviewerRequest>) {
switch (e.data.type) {
case "audio": {
const tags = get(this.answerShown) ? this._cardData!.answerAvTags : this._cardData!.questionAvTags;
playAvtags({ tags: [tags[e.data.index]] });
break;
}
case "typed": {
this.currentTypedAnswer = e.data.value;
}
}
}

View file

@ -6,4 +6,9 @@ interface AudioMessage {
index: number;
}
export type ReviewerRequest = AudioMessage;
interface CompareTypedAnswerMessage {
type: "typed";
value: string;
}
export type ReviewerRequest = AudioMessage | CompareTypedAnswerMessage;