def _renderQA(self, data): "Returns hash of id, question, answer." # data is [cid, nid, mid, did, ord, tags, flds] # unpack fields and create dict flist = splitFields(data[6]) fields = {} model = self.models.get(data[2]) for (name, (idx, conf)) in self.models.fieldMap(model).items(): fields[name] = flist[idx] fields['Tags'] = data[5] fields['Type'] = model['name'] fields['Deck'] = self.decks.name(data[3]) template = model['tmpls'][data[4]] fields['Card'] = template['name'] # render q & a d = dict(id=data[0]) for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])): if type == "q": format = format.replace("cloze:", "cq:") else: format = format.replace("cloze:", "ca:") fields = runFilter("mungeFields", fields, model, data, self) html = anki.template.render(format, fields) d[type] = runFilter( "mungeQA", html, type, fields, model, data, self) return d
def _renderQA(self, model, gname, data): "Returns hash of id, question, answer." # data is [cid, fid, mid, gid, ord, tags, flds] # unpack fields and create dict flist = splitFields(data[6]) fields = {} for (name, (idx, conf)) in model.fieldMap().items(): fields[name] = flist[idx] if fields[name]: fields[name] = '<span class="fm%s-%s">%s</span>' % ( hexifyID(data[2]), hexifyID(idx), fields[name]) else: fields[name] = "" fields['Tags'] = data[5] fields['Model'] = model.name fields['Group'] = gname template = model.templates[data[4]] fields['Template'] = template['name'] # render q & a d = dict(id=data[0]) for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])): if type == "q": format = format.replace("cloze:", "cq:") else: if model.conf['clozectx']: name = "cactx:" else: name = "ca:" format = format.replace("cloze:", name) fields = runFilter("mungeFields", fields, model, gname, data, self) html = anki.template.render(format, fields) d[type] = runFilter( "mungeQA", html, type, fields, model, gname, data, self) return d
def formatQA(cid, mid, fact, tags, cm, deck, build=False): "Return a dict of {id, question, answer}" d = {'id': cid} fields = {} for (k, v) in fact.items(): fields["text:"+k] = stripHTML(v[1]) if v[1]: fields[k] = '<span class="fm%s">%s</span>' % ( hexifyID(v[0]), v[1]) else: fields[k] = u"" fields['tags'] = tags[0] fields['Tags'] = tags[0] fields['modelTags'] = tags[1] fields['cardModel'] = tags[2] # render q & a ret = [] for (type, format) in (("question", cm.qformat), ("answer", cm.aformat)): # convert old style format = re.sub("%\((.+?)\)s", "{{\\1}}", format) # allow custom rendering functions & info fields = runFilter("prepareFields", fields, cid, mid, fact, tags, cm, deck) html = render(format, fields) d[type] = runFilter("formatQA", html, type, cid, mid, fact, tags, cm, deck, build) return d
def _renderPreview(self): self.cancelPreviewTimer() c = self.card ti = self.maybeTextInput bodyclass = bodyClass(self.mw.col, c) q = ti(mungeQA(self.mw.col, c.q(reload=True))) q = runFilter("prepareQA", q, c, "clayoutQuestion") a = ti(mungeQA(self.mw.col, c.a()), type='a') a = runFilter("prepareQA", a, c, "clayoutAnswer") # use _showAnswer to avoid the longer delay self.pform.frontWeb.eval("_showAnswer(%s,'%s');" % (json.dumps(q), bodyclass)) self.pform.backWeb.eval("_showAnswer(%s, '%s');" % (json.dumps(a), bodyclass)) clearAudioQueue() if c.id not in self.playedAudio: playFromText(c.q()) playFromText(c.a()) self.playedAudio[c.id] = True self.updateCardNames()
def addNote(self, deckName, modelName, fields, tags=list()): note = self.createNote(deckName, modelName, fields, tags) if note is not None: collection = self.collection() collection.addNote(note) collection.autosave() self.startEditing() srcIdx = 0 for c, name in enumerate(mw.col.models.fieldNames(note.model())): if name == 'Expression': srcIdx = c print "Source", srcIdx showInfo(str(srcIdx)) runFilter("editFocusLost", False, note, srcIdx)#note.fields.index('Expression')) return note.id
def modSchema(self, check): "Mark schema modified. Call this first so user can abort if necessary." if not self.schemaChanged(): if check and not runFilter("modSchema", True): raise AnkiError("abortSchemaMod") self.scm = intTime(1000) self.setMod()
def setupWeb(self): self.web = EditorWebView(self.widget, self) self.web.title = "editor" self.web.allowDrops = True self.web.onBridgeCmd = self.onBridgeCmd self.outerLayout.addWidget(self.web, 1) self.web.onLoadFinished = self._loadFinished topbuts = """ <div id="topbutsleft" style="float:left;"> <button onclick="pycmd('fields')">%(flds)s...</button> <button onclick="pycmd('cards')">%(cards)s...</button> </div> <div id="topbutsright" style="float:right;"> <button tabindex=-1 class=linkb type="button" id=bold onclick="pycmd('bold');return false;"><img class=topbut src="qrc:/icons/text_bold.png"></button> <button tabindex=-1 class=linkb type="button" id=italic onclick="pycmd('italic');return false;"><img class=topbut src="qrc:/icons/text_italic.png"></button> <button tabindex=-1 class=linkb type="button" id=underline onclick="pycmd('underline');return false;"><img class=topbut src="qrc:/icons/text_under.png"></button> <button tabindex=-1 class=linkb type="button" id=superscript onclick="pycmd('super');return false;"><img class=topbut src="qrc:/icons/text_super.png"></button> <button tabindex=-1 class=linkb type="button" id=subscript onclick="pycmd('sub');return false;"><img class=topbut src="qrc:/icons/text_sub.png"></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('clear');return false;"><img class=topbut src="qrc:/icons/text_clear.png"></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('colour');return false;"><div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;" class=topbut></div></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('changeCol');return false;"><div style="display:inline-block; border-radius: 5px;" class="topbut rainbow"></div></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('cloze');return false;"><img class=topbut src="qrc:/icons/text_cloze.png"></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('attach');return false;"><img class=topbut src="qrc:/icons/paperclip.png"></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('record');return false;"><img class=topbut src="qrc:/icons/media-record.png"></button> <button tabindex=-1 class=linkb type="button" onclick="pycmd('more');return false;"><img class=topbut src="qrc:/icons/more.png"></button> </div> """ % dict(flds=_("Fields"), cards=_("Cards")) topbuts = runFilter("setupEditorButtons", topbuts) self.web.stdHtml(_html % ( self.mw.baseHTML(), anki.js.jquery, topbuts, _("Show Duplicates")))
def formatQA(cid, mid, fact, tags, cm): "Return a dict of {id, question, answer}" d = {'id': cid} fields = {} for (k, v) in fact.items(): fields["text:"+k] = v[1] if v[1]: fields[k] = '<span class="fm%s">%s</span>' % ( hexifyID(v[0]), v[1]) else: fields[k] = u"" fields['tags'] = tags[0] fields['Tags'] = tags[0] fields['modelTags'] = tags[1] fields['cardModel'] = tags[2] # render q & a ret = [] for (type, format) in (("question", cm.qformat), ("answer", cm.aformat)): try: html = format % fields except (KeyError, TypeError, ValueError): html = _("[invalid question/answer format]") d[type] = runFilter("formatQA", html, type, cid, mid, fact, tags, cm) return d
def _showQuestion(self): self._reps += 1 self.state = "question" self.typedAnswer = None c = self.card # grab the question and play audio if c.isEmpty(): q = _("""\ The front of this card is empty. Please run Tools>Empty Cards.""") else: q = c.q() if self.autoplay(c): playFromText(q) # render & update bottom q = self._mungeQA(q) q = runFilter("prepareQA", q, c, "reviewQuestion") bodyclass = bodyClass(self.mw.col, c) self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass)) self._drawFlag() self._drawMark() self._showAnswerButton() # if we have a type answer field, focus main web if self.typeCorrect: self.mw.web.setFocus() # user hook runHook('showQuestion')
def setupStyle(self): buf = "" if isWin and platform.release() == '10': # add missing bottom border to menubar buf += """ QMenuBar { border-bottom: 1px solid #aaa; background: white; } """ # qt bug? setting the above changes the browser sidebar # to white as well, so set it back buf += """ QTreeWidget { background: #eee; } """ # allow addons to modify the styling buf = runFilter("setupStyle", buf) # allow users to extend styling p = os.path.join(aqt.mw.pm.base, "style.css") if os.path.exists(p): buf += open(p).read() self.app.setStyleSheet(buf)
def myRenderQA(self, data, qfmt=None, afmt=None): "Returns hash of id, question, answer." # data is [cid, nid, mid, did, ord, tags, flds, flags] # unpack fields and create dict flist = splitFields(data[6]) fields = {} model = self.models.get(data[2]) for (name, (idx, conf)) in self.models.fieldMap(model).items(): fields[name] = flist[idx] if len(data) > 7: shakyWarningFieldName = _getShakyWarningFieldName(data[7]) if shakyWarningFieldName: fields[shakyWarningFieldName] = shakyWarningFieldName fields['Tags'] = data[5].strip() fields['Type'] = model['name'] fields['Deck'] = self.decks.name(data[3]) if model['type'] == MODEL_STD: template = model['tmpls'][data[4]] else: template = model['tmpls'][0] fields['Card'] = template['name'] fields['c%d' % (data[4]+1)] = "1" # render q & a d = dict(id=data[0]) qfmt = qfmt or template['qfmt'] afmt = afmt or template['afmt'] for (type, format) in (("q", qfmt), ("a", afmt)): if type == "q": format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format) format = format.replace("<%cloze:", "<%%cq:%d:" % ( data[4]+1)) else: format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (data[4]+1), format) format = format.replace("<%cloze:", "<%%ca:%d:" % ( data[4]+1)) fields['FrontSide'] = stripSounds(d['q']) fields = runFilter("mungeFields", fields, model, data, self) html = anki.template.render(format, fields) d[type] = runFilter( "mungeQA", html, type, fields, model, data, self) # empty cloze? if type == 'q' and model['type'] == MODEL_CLOZE: if not self.models._availClozeOrds(model, data[6], False): d['q'] += ("<p>" + _( "Please edit this note and add some cloze deletions. (%s)") % ( "<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))) return d
def updateCard(self): c = self.cards[self.currentCard] styles = (self.deck.css + ("\nhtml { background: %s }" % c.cardModel.lastFontColour) + "\ndiv { white-space: pre-wrap; }") styles = runFilter("addStyles", styles, c) self.dialog.webView.setHtml( ('<html><head>%s</head><body>' % getBase(self.deck)) + "<style>" + styles + "</style>" + runFilter("drawQuestion", mungeQA(self.deck, c.htmlQuestion()), c) + "<br><br><hr><br><br>" + runFilter("drawAnswer", mungeQA(self.deck, c.htmlAnswer()), c) + "</body></html>") playFromText(c.question) playFromText(c.answer)
def addStyles(self): # card styles s = "<style>\n" if self.main.deck: s += self.main.deck.css s = runFilter("addStyles", s, self.main.currentCard) s += "</style>" return s
def modSchema(self, check=True): "Mark schema modified. Call this first so user can abort if necessary." if not self.schemaChanged(): if check and not runFilter("modSchema", True): raise AnkiError("abortSchemaMod") # next sync will be full self.emptyTrash() self.scm = intTime()
def addStyles(self): # card styles s = "<style>\n" if self.main.deck: s += self.main.deck.css s += "div { white-space: pre-wrap; }\n" s = runFilter("addStyles", s, self.main.currentCard) s += "</style>" return s
def renderPreview(self): c = self.card styles = self.deck.rebuildCSS() + ("\nhtml { background: %s }" % c.cardModel.lastFontColour) styles = runFilter("addStyles", styles, c) self.form.preview.setHtml( ("<html><head>%s</head><body>" % getBase(self.deck, c)) + "<style>" + styles + "</style>" + runFilter("drawQuestion", mungeQA(self.deck, c.htmlQuestion()), c) + "<hr>" + runFilter("drawAnswer", mungeQA(self.deck, c.htmlAnswer()), c) + "</body></html>" ) clearAudioQueue() if c.id not in self.playedAudio: playFromText(c.question) playFromText(c.answer) self.playedAudio[c.id] = True
def bridge(self, str): if not self.note or not runHook: # shutdown return # focus lost or key/button pressed? if str.startswith("blur") or str.startswith("key"): (type, txt) = str.split(":", 1) txt = self.mungeHTML(txt) # misbehaving apps may include a null byte in the text txt = txt.replace("\x00", "") # reverse the url quoting we added to get images to display txt = unicode(urllib2.unquote( txt.encode("utf8")), "utf8", "replace") # make sure a trailing <br /> is removed txt = re.sub("(<br />)*$", "", txt) self.note.fields[self.currentField] = txt if not self.addMode: self.note.flush() self.mw.requireReset() if type == "blur": self.disableButtons() # run any filters if runFilter( "editFocusLost", False, self.note, self.currentField): # something updated the note; schedule reload def onUpdate(): self.stealFocus = True self.loadNote() self.stealFocus = False self.checkValid() self.mw.progress.timer(100, onUpdate, False) else: self.checkValid() else: runHook("editTimer", self.note) self.checkValid() # focused into field? elif str.startswith("focus"): (type, num) = str.split(":", 1) self.enableButtons() self.currentField = int(num) runHook("editFocusGained", self.note, self.currentField) # state buttons changed? elif str.startswith("state"): (cmd, txt) = str.split(":", 1) r = json.loads(txt) self._buttons['text_bold'].setChecked(r['bold']) self._buttons['text_italic'].setChecked(r['italic']) self._buttons['text_under'].setChecked(r['under']) self._buttons['text_super'].setChecked(r['super']) self._buttons['text_sub'].setChecked(r['sub']) elif str.startswith("dupes"): self.showDupes() else: print str
def render_unescaped(self, tag_name=None, context=None): """Render a tag without escaping it.""" txt = get_or_attr(context, tag_name) if txt is not None: # Some field names could have colons in them avoid # interpreting these as field modifiers better would # probably be to put some restrictions on field names return txt # field modifiers parts = tag_name.split(':') extra = None if len(parts) == 1 or parts[0] == '': return '{unknown field %s}' % tag_name else: mods, tag = parts[:-1], parts[-1] # py3k has *mods, tag = parts txt = get_or_attr(context, tag) # Since 'text:' and other mods can affect html on which Anki # relies to process clozes, we need to make sure clozes are # always treated after all the other mods, regardless of how # they're specified in the template, so that {{cloze:text: == # {{text:cloze: # For type:, we return directly since no other mod than cloze # (or other pre-defined mods) can be present and those are # treated separately mods.reverse() mods.sort(key=lambda s: not s == "type") for mod in mods: # built-in modifiers if mod == 'text': # strip html txt = stripHTML(txt) if txt else "" elif mod == 'type': # Type answer field; convert it to [[type:...]] for # the gui code to process return "[[%s]]" % tag_name elif mod.startswith('cq-') or mod.startswith('ca-'): # cloze deletion mod, extra = mod.split("-") txt = self.clozeText( txt, extra, mod[1]) if txt and extra else "" else: # hook-based field modifier mod, extra = re.search("^(.*?)(?:\((.*)\))?$", mod).groups() txt = runFilter('fmod_' + mod, txt or '', extra or '', context, tag, tag_name) if txt is None: return '{unknown field %s}' % tag_name return txt
def drawQuestion(self, nosound=False): "Show the question." if not self.main.config['splitQA']: self.write("<br>") q = self.main.currentCard.htmlQuestion() if self.haveTop: height = 35 else: height = 45 q = runFilter("drawQuestion", q) self.write(self.center(self.mungeQA(self.main.deck, q), height)) if self.state != self.oldState and not nosound: playFromText(q)
def bridge(self, str): if not self.note or not runHook: # shutdown return # focus lost or key/button pressed? if str.startswith("blur") or str.startswith("key"): (type, txt) = str.split(":", 1) txt = self.mungeHTML(txt) # misbehaving apps may include a null byte in the text txt = txt.replace("\x00", "") # reverse the url quoting we added to get images to display txt = unicode(urllib2.unquote( txt.encode("utf8")), "utf8", "replace") self.note.fields[self.currentField] = txt if type == "blur": self.disableButtons() # run any filters if runFilter( "editFocusLost", False, self.note, self.currentField): # something updated the note; schedule reload def onUpdate(): self.loadNote() self.checkValid() else: self.checkValid() else: runHook("editTimer", self.note) self.checkValid() # focused into field? elif str.startswith("focus"): (type, num) = str.split(":", 1) self.enableButtons() self.currentField = int(num) # state buttons changed? elif str.startswith("state"): (cmd, txt) = str.split(":", 1) r = json.loads(txt) self._buttons['text_bold'].setChecked(r['bold']) self._buttons['text_italic'].setChecked(r['italic']) self._buttons['text_under'].setChecked(r['under']) self._buttons['text_super'].setChecked(r['super']) self._buttons['text_sub'].setChecked(r['sub']) elif str.startswith("dupes"): self.showDupes() # save current selection elif str.startswith("selection"): (type, start, end) = str.split(":", 2) self.currentSelection = (int(start), int(end)) else: print str
def setupWeb(self): self.web = EditorWebView(self.widget, self) self.web.title = "editor" self.web.allowDrops = True self.web.onBridgeCmd = self.onBridgeCmd self.outerLayout.addWidget(self.web, 1) righttopbtns = list() righttopbtns.append(self._addButton('text_bold', 'bold', _("Bold text (Ctrl+B)"), id='bold')) righttopbtns.append(self._addButton('text_italic', 'italic', _("Italic text (Ctrl+I)"), id='italic')) righttopbtns.append(self._addButton('text_under', 'underline', _("Underline text (Ctrl+U)"), id='underline')) righttopbtns.append(self._addButton('text_super', 'super', _("Superscript (Ctrl++)"), id='superscript')) righttopbtns.append(self._addButton('text_sub', 'sub', _("Subscript (Ctrl+=)"), id='subscript')) righttopbtns.append(self._addButton('text_clear', 'clear', _("Remove formatting (Ctrl+R)"))) # The color selection buttons do not use an icon so the HTML must be specified manually tip = _("Set foreground colour (F7)") righttopbtns.append('''<button tabindex=-1 class=linkb title="{}" type="button" onclick="pycmd('colour');return false;"> <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;" class=topbut></div></button>'''.format(tip)) tip = _("Change colour (F8)") righttopbtns.append('''<button tabindex=-1 class=linkb title="{}" type="button" onclick="pycmd('changeCol');return false;"> <div style="display:inline-block; border-radius: 5px;" class="topbut rainbow"></div></button>'''.format(tip)) righttopbtns.append(self._addButton('text_cloze', 'cloze', _("Cloze deletion (Ctrl+Shift+C)"))) righttopbtns.append(self._addButton('paperclip', 'attach', _("Attach pictures/audio/video (F3)"))) righttopbtns.append(self._addButton('media-record', 'record', _("Record audio (F5)"))) righttopbtns.append(self._addButton('more', 'more')) righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" style="float:left;"> <button title='%(fldsTitle)s' onclick="pycmd('fields')">%(flds)s...</button> <button title='%(cardsTitle)s' onclick="pycmd('cards')">%(cards)s...</button> </div> <div id="topbutsright" style="float:right;"> %(rightbts)s </div> """ % dict(flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns), fldsTitle=_("Customize Fields"), cardsTitle=shortcut(_("Customize Card Templates (Ctrl+L)"))) bgcol = self.mw.app.palette().window().color().name() # then load page self.web.stdHtml(_html % ( bgcol, bgcol, topbuts, _("Show Duplicates")), css=["editor.css"], js=["jquery.js", "editor.js"])
def setupShortcuts(self): cuts = [ ("Ctrl+L", self.onCardLayout), ("Ctrl+B", self.toggleBold), ("Ctrl+I", self.toggleItalic), ("Ctrl+U", self.toggleUnderline), ("Ctrl+Shift+=", self.toggleSuper), ("Ctrl+=", self.toggleSub), ("Ctrl+R", self.removeFormat), ("F7", self.onForeground), ("F8", self.onChangeCol), ("Ctrl+Shift+C", self.onCloze), ("Ctrl+Shift+Alt+C", self.onCloze), ("F3", self.onAddMedia), ("F5", self.onRecSound), ("Ctrl+T, T", self.insertLatex), ("Ctrl+T, E", self.insertLatexEqn), ("Ctrl+T, M", self.insertLatexMathEnv), ("Ctrl+Shift+X", self.onHtmlEdit), ("Ctrl+Shift+T", lambda: self.tags.setFocus), ] runFilter("setupEditorShortcuts", cuts) for keys, fn in cuts: QShortcut(QKeySequence(keys), self.widget, activated=fn)
def onBridgeCmd(self, cmd): if not self.note or not runHook: # shutdown return # focus lost or key/button pressed? if cmd.startswith("blur") or cmd.startswith("key"): (type, ord, nid, txt) = cmd.split(":", 3) ord = int(ord) try: nid = int(nid) except ValueError: nid = 0 if nid != self.note.id: print("ignored late blur") return txt = urllib.parse.unquote(txt) txt = unicodedata.normalize("NFC", txt) txt = self.mungeHTML(txt) # misbehaving apps may include a null byte in the text txt = txt.replace("\x00", "") # reverse the url quoting we added to get images to display txt = self.mw.col.media.escapeImages(txt, unescape=True) self.note.fields[ord] = txt if not self.addMode: self.note.flush() self.mw.requireReset() if type == "blur": self.currentField = None # run any filters if runFilter( "editFocusLost", False, self.note, ord): # something updated the note; update it after a subsequent focus # event has had time to fire self.mw.progress.timer(100, self.loadNoteKeepingFocus, False) else: self.checkValid() else: runHook("editTimer", self.note) self.checkValid() # focused into field? elif cmd.startswith("focus"): (type, num) = cmd.split(":", 1) self.currentField = int(num) runHook("editFocusGained", self.note, self.currentField) elif cmd in self._links: self._links[cmd](self) else: print("uncaught cmd", cmd)
def _showAnswer(self): if self.mw.state != "review": # showing resetRequired screen; ignore space return self.state = "answer" c = self.card a = c.a() # play audio? if self.autoplay(c): playFromText(a) a = self._mungeQA(a) a = runFilter("prepareQA", a, c, "reviewAnswer") # render and update bottom self.web.eval("_showAnswer(%s);" % json.dumps(a)) self._showEaseButtons() # user hook runHook('showAnswer')
def drawAnswer(self): "Show the answer." a = self.main.currentCard.htmlAnswer() a = runFilter("drawAnswer", a, self.main.currentCard) if self.main.currentCard.cardModel.typeAnswer: try: cor = stripMedia(stripHTML(self.main.currentCard.fact[self.main.currentCard.cardModel.typeAnswer])) except KeyError: self.main.currentCard.cardModel.typeAnswer = "" cor = "" if cor: given = unicode(self.main.typeAnswerField.text()) res = self.correct(cor, given) a = res + "<br>" + a self.write(self.center("<span id=answer />" + self.mungeQA(self.main.deck, a))) if self.state != self.oldState: playFromText(a)
def onBridgeCmd(self, cmd): if not self.note or not runHook: # shutdown return # focus lost or key/button pressed? if cmd.startswith("blur") or cmd.startswith("key"): (type, txt) = cmd.split(":", 1) txt = urllib.parse.unquote(txt) txt = unicodedata.normalize("NFC", txt) txt = self.mungeHTML(txt) # misbehaving apps may include a null byte in the text txt = txt.replace("\x00", "") # reverse the url quoting we added to get images to display txt = self.mw.col.media.escapeImages(txt, unescape=True) self.note.fields[self.currentField] = txt if not self.addMode: self.note.flush() self.mw.requireReset() if type == "blur": # run any filters if runFilter( "editFocusLost", False, self.note, self.currentField): # something updated the note; schedule reload def onUpdate(): if not self.note: return self.stealFocus = True self.loadNote() self.checkValid() self.mw.progress.timer(100, onUpdate, False) else: self.checkValid() else: runHook("editTimer", self.note) self.checkValid() # focused into field? elif cmd.startswith("focus"): (type, num) = cmd.split(":", 1) self.currentField = int(num) runHook("editFocusGained", self.note, self.currentField) elif cmd in self._links: self._links[cmd](self) else: print("uncaught cmd", cmd)
def render_unescaped(self, tag_name=None, context=None): """Render a tag without escaping it.""" txt = get_or_attr(context, tag_name) if txt is not None: # some field names could have colons in them # avoid interpreting these as field modifiers # better would probably be to put some restrictions on field names return txt # field modifiers parts = tag_name.split(':', 2) extra = None if len(parts) == 1 or parts[0] == '': return '{unknown field %s}' % tag_name elif len(parts) == 2: (mod, tag) = parts elif len(parts) == 3: (mod, extra, tag) = parts txt = get_or_attr(context, tag) # built-in modifiers if mod == 'text': # strip html if txt: return stripHTML(txt) return "" elif mod == 'type': # type answer field; convert it to [[type:...]] for the gui code # to process return "[[%s]]" % tag_name elif mod == 'cq' or mod == 'ca': # cloze deletion if txt and extra: return self.clozeText(txt, extra, mod[1]) else: return "" else: # hook-based field modifier txt = runFilter('fmod_' + mod, txt or '', extra, context, tag, tag_name) if txt is None: return '{unknown field %s}' % tag_name return txt
def setupWeb(self): self.web = EditorWebView(self.widget, self) self.web.title = "editor" self.web.allowDrops = True self.web.onBridgeCmd = self.onBridgeCmd self.outerLayout.addWidget(self.web, 1) self.web.onLoadFinished = self._loadFinished righttopbtns = list() righttopbtns.append(self._addButton('text_bold', 'bold', "Bold text (Ctrl+B)", id='bold')) righttopbtns.append(self._addButton('text_italic', 'italic', "Italic text (Ctrl+I)", id='italic')) righttopbtns.append(self._addButton('text_under', 'underline', "Underline text (Ctrl+U)", id='underline')) righttopbtns.append(self._addButton('text_super', 'super', "Superscript (Ctrl+Shift+=)", id='superscript')) righttopbtns.append(self._addButton('text_sub', 'sub', "Subscript (Ctrl+=)", id='subscript')) righttopbtns.append(self._addButton('text_clear', 'clear', "Remove formatting (Ctrl+R)")) # The color selection buttons do not use an icon so the HTML must be specified manually righttopbtns.append('''<button tabindex=-1 class=linkb title="Set foreground colour (F7)" type="button" onclick="pycmd('colour');return false;"> <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;" class=topbut></div></button>''') righttopbtns.append('''<button tabindex=-1 class=linkb title="Change colour (F8)" type="button" onclick="pycmd('changeCol');return false;"> <div style="display:inline-block; border-radius: 5px;" class="topbut rainbow"></div></button>''') righttopbtns.append(self._addButton('text_cloze', 'cloze', "Cloze deletion (Ctrl+Shift+C)")) righttopbtns.append(self._addButton('paperclip', 'attach', "Attach pictures/audio/video (F3)")) righttopbtns.append(self._addButton('media-record', 'record', "Record audio (F5)")) righttopbtns.append(self._addButton('more', 'more')) righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" style="float:left;"> <button onclick="pycmd('fields')">%(flds)s...</button> <button onclick="pycmd('cards')">%(cards)s...</button> </div> <div id="topbutsright" style="float:right;"> %(rightbts)s </div> """ % dict(flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns)) bgcol = self.mw.app.palette().window().color().name() # then load page self.web.stdHtml(_html % ( bgcol, topbuts, _("Show Duplicates")), js=anki.js.jquery, head=self.mw.baseHTML())
def render_unescaped(self, tag_name=None, context=None): """Render a tag without escaping it.""" txt = get_or_attr(context, tag_name) if txt is not None: # some field names could have colons in them # avoid interpreting these as field modifiers # better would probably be to put some restrictions on field names return txt # field modifiers parts = tag_name.split(':',2) extra = None if len(parts) == 1 or parts[0] == '': return '{unknown field %s}' % tag_name elif len(parts) == 2: (mod, tag) = parts elif len(parts) == 3: (mod, extra, tag) = parts txt = get_or_attr(context, tag) # built-in modifiers if mod == 'text': # strip html if txt: return stripHTML(txt) return "" elif mod == 'type': # type answer field; convert it to [[type:...]] for the gui code # to process return "[[%s]]" % tag_name elif mod == 'cq' or mod == 'ca': # cloze deletion if txt and extra: return self.clozeText(txt, extra, mod[1]) else: return "" else: # hook-based field modifier txt = runFilter('fmod_' + mod, txt or '', extra, context, tag, tag_name); if txt is None: return '{unknown field %s}' % tag_name return txt
def setupWeb(self): self.web = EditorWebView(self.widget, self) self.web.title = "editor" self.web.allowDrops = True self.web.onBridgeCmd = self.onBridgeCmd self.outerLayout.addWidget(self.web, 1) self.web.onLoadFinished = self._loadFinished righttopbtns = list() righttopbtns.append(self._addButton('text_bold', 'bold', "Bold text (Ctrl+B)", id='bold')) righttopbtns.append(self._addButton('text_italic', 'italic', "Italic text (Ctrl+I)", id='italic')) righttopbtns.append(self._addButton('text_under', 'underline', "Underline text (Ctrl+U)", id='underline')) righttopbtns.append(self._addButton('text_super', 'super', "Superscript (Ctrl+Shift+=)", id='superscript')) righttopbtns.append(self._addButton('text_sub', 'sub', "Subscript (Ctrl+=)", id='subscript')) righttopbtns.append(self._addButton('text_clear', 'clear', "Remove formatting (Ctrl+R)")) # The color selection buttons do not use an icon so the HTML must be specified manually righttopbtns.append('''<button tabindex=-1 class=linkb title="Set foreground colour (F7)" type="button" onclick="pycmd('colour');return false;"> <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;" class=topbut></div></button>''') righttopbtns.append('''<button tabindex=-1 class=linkb title="Change colour (F8)" type="button" onclick="pycmd('changeCol');return false;"> <div style="display:inline-block; border-radius: 5px;" class="topbut rainbow"></div></button>''') righttopbtns.append(self._addButton('text_cloze', 'cloze', "Cloze deletion (Ctrl+Shift+C)")) righttopbtns.append(self._addButton('paperclip', 'attach', "Attach pictures/audio/video (F3)")) righttopbtns.append(self._addButton('media-record', 'record', "Record audio (F5)")) righttopbtns.append(self._addButton('more', 'more')) righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" style="float:left;"> <button onclick="pycmd('fields')">%(flds)s...</button> <button onclick="pycmd('cards')">%(cards)s...</button> </div> <div id="topbutsright" style="float:right;"> %(rightbts)s </div> """ % dict(flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns)) self.web.stdHtml(_html % ( self.mw.baseHTML(), anki.js.jquery, topbuts, _("Show Duplicates")))
def drawAnswer(self): "Show the answer." a = self.main.currentCard.htmlAnswer() a = runFilter("drawAnswer", a, self.main.currentCard) if self.main.currentCard.cardModel.typeAnswer: try: cor = stripMedia( stripHTML(self.main.currentCard.fact[ self.main.currentCard.cardModel.typeAnswer])) except KeyError: self.main.currentCard.cardModel.typeAnswer = "" cor = "" if cor: given = unicode(self.main.typeAnswerField.text()) res = self.correct(cor, given) a = res + "<br>" + a self.write( self.center('<span id=answer />' + self.mungeQA(self.main.deck, a))) if self.state != self.oldState: playFromText(a)
def setupWeb(self) -> None: self.web = EditorWebView(self.widget, self) self.web.set_bridge_command(self.onBridgeCmd, self) self.outerLayout.addWidget(self.web, 1) # then load page self.web.stdHtml( "", css=["css/editor.css"], js=["js/editor.js"], context=self, default_css=False, ) lefttopbtns: list[str] = [] gui_hooks.editor_did_init_left_buttons(lefttopbtns, self) lefttopbtns_defs = [ f"noteEditorPromise.then((noteEditor) => noteEditor.toolbar.notetypeButtons.appendButton({{ component: editorToolbar.Raw, props: {{ html: {json.dumps(button)} }} }}, -1));" for button in lefttopbtns ] lefttopbtns_js = "\n".join(lefttopbtns_defs) righttopbtns: list[str] = [] gui_hooks.editor_did_init_buttons(righttopbtns, self) # legacy filter righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) righttopbtns_defs = ", ".join( [json.dumps(button) for button in righttopbtns]) righttopbtns_js = (f""" noteEditorPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{ component: editorToolbar.AddonButtons, id: "addons", props: {{ buttons: [ {righttopbtns_defs} ] }}, }})); """ if len(righttopbtns) > 0 else "") self.web.eval(f"{lefttopbtns_js} {righttopbtns_js}")
def formatQA(cid, mid, fact, tags, cm): "Return a dict of {id, question, answer}" d = {'id': cid} fields = {} for (k, v) in fact.items(): fields["text:" + k] = stripHTML(v[1]) if v[1]: fields[k] = '<span class="fm%s">%s</span>' % (hexifyID(v[0]), v[1]) else: fields[k] = u"" fields['tags'] = tags[0] fields['Tags'] = tags[0] fields['modelTags'] = tags[1] fields['cardModel'] = tags[2] # render q & a ret = [] for (type, format) in (("question", cm.qformat), ("answer", cm.aformat)): try: html = format % fields except (KeyError, TypeError, ValueError): html = _("[invalid question/answer format]") d[type] = runFilter("formatQA", html, type, cid, mid, fact, tags, cm) return d
def apply_custom_filters( rendered: TemplateReplacementList, ctx: TemplateRenderContext, front_side: str | None, ) -> str: "Complete rendering by applying any pending custom filters." # template already fully rendered? if len(rendered) == 1 and isinstance(rendered[0], str): return rendered[0] res = "" for node in rendered: if isinstance(node, str): res += node else: # do we need to inject in FrontSide? if node.field_name == "FrontSide" and front_side is not None: node.current_text = front_side field_text = node.current_text for filter_name in node.filters: field_text = hooks.field_filter( field_text, node.field_name, filter_name, ctx ) # legacy hook - the second and fifth argument are no longer used. field_text = hooks.runFilter( f"fmod_{filter_name}", field_text, "", ctx.note().items(), node.field_name, "", ) res += field_text return res
def showQuestionWithoutAudio(self): self._reps += 1 self.state = "question" self.typedAnswer = None c = self.card # grab the question and play audio if c.isEmpty(): q = _("""\ The front of this card is empty. Please run Tools>Empty Cards.""") else: q = c.q() # render & update bottom q = self._mungeQA(q) q = runFilter("prepareQA", q, c, "reviewQuestion") bodyclass = bodyClass(self.mw.col, c) self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass)) self._drawFlag() self._drawMark() self._showAnswerButton() # if we have a type answer field, focus main web if self.typeCorrect: self.mw.web.setFocus()
def fonts(self): return [(runFilter("mungeEditingFontName", f["font"]), f["size"], f["rtl"]) for f in self.note.model()["flds"]]
def setupWeb(self): self.web = EditorWebView(self.widget, self) self.web.title = "editor" self.web.allowDrops = True self.web.onBridgeCmd = self.onBridgeCmd self.outerLayout.addWidget(self.web, 1) righttopbtns: List[str] = [ self._addButton("text_bold", "bold", _("Bold text (Ctrl+B)"), id="bold"), self._addButton("text_italic", "italic", _("Italic text (Ctrl+I)"), id="italic"), self._addButton("text_under", "underline", _("Underline text (Ctrl+U)"), id="underline"), self._addButton("text_super", "super", _("Superscript (Ctrl++)"), id="superscript"), self._addButton("text_sub", "sub", _("Subscript (Ctrl+=)"), id="subscript"), self._addButton("text_clear", "clear", _("Remove formatting (Ctrl+R)")), ] # The color selection buttons do not use an icon so the HTML must be specified manually tip = _("Set foreground colour (F7)") righttopbtns.append("""<button tabindex=-1 class=linkb title="{}" type="button" onclick="pycmd('colour');return false;"> <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;" class=topbut></div></button>""".format(tip)) tip = _("Change colour (F8)") righttopbtns.extend([ """<button tabindex=-1 class=linkb title="{}" type="button" onclick="pycmd('changeCol');return false;"> <div style="display:inline-block; border-radius: 5px;" class="topbut rainbow"></div></button>""".format(tip), self._addButton("text_cloze", "cloze", _("Cloze deletion (Ctrl+Shift+C)")), self._addButton("paperclip", "attach", _("Attach pictures/audio/video (F3)")), self._addButton("media-record", "record", _("Record audio (F5)")), self._addButton("more", "more"), ]) righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" style="float:left;"> <button title='%(fldsTitle)s' onclick="pycmd('fields')">%(flds)s...</button> <button title='%(cardsTitle)s' onclick="pycmd('cards')">%(cards)s...</button> </div> <div id="topbutsright" style="float:right;"> %(rightbts)s </div> """ % dict( flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns), fldsTitle=_("Customize Fields"), cardsTitle=shortcut(_("Customize Card Templates (Ctrl+L)")), ) bgcol = self.mw.app.palette().window().color().name() # then load page self.web.stdHtml( _html % (bgcol, bgcol, topbuts, _("Show Duplicates")), css=["editor.css"], js=["jquery.js", "editor.js"], )
def _renderQA(self, data, qfmt=None, afmt=None): """Returns dict with id, question, answer, and whether a field is shown in question. Keyword arguments: data -- [cid, nid, mid, did, ord, tags, flds, cardFlags] (see db documentation for more information about those values) flds is a list of fields, not a dict. This corresponds to the information you can obtain in templates, using {{Tags}}, {{Type}}, etc.. qfmt -- question format string (as in template) afmt -- answer format string (as in template) unpack fields and create dict TODO comment better """ cid = data[0] nid = data[1] mid = data[2] did = data[3] ord = data[4] tags = data[5] flds = data[6] if len( data ) >= 8: #this is a recent addition, some older version of anki does not have data[7] cardFlags = data[7] flist = splitFields(flds) #the list of fields fields = {} # #name -> ord for each field, tags # Type: the name of the model, # Deck, Subdeck: their name # Card: the template name # cn: 1 for n being the ord+1 # FrontSide : model = self.models.get(mid) assert model is not None #new (and fieldMap and items were not variables, but directly used fieldMap = self.models.fieldMap(model) items = list(fieldMap.items()) for (name, (idx, conf)) in list(items): #conf is not used fields[name] = flist[idx] fields['Tags'] = tags.strip() fields['Type'] = model['name'] fields['Deck'] = self.decks.name(did) fields['Subdeck'] = fields['Deck'].split('::')[-1] if len(data) >= 8: fields['CardFlag'] = self._flagNameFromCardFlags(cardFlags) if model[ 'type'] == MODEL_STD: #Note that model['type'] has not the same meaning as fields['Type'] template = model['tmpls'][ord] else: #for cloze deletions template = model['tmpls'][0] fields['Card'] = template['name'] fields['c%d' % (ord + 1)] = "1" # render q & a d = dict(id=cid) # id: card id qfmt = qfmt or template['qfmt'] afmt = afmt or template['afmt'] for (type, format) in (("q", qfmt), ("a", afmt)): if type == "q": #if/else is in the loop in order for d['q'] to be defined below format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (ord + 1), format) #Replace {{'foo'cloze: by {{'foo'cq-(ord+1), where 'foo' does not begins with "type:" format = format.replace("<%cloze:", "<%%cq:%d:" % (ord + 1)) #Replace <%cloze: by <%%cq:(ord+1) else: format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (ord + 1), format) #Replace {{'foo'cloze: by {{'foo'ca-(ord+1) format = format.replace("<%cloze:", "<%%ca:%d:" % (ord + 1)) #Replace <%cloze: by <%%ca:(ord+1) fields['FrontSide'] = stripSounds(d['q']) #d['q'] is defined during loop's first iteration fields = runFilter("mungeFields", fields, model, data, self) # TODO check html, showAField = anki.template.render( format, fields) #replace everything of the form {{ by its value #MODIFIED d["showAField"] = showAField #MODIFIED d[type] = runFilter("mungeQA", html, type, fields, model, data, self) # TODO check # empty cloze? if type == 'q' and model['type'] == MODEL_CLOZE: if not self.models._availClozeOrds(model, flds, False): d['q'] += ("<p>" + _( "Please edit this note and add some cloze deletions. (%s)") % ("<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))) #in the case where there is a cloze note type #without {{cn in fields indicated by #{{cloze:fieldName; an error message should be #shown return d
def fonts(self): return [(runFilter("mungeEditingFontName", f['font']), f['size'], f['rtl']) for f in self.note.model()['flds']]
def _renderQA(self, data, qfmt=None, afmt=None): """Returns hash of id, question, answer. Keyword arguments: data -- [cid, nid, mid, did, ord, tags, flds] (see db documentation for more information about those values) This corresponds to the information you can obtain in templates, using {{Tags}}, {{Type}}, etc.. qfmt -- question format string (as in template) afmt -- answer format string (as in template) unpack fields and create dict TODO comment better """ cid, nid, mid, did, ord, tags, flds, cardFlags = data flist = splitFields(flds)#the list of fields fields = {} # #name -> ord for each field, tags # Type: the name of the model, # Deck, Subdeck: their name # Card: the template name # cn: 1 for n being the ord+1 # FrontSide : model = self.models.get(mid) for (name, (idx, conf)) in list(self.models.fieldMap(model).items()):#conf is not used fields[name] = flist[idx] fields['Tags'] = tags.strip() fields['Type'] = model['name'] fields['Deck'] = self.decks.name(did) fields['Subdeck'] = fields['Deck'].split('::')[-1] if model['type'] == MODEL_STD:#Note that model['type'] has not the same meaning as fields['Type'] template = model['tmpls'][ord] else:#for cloze deletions template = model['tmpls'][0] fields['Card'] = template['name'] fields['c%d' % (ord+1)] = "1" # render q & a d = dict(id=cid) # id: card id qfmt = qfmt or template['qfmt'] afmt = afmt or template['afmt'] start = time.clock() for (type, format) in (("q", qfmt), ("a", afmt)): if type == "q": format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (ord+1), format) #Replace {{'foo'cloze: by {{'foo'cq-(ord+1), where 'foo' does not begins with "type:" format = format.replace("<%cloze:", "<%%cq:%d:" % ( ord+1)) #Replace <%cloze: by <%%cq:(ord+1) else: format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (ord+1), format) #Replace {{'foo'cloze: by {{'foo'ca-(ord+1) format = format.replace("<%cloze:", "<%%ca:%d:" % ( ord+1)) #Replace <%cloze: by <%%ca:(ord+1) fields['FrontSide'] = stripSounds(d['q']) #d['q'] is defined during loop's first iteration fields = runFilter("mungeFields", fields, model, data, self) # TODO check html = anki.template.render(format, fields) #replace everything of the form {{ by its value TODO check d[type] = runFilter( "mungeQA", html, type, fields, model, data, self) # TODO check # empty cloze? if type == 'q' and model['type'] == MODEL_CLOZE: if not self.models._availClozeOrds(model, flds, False): d['q'] += ("<p>" + _( "Please edit this note and add some cloze deletions. (%s)") % ( "<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))) #in the case where there is a cloze note type #without {{cn in fields indicated by #{{cloze:fieldName; an error message should be #shown end = time.clock() dif = end-start print(f"Renders took {dif} seconds") return d
def remEmptyCards(self, ids): if not ids: return if runFilter("remEmptyCards", len(ids), True): self.remCards(ids)
def report(self): "Return an HTML string with a report." fmtPerc = anki.utils.fmtPercentage fmtFloat = anki.utils.fmtFloat if self.deck.isEmpty(): return _("Please add some cards first.") + "<p/>" d = self.deck html = "<h1>" + _("Deck Statistics") + "</h1>" html += _("Deck created: <b>%s</b> ago<br>") % self.createdTimeStr() total = d.cardCount new = d.newCountAll() young = d.youngCardCount() old = d.matureCardCount() newP = new / float(total) * 100 youngP = young / float(total) * 100 oldP = old / float(total) * 100 stats = d.getStats() (stats["new"], stats["newP"]) = (new, newP) (stats["old"], stats["oldP"]) = (old, oldP) (stats["young"], stats["youngP"]) = (young, youngP) html += _("Total number of cards:") + " <b>%d</b><br>" % total html += _( "Total number of facts:") + " <b>%d</b><br><br>" % d.factCount html += "<b>" + _("Card Maturity") + "</b><br>" html += _("Mature cards: <!--card count-->" ) + " <b>%(old)d</b> (%(oldP)s)<br>" % { 'old': stats['old'], 'oldP': fmtPerc(stats['oldP']) } html += _("Young cards: <!--card count-->" ) + " <b>%(young)d</b> (%(youngP)s)<br>" % { 'young': stats['young'], 'youngP': fmtPerc(stats['youngP']) } html += _("Unseen cards:") + " <b>%(new)d</b> (%(newP)s)<br>" % { 'new': stats['new'], 'newP': fmtPerc(stats['newP']) } avgInt = self.getAverageInterval() if avgInt: html += _("Average interval: ") + ( "<b>%s</b> ") % fmtFloat(avgInt) + _("days") html += "<br>" html += "<br>" inactive = d.inactiveCardCount() suspended = d.suspendedCardCount() active = total - inactive - suspended inactiveP = inactive / float(total) * 100 suspendedP = suspended / float(total) * 100 activeP = active / float(total) * 100 html += "<b>" + _("Card State") + "</b><br>" html += _("Active cards:") + " <b>%(a)d</b> (%(b)s)<br>" % { 'a': active, 'b': fmtPerc(activeP) } html += _("Inactive cards:") + " <b>%(a)d</b> (%(b)s)<br>" % { 'a': inactive, 'b': fmtPerc(inactiveP) } html += _("Suspended cards:") + " <b>%(a)d</b> (%(b)s)<br><br>" % { 'a': suspended, 'b': fmtPerc(suspendedP) } html += "<b>" + _("Correct Answers") + "</b><br>" html += _("Mature cards: <!--correct answers-->") + " <b>" + fmtPerc( stats['gMatureYes%']) + ("</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf': stats['gMatureYes'], 'totalSum': stats['gMatureTotal'] } + "<br>") html += _("Young cards: <!--correct answers-->") + " <b>" + fmtPerc( stats['gYoungYes%']) + ("</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf': stats['gYoungYes'], 'totalSum': stats['gYoungTotal'] } + "<br>") html += _("First-seen cards:") + " <b>" + fmtPerc( stats['gNewYes%']) + ("</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf': stats['gNewYes'], 'totalSum': stats['gNewTotal'] } + "<br><br>") # average pending time existing = d.cardCount - d.newCountToday def tr(a, b): return "<tr><td>%s</td><td align=right>%s</td></tr>" % (a, b) def repsPerDay(reps, days): retval = ("<b>%d</b> " % reps) + ngettext("rep", "reps", reps) retval += ("/<b>%d</b> " % days) + ngettext("day", "days", days) return retval if existing and avgInt: html += "<b>" + _("Recent Work") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr( _("In last week"), repsPerDay(self.getRepsDone(-7, 0), self.getDaysReviewed(-7, 0))) html += tr( _("In last month"), repsPerDay(self.getRepsDone(-30, 0), self.getDaysReviewed(-30, 0))) html += tr( _("In last 3 months"), repsPerDay(self.getRepsDone(-92, 0), self.getDaysReviewed(-92, 0))) html += tr( _("In last 6 months"), repsPerDay(self.getRepsDone(-182, 0), self.getDaysReviewed(-182, 0))) html += tr( _("In last year"), repsPerDay(self.getRepsDone(-365, 0), self.getDaysReviewed(-365, 0))) html += tr( _("Deck life"), repsPerDay(self.getRepsDone(-13000, 0), self.getDaysReviewed(-13000, 0))) html += "</table>" html += "<br><br><b>" + _("Average Daily Reviews") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("Deck life"), ("<b>%s</b> ") % (fmtFloat(self.getSumInverseRoundInterval())) + _("cards/day")) html += tr(_("In next week"), ("<b>%s</b> ") % (fmtFloat(self.getWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In next month"), ("<b>%s</b> ") % (fmtFloat(self.getWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last week"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In last month"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last 3 months"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(92))) + _("cards/day")) html += tr(_("In last 6 months"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(182))) + _("cards/day")) html += tr(_("In last year"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(365))) + _("cards/day")) html += "</table>" html += "<br><br><b>" + _("Average Added") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr( _("Deck life"), _("<b>%(a)s</b>/day, <b>%(b)s</b>/mon") % { 'a': fmtFloat(self.newAverage()), 'b': fmtFloat(self.newAverage() * 30) }) np = self.getNewPeriod(7) html += tr( _("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(7)) })) np = self.getNewPeriod(30) html += tr( _("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(30)) })) np = self.getNewPeriod(92) html += tr( _("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(92)) })) np = self.getNewPeriod(182) html += tr( _("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(182)) })) np = self.getNewPeriod(365) html += tr( _("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(365)) })) html += "</table>" html += "<br><br><b>" + _("Average New Seen") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" np = self.getFirstPeriod(7) html += tr( _("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(7)) })) np = self.getFirstPeriod(30) html += tr( _("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(30)) })) np = self.getFirstPeriod(92) html += tr( _("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(92)) })) np = self.getFirstPeriod(182) html += tr( _("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(182)) })) np = self.getFirstPeriod(365) html += tr( _("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(365)) })) html += "</table>" html += "<br><br><b>" + _("Card Ease") + "</b><br>" html += _("Lowest factor: %.2f") % d.s.scalar( "select min(factor) from cards") + "<br>" html += _("Average factor: %.2f") % d.s.scalar( "select avg(factor) from cards") + "<br>" html += _("Highest factor: %.2f") % d.s.scalar( "select max(factor) from cards") + "<br>" html = runFilter("deckStats", html) return html
def _renderQA(self, data, qfmt=None, afmt=None): "Returns hash of id, question, answer." # data is [cid, nid, mid, did, ord, tags, flds] # unpack fields and create dict flist = splitFields(data[6]) fields = {} model = self.models.get(data[2]) for (name, (idx, conf)) in self.models.fieldMap(model).items(): fields[name] = flist[idx] fields['Tags'] = data[5].strip() fields['Type'] = model['name'] fields['Deck'] = self.decks.name(data[3]) fields['Subdeck'] = fields['Deck'].split('::')[-1] if model['type'] == MODEL_STD: template = model['tmpls'][data[4]] else: template = model['tmpls'][0] fields['Card'] = template['name'] fields['c%d' % (data[4]+1)] = "1" # render q & a d = dict(id=data[0]) qfmt = qfmt or template['qfmt'] afmt = afmt or template['afmt'] for (type, format) in (("q", qfmt), ("a", afmt)): if type == "q": format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format) format = format.replace("<%cloze:", "<%%cq:%d:" % ( data[4]+1)) else: format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (data[4]+1), format) format = format.replace("<%cloze:", "<%%ca:%d:" % ( data[4]+1)) fields['FrontSide'] = stripSounds(d['q']) fields = runFilter("mungeFields", fields, model, data, self) html = anki.template.render(format, fields) d[type] = html ############################ tags = getTagMatches(d["a"]) cid = data[0] if tags: if hasattr(self, "_spLast") and self._spLast == cid and tags == self._spTags: # preserve last replacements across card reloading # (required for: previewer, card templates editor) replacements = self._spRepl else: seq_parser = Parser() replacements = seq_parser.parse(tags) self._spLast = cid self._spTags = tags self._spRepl = replacements else: if hasattr(self, "_spTags"): del self._spTags if hasattr(self, "_spRepl"): del self._spRepl if hasattr(self, "_spLast"): del self._spLast for type in ("q", "a"): html = d[type] if tags: format_str = getFormatString(html) html = formatTagString(format_str, replacements) d[type] = runFilter( "mungeQA", html, type, fields, model, data, self) # empty cloze? if type == 'q' and model['type'] == MODEL_CLOZE: if not self.models._availClozeOrds(model, data[6], False): d['q'] += ("<p>" + _( "Please edit this note and add some cloze deletions. (%s)") % ( "<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))) ############################# return d
def setupWeb(self) -> None: self.web = EditorWebView(self.widget, self) self.web.allowDrops = True self.web.set_bridge_command(self.onBridgeCmd, self) self.outerLayout.addWidget(self.web, 1) lefttopbtns: List[str] = [ self._addButton( None, "fields", tr.editing_customize_fields(), f"{tr.editing_fields()}...", disables=False, rightside=False, ), self._addButton( None, "cards", tr.editing_customize_card_templates_ctrlandl(), f"{tr.editing_cards()}...", disables=False, rightside=False, ), ] gui_hooks.editor_did_init_left_buttons(lefttopbtns, self) righttopbtns: List[str] = [ self._addButton("text_bold", "bold", tr.editing_bold_text_ctrlandb(), id="bold"), self._addButton( "text_italic", "italic", tr.editing_italic_text_ctrlandi(), id="italic", ), self._addButton( "text_under", "underline", tr.editing_underline_text_ctrlandu(), id="underline", ), self._addButton( "text_super", "super", tr.editing_superscript_ctrlandand(), id="superscript", ), self._addButton("text_sub", "sub", tr.editing_subscript_ctrland(), id="subscript"), self._addButton("text_clear", "clear", tr.editing_remove_formatting_ctrlandr()), self._addButton( None, "colour", tr.editing_set_foreground_colour_f7(), """ <span id="forecolor" class="topbut rounded" style="background: #000"></span> """, ), self._addButton( None, "changeCol", tr.editing_change_colour_f8(), """ <span class="topbut rounded rainbow"></span> """, ), self._addButton("text_cloze", "cloze", tr.editing_cloze_deletion_ctrlandshiftandc()), self._addButton("paperclip", "attach", tr.editing_attach_picturesaudiovideo_f3()), self._addButton("media-record", "record", tr.editing_record_audio_f5()), self._addButton("more", "more"), ] gui_hooks.editor_did_init_buttons(righttopbtns, self) # legacy filter righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" class="topbuts"> %(leftbts)s </div> <div id="topbutsright" class="topbuts"> %(rightbts)s </div> """ % dict( leftbts="".join(lefttopbtns), rightbts="".join(righttopbtns), ) bgcol = self.mw.app.palette().window().color().name() # type: ignore # then load page self.web.stdHtml( _html % (bgcol, topbuts, tr.editing_show_duplicates()), css=[ "css/vendor/bootstrap.min.css", "css/editor.css", ], js=[ "js/vendor/jquery.min.js", "js/vendor/bootstrap.bundle.min.js", "js/editor.js", ], context=self, ) self.web.eval("preventButtonFocus();")
def setupWeb(self) -> None: self.web = EditorWebView(self.widget, self) self.web.allowDrops = True self.web.set_bridge_command(self.onBridgeCmd, self) self.outerLayout.addWidget(self.web, 1) lefttopbtns: List[str] = [ self._addButton( None, "fields", tr(TR.EDITING_CUSTOMIZE_FIELDS), tr(TR.EDITING_FIELDS) + "...", disables=False, rightside=False, ), self._addButton( None, "cards", tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL), tr(TR.EDITING_CARDS) + "...", disables=False, rightside=False, ), ] gui_hooks.editor_did_init_left_buttons(lefttopbtns, self) righttopbtns: List[str] = [ self._addButton("text_bold", "bold", tr(TR.EDITING_BOLD_TEXT_CTRLANDB), id="bold"), self._addButton( "text_italic", "italic", tr(TR.EDITING_ITALIC_TEXT_CTRLANDI), id="italic", ), self._addButton( "text_under", "underline", tr(TR.EDITING_UNDERLINE_TEXT_CTRLANDU), id="underline", ), self._addButton( "text_super", "super", tr(TR.EDITING_SUPERSCRIPT_CTRLANDAND), id="superscript", ), self._addButton("text_sub", "sub", tr(TR.EDITING_SUBSCRIPT_CTRLAND), id="subscript"), self._addButton("text_clear", "clear", tr(TR.EDITING_REMOVE_FORMATTING_CTRLANDR)), self._addButton( None, "colour", tr(TR.EDITING_SET_FOREGROUND_COLOUR_F7), """ <div id="forecolor" style="display: inline-block; background: #000; border-radius: 5px;" class="topbut" >""", ), self._addButton( None, "changeCol", tr(TR.EDITING_CHANGE_COLOUR_F8), """ <div style="display: inline-block; border-radius: 5px;" class="topbut rainbow" >""", ), self._addButton("text_cloze", "cloze", tr(TR.EDITING_CLOZE_DELETION_CTRLANDSHIFTANDC)), self._addButton("paperclip", "attach", tr(TR.EDITING_ATTACH_PICTURESAUDIOVIDEO_F3)), self._addButton("media-record", "record", tr(TR.EDITING_RECORD_AUDIO_F5)), self._addButton("more", "more"), ] gui_hooks.editor_did_init_buttons(righttopbtns, self) # legacy filter righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" style="float:left;"> %(leftbts)s </div> <div id="topbutsright" style="float:right;"> %(rightbts)s </div> """ % dict( leftbts="".join(lefttopbtns), rightbts="".join(righttopbtns), ) bgcol = self.mw.app.palette().window().color().name() # type: ignore # then load page self.web.stdHtml( _html % (bgcol, bgcol, topbuts, tr(TR.EDITING_SHOW_DUPLICATES)), css=["css/editor.css"], js=["js/vendor/jquery.min.js", "js/editor.js"], context=self, )
def report(self): "Return an HTML string with a report." fmtPerc = anki.utils.fmtPercentage fmtFloat = anki.utils.fmtFloat if self.deck.isEmpty(): return _("Please add some cards first.") + "<p/>" d = self.deck html="<h1>" + _("Deck Statistics") + "</h1>" html += _("Deck created: <b>%s</b> ago<br>") % self.createdTimeStr() total = d.cardCount new = d.newCountAll() young = d.youngCardCount() old = d.matureCardCount() newP = new / float(total) * 100 youngP = young / float(total) * 100 oldP = old / float(total) * 100 stats = d.getStats() (stats["new"], stats["newP"]) = (new, newP) (stats["old"], stats["oldP"]) = (old, oldP) (stats["young"], stats["youngP"]) = (young, youngP) html += _("Total number of cards:") + " <b>%d</b><br>" % total html += _("Total number of facts:") + " <b>%d</b><br><br>" % d.factCount html += "<b>" + _("Card Maturity") + "</b><br>" html += _("Mature cards: <!--card count-->") + " <b>%(old)d</b> (%(oldP)s)<br>" % { 'old': stats['old'], 'oldP' : fmtPerc(stats['oldP'])} html += _("Young cards: <!--card count-->") + " <b>%(young)d</b> (%(youngP)s)<br>" % { 'young': stats['young'], 'youngP' : fmtPerc(stats['youngP'])} html += _("Unseen cards:") + " <b>%(new)d</b> (%(newP)s)<br>" % { 'new': stats['new'], 'newP' : fmtPerc(stats['newP'])} avgInt = self.getAverageInterval() if avgInt: html += _("Average interval: ") + ("<b>%s</b> ") % fmtFloat(avgInt) + _("days") html += "<br>" html += "<br>" inactive = d.inactiveCardCount() suspended = d.suspendedCardCount() active = total - inactive - suspended inactiveP = inactive / float(total) * 100 suspendedP = suspended / float(total) * 100 activeP = active / float(total) * 100 html += "<b>" + _("Card State") + "</b><br>" html += _("Active cards:") + " <b>%(a)d</b> (%(b)s)<br>" % { 'a': active, 'b' : fmtPerc(activeP)} html += _("Inactive cards:") + " <b>%(a)d</b> (%(b)s)<br>" % { 'a': inactive, 'b' : fmtPerc(inactiveP)} html += _("Suspended cards:") + " <b>%(a)d</b> (%(b)s)<br><br>" % { 'a': suspended, 'b' : fmtPerc(suspendedP)} html += "<b>" + _("Correct Answers") + "</b><br>" html += _("Mature cards: <!--correct answers-->") + " <b>" + fmtPerc(stats['gMatureYes%']) + ( "</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf' : stats['gMatureYes'], 'totalSum' : stats['gMatureTotal'] } + "<br>") html += _("Young cards: <!--correct answers-->") + " <b>" + fmtPerc(stats['gYoungYes%']) + ( "</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf' : stats['gYoungYes'], 'totalSum' : stats['gYoungTotal'] } + "<br>") html += _("First-seen cards:") + " <b>" + fmtPerc(stats['gNewYes%']) + ( "</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf' : stats['gNewYes'], 'totalSum' : stats['gNewTotal'] } + "<br><br>") # average pending time existing = d.cardCount - d.newCountToday def tr(a, b): return "<tr><td>%s</td><td align=right>%s</td></tr>" % (a, b) if existing and avgInt: html += "<b>" + _("Recent Work") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("In last week"), ("<b>%d</b> reps/<b>%d</b> days") % ( self.getRepsDone(-7, 0), self.getDaysReviewed(-7, 0))) html += tr(_("In last month"), ("<b>%d</b> reps/<b>%d</b> days") % ( self.getRepsDone(-30, 0), self.getDaysReviewed(-30, 0))) html += tr(_("In last 3 months"), ("<b>%d</b> reps/<b>%d</b> days") % ( self.getRepsDone(-92, 0), self.getDaysReviewed(-92, 0))) html += tr(_("In last 6 months"), ("<b>%d</b> reps/<b>%d</b> days") % ( self.getRepsDone(-182, 0), self.getDaysReviewed(-182, 0))) html += tr(_("In last year"), ("<b>%d</b> reps/<b>%d</b> days") % ( self.getRepsDone(-365, 0), self.getDaysReviewed(-365, 0))) html += tr(_("Deck life"), ("<b>%d</b> reps/<b>%d</b> days") % ( self.getRepsDone(-13000, 0), self.getDaysReviewed(-13000, 0))) html += "</table>" html += "<br><br><b>" + _("Average Daily Reviews") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("Deck life"), ("<b>%s</b> ") % ( fmtFloat(self.getSumInverseRoundInterval())) + _("cards/day")) html += tr(_("In next week"), ("<b>%s</b> ") % ( fmtFloat(self.getWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In next month"), ("<b>%s</b> ") % ( fmtFloat(self.getWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last week"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In last month"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last 3 months"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(92))) + _("cards/day")) html += tr(_("In last 6 months"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(182))) + _("cards/day")) html += tr(_("In last year"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(365))) + _("cards/day")) html += "</table>" html += "<br><br><b>" + _("Average Added") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("Deck life"), _("<b>%(a)s</b>/day, <b>%(b)s</b>/mon") % { 'a': fmtFloat(self.newAverage()), 'b': fmtFloat(self.newAverage()*30)}) np = self.getNewPeriod(7) html += tr(_("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(7))})) np = self.getNewPeriod(30) html += tr(_("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getNewPeriod(92) html += tr(_("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getNewPeriod(182) html += tr(_("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getNewPeriod(365) html += tr(_("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) html += "</table>" html += "<br><br><b>" + _("Average New Seen") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" np = self.getFirstPeriod(7) html += tr(_("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(7))})) np = self.getFirstPeriod(30) html += tr(_("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getFirstPeriod(92) html += tr(_("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getFirstPeriod(182) html += tr(_("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getFirstPeriod(365) html += tr(_("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) html += "</table>" html += "<br><br><b>" + _("Card Ease") + "</b><br>" html += _("Lowest factor: %.2f") % d.s.scalar( "select min(factor) from cards") + "<br>" html += _("Average factor: %.2f") % d.s.scalar( "select avg(factor) from cards") + "<br>" html += _("Highest factor: %.2f") % d.s.scalar( "select max(factor) from cards") + "<br>" html = runFilter("deckStats", html) return html
def setupWeb(self) -> None: self.web = EditorWebView(self.widget, self) self.web.allowDrops = True self.web.set_bridge_command(self.onBridgeCmd, self) self.outerLayout.addWidget(self.web, 1) lefttopbtns: List[str] = [ self._addButton( None, "fields", _("Customize Fields"), _("Fields") + "...", disables=False, rightside=False, ), self._addButton( None, "cards", _("Customize Card Templates (Ctrl+L)"), _("Cards") + "...", disables=False, rightside=False, ), ] gui_hooks.editor_did_init_left_buttons(lefttopbtns, self) righttopbtns: List[str] = [ self._addButton("text_bold", "bold", _("Bold text (Ctrl+B)"), id="bold"), self._addButton("text_italic", "italic", _("Italic text (Ctrl+I)"), id="italic"), self._addButton("text_under", "underline", _("Underline text (Ctrl+U)"), id="underline"), self._addButton("text_super", "super", _("Superscript (Ctrl++)"), id="superscript"), self._addButton("text_sub", "sub", _("Subscript (Ctrl+=)"), id="subscript"), self._addButton("text_clear", "clear", _("Remove formatting (Ctrl+R)")), self._addButton( None, "colour", _("Set foreground colour (F7)"), """ <div id="forecolor" style="display: inline-block; background: #000; border-radius: 5px;" class="topbut" >""", ), self._addButton( None, "changeCol", _("Change colour (F8)"), """ <div style="display: inline-block; border-radius: 5px;" class="topbut rainbow" >""", ), self._addButton("text_cloze", "cloze", _("Cloze deletion (Ctrl+Shift+C)")), self._addButton("paperclip", "attach", _("Attach pictures/audio/video (F3)")), self._addButton("media-record", "record", _("Record audio (F5)")), self._addButton("more", "more"), ] gui_hooks.editor_did_init_buttons(righttopbtns, self) # legacy filter righttopbtns = runFilter("setupEditorButtons", righttopbtns, self) topbuts = """ <div id="topbutsleft" style="float:left;"> %(leftbts)s </div> <div id="topbutsright" style="float:right;"> %(rightbts)s </div> """ % dict( leftbts="".join(lefttopbtns), rightbts="".join(righttopbtns), ) bgcol = self.mw.app.palette().window().color().name() # type: ignore # then load page self.web.stdHtml( _html % (bgcol, bgcol, topbuts, _("Show Duplicates")), css=["css/editor.css"], js=["js/vendor/jquery.js", "js/editor.js"], context=self, )
def modSchema(self, check=True): "Mark schema modified. Call this first so user can abort if necessary." if not self.schemaChanged(): if check and not runFilter("modSchema", True): raise AnkiError("abortSchemaMod") self.scm = intTime(1000)
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)