def on_card_did_render( output: TemplateRenderOutput, ctx: TemplateRenderContext ) -> None: output.question_text = render_latex( output.question_text, ctx.note_type(), ctx.col() ) output.answer_text = render_latex(output.answer_text, ctx.note_type(), ctx.col())
def on_card_did_render(text: Tuple[str, str], ctx: TemplateRenderContext) -> Tuple[str, str]: qtext, atext = text qtext = render_latex(qtext, ctx.note_type(), ctx.col()) atext = render_latex(atext, ctx.note_type(), ctx.col()) return (qtext, atext)
def on_field_filter(text, field, filter, context: TemplateRenderContext): if filter != "clickable" or field != "Tags": return text kbd = """ <kbd onclick="ct_click('{tag}')" ondblclick="ct_dblclick('{tag}', '{deck}')"> {tag} </kbd> """ return "".join([ kbd.format(tag=tag, deck=context.fields()["Deck"]) for tag in context.fields()["Tags"].split() ])
def on_field_filter(text, field, filter, context: TemplateRenderContext): if filter != 'clickable' or field != 'Tags': return text kbd = """ <kbd onclick="ct_click('{tag}')" ondblclick="ct_dblclick('{tag}', '{deck}')"> {tag} </kbd> """ return ''.join([ kbd.format(tag=tag, deck=context.fields()['Deck']) for tag in context.fields()['Tags'].split() ])
def hanzi_context( txt: str, field_name: str, filter_name: str, context: TemplateRenderContext, ) -> str: if not filter_name.startswith("hanzi_context"): # not our filter, return string unchanged return txt ''' For use on a Hanzi field. Return a list of all the other Hanzi synonyms, with the common characters hidden, to allow the user to identify the correct hanzi from a note. ''' other_hanzi = [] for k, v in context.iteritems(): if re.match(r'Hanzi.*', k, flags=re.IGNORECASE) and v != txt : other_hanzi += [k] if len(other_hanzi)<1: return "" other_hanzi.sort() other_hanzi_values = [] for v in other_hanzi: value = stripHTML(re.sub(r, r'\1', no_sound(context[v]))) if len(value)>0: other_hanzi_values += [value] if len(other_hanzi_values)<1: return "" def concat(a, b): return a + " / " + b context_string = reduce(concat, other_hanzi_values) for h in txt: if h >= u'\u4e00' and h <= u'\u9fff': context_string = re.sub(h, " _ ", context_string) context_string = re.sub(" ", " ", context_string) return context_string
def on_edit_filter(text, field, filter, context: TemplateRenderContext): if filter != "edit": return text if not safe_to_edit(text): return text config = mw.addonManager.getConfig(__name__) card = context.card() nid = card.nid if card is not None else "" text = """<%s contenteditable="true" data-field="%s" data-nid="%s">%s</%s>""" % ( config['tag'], field, nid, text, config['tag']) text += """<script>""" text += """ $("[contenteditable=true][data-field='%s']").blur(function() { pycmd("ankisave#" + $(this).data("field") + "#" + $(this).data("nid") + "#" + $(this).html()); }); """ % field if config['tag'] == "span": text += """ $("[contenteditable=true][data-field='%s']").keydown(function(evt) { if (evt.keyCode == 8) { evt.stopPropagation(); } }); """ % field text += """ $("[contenteditable=true][data-field='%s']").focus(function() { pycmd("ankisave!speedfocus#"); }); """ % field text += """</script>""" return text
def on_field_filter(text, field, filter, context: TemplateRenderContext): if filter != 'clickable': return text if field == "Tags": kbd = """ <kbd onclick="ct_click('{fieldcontent}', '{fieldname}')"> {fieldcontent} </kbd> """ return ''.join([ kbd.format(fieldname=field[:-1], fieldcontent=fieldcontent) for fieldcontent in context.fields()['Tags'].split() ]) kbd = """ <button class="xbutton" onclick="ct_click('{fieldcontent}', '{fieldname}')"><span id = "button-{fieldname}">※</span></button>{fieldcontent} """ return kbd.format(fieldname=field, fieldcontent=context.fields()[field])
def on_card_did_render(output: TemplateRenderOutput, context: TemplateRenderContext): # let's uppercase the characters of the front text output.question_text = output.question_text.upper() # if the note is tagged "easy", show the answer in green # otherwise, in red if context.note().has_tag("easy"): colour = "green" else: colour = "red" output.answer_text += f"<style>.card {{ color: {colour}; }}</style>"
def on_field_filter(text: str, field: str, filter: str, context: TemplateRenderContext) -> str: if not filter.startswith("info-"): return text # generate fields if not yet generated if "info_fields" not in context.extra_state: context.extra_state["info_fields"] = get_all_fields(context) info_fields: Dict[str, Any] = context.extra_state["info_fields"] # extract the requested field info, field = filter.split("-", maxsplit=1) return str(info_fields.get(field, f"Unknown field: {field}"))
def ephemeral_card_for_rendering(self) -> Card: card = Card(self.col) card.ord = self.ord card.did = 1 template = copy.copy(self.current_template()) # may differ in cloze case template["ord"] = card.ord output = TemplateRenderContext.from_card_layout( self.note, card, notetype=self.model, template=template, fill_empty=self.fill_empty_action_toggled, ).render() card.set_render_output(output) return card
def build_export_context(self): print(f"Exporting {self.deck_name} deck") for card in get_cards(self.collection, self.deck_name): note = self.collection.getNote(card.nid) self.css_fragments.append( self.collection.models.get(note.mid)['css']) rendering = TemplateRenderContext.from_existing_card( card, False).render() answer_text, images = extract_image_names(rendering.answer_text) self.images += images self.card_fragments.append( self.get_card_fragment(answer_text, card, note)) self.collection.close() print(f"Exporting {len(self.card_fragments)} cards")
def _renderQA(self, data: QAData, qfmt: Optional[str] = None, afmt: Optional[str] = None) -> Dict[str, Union[str, int]]: # extract info from data split_fields = splitFields(data[6]) card_ord = data[4] model = self.models.get(data[2]) if model["type"] == MODEL_STD: template = model["tmpls"][data[4]] else: template = model["tmpls"][0] flag = data[7] deck_id = data[3] card_id = data[0] tags = data[5] qfmt = qfmt or template["qfmt"] afmt = afmt or template["afmt"] # create map of field names -> field content fields: Dict[str, str] = {} for (name, (idx, conf)) in list(self.models.fieldMap(model).items()): fields[name] = split_fields[idx] # add special fields fields["Tags"] = tags.strip() fields["Type"] = model["name"] fields["Deck"] = self.decks.name(deck_id) fields["Subdeck"] = fields["Deck"].split("::")[-1] fields["Card"] = template["name"] fields["CardFlag"] = self._flagNameFromCardFlags(flag) fields["c%d" % (card_ord + 1)] = "1" # legacy hook fields = runFilter("mungeFields", fields, model, data, self) ctx = TemplateRenderContext(self, data, fields) # render fields. if any custom filters are encountered, # the field_filter hook will be called. try: qtext, atext = render_card(self, qfmt, afmt, ctx) except anki.rsbackend.BackendException as e: errmsg = _("Card template has a problem:") + f"<br>{e}" qtext = errmsg atext = errmsg # avoid showing the user a confusing blank card if they've # forgotten to add a cloze deletion if model["type"] == MODEL_CLOZE: if not self.models._availClozeOrds(model, data[6], False): qtext = (qtext + "<p>" + _( "Please edit this note and add some cloze deletions. (%s)") % ("<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))) # allow add-ons to modify the generated result (qtext, atext) = hooks.card_did_render((qtext, atext), ctx) # legacy hook qtext = runFilter("mungeQA", qtext, "q", fields, model, data, self) atext = runFilter("mungeQA", atext, "a", fields, model, data, self) return dict(q=qtext, a=atext, id=card_id)
def get_all_fields(context: TemplateRenderContext) -> Dict[str, Any]: addInfo: Dict[str, Any] = {} card = context.card() # note: card will no longer be none, and this if statement can be removed if card is not None: r = mw.reviewer d = mw.col cs = CardStats(d, r.card) if card.odid: conf = d.decks.confForDid(card.odid) else: conf = d.decks.confForDid(card.did) (first, last, cnt, total) = mw.col.db.first( "select min(id), max(id), count(), sum(time)/1000 from revlog where cid = :id", id=card.id, ) if cnt: addInfo["FirstReview"] = time.strftime( "%a, %d %b %Y %H:%M:%S", time.localtime(first / 1000) ) addInfo["LastReview"] = time.strftime( "%a, %d %b %Y %H:%M:%S", time.localtime(last / 1000) ) # https://docs.python.org/2/library/datetime.html #todo addInfo["TimeAvg"] = timefn(total / float(cnt)) addInfo["TimeTotal"] = timefn(total) cOverdueIvl = valueForOverdue(card.odid, card.queue, card.type, card.due, d) if cOverdueIvl: addInfo["overdue_fmt"] = ( str(cOverdueIvl) + " day" + ("s" if cOverdueIvl > 1 else "") ) addInfo["overdue_days"] = str(cOverdueIvl) addInfo["external_file_link"] = external_file_link(card, context.note_type()) addInfo["Ord"] = card.ord addInfo["Did"] = card.did addInfo["Due"] = card.due addInfo["Id"] = card.id addInfo["Ivl"] = card.ivl addInfo["Queue"] = card.queue addInfo["Reviews"] = card.reps addInfo["Lapses"] = card.lapses addInfo["Type"] = card.type addInfo["Nid"] = card.nid addInfo["Mod"] = time.strftime("%Y-%m-%d", time.localtime(card.mod)) addInfo["Usn"] = card.usn addInfo["Factor"] = card.factor addInfo["Ease"] = int(card.factor / 10) addInfo["Review?"] = "Review" if card.type == 2 else "" addInfo["New?"] = "New" if card.type == 0 else "" addInfo["Learning?"] = "Learning" if card.type == 1 else "" addInfo["TodayLearning?"] = ( "Learning" if card.type == 1 and card.queue == 1 else "" ) addInfo["DayLearning?"] = ( "Learning" if card.type == 1 and card.queue == 3 else "" ) addInfo["Young"] = "Young" if card.type == 2 and card.ivl < 21 else "" addInfo["Mature"] = "Mature" if card.type == 2 and card.ivl > 20 else "" if gc("make_deck_options_available", False): addInfo["Options_Group_ID"] = conf["id"] addInfo["Options_Group_Name"] = conf["name"] addInfo["Ignore_answer_times_longer_than"] = conf["maxTaken"] addInfo["Show_answer_time"] = conf["timer"] addInfo["Auto_play_audio"] = conf["autoplay"] addInfo["When_answer_shown_replay_q"] = conf["replayq"] addInfo["is_filtered_deck"] = conf["dyn"] addInfo["deck_usn"] = conf["usn"] addInfo["deck_mod_time"] = conf["mod"] addInfo["new__steps_in_minutes"] = conf["new"]["delays"] addInfo["new__order_of_new_cards"] = conf["new"]["order"] addInfo["new__cards_per_day"] = conf["new"]["perDay"] addInfo["graduating_interval"] = conf["new"]["ints"][0] addInfo["easy_interval"] = conf["new"]["ints"][1] addInfo["Starting_ease"] = int(conf["new"]["initialFactor"] / 10) addInfo["bury_related_new_cards"] = conf["new"]["bury"] addInfo["MaxiumReviewsPerDay"] = conf["rev"]["perDay"] addInfo["EasyBonus"] = int(100 * conf["rev"]["ease4"]) addInfo["IntervalModifier"] = int(100 * conf["rev"]["ivlFct"]) addInfo["MaximumInterval"] = conf["rev"]["maxIvl"] addInfo["bur_related_reviews_until_next_day"] = conf["rev"]["bury"] addInfo["lapse_learning_steps"] = conf["lapse"]["delays"] addInfo["lapse_new_ivl"] = int(100 * conf["lapse"]["mult"]) addInfo["lapse_min_ivl"] = conf["lapse"]["minInt"] addInfo["lapse_leech_threshold"] = conf["lapse"]["leechFails"] addInfo["lapse_leech_action"] = conf["lapse"]["leechAction"] addInfo["Date_Created"] = time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime(card.nid / 1000) ) # add your additional fields here # for debugging quickly tt = " <table>" + "\n" for k in sorted(addInfo.keys()): tt += '<tr><td align="left">%s </td><td align="left"> %s </td></tr> +\n' % ( k, addInfo[k], ) tt += " </table>" + "\n" addInfo["allfields"] = tt return addInfo
def note_creation(ctx: TemplateRenderContext) -> str: # convert millisecond timestamp to seconds note_creation_unix_timestamp = ctx.note().id // 1000 # convert timestamp to a human years-months-days return time.strftime("%Y-%m-%d", time.localtime(note_creation_unix_timestamp))
def card_interval(ctx: TemplateRenderContext) -> str: return str(ctx.card().ivl)
def on_card_did_render(output: TemplateRenderOutput, context: TemplateRenderContext): #TODO lvl == 0; review == 0 -> show word translation # f = open("/Users/ihor/Library/Application Support/Anki2/addons21/2055492100/log.txt", "a") # f.write(str(context.card())) # f.write("\n") # f.write(str(context.note())) # f.write("\n") # f.write(str(context.fields())) # f.write("\n") # f.write(str(context.fields())) # f.write("\n") # f.write(str(context.fields()["translation"])) # f.write("\n") # f.write(str(output)) # f.write("\n") # f.close() # word { # debug { # output.question_text += f"<style>#debug {{ display: block; }}</style>" # # if context.card().did == gc("deckId") and context.card().ivl < 5 and context.card().reps > 5: # output.question_text += f"<style>#word {{ color: red; }}</style>" #lapses=0 # HIGHLIGHT EASY CARDS if context.card().did == gc("deckId") and context.card().lapses < 2 and context.card().reps > 6: # output.question_text += f"<style>.easy {{ display: block !important; }}</style>" output.question_text += f"<style>body {{ background-color: #7cedff21 !important; }}</style>" # HIDE GIF FOR CARDS WITH SOME PROGRESS if context.card().did == gc("deckId") and context.card().reps > 10 and context.card().ivl <= 10: output.question_text += f"<style>#gif {{ display: none; }}</style>" # SHOW TRANSLATION FOR NEW CARDS if context.card().did == gc("deckId") and context.card().ivl == 0 and context.card().reps == 0: output.question_text += f"<style>.hint {{ display: block; }}</style>" #FILL BLANK FOR MORE MATURE CARDS if context.card().did == gc("filteredDeckId") and context.card().ivl > 5: output.question_text += f"<style>#context, #word {{ display: none; }}</style>"'' output.question_text += f"<style>#fill-blank-block, .hint.word, #gif {{ display: block; }}</style>"'' # add original clip audio to back output.answer_av_tags = [output.question_av_tags[0]] # clear front output.question_av_tags = [] if (context.card().did == gc("deckId") or context.card().did == 1606067545027) and context.card().ivl > 10: output.question_text += f"<style>#gif, #context {{ display: none; }}</style>" output.question_av_tags = [output.answer_av_tags[0]]