def wrapper(*args, **kwargs) -> str: s = f(*args, **kwargs) s = htmlToTextLine(s) s = s.encode('utf-8')[:max_len_bytes].decode('utf-8', errors='ignore') s = unicodedata.normalize('NFC', s) s = replace_forbidden_chars(s) s = s.lower() s = s.strip('-_ ') return s if s else FilePathFactory.default_prefix
def history(line, note): fields = note.fields col = note.col model = note.model() sort_idx = model["sortf"] sort_field = fields.pop(sort_idx) fields = [field_content for field_content in fields if field_content] fields = [sort_field] + fields txt = htmlToTextLine(", ".join(fields)) nb_char = getUserOption("Number of character") if len(txt) > nb_char: txt = txt[:nb_char] + "..." return txt
def onHistory(self): m = QMenu(self) for nid in self.history: if self.mw.col.findNotes("nid:%s" % nid): fields = self.mw.col.getNote(nid).fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 30: txt = txt[:30] + "..." a = m.addAction(_('Edit "%s"') % txt) a.triggered.connect(lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(_("(Note deleted)")) a.setEnabled(False) runHook("AddCards.onHistory", self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def onHistory(self): m = QMenu(self) for nid in self.history: if self.mw.col.findNotes("nid:%s" % nid): fields = self.mw.col.getNote(nid).fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 30: txt = txt[:30] + "..." a = m.addAction(_("Edit \"%s\"") % txt) a.triggered.connect(lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(_("(Note deleted)")) a.setEnabled(False) runHook("AddCards.onHistory", self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0,0)))
def onHistory(self) -> None: m = QMenu(self) for nid in self.history: if self.mw.col.findNotes("nid:%s" % nid): note = self.mw.col.getNote(nid) fields = note.fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 30: txt = txt[:30] + "..." line = tr(TR.ADDING_EDIT, val=txt) line = gui_hooks.addcards_will_add_history_entry(line, note) a = m.addAction(line) qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(tr(TR.ADDING_NOTE_DELETED)) a.setEnabled(False) gui_hooks.add_cards_will_show_history_menu(self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def onHistory(self): m = QMenu(self) ah = self.mw.pm.profile.get("addHistory", []) for nid in ah: if self.mw.col.findNotes("nid:%d" % nid): fields = self.mw.col.getNote(nid).fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 50: txt = txt[:20] + " ... " + txt[-25:] a = m.addAction(_("Edit \"%s\"") % txt) a.triggered.connect(lambda b, nid=nid: self.editHistory(nid)) cnt = len(m.actions()) if cnt < len(ah): a = m.addAction(_("[ Note(s) deleted ]")) a.setEnabled(False) a = m.addAction(_("[ Browse All ]")) a.triggered.connect(lambda b, nid=0: self.editHistory(nid)) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def onHistory(self) -> None: m = QMenu(self) for nid in self.history: if self.col.find_notes( self.col.build_search_string(SearchNode(nid=nid))): note = self.col.get_note(nid) fields = note.fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 30: txt = f"{txt[:30]}..." line = tr.adding_edit(val=txt) line = gui_hooks.addcards_will_add_history_entry(line, note) a = m.addAction(line) qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(tr.adding_note_deleted()) a.setEnabled(False) gui_hooks.add_cards_will_show_history_menu(self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def getCardsBatch(msg): typeCheck(msg, {'cardIds': list}) with Col() as col: noteDict = {} cards = [col.getCard(cid) for cid in msg['cardIds']] ret = [] for card in cards: try: note = noteDict[card.nid] except KeyError: note = noteDict[card.nid] = card.note() model = card.model() # Code from aqt/browser.py if card.odid: # Special case: filtered decks due = '(filtered)' elif card.queue == 1: # Learning card due = card.due elif card.queue == 0 or card.type == 0: # New cards due = '(new card)' elif card.queue in (2, 3) or (card.type == 2 and card.queue < 0): due = col.crt + 86400 * card.due else: due = '' ret.append({ 'id': card.id, 'deck': col.decks.get(card.did)['name'], 'noteId': note.id, 'ord': card.ord, 'model': model['name'], 'preview': htmlToTextLine(card.q(browser=True)), 'tags': note.tags, 'createdAt': card.id // 1000, 'updatedAt': card.mod, 'due': due, 'type': card.type, 'queue': card.queue, }) return emit.emitResult(ret)
def onHistory(self) -> None: m = QMenu(self) for nid in self.history: if self.col.find_notes( self.col.build_search_string(SearchNode(nid=nid))): note = self.col.get_note(nid) fields = note.fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 30: txt = f"{txt[:30]}..." line = tr.adding_edit(val=txt) line = gui_hooks.addcards_will_add_history_entry(line, note) line = line.replace("&", "&&") # In qt action "&i" means "underline i, trigger this line when i is pressed". # except for "&&" which is replaced by a single "&" a = m.addAction(line) qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(tr.adding_note_deleted()) a.setEnabled(False) gui_hooks.add_cards_will_show_history_menu(self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def saveField(note, fld, val): if fld == "Tags": tagsTxt = unicodedata.normalize("NFC", htmlToTextLine(val)) txt = mw.col.tags.canonify(mw.col.tags.split(tagsTxt)) field = note.tags else: # https://github.com/dae/anki/blob/47eab46f05c8cc169393c785f4c3f49cf1d7cca8/aqt/editor.py#L257-L263 txt = urllib.parse.unquote(val) txt = unicodedata.normalize("NFC", txt) txt = Editor.mungeHTML(None, txt) txt = txt.replace("\x00", "") txt = mw.col.media.escapeImages(txt, unescape=True) field = note[fld] if field == txt: return config = mw.addonManager.getConfig(__name__) if config['undo']: mw.checkpoint("Edit Field") if fld == "Tags": note.tags = txt else: note[fld] = txt note.flush()
def saveField(note, fld, val): if fld == "Tags": tagsTxt = unicodedata.normalize("NFC", htmlToTextLine(val)) txt = mw.col.tags.canonify(mw.col.tags.split(tagsTxt)) field = note.tags else: # aqt.editor.Editor.onBridgeCmd txt = unicodedata.normalize("NFC", val) txt = Editor.mungeHTML(None, txt) txt = txt.replace("\x00", "") txt = mw.col.media.escapeImages(txt, unescape=True) field = note[fld] if field == txt: return if config['undo']: mw.checkpoint("Edit Field") if fld == "Tags": note.tags = txt else: note[fld] = txt note.flush()
def addNote(msg): typeCheck(msg, {'deck': str, 'model': str, 'fields': list, 'tags': list}) with Col() as col: model = col.models.byName(msg['model']) did = col.decks.id(msg['deck'], create=True) # cf) Create deck if not exists fields = msg['fields'] tags = msg['tags'] if not htmlToTextLine(fields[0]): return emit.emitError('First field should not be empty') if isClozeNote(fields) and model['type'] == 0: if msg['model'] in ('Basic', _('Basic')): # Automatic Basic → Cloze model = col.models.byName('Cloze') or col.models.byName( _('Cloze')) else: model = None if not model: return emit.emitError( 'You need a cloze note type to make cloze notes') if not isClozeNote(fields) and model['type'] == 1: return emit.emitError('You need at least one cloze deletion.') note = Note(col, model) if len(note.fields) != len(fields): raise RuntimeError('Field number mismatch') note.fields[:] = fields note.tags[:] = tags model['did'] = did cardNum = col.addNote(note) return emit.emitResult({'noteId': note.id, 'cardNum': cardNum})
def callback(fieldText): if fieldText: fieldText = htmlToTextLine(fieldText) self._show(fieldText, *args, **kwargs)
def report_mod(self): from anki import version anki_version = int(version.replace('.', '')) if anki_version > 2119: from aqt.theme import theme_manager config = mw.addonManager.getConfig(__name__) infobar_created = config['Card Info sidebar_ Created'] infobar_edited = config['Card Info sidebar_ Edited'] infobar_firstReview = config['Card Info sidebar_ First Review'] infobar_latestReview = config['Card Info sidebar_ Latest Review'] infobar_due = config['Card Info sidebar_ Due'] infobar_interval = config['Card Info sidebar_ Interval'] infobar_ease = config['Card Info sidebar_ Ease'] infobar_reviews = config['Card Info sidebar_ Reviews'] infobar_lapses = config['Card Info sidebar_ Lapses'] infobar_correctPercent = config['Card Info Sidebar_ COrrect Percent'] infobar_fastestReview = config['Card Info Sidebar_ Fastest Review'] infobar_slowestReview = config['Card Info Sidebar_ Slowest Review'] infobar_avgTime = config['Card Info sidebar_ Average Time'] infobar_totalTime = config['Card Info sidebar_ Total Time'] infobar_cardType = config['Card Info sidebar_ Card Type'] infobar_noteType = config['Card Info sidebar_ Note Type'] infobar_deck = config['Card Info sidebar_ Deck'] infobar_tags = config['Card Info sidebar_ Tags'] infobar_noteID = config['Card Info Sidebar_ Note ID'] infobar_cardID = config['Card Info Sidebar_ Card ID'] infobar_sortField = config['Card Info sidebar_ Sort Field'] c = self.card fmt = lambda x, **kwargs: fmtTimeSpan(x, short=True, **kwargs) self.txt = "<table width=100%>" if infobar_created: self.addLine( "Created", time.strftime("%Y-%m-%d | %H:%M", time.localtime(c.id / 1000))) if infobar_edited: if c.note().mod != False and time.localtime( c.id / 1000) != time.localtime(c.note().mod): self.addLine( "Edited", time.strftime("%Y-%m-%d | %H:%M", time.localtime(c.note().mod))) first = self.col.db.scalar("select min(id) from revlog where cid = ?", c.id) last = self.col.db.scalar("select max(id) from revlog where cid = ?", c.id) if first: if infobar_firstReview: self.addLine( "First Review", time.strftime("%Y-%m-%d | %H:%M", time.localtime(first / 1000))) if infobar_latestReview: self.addLine( "Latest Review", time.strftime("%Y-%m-%d | %H:%M", time.localtime(last / 1000))) if c.type != 0: if c.odid or c.queue < 0: next = None else: if c.queue in (2, 3): next = time.time() + ( (c.due - self.col.sched.today) * 86400) else: next = c.due next = self.date(next) if next: if infobar_due: self.addLine("Due", next) if c.queue == 2: if infobar_interval: self.addLine("Interval", fmt(c.ivl * 86400)) if infobar_ease: self.addLine("Ease", "%d%%" % (c.factor / 10.0)) if infobar_lapses: self.addLine("Lapses", "%d" % c.lapses) if self.col.schedVer() == 1: pressed_again = mw.col.db.scalar( "select sum(case when ease = 1 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_good = mw.col.db.scalar( "select sum(case when ease = 2 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_easy = mw.col.db.scalar( "select sum(case when ease = 3 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_all = pressed_again + pressed_good + pressed_easy self.addLine( "Again", "{} | {:.0f}%".format( str(pressed_again).rjust(4), float(pressed_again / pressed_all) * 100)) self.addLine( "Good", "{} | {:.0f}%".format( str(pressed_good).rjust(4), float(pressed_good / pressed_all) * 100)) self.addLine( "Easy", "{} | {:.0f}%".format( str(pressed_easy).rjust(4), float(pressed_easy / pressed_all) * 100)) elif self.col.schedVer() == 2: pressed_again = mw.col.db.scalar( "select sum(case when ease = 1 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_hard = mw.col.db.scalar( "select sum(case when ease = 2 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_good = mw.col.db.scalar( "select sum(case when ease = 3 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_easy = mw.col.db.scalar( "select sum(case when ease = 4 then 1 else 0 end) from revlog where cid = ?", c.id) pressed_all = pressed_again + pressed_hard + pressed_good + pressed_easy self.addLine( "Again", "{} | {:.0f}%".format( str(pressed_again).rjust(4), float(pressed_again / pressed_all) * 100)) self.addLine( "Hard", "{} | {:.0f}%".format( str(pressed_hard).rjust(4), float(pressed_hard / pressed_all) * 100)) self.addLine( "Good", "{} | {:.0f}%".format( str(pressed_good).rjust(4), float(pressed_good / pressed_all) * 100)) self.addLine( "Easy", "{} | {:.0f}%".format( str(pressed_easy).rjust(4), float(pressed_easy / pressed_all) * 100)) if infobar_reviews: self.addLine("Reviews", "%d" % c.reps) (cnt, total) = self.col.db.first( "select count(), sum(time)/1000 from revlog where cid = ?", c.id) if infobar_correctPercent and c.reps > 0: self.addLine( "Correct Percentage", "{:.0f}%".format( float((c.reps - c.lapses) / c.reps) * 100)) if infobar_fastestReview: fastes_rev = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? order by time asc limit 1", c.id) self.addLine("Fastest Review", self.time(fastes_rev)) if infobar_slowestReview: slowest_rev = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? order by time desc limit 1", c.id) self.addLine("Slowest Review", self.time(slowest_rev)) if cnt: if infobar_avgTime: self.addLine("Average Time", self.time(total / float(cnt))) if infobar_totalTime: self.addLine("Total Time", self.time(total)) elif c.queue == 0: if infobar_due: self.addLine("Position", c.due) if infobar_cardType: self.addLine("Card Type", c.template()['name']) if infobar_noteType: self.addLine("Note Type", c.model()['name']) if infobar_noteID: self.addLine("Note ID", c.nid) if infobar_cardID: self.addLine("Card ID", c.id) if infobar_deck: self.addLine("Deck", self.col.decks.name(c.did)) if c.note().tags: if infobar_tags: self.addLine("Tags", " | ".join(c.note().tags)) f = c.note() sort_field = htmlToTextLine(f.fields[self.col.models.sortIdx( f.model())]) if infobar_sortField: if len(sort_field) > 40: self.addLine( "Sort Field", "[{}<br>{}<br>{}...]".format(sort_field[:20], sort_field[20:41], sort_field[41:58])) else: self.addLine( "Sort Field", htmlToTextLine(f.fields[self.col.models.sortIdx( f.model())])) self.txt += "</table>" return self.txt
def smart_copy(changed, note, current_field_index): configuration = _create_configuration_from_config() if not _model_is_correct_type(configuration, note.model()): return changed if note.keys()[current_field_index] != configuration.subject_field_name: return changed text_to_search = ( htmlToTextLine(mw.col.media.strip(note[configuration.subject_field_name])).strip() ) if not text_to_search: return changed note_ids = ( mw.col.db.list("SELECT id FROM notes WHERE flds LIKE " + f"'%{FIELD_SEPARATOR}{text_to_search}{FIELD_SEPARATOR}%'") ) note_changed = False for whole_text_configuration in configuration.whole_text_configurations: source = whole_text_configuration.field_name_to_copy_from destination = whole_text_configuration.field_name_to_copy_to if destination not in note or \ (note[destination] and whole_text_configuration.copy_only_if_field_empty): continue note_to_copy_from = ( _get_note_from_note_id_with_model(note_ids, whole_text_configuration.model_name) ) if note_to_copy_from is None or source not in note_to_copy_from: continue source_value = note_to_copy_from[source] if whole_text_configuration.regex_remove: source_value = re.sub(whole_text_configuration.regex_remove, "", source_value) source_value = ( _source_value_after_blanking_out_word(source_value, whole_text_configuration, text_to_search) ) if _source_exists_in_destination(source_value, note[destination]): continue if not note[destination]: note[destination] = source_value else: note[destination] += "<br>" + source_value note_changed = True for per_character_configuration in configuration.per_character_configurations: source = per_character_configuration.field_name_to_copy_from index_of_filtered_character = 0 for character in text_to_search: if not per_character_configuration.filter_characters(character): continue if index_of_filtered_character >= len(per_character_configuration.field_names_to_copy_to): break destination = per_character_configuration.field_names_to_copy_to[index_of_filtered_character] index_of_filtered_character += 1 if destination not in note or \ (note[destination] and per_character_configuration.copy_only_if_field_empty): continue note_ids = ( mw.col.db.list("SELECT id FROM notes WHERE flds LIKE " + f"'%{FIELD_SEPARATOR}{character}{FIELD_SEPARATOR}%'") ) note_to_copy_from = ( _get_note_from_note_id_with_model(note_ids, per_character_configuration.model_name) ) if note_to_copy_from is None or source not in note_to_copy_from: continue source_value = note_to_copy_from[source] if _source_exists_in_destination(source_value, note[destination]): continue if not note[destination]: note[destination] = source_value else: note[destination] += "<br>" + source_value note_changed = True if not note_changed: return changed if note.id != 0: note.flush() return True