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
Пример #3
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)))
Пример #4
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)))
Пример #5
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)))
Пример #6
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)))
Пример #7
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)))
Пример #8
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)
Пример #9
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)
             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)))
Пример #10
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()
Пример #12
0
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})
Пример #13
0
 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
Пример #15
0
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