mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Add card position column and always show position in card info (#3471)
* Expose original position to columns and card info * Fix contributors * Change routine name and return, fix SQL file, utilize coalesce inline * Handle cards without original position * Remove unecessary conversion
This commit is contained in:
parent
a982720a42
commit
7439c657f0
5 changed files with 81 additions and 28 deletions
|
@ -46,6 +46,7 @@ pub enum Column {
|
|||
NoteMod,
|
||||
#[strum(serialize = "note")]
|
||||
Notetype,
|
||||
OriginalPosition,
|
||||
Question,
|
||||
#[strum(serialize = "cardReps")]
|
||||
Reps,
|
||||
|
@ -161,6 +162,7 @@ impl Column {
|
|||
Self::NoteCreation => tr.browsing_created(),
|
||||
Self::NoteMod => tr.search_note_modified(),
|
||||
Self::Notetype => tr.card_stats_note_type(),
|
||||
Self::OriginalPosition => tr.card_stats_new_card_position(),
|
||||
Self::Question => tr.browsing_question(),
|
||||
Self::Reps => tr.scheduling_reviews(),
|
||||
Self::SortField => tr.browsing_sort_field(),
|
||||
|
@ -226,6 +228,7 @@ impl Column {
|
|||
| Column::Interval
|
||||
| Column::NoteCreation
|
||||
| Column::NoteMod
|
||||
| Column::OriginalPosition
|
||||
| Column::Reps => Sorting::Descending,
|
||||
Column::Stability | Column::Difficulty | Column::Retrievability => {
|
||||
if notes {
|
||||
|
@ -432,6 +435,7 @@ impl RowContext {
|
|||
Column::NoteCreation => self.note_creation_str(),
|
||||
Column::SortField => self.note_field_str(),
|
||||
Column::NoteMod => self.note.mtime.date_and_time_string(),
|
||||
Column::OriginalPosition => self.card_original_position(),
|
||||
Column::Tags => self.note.tags.join(" "),
|
||||
Column::Notetype => self.notetype.name.to_owned(),
|
||||
Column::Stability => self.fsrs_stability_str(),
|
||||
|
@ -441,6 +445,17 @@ impl RowContext {
|
|||
})
|
||||
}
|
||||
|
||||
fn card_original_position(&self) -> String {
|
||||
let card = &self.cards[0];
|
||||
if let Some(pos) = &card.original_position {
|
||||
pos.to_string()
|
||||
} else if card.ctype == CardType::New {
|
||||
card.due.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn note_creation_str(&self) -> String {
|
||||
TimestampMillis(self.note.id.into())
|
||||
.as_secs()
|
||||
|
|
|
@ -370,6 +370,7 @@ fn card_order_from_sort_column(column: Column, timing: SchedTimingToday) -> Cow<
|
|||
Column::NoteCreation => "n.id asc, c.ord asc".into(),
|
||||
Column::NoteMod => "n.mod asc, c.ord asc".into(),
|
||||
Column::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(),
|
||||
Column::OriginalPosition => "(select pos from sort_order where nid = c.nid) asc".into(),
|
||||
Column::Reps => "c.reps asc".into(),
|
||||
Column::SortField => "n.sfld collate nocase asc, c.ord asc".into(),
|
||||
Column::Tags => "n.tags asc".into(),
|
||||
|
@ -394,6 +395,7 @@ fn note_order_from_sort_column(column: Column) -> Cow<'static, str> {
|
|||
| Column::Ease
|
||||
| Column::Interval
|
||||
| Column::Lapses
|
||||
| Column::OriginalPosition
|
||||
| Column::Reps => "(select pos from sort_order where nid = n.id) asc".into(),
|
||||
Column::NoteCreation => "n.id asc".into(),
|
||||
Column::NoteMod => "n.mod asc".into(),
|
||||
|
@ -416,6 +418,7 @@ fn prepare_sort(col: &mut Collection, column: Column, item_type: ReturnItemType)
|
|||
Column::Cards => include_str!("template_order.sql"),
|
||||
Column::Deck => include_str!("deck_order.sql"),
|
||||
Column::Notetype => include_str!("notetype_order.sql"),
|
||||
Column::OriginalPosition => include_str!("note_original_position_order.sql"),
|
||||
_ => return Ok(()),
|
||||
},
|
||||
ReturnItemType::Notes => match column {
|
||||
|
@ -429,6 +432,7 @@ fn prepare_sort(col: &mut Collection, column: Column, item_type: ReturnItemType)
|
|||
Column::Ease => include_str!("note_ease_order.sql"),
|
||||
Column::Interval => include_str!("note_interval_order.sql"),
|
||||
Column::Lapses => include_str!("note_lapses_order.sql"),
|
||||
Column::OriginalPosition => include_str!("note_original_position_order.sql"),
|
||||
Column::Reps => include_str!("note_reps_order.sql"),
|
||||
Column::Notetype => include_str!("notetype_order.sql"),
|
||||
_ => return Ok(()),
|
||||
|
|
16
rslib/src/search/note_original_position_order.sql
Normal file
16
rslib/src/search/note_original_position_order.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
DROP TABLE IF EXISTS sort_order;
|
||||
CREATE TEMPORARY TABLE sort_order (
|
||||
pos integer PRIMARY KEY,
|
||||
nid integer NOT NULL UNIQUE
|
||||
);
|
||||
INSERT INTO sort_order (nid)
|
||||
SELECT nid
|
||||
FROM cards
|
||||
GROUP BY nid
|
||||
ORDER BY COALESCE(
|
||||
extract_original_position(data),
|
||||
CASE
|
||||
WHEN type == 0 THEN due
|
||||
ELSE 0
|
||||
END
|
||||
);
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
use fsrs::FSRS;
|
||||
|
||||
use crate::card::CardQueue;
|
||||
use crate::card::CardType;
|
||||
use crate::prelude::*;
|
||||
use crate::revlog::RevlogEntry;
|
||||
|
@ -28,7 +27,6 @@ impl Collection {
|
|||
let revlog = self.storage.get_revlog_entries_for_card(card.id)?;
|
||||
|
||||
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
|
||||
let (due_date, due_position) = self.due_date_and_position(&card)?;
|
||||
let timing = self.timing_today()?;
|
||||
let days_elapsed = self
|
||||
.storage
|
||||
|
@ -62,8 +60,8 @@ impl Collection {
|
|||
added: card.id.as_secs().0,
|
||||
first_review: revlog.first().map(|entry| entry.id.as_secs().0),
|
||||
latest_review: revlog.last().map(|entry| entry.id.as_secs().0),
|
||||
due_date,
|
||||
due_position,
|
||||
due_date: self.due_date(&card)?,
|
||||
due_position: self.position(&card),
|
||||
interval: card.interval,
|
||||
ease: card.ease_factor as u32,
|
||||
reviews: card.reps,
|
||||
|
@ -85,37 +83,33 @@ impl Collection {
|
|||
})
|
||||
}
|
||||
|
||||
fn due_date_and_position(&mut self, card: &Card) -> Result<(Option<i64>, Option<i32>)> {
|
||||
let due = if card.original_due != 0 {
|
||||
card.original_due
|
||||
} else {
|
||||
card.due
|
||||
};
|
||||
fn due_date(&mut self, card: &Card) -> Result<Option<i64>> {
|
||||
Ok(match card.ctype {
|
||||
CardType::New => {
|
||||
if matches!(card.queue, CardQueue::Review | CardQueue::DayLearn) {
|
||||
// new preview card not answered yet
|
||||
(None, card.original_position.map(|u| u as i32))
|
||||
CardType::New => None,
|
||||
CardType::Review | CardType::Learn | CardType::Relearn => {
|
||||
let due = card.due;
|
||||
if !is_unix_epoch_timestamp(due) {
|
||||
let days_remaining = due - (self.timing_today()?.days_elapsed as i32);
|
||||
let mut due_timestamp = TimestampSecs::now();
|
||||
due_timestamp.0 += (days_remaining as i64) * 86_400;
|
||||
Some(due_timestamp.0)
|
||||
} else {
|
||||
(None, Some(due))
|
||||
Some(due as i64)
|
||||
}
|
||||
}
|
||||
CardType::Review | CardType::Learn | CardType::Relearn => (
|
||||
{
|
||||
if !is_unix_epoch_timestamp(due) {
|
||||
let days_remaining = due - (self.timing_today()?.days_elapsed as i32);
|
||||
let mut due = TimestampSecs::now();
|
||||
due.0 += (days_remaining as i64) * 86_400;
|
||||
Some(due.0)
|
||||
} else {
|
||||
Some(due as i64)
|
||||
}
|
||||
},
|
||||
None,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn position(&mut self, card: &Card) -> Option<i32> {
|
||||
if let Some(original_pos) = card.original_position {
|
||||
return Some(original_pos as i32);
|
||||
}
|
||||
match card.ctype {
|
||||
CardType::New => Some(card.due),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn stats_revlog_entries_with_memory_state(
|
||||
self: &mut Collection,
|
||||
card: &Card,
|
||||
|
|
|
@ -70,6 +70,7 @@ fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
|
|||
add_regexp_tags_function(&db)?;
|
||||
add_without_combining_function(&db)?;
|
||||
add_fnvhash_function(&db)?;
|
||||
add_extract_original_position_function(&db)?;
|
||||
add_extract_custom_data_function(&db)?;
|
||||
add_extract_fsrs_variable(&db)?;
|
||||
add_extract_fsrs_retrievability(&db)?;
|
||||
|
@ -205,6 +206,29 @@ fn add_regexp_tags_function(db: &Connection) -> rusqlite::Result<()> {
|
|||
)
|
||||
}
|
||||
|
||||
/// eg. extract_original_position(c.data) -> number | null
|
||||
/// Parse original card position from c.data (this is only populated after card
|
||||
/// has been reviewed)
|
||||
fn add_extract_original_position_function(db: &Connection) -> rusqlite::Result<()> {
|
||||
db.create_scalar_function(
|
||||
"extract_original_position",
|
||||
1,
|
||||
FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
move |ctx| {
|
||||
assert_eq!(ctx.len(), 1, "called with unexpected number of arguments");
|
||||
|
||||
let Ok(card_data) = ctx.get_raw(0).as_str() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
match &CardData::from_str(card_data).original_position {
|
||||
Some(position) => Ok(Some(*position as i64)),
|
||||
None => Ok(None),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// eg. extract_custom_data(card.data, 'r') -> string | null
|
||||
fn add_extract_custom_data_function(db: &Connection) -> rusqlite::Result<()> {
|
||||
db.create_scalar_function(
|
||||
|
|
Loading…
Reference in a new issue