mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 15:02:21 -04:00
Merge pull request #923 from RumovZ/fix-write-dupe
Fix writing dupe node and escape issues
This commit is contained in:
commit
23bc7ac892
8 changed files with 47 additions and 12 deletions
|
@ -48,6 +48,8 @@ DeckTreeNode = pb.DeckTreeNode
|
||||||
StockNoteType = pb.StockNoteType
|
StockNoteType = pb.StockNoteType
|
||||||
FilterToSearchIn = pb.FilterToSearchIn
|
FilterToSearchIn = pb.FilterToSearchIn
|
||||||
NamedFilter = pb.FilterToSearchIn.NamedFilter
|
NamedFilter = pb.FilterToSearchIn.NamedFilter
|
||||||
|
DupeIn = pb.FilterToSearchIn.DupeIn
|
||||||
|
BackendNoteTypeID = pb.NoteTypeID
|
||||||
ConcatSeparator = pb.ConcatenateSearchesIn.Separator
|
ConcatSeparator = pb.ConcatenateSearchesIn.Separator
|
||||||
SyncAuth = pb.SyncAuth
|
SyncAuth = pb.SyncAuth
|
||||||
SyncOutput = pb.SyncCollectionOut
|
SyncOutput = pb.SyncCollectionOut
|
||||||
|
|
|
@ -21,8 +21,10 @@ from anki.lang import without_unicode_isolation
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.rsbackend import (
|
from anki.rsbackend import (
|
||||||
|
BackendNoteTypeID,
|
||||||
ConcatSeparator,
|
ConcatSeparator,
|
||||||
DeckTreeNode,
|
DeckTreeNode,
|
||||||
|
DupeIn,
|
||||||
FilterToSearchIn,
|
FilterToSearchIn,
|
||||||
InvalidInput,
|
InvalidInput,
|
||||||
NamedFilter,
|
NamedFilter,
|
||||||
|
@ -2016,6 +2018,17 @@ where id in %s"""
|
||||||
# Edit: finding dupes
|
# Edit: finding dupes
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
# filter called by the editor
|
||||||
|
def search_dupe(self, mid: int, text: str):
|
||||||
|
self.form.searchEdit.lineEdit().setText(
|
||||||
|
self.col.backend.filter_to_search(
|
||||||
|
FilterToSearchIn(
|
||||||
|
dupe=DupeIn(mid=BackendNoteTypeID(ntid=mid), text=text)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.onSearchActivated()
|
||||||
|
|
||||||
def onFindDupes(self):
|
def onFindDupes(self):
|
||||||
self.editor.saveNow(self._onFindDupes)
|
self.editor.saveNow(self._onFindDupes)
|
||||||
|
|
||||||
|
|
|
@ -537,12 +537,8 @@ class Editor:
|
||||||
self.web.eval("setBackgrounds(%s);" % json.dumps(cols))
|
self.web.eval("setBackgrounds(%s);" % json.dumps(cols))
|
||||||
|
|
||||||
def showDupes(self):
|
def showDupes(self):
|
||||||
contents = self.note.fields[0].replace('"', r"\"")
|
|
||||||
browser = aqt.dialogs.open("Browser", self.mw)
|
browser = aqt.dialogs.open("Browser", self.mw)
|
||||||
browser.form.searchEdit.lineEdit().setText(
|
browser.search_dupe(self.note.model()["id"], self.note.fields[0])
|
||||||
'"dupe:%s,%s"' % (self.note.model()["id"], contents)
|
|
||||||
)
|
|
||||||
browser.onSearchActivated()
|
|
||||||
|
|
||||||
def fieldsAreBlank(self, previousNote=None):
|
def fieldsAreBlank(self, previousNote=None):
|
||||||
if not self.note:
|
if not self.note:
|
||||||
|
|
|
@ -775,12 +775,17 @@ message FilterToSearchIn {
|
||||||
NO_FLAG = 15;
|
NO_FLAG = 15;
|
||||||
ANY_FLAG = 16;
|
ANY_FLAG = 16;
|
||||||
}
|
}
|
||||||
|
message DupeIn {
|
||||||
|
NoteTypeID mid = 1;
|
||||||
|
string text = 2;
|
||||||
|
}
|
||||||
oneof filter {
|
oneof filter {
|
||||||
NamedFilter name = 1;
|
NamedFilter name = 1;
|
||||||
string tag = 2;
|
string tag = 2;
|
||||||
string deck = 3;
|
string deck = 3;
|
||||||
string note = 4;
|
string note = 4;
|
||||||
uint32 template = 5;
|
uint32 template = 5;
|
||||||
|
DupeIn dupe = 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -327,6 +327,10 @@ impl From<pb::FilterToSearchIn> for Node<'_> {
|
||||||
Filter::Template(u) => {
|
Filter::Template(u) => {
|
||||||
Node::Search(SearchNode::CardTemplate(TemplateKind::Ordinal(u as u16)))
|
Node::Search(SearchNode::CardTemplate(TemplateKind::Ordinal(u as u16)))
|
||||||
}
|
}
|
||||||
|
Filter::Dupe(dupe) => Node::Search(SearchNode::Duplicates {
|
||||||
|
note_type_id: dupe.mid.unwrap_or(pb::NoteTypeId { ntid: 0 }).into(),
|
||||||
|
text: dupe.text.into(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,8 +293,8 @@ fn search_node_for_text_with_argument<'a>(
|
||||||
"is" => parse_state(val)?,
|
"is" => parse_state(val)?,
|
||||||
"flag" => parse_flag(val)?,
|
"flag" => parse_flag(val)?,
|
||||||
"rated" => parse_rated(val)?,
|
"rated" => parse_rated(val)?,
|
||||||
|
"dupe" => parse_dupe(val)?,
|
||||||
"resched" => parse_resched(val)?,
|
"resched" => parse_resched(val)?,
|
||||||
"dupe" => parse_dupes(val)?,
|
|
||||||
"prop" => parse_prop(val)?,
|
"prop" => parse_prop(val)?,
|
||||||
"re" => SearchNode::Regex(unescape_quotes(val)),
|
"re" => SearchNode::Regex(unescape_quotes(val)),
|
||||||
"nc" => SearchNode::NoCombining(unescape(val)?),
|
"nc" => SearchNode::NoCombining(unescape(val)?),
|
||||||
|
@ -392,14 +392,14 @@ fn parse_resched(val: &str) -> ParseResult<SearchNode<'static>> {
|
||||||
Ok(SearchNode::Rated { days, ease })
|
Ok(SearchNode::Rated { days, ease })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// eg dupes:1231,hello
|
/// eg dupe:1231,hello
|
||||||
fn parse_dupes(val: &str) -> ParseResult<SearchNode> {
|
fn parse_dupe(val: &str) -> ParseResult<SearchNode> {
|
||||||
let mut it = val.splitn(2, ',');
|
let mut it = val.splitn(2, ',');
|
||||||
let mid: NoteTypeID = it.next().unwrap().parse()?;
|
let mid: NoteTypeID = it.next().unwrap().parse()?;
|
||||||
let text = it.next().ok_or(ParseError {})?;
|
let text = it.next().ok_or(ParseError {})?;
|
||||||
Ok(SearchNode::Duplicates {
|
Ok(SearchNode::Duplicates {
|
||||||
note_type_id: mid,
|
note_type_id: mid,
|
||||||
text: unescape_quotes(text),
|
text: unescape_quotes_and_backslashes(text),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,6 +478,15 @@ fn unescape_quotes(s: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For non-globs like dupe text without any assumption about the content
|
||||||
|
fn unescape_quotes_and_backslashes(s: &str) -> Cow<str> {
|
||||||
|
if s.contains('"') || s.contains('\\') {
|
||||||
|
s.replace(r#"\""#, "\"").replace(r"\\", r"\").into()
|
||||||
|
} else {
|
||||||
|
s.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Unescape chars with special meaning to the parser.
|
/// Unescape chars with special meaning to the parser.
|
||||||
fn unescape(txt: &str) -> ParseResult<Cow<str>> {
|
fn unescape(txt: &str) -> ParseResult<Cow<str>> {
|
||||||
if is_invalid_escape(txt) {
|
if is_invalid_escape(txt) {
|
||||||
|
|
|
@ -123,7 +123,7 @@ impl SqlWriter<'_> {
|
||||||
self.write_single_field(&norm(field), &self.norm_note(text), *is_re)?
|
self.write_single_field(&norm(field), &self.norm_note(text), *is_re)?
|
||||||
}
|
}
|
||||||
SearchNode::Duplicates { note_type_id, text } => {
|
SearchNode::Duplicates { note_type_id, text } => {
|
||||||
self.write_dupes(*note_type_id, &self.norm_note(text))?
|
self.write_dupe(*note_type_id, &self.norm_note(text))?
|
||||||
}
|
}
|
||||||
SearchNode::Regex(re) => self.write_regex(&self.norm_note(re)),
|
SearchNode::Regex(re) => self.write_regex(&self.norm_note(re)),
|
||||||
SearchNode::NoCombining(text) => self.write_no_combining(&self.norm_note(text)),
|
SearchNode::NoCombining(text) => self.write_no_combining(&self.norm_note(text)),
|
||||||
|
@ -443,7 +443,7 @@ impl SqlWriter<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_dupes(&mut self, ntid: NoteTypeID, text: &str) -> Result<()> {
|
fn write_dupe(&mut self, ntid: NoteTypeID, text: &str) -> Result<()> {
|
||||||
let text_nohtml = strip_html_preserving_media_filenames(text);
|
let text_nohtml = strip_html_preserving_media_filenames(text);
|
||||||
let csum = field_checksum(text_nohtml.as_ref());
|
let csum = field_checksum(text_nohtml.as_ref());
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ fn write_search_node(node: &SearchNode) -> String {
|
||||||
NoteType(s) => quote(&format!("note:{}", s)),
|
NoteType(s) => quote(&format!("note:{}", s)),
|
||||||
Rated { days, ease } => write_rated(days, ease),
|
Rated { days, ease } => write_rated(days, ease),
|
||||||
Tag(s) => quote(&format!("tag:{}", s)),
|
Tag(s) => quote(&format!("tag:{}", s)),
|
||||||
Duplicates { note_type_id, text } => quote(&format!("dupes:{},{}", note_type_id, text)),
|
Duplicates { note_type_id, text } => write_dupe(note_type_id, text),
|
||||||
State(k) => write_state(k),
|
State(k) => write_state(k),
|
||||||
Flag(u) => format!("\"flag:{}\"", u),
|
Flag(u) => format!("\"flag:{}\"", u),
|
||||||
NoteIDs(s) => format!("\"nid:{}\"", s),
|
NoteIDs(s) => format!("\"nid:{}\"", s),
|
||||||
|
@ -163,6 +163,12 @@ fn write_rated(days: &u32, ease: &EaseKind) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Escape double quotes and backslashes: \"
|
||||||
|
fn write_dupe(note_type_id: &NoteTypeIDType, text: &str) -> String {
|
||||||
|
let esc = text.replace(r"\", r"\\").replace('"', r#"\""#);
|
||||||
|
format!("\"dupe:{},{}\"", note_type_id, esc)
|
||||||
|
}
|
||||||
|
|
||||||
fn write_state(kind: &StateKind) -> String {
|
fn write_state(kind: &StateKind) -> String {
|
||||||
use StateKind::*;
|
use StateKind::*;
|
||||||
format!(
|
format!(
|
||||||
|
|
Loading…
Reference in a new issue