mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Optionally export deck and notetype with CSV
This commit is contained in:
parent
fc31478196
commit
6cd10e858e
12 changed files with 215 additions and 38 deletions
|
@ -38,3 +38,5 @@ exporting-processed-media-files =
|
||||||
[one] Processed { $count } media file...
|
[one] Processed { $count } media file...
|
||||||
*[other] Processed { $count } media files...
|
*[other] Processed { $count } media files...
|
||||||
}
|
}
|
||||||
|
exporting-include-deck = Include deck
|
||||||
|
exporting-include-notetype = Include notetype
|
||||||
|
|
|
@ -176,7 +176,9 @@ message ExportNoteCsvRequest {
|
||||||
string out_path = 1;
|
string out_path = 1;
|
||||||
bool with_html = 2;
|
bool with_html = 2;
|
||||||
bool with_tags = 3;
|
bool with_tags = 3;
|
||||||
ExportLimit limit = 4;
|
bool with_deck = 4;
|
||||||
|
bool with_notetype = 5;
|
||||||
|
ExportLimit limit = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ExportLimit {
|
message ExportLimit {
|
||||||
|
|
|
@ -423,11 +423,15 @@ class Collection(DeprecatedNamesMixin):
|
||||||
limit: ExportLimit,
|
limit: ExportLimit,
|
||||||
with_html: bool,
|
with_html: bool,
|
||||||
with_tags: bool,
|
with_tags: bool,
|
||||||
|
with_deck: bool,
|
||||||
|
with_notetype: bool,
|
||||||
) -> int:
|
) -> int:
|
||||||
return self._backend.export_note_csv(
|
return self._backend.export_note_csv(
|
||||||
out_path=out_path,
|
out_path=out_path,
|
||||||
with_html=with_html,
|
with_html=with_html,
|
||||||
with_tags=with_tags,
|
with_tags=with_tags,
|
||||||
|
with_deck=with_deck,
|
||||||
|
with_notetype=with_notetype,
|
||||||
limit=pb_export_limit(limit),
|
limit=pb_export_limit(limit),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,9 @@ class ExportDialog(QDialog):
|
||||||
self.frm.includeHTML.setVisible(False)
|
self.frm.includeHTML.setVisible(False)
|
||||||
# show deck list?
|
# show deck list?
|
||||||
self.frm.deck.setVisible(not self.isVerbatim)
|
self.frm.deck.setVisible(not self.isVerbatim)
|
||||||
|
# used by the new export screen
|
||||||
|
self.frm.includeDeck.setVisible(False)
|
||||||
|
self.frm.includeNotetype.setVisible(False)
|
||||||
|
|
||||||
def accept(self) -> None:
|
def accept(self) -> None:
|
||||||
self.exporter.includeSched = self.frm.includeSched.isChecked()
|
self.exporter.includeSched = self.frm.includeSched.isChecked()
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>563</width>
|
<width>610</width>
|
||||||
<height>245</height>
|
<height>348</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -87,6 +87,26 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="includeDeck">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>exporting_include_deck</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="includeNotetype">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>exporting_include_notetype</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="includeHTML">
|
<widget class="QCheckBox" name="includeHTML">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
|
@ -91,6 +91,8 @@ class ExportDialog(QDialog):
|
||||||
self.frm.includeMedia.setVisible(self.exporter.show_include_media)
|
self.frm.includeMedia.setVisible(self.exporter.show_include_media)
|
||||||
self.frm.includeTags.setVisible(self.exporter.show_include_tags)
|
self.frm.includeTags.setVisible(self.exporter.show_include_tags)
|
||||||
self.frm.includeHTML.setVisible(self.exporter.show_include_html)
|
self.frm.includeHTML.setVisible(self.exporter.show_include_html)
|
||||||
|
self.frm.includeDeck.setVisible(self.exporter.show_include_deck)
|
||||||
|
self.frm.includeNotetype.setVisible(self.exporter.show_include_notetype)
|
||||||
self.frm.legacy_support.setVisible(self.exporter.show_legacy_support)
|
self.frm.legacy_support.setVisible(self.exporter.show_legacy_support)
|
||||||
self.frm.deck.setVisible(self.exporter.show_deck_list)
|
self.frm.deck.setVisible(self.exporter.show_deck_list)
|
||||||
|
|
||||||
|
@ -135,6 +137,8 @@ class ExportDialog(QDialog):
|
||||||
include_media=self.frm.includeMedia.isChecked(),
|
include_media=self.frm.includeMedia.isChecked(),
|
||||||
include_tags=self.frm.includeTags.isChecked(),
|
include_tags=self.frm.includeTags.isChecked(),
|
||||||
include_html=self.frm.includeHTML.isChecked(),
|
include_html=self.frm.includeHTML.isChecked(),
|
||||||
|
include_deck=self.frm.includeDeck.isChecked(),
|
||||||
|
include_notetype=self.frm.includeNotetype.isChecked(),
|
||||||
legacy_support=self.frm.legacy_support.isChecked(),
|
legacy_support=self.frm.legacy_support.isChecked(),
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
@ -165,6 +169,8 @@ class Options:
|
||||||
include_media: bool
|
include_media: bool
|
||||||
include_tags: bool
|
include_tags: bool
|
||||||
include_html: bool
|
include_html: bool
|
||||||
|
include_deck: bool
|
||||||
|
include_notetype: bool
|
||||||
legacy_support: bool
|
legacy_support: bool
|
||||||
limit: ExportLimit
|
limit: ExportLimit
|
||||||
|
|
||||||
|
@ -177,6 +183,8 @@ class Exporter(ABC):
|
||||||
show_include_tags = False
|
show_include_tags = False
|
||||||
show_include_html = False
|
show_include_html = False
|
||||||
show_legacy_support = False
|
show_legacy_support = False
|
||||||
|
show_include_deck = False
|
||||||
|
show_include_notetype = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
@ -255,6 +263,8 @@ class NoteCsvExporter(Exporter):
|
||||||
show_deck_list = True
|
show_deck_list = True
|
||||||
show_include_html = True
|
show_include_html = True
|
||||||
show_include_tags = True
|
show_include_tags = True
|
||||||
|
show_include_deck = True
|
||||||
|
show_include_notetype = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def name() -> str:
|
def name() -> str:
|
||||||
|
@ -269,6 +279,8 @@ class NoteCsvExporter(Exporter):
|
||||||
limit=options.limit,
|
limit=options.limit,
|
||||||
with_html=options.include_html,
|
with_html=options.include_html,
|
||||||
with_tags=options.include_tags,
|
with_tags=options.include_tags,
|
||||||
|
with_deck=options.include_deck,
|
||||||
|
with_notetype=options.include_notetype,
|
||||||
),
|
),
|
||||||
success=lambda count: tooltip(
|
success=lambda count: tooltip(
|
||||||
tr.exporting_note_exported(count=count), parent=mw
|
tr.exporting_note_exported(count=count), parent=mw
|
||||||
|
|
|
@ -93,16 +93,8 @@ impl ImportExportService for Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_note_csv(&self, input: pb::ExportNoteCsvRequest) -> Result<pb::UInt32> {
|
fn export_note_csv(&self, input: pb::ExportNoteCsvRequest) -> Result<pb::UInt32> {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| col.export_note_csv(input, self.export_progress_fn()))
|
||||||
col.export_note_csv(
|
.map(Into::into)
|
||||||
&input.out_path,
|
|
||||||
SearchNode::from(input.limit.unwrap_or_default()),
|
|
||||||
input.with_html,
|
|
||||||
input.with_tags,
|
|
||||||
self.export_progress_fn(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_card_csv(&self, input: pb::ExportCardCsvRequest) -> Result<pb::UInt32> {
|
fn export_card_csv(&self, input: pb::ExportCardCsvRequest) -> Result<pb::UInt32> {
|
||||||
|
|
|
@ -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 std::{borrow::Cow, fs::File, io::Write};
|
use std::{borrow::Cow, collections::HashMap, fs::File, io::Write, sync::Arc};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
@ -9,10 +9,11 @@ use regex::Regex;
|
||||||
|
|
||||||
use super::metadata::Delimiter;
|
use super::metadata::Delimiter;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
backend_proto::ExportNoteCsvRequest,
|
||||||
import_export::{ExportProgress, IncrementableProgress},
|
import_export::{ExportProgress, IncrementableProgress},
|
||||||
notetype::RenderCardOutput,
|
notetype::RenderCardOutput,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
search::SortMode,
|
search::{SearchNode, SortMode},
|
||||||
template::RenderedNode,
|
template::RenderedNode,
|
||||||
text::{html_to_text_line, CowMapping},
|
text::{html_to_text_line, CowMapping},
|
||||||
};
|
};
|
||||||
|
@ -45,21 +46,19 @@ impl Collection {
|
||||||
|
|
||||||
pub fn export_note_csv(
|
pub fn export_note_csv(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &str,
|
mut request: ExportNoteCsvRequest,
|
||||||
search: impl TryIntoSearch,
|
|
||||||
with_html: bool,
|
|
||||||
with_tags: bool,
|
|
||||||
progress_fn: impl 'static + FnMut(ExportProgress, bool) -> bool,
|
progress_fn: impl 'static + FnMut(ExportProgress, bool) -> bool,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
let mut progress = IncrementableProgress::new(progress_fn);
|
let mut progress = IncrementableProgress::new(progress_fn);
|
||||||
progress.call(ExportProgress::File)?;
|
progress.call(ExportProgress::File)?;
|
||||||
let mut incrementor = progress.incrementor(ExportProgress::Notes);
|
let mut incrementor = progress.incrementor(ExportProgress::Notes);
|
||||||
|
|
||||||
let mut writer = file_writer_with_header(path)?;
|
self.search_notes_into_table(request.search_node())?;
|
||||||
self.search_notes_into_table(search)?;
|
let ctx = NoteContext::new(&request, self)?;
|
||||||
|
let mut writer = note_file_writer_with_header(&request.out_path, &ctx)?;
|
||||||
self.storage.for_each_note_in_search(|note| {
|
self.storage.for_each_note_in_search(|note| {
|
||||||
incrementor.increment()?;
|
incrementor.increment()?;
|
||||||
writer.write_record(note_record(¬e, with_html, with_tags))?;
|
writer.write_record(ctx.record(¬e))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
|
@ -79,15 +78,41 @@ impl Collection {
|
||||||
|
|
||||||
fn file_writer_with_header(path: &str) -> Result<csv::Writer<File>> {
|
fn file_writer_with_header(path: &str) -> Result<csv::Writer<File>> {
|
||||||
let mut file = File::create(path)?;
|
let mut file = File::create(path)?;
|
||||||
write_header(&mut file)?;
|
write_file_header(&mut file)?;
|
||||||
Ok(csv::WriterBuilder::new()
|
Ok(csv::WriterBuilder::new()
|
||||||
.delimiter(DELIMITER.byte())
|
.delimiter(DELIMITER.byte())
|
||||||
.flexible(true)
|
|
||||||
.from_writer(file))
|
.from_writer(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_header(writer: &mut impl Write) -> Result<()> {
|
fn write_file_header(writer: &mut impl Write) -> Result<()> {
|
||||||
write!(writer, "#separator:{}\n#html:true\n", DELIMITER.name())?;
|
writeln!(writer, "#separator:{}", DELIMITER.name())?;
|
||||||
|
writeln!(writer, "#html:true")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn note_file_writer_with_header(path: &str, ctx: &NoteContext) -> Result<csv::Writer<File>> {
|
||||||
|
let mut file = File::create(path)?;
|
||||||
|
write_note_file_header(&mut file, ctx)?;
|
||||||
|
Ok(csv::WriterBuilder::new()
|
||||||
|
.delimiter(DELIMITER.byte())
|
||||||
|
.from_writer(file))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_note_file_header(writer: &mut impl Write, ctx: &NoteContext) -> Result<()> {
|
||||||
|
write_file_header(writer)?;
|
||||||
|
write_column_header(ctx, writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_column_header(ctx: &NoteContext, writer: &mut impl Write) -> Result<()> {
|
||||||
|
for (name, column) in [
|
||||||
|
("notetype", ctx.notetype_column()),
|
||||||
|
("deck", ctx.deck_column()),
|
||||||
|
("tags", ctx.tags_column()),
|
||||||
|
] {
|
||||||
|
if let Some(index) = column {
|
||||||
|
writeln!(writer, "#{name} column:{index}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,18 +142,6 @@ fn rendered_nodes_to_str(nodes: &[RenderedNode]) -> String {
|
||||||
.join("")
|
.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_record(note: &Note, with_html: bool, with_tags: bool) -> Vec<String> {
|
|
||||||
let mut fields: Vec<_> = note
|
|
||||||
.fields()
|
|
||||||
.iter()
|
|
||||||
.map(|f| field_to_record_field(f, with_html))
|
|
||||||
.collect();
|
|
||||||
if with_tags {
|
|
||||||
fields.push(note.tags.join(" "));
|
|
||||||
}
|
|
||||||
fields
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_to_record_field(field: &str, with_html: bool) -> String {
|
fn field_to_record_field(field: &str, with_html: bool) -> String {
|
||||||
let mut text = strip_redundant_sections(field);
|
let mut text = strip_redundant_sections(field);
|
||||||
if !with_html {
|
if !with_html {
|
||||||
|
@ -157,3 +170,92 @@ fn strip_answer_side_question(text: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
RE.replace_all(text.as_ref(), "")
|
RE.replace_all(text.as_ref(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NoteContext {
|
||||||
|
with_html: bool,
|
||||||
|
with_tags: bool,
|
||||||
|
with_deck: bool,
|
||||||
|
with_notetype: bool,
|
||||||
|
notetypes: HashMap<NotetypeId, Arc<Notetype>>,
|
||||||
|
deck_ids: HashMap<NoteId, DeckId>,
|
||||||
|
deck_names: HashMap<DeckId, String>,
|
||||||
|
field_columns: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteContext {
|
||||||
|
/// Caller must have searched notes into table.
|
||||||
|
fn new(request: &ExportNoteCsvRequest, col: &mut Collection) -> Result<Self> {
|
||||||
|
let notetypes = col.get_all_notetypes_of_search_notes()?;
|
||||||
|
let field_columns = notetypes
|
||||||
|
.values()
|
||||||
|
.map(|nt| nt.fields.len())
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let deck_ids = col.storage.all_decks_of_search_notes()?;
|
||||||
|
let deck_names = HashMap::from_iter(col.storage.get_all_deck_names()?.into_iter());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
with_html: request.with_html,
|
||||||
|
with_tags: request.with_tags,
|
||||||
|
with_deck: request.with_deck,
|
||||||
|
with_notetype: request.with_notetype,
|
||||||
|
notetypes,
|
||||||
|
field_columns,
|
||||||
|
deck_ids,
|
||||||
|
deck_names,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notetype_column(&self) -> Option<usize> {
|
||||||
|
self.with_notetype.then(|| 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deck_column(&self) -> Option<usize> {
|
||||||
|
self.with_deck
|
||||||
|
.then(|| 1 + self.notetype_column().unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags_column(&self) -> Option<usize> {
|
||||||
|
self.with_tags
|
||||||
|
.then(|| 1 + self.deck_column().unwrap_or_default() + self.field_columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record<'i, 's: 'i, 'n: 'i>(&'s self, note: &'n Note) -> impl Iterator<Item = String> + 'i {
|
||||||
|
self.notetype_name(note)
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.deck_name(note).into_iter())
|
||||||
|
.chain(self.note_fields(note).into_iter())
|
||||||
|
.chain(self.with_tags.then(|| note.tags.join(" ")).into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notetype_name(&self, note: &Note) -> Option<String> {
|
||||||
|
self.with_notetype.then(|| {
|
||||||
|
self.notetypes
|
||||||
|
.get(¬e.notetype_id)
|
||||||
|
.map_or(String::new(), |nt| nt.name.clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deck_name(&self, note: &Note) -> Option<String> {
|
||||||
|
self.with_deck.then(|| {
|
||||||
|
self.deck_ids
|
||||||
|
.get(¬e.id)
|
||||||
|
.and_then(|did| self.deck_names.get(did))
|
||||||
|
.map_or(String::new(), |name| name.clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn note_fields<'n>(&self, note: &'n Note) -> impl Iterator<Item = String> + 'n {
|
||||||
|
let with_html = self.with_html;
|
||||||
|
note.fields()
|
||||||
|
.iter()
|
||||||
|
.map(move |f| field_to_record_field(f, with_html))
|
||||||
|
.pad_using(self.field_columns, |_| String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExportNoteCsvRequest {
|
||||||
|
fn search_node(&mut self) -> SearchNode {
|
||||||
|
SearchNode::from(self.limit.take().unwrap_or_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -218,6 +218,21 @@ impl Collection {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_all_notetypes_of_search_notes(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<HashMap<NotetypeId, Arc<Notetype>>> {
|
||||||
|
self.storage
|
||||||
|
.all_notetypes_of_search_notes()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|ntid| {
|
||||||
|
self.get_notetype(ntid)
|
||||||
|
.transpose()
|
||||||
|
.unwrap()
|
||||||
|
.map(|nt| (ntid, nt))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_notetype(&mut self, ntid: NotetypeId) -> Result<OpOutput<()>> {
|
pub fn remove_notetype(&mut self, ntid: NotetypeId) -> Result<OpOutput<()>> {
|
||||||
self.transact(Op::RemoveNotetype, |col| col.remove_notetype_inner(ntid))
|
self.transact(Op::RemoveNotetype, |col| col.remove_notetype_inner(ntid))
|
||||||
}
|
}
|
||||||
|
|
8
rslib/src/storage/deck/all_decks_of_search_notes.sql
Normal file
8
rslib/src/storage/deck/all_decks_of_search_notes.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
SELECT nid,
|
||||||
|
did
|
||||||
|
FROM cards
|
||||||
|
WHERE ord = 0
|
||||||
|
AND nid IN (
|
||||||
|
SELECT nid
|
||||||
|
FROM search_nids
|
||||||
|
)
|
|
@ -131,6 +131,14 @@ impl SqliteStorage {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the deck id of the first card of every searched note.
|
||||||
|
pub(crate) fn all_decks_of_search_notes(&self) -> Result<HashMap<NoteId, DeckId>> {
|
||||||
|
self.db
|
||||||
|
.prepare_cached(include_str!("all_decks_of_search_notes.sql"))?
|
||||||
|
.query_and_then([], |r| Ok((r.get(0)?, r.get(1)?)))?
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// caller should ensure name unique
|
// caller should ensure name unique
|
||||||
pub(crate) fn add_deck(&self, deck: &mut Deck) -> Result<()> {
|
pub(crate) fn add_deck(&self, deck: &mut Deck) -> Result<()> {
|
||||||
assert!(deck.id.0 == 0);
|
assert!(deck.id.0 == 0);
|
||||||
|
|
|
@ -116,6 +116,15 @@ impl SqliteStorage {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn all_notetypes_of_search_notes(&self) -> Result<Vec<NotetypeId>> {
|
||||||
|
self.db
|
||||||
|
.prepare_cached(
|
||||||
|
"SELECT DISTINCT mid FROM notes WHERE id IN (SELECT nid FROM search_nids)",
|
||||||
|
)?
|
||||||
|
.query_and_then([], |r| Ok(r.get(0)?))?
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_all_notetype_names(&self) -> Result<Vec<(NotetypeId, String)>> {
|
pub fn get_all_notetype_names(&self) -> Result<Vec<(NotetypeId, String)>> {
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached(include_str!("get_notetype_names.sql"))?
|
.prepare_cached(include_str!("get_notetype_names.sql"))?
|
||||||
|
|
Loading…
Reference in a new issue