mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
introduce separate routine to remove tags from specific notes
We were (ab)using the bulk update routine to do deletions, but that code was really intended to be used for finding&replacing, where an exact match is not a requirement.
This commit is contained in:
parent
33d467e688
commit
08895c58d9
10 changed files with 181 additions and 70 deletions
|
@ -68,9 +68,15 @@ class TagManager:
|
||||||
# Bulk addition/removal from specific notes
|
# Bulk addition/removal from specific notes
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
def bulk_add(self, nids: Sequence[int], tags: str) -> OpChangesWithCount:
|
def bulk_add(self, note_ids: Sequence[int], tags: str) -> OpChangesWithCount:
|
||||||
"""Add space-separate tags to provided notes, returning changed count."""
|
"""Add space-separate tags to provided notes, returning changed count."""
|
||||||
return self.col._backend.add_note_tags(nids=nids, tags=tags)
|
return self.col._backend.add_note_tags(note_ids=note_ids, tags=tags)
|
||||||
|
|
||||||
|
def bulk_remove(self, note_ids: Sequence[int], tags: str) -> OpChangesWithCount:
|
||||||
|
return self.col._backend.remove_note_tags(note_ids=note_ids, tags=tags)
|
||||||
|
|
||||||
|
# Find&replace
|
||||||
|
#############################################################
|
||||||
|
|
||||||
def bulk_update(
|
def bulk_update(
|
||||||
self, nids: Sequence[int], tags: str, replacement: str, regex: bool
|
self, nids: Sequence[int], tags: str, replacement: str, regex: bool
|
||||||
|
@ -81,9 +87,6 @@ class TagManager:
|
||||||
nids=nids, tags=tags, replacement=replacement, regex=regex
|
nids=nids, tags=tags, replacement=replacement, regex=regex
|
||||||
)
|
)
|
||||||
|
|
||||||
def bulk_remove(self, nids: Sequence[int], tags: str) -> OpChangesWithCount:
|
|
||||||
return self.bulk_update(nids, tags, "", False)
|
|
||||||
|
|
||||||
# Bulk addition/removal based on tag
|
# Bulk addition/removal based on tag
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
|
@ -167,7 +170,7 @@ class TagManager:
|
||||||
if add:
|
if add:
|
||||||
self.bulk_add(ids, tags)
|
self.bulk_add(ids, tags)
|
||||||
else:
|
else:
|
||||||
self.bulk_update(ids, tags, "", False)
|
self.bulk_remove(ids, tags)
|
||||||
|
|
||||||
def bulkRem(self, ids: List[int], tags: str) -> None:
|
def bulkRem(self, ids: List[int], tags: str) -> None:
|
||||||
self.bulkAdd(ids, tags, False)
|
self.bulkAdd(ids, tags, False)
|
||||||
|
|
|
@ -1353,7 +1353,14 @@ where id in %s"""
|
||||||
tags := tags or self._prompt_for_tags(tr(TR.BROWSING_ENTER_TAGS_TO_ADD))
|
tags := tags or self._prompt_for_tags(tr(TR.BROWSING_ENTER_TAGS_TO_ADD))
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
add_tags(mw=self.mw, note_ids=self.selected_notes(), space_separated_tags=tags)
|
add_tags(
|
||||||
|
mw=self.mw,
|
||||||
|
note_ids=self.selected_notes(),
|
||||||
|
space_separated_tags=tags,
|
||||||
|
success=lambda out: tooltip(
|
||||||
|
tr(TR.BROWSING_NOTES_UPDATED, count=out.count), parent=self
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@ensure_editor_saved_on_trigger
|
@ensure_editor_saved_on_trigger
|
||||||
def remove_tags_from_selected_notes(self, tags: Optional[str] = None) -> None:
|
def remove_tags_from_selected_notes(self, tags: Optional[str] = None) -> None:
|
||||||
|
@ -1363,7 +1370,12 @@ where id in %s"""
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
remove_tags_for_notes(
|
remove_tags_for_notes(
|
||||||
mw=self.mw, note_ids=self.selected_notes(), space_separated_tags=tags
|
mw=self.mw,
|
||||||
|
note_ids=self.selected_notes(),
|
||||||
|
space_separated_tags=tags,
|
||||||
|
success=lambda out: tooltip(
|
||||||
|
tr(TR.BROWSING_NOTES_UPDATED, count=out.count), parent=self
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _prompt_for_tags(self, prompt: str) -> Optional[str]:
|
def _prompt_for_tags(self, prompt: str) -> Optional[str]:
|
||||||
|
|
|
@ -39,14 +39,28 @@ def remove_notes(
|
||||||
mw.perform_op(lambda: mw.col.remove_notes(note_ids), success=success)
|
mw.perform_op(lambda: mw.col.remove_notes(note_ids), success=success)
|
||||||
|
|
||||||
|
|
||||||
def add_tags(*, mw: AnkiQt, note_ids: Sequence[int], space_separated_tags: str) -> None:
|
def add_tags(
|
||||||
mw.perform_op(lambda: mw.col.tags.bulk_add(note_ids, space_separated_tags))
|
*,
|
||||||
|
mw: AnkiQt,
|
||||||
|
note_ids: Sequence[int],
|
||||||
|
space_separated_tags: str,
|
||||||
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
|
) -> None:
|
||||||
|
mw.perform_op(
|
||||||
|
lambda: mw.col.tags.bulk_add(note_ids, space_separated_tags), success=success
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def remove_tags_for_notes(
|
def remove_tags_for_notes(
|
||||||
*, mw: AnkiQt, note_ids: Sequence[int], space_separated_tags: str
|
*,
|
||||||
|
mw: AnkiQt,
|
||||||
|
note_ids: Sequence[int],
|
||||||
|
space_separated_tags: str,
|
||||||
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.perform_op(lambda: mw.col.tags.bulk_remove(note_ids, space_separated_tags))
|
mw.perform_op(
|
||||||
|
lambda: mw.col.tags.bulk_remove(note_ids, space_separated_tags), success=success
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def clear_unused_tags(*, mw: AnkiQt, parent: QWidget) -> None:
|
def clear_unused_tags(*, mw: AnkiQt, parent: QWidget) -> None:
|
||||||
|
|
|
@ -152,8 +152,6 @@ service NotesService {
|
||||||
rpc UpdateNote(UpdateNoteIn) returns (OpChanges);
|
rpc UpdateNote(UpdateNoteIn) returns (OpChanges);
|
||||||
rpc GetNote(NoteID) returns (Note);
|
rpc GetNote(NoteID) returns (Note);
|
||||||
rpc RemoveNotes(RemoveNotesIn) returns (OpChanges);
|
rpc RemoveNotes(RemoveNotesIn) returns (OpChanges);
|
||||||
rpc AddNoteTags(AddNoteTagsIn) returns (OpChangesWithCount);
|
|
||||||
rpc UpdateNoteTags(UpdateNoteTagsIn) returns (OpChangesWithCount);
|
|
||||||
rpc ClozeNumbersInNote(Note) returns (ClozeNumbersInNoteOut);
|
rpc ClozeNumbersInNote(Note) returns (ClozeNumbersInNoteOut);
|
||||||
rpc AfterNoteUpdates(AfterNoteUpdatesIn) returns (OpChanges);
|
rpc AfterNoteUpdates(AfterNoteUpdatesIn) returns (OpChanges);
|
||||||
rpc FieldNamesForNotes(FieldNamesForNotesIn) returns (FieldNamesForNotesOut);
|
rpc FieldNamesForNotes(FieldNamesForNotesIn) returns (FieldNamesForNotesOut);
|
||||||
|
@ -224,6 +222,9 @@ service TagsService {
|
||||||
rpc TagTree(Empty) returns (TagTreeNode);
|
rpc TagTree(Empty) returns (TagTreeNode);
|
||||||
rpc DragDropTags(DragDropTagsIn) returns (Empty);
|
rpc DragDropTags(DragDropTagsIn) returns (Empty);
|
||||||
rpc RenameTags(RenameTagsIn) returns (OpChangesWithCount);
|
rpc RenameTags(RenameTagsIn) returns (OpChangesWithCount);
|
||||||
|
rpc AddNoteTags(NoteIDsAndTagsIn) returns (OpChangesWithCount);
|
||||||
|
rpc RemoveNoteTags(NoteIDsAndTagsIn) returns (OpChangesWithCount);
|
||||||
|
rpc UpdateNoteTags(UpdateNoteTagsIn) returns (OpChangesWithCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
service SearchService {
|
service SearchService {
|
||||||
|
@ -1043,8 +1044,8 @@ message AfterNoteUpdatesIn {
|
||||||
bool generate_cards = 3;
|
bool generate_cards = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddNoteTagsIn {
|
message NoteIDsAndTagsIn {
|
||||||
repeated int64 nids = 1;
|
repeated int64 note_ids = 1;
|
||||||
string tags = 2;
|
string tags = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,25 +87,6 @@ impl NotesService for Backend {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_note_tags(&self, input: pb::AddNoteTagsIn) -> Result<pb::OpChangesWithCount> {
|
|
||||||
self.with_col(|col| {
|
|
||||||
col.add_tags_to_notes(&to_note_ids(input.nids), &input.tags)
|
|
||||||
.map(Into::into)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_note_tags(&self, input: pb::UpdateNoteTagsIn) -> Result<pb::OpChangesWithCount> {
|
|
||||||
self.with_col(|col| {
|
|
||||||
col.replace_tags_for_notes(
|
|
||||||
&to_note_ids(input.nids),
|
|
||||||
&input.tags,
|
|
||||||
&input.replacement,
|
|
||||||
input.regex,
|
|
||||||
)
|
|
||||||
.map(Into::into)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cloze_numbers_in_note(&self, note: pb::Note) -> Result<pb::ClozeNumbersInNoteOut> {
|
fn cloze_numbers_in_note(&self, note: pb::Note) -> Result<pb::ClozeNumbersInNoteOut> {
|
||||||
let mut set = HashSet::with_capacity(4);
|
let mut set = HashSet::with_capacity(4);
|
||||||
for field in ¬e.fields {
|
for field in ¬e.fields {
|
||||||
|
@ -158,6 +139,6 @@ impl NotesService for Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_note_ids(ids: Vec<i64>) -> Vec<NoteID> {
|
pub(super) fn to_note_ids(ids: Vec<i64>) -> Vec<NoteID> {
|
||||||
ids.into_iter().map(NoteID).collect()
|
ids.into_iter().map(NoteID).collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use super::Backend;
|
use super::{notes::to_note_ids, Backend};
|
||||||
use crate::{backend_proto as pb, prelude::*};
|
use crate::{backend_proto as pb, prelude::*};
|
||||||
pub(super) use pb::tags_service::Service as TagsService;
|
pub(super) use pb::tags_service::Service as TagsService;
|
||||||
|
|
||||||
|
@ -55,4 +55,30 @@ impl TagsService for Backend {
|
||||||
self.with_col(|col| col.rename_tag(&input.current_prefix, &input.new_prefix))
|
self.with_col(|col| col.rename_tag(&input.current_prefix, &input.new_prefix))
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_note_tags(&self, input: pb::NoteIDsAndTagsIn) -> Result<pb::OpChangesWithCount> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.add_tags_to_notes(&to_note_ids(input.note_ids), &input.tags)
|
||||||
|
.map(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_note_tags(&self, input: pb::NoteIDsAndTagsIn) -> Result<pb::OpChangesWithCount> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.remove_tags_from_notes(&to_note_ids(input.note_ids), &input.tags)
|
||||||
|
.map(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_note_tags(&self, input: pb::UpdateNoteTagsIn) -> Result<pb::OpChangesWithCount> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.replace_tags_for_notes(
|
||||||
|
&to_note_ids(input.nids),
|
||||||
|
&input.tags,
|
||||||
|
&input.replacement,
|
||||||
|
input.regex,
|
||||||
|
)
|
||||||
|
.map(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -445,7 +445,7 @@ impl super::SqliteStorage {
|
||||||
|
|
||||||
/// Injects the provided card IDs into the search_cids table, for
|
/// Injects the provided card IDs into the search_cids table, for
|
||||||
/// when ids have arrived outside of a search.
|
/// when ids have arrived outside of a search.
|
||||||
/// Clear with clear_searched_cards().
|
/// Clear with clear_searched_cards_table().
|
||||||
pub(crate) fn set_search_table_to_card_ids(
|
pub(crate) fn set_search_table_to_card_ids(
|
||||||
&mut self,
|
&mut self,
|
||||||
cards: &[CardID],
|
cards: &[CardID],
|
||||||
|
|
|
@ -20,22 +20,6 @@ pub(crate) fn join_fields(fields: &[String]) -> String {
|
||||||
fields.join("\x1f")
|
fields.join("\x1f")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn row_to_note(row: &Row) -> Result<Note> {
|
|
||||||
Ok(Note::new_from_storage(
|
|
||||||
row.get(0)?,
|
|
||||||
row.get(1)?,
|
|
||||||
row.get(2)?,
|
|
||||||
row.get(3)?,
|
|
||||||
row.get(4)?,
|
|
||||||
split_tags(row.get_raw(5).as_str()?)
|
|
||||||
.map(Into::into)
|
|
||||||
.collect(),
|
|
||||||
split_fields(row.get_raw(6).as_str()?),
|
|
||||||
Some(row.get(7)?),
|
|
||||||
Some(row.get(8).unwrap_or_default()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::SqliteStorage {
|
impl super::SqliteStorage {
|
||||||
pub fn get_note(&self, nid: NoteID) -> Result<Option<Note>> {
|
pub fn get_note(&self, nid: NoteID) -> Result<Option<Note>> {
|
||||||
self.db
|
self.db
|
||||||
|
@ -193,20 +177,28 @@ impl super::SqliteStorage {
|
||||||
pub(crate) fn get_note_tags_by_id(&mut self, note_id: NoteID) -> Result<Option<NoteTags>> {
|
pub(crate) fn get_note_tags_by_id(&mut self, note_id: NoteID) -> Result<Option<NoteTags>> {
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached(&format!("{} where id = ?", include_str!("get_tags.sql")))?
|
.prepare_cached(&format!("{} where id = ?", include_str!("get_tags.sql")))?
|
||||||
.query_and_then(&[note_id], |row| -> Result<_> {
|
.query_and_then(&[note_id], row_to_note_tags)?
|
||||||
{
|
|
||||||
Ok(NoteTags {
|
|
||||||
id: row.get(0)?,
|
|
||||||
mtime: row.get(1)?,
|
|
||||||
usn: row.get(2)?,
|
|
||||||
tags: row.get(3)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})?
|
|
||||||
.next()
|
.next()
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_note_tags_by_id_list(
|
||||||
|
&mut self,
|
||||||
|
note_ids: &[NoteID],
|
||||||
|
) -> Result<Vec<NoteTags>> {
|
||||||
|
self.set_search_table_to_note_ids(note_ids)?;
|
||||||
|
let out = self
|
||||||
|
.db
|
||||||
|
.prepare_cached(&format!(
|
||||||
|
"{} where id in (select nid from search_nids)",
|
||||||
|
include_str!("get_tags.sql")
|
||||||
|
))?
|
||||||
|
.query_and_then(NO_PARAMS, row_to_note_tags)?
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
self.clear_searched_notes_table()?;
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_note_tags_by_predicate<F>(&mut self, want: F) -> Result<Vec<NoteTags>>
|
pub(crate) fn get_note_tags_by_predicate<F>(&mut self, want: F) -> Result<Vec<NoteTags>>
|
||||||
where
|
where
|
||||||
F: Fn(&str) -> bool,
|
F: Fn(&str) -> bool,
|
||||||
|
@ -217,12 +209,7 @@ impl super::SqliteStorage {
|
||||||
while let Some(row) = rows.next()? {
|
while let Some(row) = rows.next()? {
|
||||||
let tags = row.get_raw(3).as_str()?;
|
let tags = row.get_raw(3).as_str()?;
|
||||||
if want(tags) {
|
if want(tags) {
|
||||||
output.push(NoteTags {
|
output.push(row_to_note_tags(row)?)
|
||||||
id: row.get(0)?,
|
|
||||||
mtime: row.get(1)?,
|
|
||||||
usn: row.get(2)?,
|
|
||||||
tags: tags.to_owned(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(output)
|
Ok(output)
|
||||||
|
@ -234,4 +221,56 @@ impl super::SqliteStorage {
|
||||||
.execute(params![note.mtime, note.usn, note.tags, note.id])?;
|
.execute(params![note.mtime, note.usn, note.tags, note.id])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_searched_notes_table(&self) -> Result<()> {
|
||||||
|
self.db
|
||||||
|
.execute_batch(include_str!("search_nids_setup.sql"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_searched_notes_table(&self) -> Result<()> {
|
||||||
|
self.db
|
||||||
|
.execute("drop table if exists search_nids", NO_PARAMS)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects the provided card IDs into the search_nids table, for
|
||||||
|
/// when ids have arrived outside of a search.
|
||||||
|
/// Clear with clear_searched_notes_table().
|
||||||
|
fn set_search_table_to_note_ids(&mut self, notes: &[NoteID]) -> Result<()> {
|
||||||
|
self.setup_searched_notes_table()?;
|
||||||
|
let mut stmt = self
|
||||||
|
.db
|
||||||
|
.prepare_cached("insert into search_nids values (?)")?;
|
||||||
|
for nid in notes {
|
||||||
|
stmt.execute(&[nid])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_to_note(row: &Row) -> Result<Note> {
|
||||||
|
Ok(Note::new_from_storage(
|
||||||
|
row.get(0)?,
|
||||||
|
row.get(1)?,
|
||||||
|
row.get(2)?,
|
||||||
|
row.get(3)?,
|
||||||
|
row.get(4)?,
|
||||||
|
split_tags(row.get_raw(5).as_str()?)
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
|
split_fields(row.get_raw(6).as_str()?),
|
||||||
|
Some(row.get(7)?),
|
||||||
|
Some(row.get(8).unwrap_or_default()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_to_note_tags(row: &Row) -> Result<NoteTags> {
|
||||||
|
Ok(NoteTags {
|
||||||
|
id: row.get(0)?,
|
||||||
|
mtime: row.get(1)?,
|
||||||
|
usn: row.get(2)?,
|
||||||
|
tags: row.get(3)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
2
rslib/src/storage/note/search_nids_setup.sql
Normal file
2
rslib/src/storage/note/search_nids_setup.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
DROP TABLE IF EXISTS search_nids;
|
||||||
|
CREATE TEMPORARY TABLE search_nids (nid integer PRIMARY KEY NOT NULL);
|
|
@ -10,6 +10,17 @@ impl Collection {
|
||||||
self.transact(Op::RemoveTag, |col| col.remove_tags_inner(tags))
|
self.transact(Op::RemoveTag, |col| col.remove_tags_inner(tags))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove whitespace-separated tags from provided notes.
|
||||||
|
pub fn remove_tags_from_notes(
|
||||||
|
&mut self,
|
||||||
|
nids: &[NoteID],
|
||||||
|
tags: &str,
|
||||||
|
) -> Result<OpOutput<usize>> {
|
||||||
|
self.transact(Op::RemoveTag, |col| {
|
||||||
|
col.remove_tags_from_notes_inner(nids, tags)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove tags not referenced by notes, returning removed count.
|
/// Remove tags not referenced by notes, returning removed count.
|
||||||
pub fn clear_unused_tags(&mut self) -> Result<OpOutput<usize>> {
|
pub fn clear_unused_tags(&mut self) -> Result<OpOutput<usize>> {
|
||||||
self.transact(Op::ClearUnusedTags, |col| col.clear_unused_tags_inner())
|
self.transact(Op::ClearUnusedTags, |col| col.clear_unused_tags_inner())
|
||||||
|
@ -43,6 +54,28 @@ impl Collection {
|
||||||
Ok(match_count)
|
Ok(match_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_tags_from_notes_inner(&mut self, nids: &[NoteID], tags: &str) -> Result<usize> {
|
||||||
|
let usn = self.usn()?;
|
||||||
|
|
||||||
|
let mut re = PrefixReplacer::new(tags)?;
|
||||||
|
let mut match_count = 0;
|
||||||
|
let notes = self.storage.get_note_tags_by_id_list(nids)?;
|
||||||
|
|
||||||
|
for mut note in notes {
|
||||||
|
if !re.is_match(¬e.tags) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match_count += 1;
|
||||||
|
let original = note.clone();
|
||||||
|
note.tags = re.remove(¬e.tags);
|
||||||
|
note.set_modified(usn);
|
||||||
|
self.update_note_tags_undoable(¬e, original)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match_count)
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_unused_tags_inner(&mut self) -> Result<usize> {
|
fn clear_unused_tags_inner(&mut self) -> Result<usize> {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let in_notes = self.storage.all_tags_in_notes()?;
|
let in_notes = self.storage.all_tags_in_notes()?;
|
||||||
|
|
Loading…
Reference in a new issue