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:
Taylor Obyen 2024-10-11 06:14:07 -04:00 committed by GitHub
parent a982720a42
commit 7439c657f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 81 additions and 28 deletions

View file

@ -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()

View file

@ -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(()),

View 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
);

View file

@ -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,

View file

@ -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(