[Userscript] WaniKani Open Framework Date Filters

Requirements:

[ General Script Installation instructions ] :point_left: You’ll need a script host plugin like TamperMonkey
[ Open Framework Installation ]

Download the script here:

Description

This is a collection of filters for use with Self Study Quiz and Item Inspector scripts. The filters will be automatically added to the Filter tab in both software settings dialog.

They cover the following dates for your items.

  • When the next review is scheduled.
  • When the last review was taken
  • When a lesson was taken.
  • When an item passed guru
  • When an item was burned
  • When an item was resurrected
  • When an item was unlocked

For each date three filters are provided. The exception is the “Have Burned” filter which is not provided here because it is included in the default filters provided by the Open Framework.

Filters

  • The “Had” filter selects all items for which the event associated with the date had occurred. Or alternatively it selects all items for which the event had not occurred.

  • The “After” filter selects all items for which the event has occurred at a date later than or the same as the specified date.

  • The “Before” filter selects all items for which the event has occurred at a date earlier than or the same as the specified date.

  • Used together the “After” and “Before” filters select items on a range of dates.

Note to developers

You should use wkof.wait_state('dateFilters', 'ready').to wait for the completion of the filters initialization.

Acknowledgements

Big thanks to @seanblue @Kumirei and @rfindley whose advices have made this project possible. In particular @rfindley explained some fine points of the Open Framework and @seanblue provided some reference code that was used as a template.

Thanks again to @rfindley for providing reference code for the support of relative time.

Version history

Version 1.3.0 - Added filters for last review date
Version 1.2.3 - Fixing file version problem.
Version 1.2.0 - Added support of wkof.set_state('dateFilters' , 'ready').
Version 1.1.0 - Added support of relative time.
Version 1.0.0 - Original release.

8 Likes

Great addition to the filters!

Could also be useful to support relative dates:

code
// Support relative dates.
// Examples:
//   "-3d"     -->  Three days ago
//   "-4h30m"  -->  Four and a half hours ago
//   "+10d"    -->  Ten days from now.

var user_input = '-1d6h3m';
user_input = user_input.trim();

var date;

// Check if it's a relative date
var diff_match = user_input.match(/^([+-])(?:(\d+)[dD])?(?:(\d+)[hH])?(?:(\d+)[mM])?(?:(\d+)[sS])?/);
if (diff_match !== null) {
  // It's a relative date.
  date = Date.now();
  var sign = (diff_match[1] === '+' ? 1 : -1);
  var days = (diff_match[2] || 0) * 86400000;
  var hrs = (diff_match[3] || 0) * 3600000;
  var min = (diff_match[4] || 0) * 60000;
  var sec = (diff_match[5] || 0) * 1000;
  date = new Date(date + sign * (days + hrs + min + sec));
} else {
  // It's not a relative date.  Assume it's an absolute date.
  // Any date string that Javascript understands is accepted.
  date = new Date(user_input);
}

// Make sure Javascript understands the entered value as a date.
if (isNaN(date.valueOf())) {
  console.log('Invalid date!');
} else {
  console.log('Date is '+date);
}

(Side note: When it’s not a relative date, you could just pass the user input to Javascript to interpret as a date, and verify that the result isn’t NaN. That way it accepts any locale-specific date format.)

2 Likes

I thought about that. I didn’t include it because I was uncertain about what “relative” means. Is it relative to when the parameter is changed in the settings? Or is it relative to when the filter is run? Which should it be?

Once I make my mind about this I will include support for relative time in my collection of time routines.

1 Like

I would assume it should be relative to when the filter was run. Super useful if you wanted, for example, to quiz on all items reviewed (or lessons completed) in the last 4 hours.

3 Likes

Ok. I will go for this.

3 Likes

I’ve also been wanting to figure out a good way to support multiple parameters in a single filter. So, for example, you could do a date range in a single filter:

[x] Between dates:  [xxxx-xx-xx]
                    [xxxx-xx-xx]

Currently, it would probably have to be an ‘html’ type, but I really haven’t spent time exploring how well the experimental ‘html’ type works as-is.

2 Likes

I considered trying this. I gave up on the idea when I noticed this type doesn’t automatically store a value in the settings. Then I didn’t trust the framework would know which value to pass as a parameter to the filter. For now two filters to make a date range works. This lack of support is not a show stopper.

I would be convenient to inform the framework of which values may be associated with a html or button types. Then it may pass a configuration object as a parameter to a filter. The information may be structured so I think the framework should support storing in the settings an object that the html/button/filter code is free to interpret.

We may want to launch a full dialog and a button would be better than html in these cases. This is why I think a value should be associated with both a button and html.

Yes, if I remember correctly, that was the original intent of the ‘button’ type.

I would have to study the ‘html’ type more to be sure how to implement an associated value.

1 Like

How do we inform the framework of which value(s) to pass as a parameter to a filter? I don’t see support for this in the documentation.

Currently, there is no way to specify.
Since all filters are currently only one field, the value is extracted as elem.val() inside the on(‘change’) event.

If you use something like ‘html’ or ‘button’, your html (for ‘html’) or popup (for ‘button’) could include a unique tag for the field, and your filter could fetch and interpret the field.

Yes. You are the second one to mention something like this. @Kumirei too seems to prefer Date.parse in most circumstances. But in the Mozilla documentation I find this.

Note: Parsing of strings with Date.parse is strongly discouraged due to browser differences and inconsistencies.

And here there is more details

It is not recommended to use Date.parse as until ES5, parsing of strings was entirely implementation dependent. There are still many differences in how different hosts parse date strings, therefore date strings should be manually parsed (a library can help if many different formats are to be accommodated).

This scares me a bit. By nailing the format I work around these differences. But if I am wrong I am willing to hear about it.

1 Like

Well, mozilla.org is generally a good source, so I can’t argue with that.

For my part, I’ve just found that browser developers are almost always more knowledgeable about the wide variety of differences in date representation around the world. I learned that lesson with Ultimate Timeline :slight_smile:

2 Likes

I don’t understand. A html or dialog contains several fields. Which one will be the elem for elem.val() purpose? And how is this information available to the filter? The information is different for each preset in each script. How do I locate the correct one from a filter? Do I miss something?

In the wkof Settings module [here], it calls an event handler depending on the type of element:

dialog_elem.find('.setting[multiple]').on('mousedown', toggle_multi.bind(null,context));
dialog_elem.find('.setting').on('change', setting_changed.bind(null,context));
dialog_elem.find('form').on('submit', function(){return false;});
dialog_elem.find('button.setting').on('click', setting_button_clicked.bind(null,context));

Most input types have class .setting, so it is handled in the setting_changed() function, which has call spec setting_changed(context, event).

And elem = $(event.currentTarget)

For button types, it simply calls the on_click handler, and it’s up to you to check your own values when the user clicks the close button in your pop-up dialog.

I don’t remember enough about ‘html’ to know how it works.

Of course, I didn’t implement all features of wkof in Self-Study. I figured I would add support as coders needed them, which usually helps define how those features should be implemented.

[edit:]
Regarding html, the event handlers above will still affect elements inside an ‘html’ object. So, if you tag your fields with .setting, the event handler will be called. So, take a look at setting_changed() in the Settings module (linked at the top of this post) to see how it handles events on .settings objects. There may be a way to get it to respond correctly to custom html types.

Supporting locale date formats is a rabbit hole. There are entire libraries available to work around Date.parse limitations. There is no way to get it right without resorting to such a library. I don’t want to go that route. I think this is overkill for Wanikani scripting purposes. This is why I have cut off locale date support.

But I sense an appetite from two prominent developers to use whatever Date.parse provides. Perhaps I should add this as an option in my collection of time functions because opinions differ on this matter.

From my perspective, I wouldn’t claim an ‘appetite’. I just saw you putting in the work to parse dates, and thought the ‘lazy’ method might work, and avoid some potential locale pitfalls in the process. But you make valid points, too.

1 Like

I wouldn’t either. It’s just that for my purposes as a hobbyist scripter Date.parse is very convenient and works well enough. Also I literally learned to program on here and haven’t really done anything else, so people shouldn’t listen to me

1 Like

i avoid these pitfalls by not supporting locale. I agree that if someone wants to support locale Date.parse is the most sensible way for wanikani scripting purposes.

2 Likes

I’m still amazed how much and how fast you’ve learned. It doesn’t matter where you learn it. As long as you never stop learning, you’re worth listening to! :slight_smile:

2 Likes

You are a better script programmer than I am. Of course I will listen to you.

1 Like