WaniKani Item Inspector

Inspect Items in Tabular Format

目前為 2020-09-03 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          WaniKani Item Inspector
// @namespace     wk-dashboard-item-inspector
// @description   Inspect Items in Tabular Format
// @author        prouleau
// @version       1.3.3
// @include       https://www.wanikani.com/dashboard
// @include       https://www.wanikani.com/
// @grant         none
// ==/UserScript==

(function() {
    'use strict';
    //------------------------------
	// Wanikani Framework
	//------------------------------
	if (!window.wkof) {
		let response = confirm('WaniKani Dashboard Leech List script requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.');

		if (response) {
			window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
		}

		return;
	}

	const config = {
		wk_items: {
			options: {
				review_statistics: true
			},
            filters: {
                level: '1..+0', //only retrieve items from lv 1 up to and including current level
                srs: {value: 'lock, init, burn', invert: true} //exlude locked, initial and burned items
            }
		}
	};


    var quiz_setup_state = 'init';
    function open_settings_dialog() {
        wkof.Settings.load('Item_Inspector')
           .then(function(){
                quiz_setup_state = 'ready';
                init_settings();
            })
        open_quiz_settings();
     }


    //------------------------------
	// Styling
	//------------------------------
    var leechTableCss = `
        .noLeeches {
            margin: 0 0 20px 0;
        }
        .radicalCharacterImgSize {
            font-size: 12px;
        }

        /* Control Bar */

       .WkitControlBar {
              margin: 7px 0.6% 7px 2.6%;
        }

        /* to prevent rendering problems */
        .WkitTopBar {
              display: inline-block;
              width: 103%;
              margin-left: -29px;
         }

        .WkitHeader {
              background-color: LightGray;
              height: 40px;
              border-radius: 5px;
         }

        .WkitTitle {
               font-size: 150%;
               padding-right: 285px;
               text-align: center;
        }

        .WkitButton {
               display: inline;
               float: left;
               border-width: 1px;
               border-radius: 3px;
               margin: 5px;
               margin-top: 8px;
        }

        .WkitSelector {
               display: inline;
               float: left;
               margin-left: 5px;
               margin-top: 5px;
               border-width: 1px;
               border-color: Black;
               color: Black;
               background-color: LightGray;
        }

        .emptyMessage {
              margin: 7px 0.6% 7px 2.6%;
              padding: 5px;
              padding-left: 15px;
              padding-top: 10px;
              background-color: Azure;
              border-radius: 8px;
         }

        /* TOOL TIPS */

        .WkisTooltip {
              position: relative;
              display: inline-block;
        }

        .WkisTooltip .WkisTooltipContent {
              display: none;
              visibility: hidden;
              background-color: black;
              color: #fff;
              font-size: 100%;
              width: max-content;
              text-align: left;
              margin: 5px;
              padding: 5px;
              border-radius: 3px;
              position: absolute;
              bottom: 30px;
              left: 0%;
              z-index: 1;
        }

        .WkisTooltip:hover div.WkisTooltipContent {
              visibility: visible;
              display: inline-block;
              z-index: 50;
        }

        .WkisTooltip .WkisTooltipContent::after {
              content: " ";
              position: absolute;
              top: 100%; /* At the bottom of the tooltip */
              left: 1em;
              margin-left: -5px;
              border-width: 5px;
              border-style: solid;
              border-color: black transparent transparent transparent;
        }

        .WkisLabel, {
              width: max-content;
              padding: 4px;
        }

        .WkisTipValue {
              width: max-content;
              padding: 4px;
        }

        /* END TOOL TIPS

        /*makes space*/
        #leech-table:after{
            clear: none;
        }
        #leaderboard:after{
            clear: none;
        }`;

    var leechStyling = document.createElement('style');
    leechStyling.type='text/css';
    if(leechStyling.styleSheet){
        leechStyling.styleSheet.cssText = leechTableCss;
    }else{
        leechStyling.appendChild(document.createTextNode(leechTableCss));
    }
    document.getElementsByTagName('head')[0].appendChild(leechStyling);

    //------------------------------
	// Menu
	//------------------------------
    var settings_dialog;

    function install_menu() {
        wkof.Menu.insert_script_link({
            script_id: 'Item_Inspector',
            name: 'Item_Inspector',
            submenu:   'Settings',
            title:     'Item Inspector',
            on_click:  open_settings_dialog
        });
    }


    //------------------------------
    // Settings - new version
    //------------------------------

    //########################################################################
    // QUIZ DATA
    //########################################################################

    var quiz = {
        // Dialogs
        dialog: null,
        settings_dialog: null,

        // Item Lists
        items: [],
        group_list: [],
        serial_list: [],
        index: null,

        // Status
        showing_help: false,
        mode: 'loading',

        // Question Info
        qinfo: {
            load: null,
            prep: null,
            cache: {},
        },

        // Stats
        stats: {
            round: 1,
            total: 0,
            correct: 0,
            incorrect: 0,
        },

        // Functions
        start: null,
        shuffle: null,
        requiz: null,
        ask: null,
        submit: null,
        prev: null,
        next: null,
        close: null,
    };


    //########################################################################
    // QUIZ SETTINGS DIALOG
    //########################################################################

    //========================================================================
    // setup_quiz_settings()
    //------------------------------------------------------------------------
    var quiz_settings_state = 'init';
    function setup_quiz_settings() {
        if (quiz_settings_state === 'init') {
            quiz_settings_state = 'loading';
            return wkof.ready('Settings')
            .then(function(){
                quiz_settings_state = 'setup';
                setup_quiz_settings();
            });
        }
        if (quiz_settings_state !== 'setup') return;

        var config = {
            script_id: 'Item_Inspector',
            title: 'Item Inspector',
            pre_open: preopen_quiz_settings,
            on_save: save_quiz_settings,
            on_close: close_quiz_settings,
            on_refresh: refresh_quiz_settings,
            no_bkgd: true,
            settings: {
                pgSettings: {type:'page',label:'Settings',content:{
                    hoursFormat: {type:'dropdown',label:'Hours Format',hover_tip:'Choose the format for displaying hours',content:{'12hours':'12h', '24hours':'24h',}
                                  },
                }},
                pg_items: {type:'page',label:'Tables',hover_tip:'Choose the table for which you want to define the settings',content:{
                    grp_ipre_list: {type:'group',label:'Table List',content:{
                        active_ipreset: {type:'list',refresh_on_change:true,on_change:refresh_table_on_preset_click,hover_tip:'Choose a table to edit',content:{}},
                    }},
                    grp_ipre: {type:'group',label:'Selected Table',content:{
                        sect_ipre_name: {type:'section',label:'Table Name'},
                        ipre_name: {type:'text',label:'Edit Table Name',on_change:refresh_ipresets,path:'@ipresets[@active_ipreset].name',hover_tip:'Enter a name for the selected table'},

                        sect_ipre_srcs: {type:'section',label:'Table Settings'},
                        ipre_srcs: {type:'tabset',content:{

                             table_contents:{type:'page',label:'Contents',hover_tip:'Define the contents of your table',content:{
                                  sect_tbl_cnts:{type:'section',label:'Table Entry'},
                                  table_data: {type:'dropdown',label:'Table Data Element',hover_tip:'The data that will be displayed on the table.',
                                               on_change:update_table_presets,
                                               content:{'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                                       'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total','Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                        'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                        'Level':'Level',
                                                        'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                        'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date',}
                                              },
                                  sort1: {type:'dropdown',label:'Primary Sort Criterion',hover_tip:'Items will be sorted by this criterion.',
                                               on_change:update_table_presets,
                                               content:{'Default':'Default', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total','Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                        'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                        'Level':'Level',
                                                        'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                        'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date',}
                                              },
                                  sort2: {type:'dropdown',label:'Secondary Sort Criterion',hover_tip:'Items will be sorted by this criterion when the primary criterion is of equal values.',
                                               on_change:update_table_presets,
                                               content:{'Default':'Default', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total','Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                        'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                        'Level':'Level',
                                                        'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                        'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date'}
                                              },
                                  sect_tbl_tooltips:{type:'section',label:'Table Tooltips'},
                                  tooltip1: {type:'dropdown',label:'First Tooltip Element',hover_tip:'The first line of data that will be displayed on the tooltip.',
                                              on_change:update_table_presets,
                                              content:{'None':'None', 'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                                       'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total','Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                       'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                       'Level':'Level',
                                                       'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                       'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date',}
                                            },
                                  tooltip2: {type:'dropdown',label:'Second Tooltip Element',hover_tip:'The second line of data that will be displayed on the tooltip.',
                                              on_change:update_table_presets,
                                              content:{'None':'None', 'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                                       'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total', 'Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                       'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                       'Level':'Level',
                                                       'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                       'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date',}
                                            },
                                  tooltip3: {type:'dropdown',label:'Third Tooltip Element',hover_tip:'The third line of  data that will be displayed on the tooltip.',
                                              on_change:update_table_presets,
                                              content:{'None':'None', 'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                                       'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total','Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                       'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                       'Level':'Level',
                                                       'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                       'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date',}
                                            },
                                  tooltip4: {type:'dropdown',label:'Fourth Tooltip Element',hover_tip:'The fourth line of  data that will be displayed on the tooltip.',
                                              on_change:update_table_presets,
                                              content:{'None':'None', 'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                                       'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                                       'Percentage_Correct': 'Percentage Correct Total','Meaning_Correct': 'Percentage Correct Meaning','Reading_Correct': 'Percentage Correct Reading',
                                                       'Meaning_Current_Streak' : 'Meaning Current Streak', 'Reading_Current_Streak': 'Reading Current Streak',
                                                       'Level':'Level',
                                                       'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                                       'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Lesson_Date':'Lesson Date',}
                                            },
                                  sect_tbl_settings:{type:'section',label:'Other Settings'},
                                  randomSelection: {type:'number',label:'Random Selection',hover_tip:'A non zero value returns a random selection of the stated number of items.\n0 returns all items.',
                                              on_change:update_table_presets,default:0
                                            },
                                 leechStreakLimit: {type:'number',label:'Leech Streak Limit',hover_tip:'Do not display an item when current streak for both meaning and reading is equal or greater to this limit.\nA value of 0 disable this feature',
                                                   on_change:update_table_presets,default:0
                                                   },
                             },
                        }},
                    }},
                }},
            },
        }};

        populate_items_config(config);

        quiz.settings_dialog = new wkof.Settings(config);

    }

    //========================================================================
    // preopen_quiz_settings()
    //------------------------------------------------------------------------
    function preopen_quiz_settings(dialog) {
        var btn_grp =
            '<div class="pre_list_btn_grp">'+
            '<button type="button" ref="###" action="new" class="ui-button ui-corner-all ui-widget" title="Create a new table">New</button>'+
            '<button type="button" ref="###" action="up" class="ui-button ui-corner-all ui-widget" title="Move the selected table up in the list"><span class="icon-arrow-up"></span></button>'+
            '<button type="button" ref="###" action="down" class="ui-button ui-corner-all ui-widget" title="Move the selected table down in the list"><span class="icon-arrow-down"></span></button>'+
            '<button type="button" ref="###" action="delete" class="ui-button ui-corner-all ui-widget" title="Delete the selected table">Delete</button>'+
            '</div>';

        var wrap = dialog.find('#Item_Inspector_active_ipreset').closest('.row');
        wrap.addClass('pre_list_wrap');
        wrap.prepend(btn_grp.replace(/###/g, 'ipreset'));
        wrap.find('.pre_list_btn_grp').on('click', 'button', preset_button_pressed);

        $('#Item_Inspector_ipre_srcs .row:first-child').each(function(i,e){
            var row = $(e);
            var right = row.find('>.right');
            row.prepend(right);
            row.addClass('src_enable');
        });

        // Customize the item source filters.
        var srcs = $('#Item_Inspector_ipre_srcs');
        var flt_grps = srcs.find('.wkof_group');
        flt_grps.addClass('filters');
        var filters = flt_grps.find('.row');
        filters.prepend('<div class="enable"><input type="checkbox"></div>');
        filters.on('change', '.enable input[type="checkbox"]', toggle_filter);

        init_settings();
        refresh_ipresets();
    }

    //========================================================================
    // open_quiz_settings()
    //------------------------------------------------------------------------
    function open_quiz_settings() {
        if (quiz_settings_state !== 'ready') return call_setup_quiz_settings();
        quiz_settings_state = 'open';
        var backup = {};
        quiz.backup = backup;
        //backup.max_quiz_size = quiz.settings.max_quiz_size;
        backup.ipre = JSON.stringify(quiz.settings.ipresets[quiz.settings.active_ipreset].content);
        quiz.settings_dialog.open();

        function call_setup_quiz_settings(){
            setup_quiz_settings();
            wkof.Settings.load('Item_Inspector')
            .then(function(){
                  quiz_settings_state = 'ready';
                  open_quiz_settings();
                  });
        };
    }

    //========================================================================
    // save_quiz_settings()
    //------------------------------------------------------------------------
    function save_quiz_settings(settings) {
        quiz.settings = settings;
        populate_presets($('#Item_Inspector_source'), settings.ipresets, settings.active_ipreset);
        quiz.settings.tablePresets[quiz.settings.active_ipreset].currentItem = 0;
        // check if the table list is empty
        if (!quiz.settings.ipresets.length){
            init_settings(); // restore defaults
            };

        fetch_items()
            .then(function(){
                     initCurrentItem();
                     updatePage()
                    })
    }

    //========================================================================
    // close_quiz_settings()
    //------------------------------------------------------------------------
    function close_quiz_settings(settings) {
        quiz_settings_state = 'setup';
    }

    //========================================================================
    // refresh_quiz_settings()
    //------------------------------------------------------------------------
    function refresh_quiz_settings(settings) {
        $('#Item_Inspector_ipre_srcs .wkof_group .row').each(function(i,e){
            var row = $(e);
            var panel = row.closest('[role="tabpanel"]');
            var source = panel.attr('id').match(/^Item_Inspector_pg_(.*)$/)[1];
            var filter_name = row.find('.setting').attr('name').slice((source+'_flt_').length);
            var preset = quiz.settings.ipresets[quiz.settings.active_ipreset].content;
            var enabled = false;
            try {
                enabled = preset[source].filters[filter_name].enabled;
            } catch(e) {}

            if (enabled) {
                row.addClass('checked');
            } else {
                row.removeClass('checked');
            }
            row.find('.enable input[type="checkbox"]').prop('checked', enabled);
        });
    }

    //========================================================================
    // refresh_ipresets()
    //------------------------------------------------------------------------
    function refresh_ipresets() {
        var settings = quiz.settings;
        populate_presets($('#Item_Inspector_active_ipreset'), settings.ipresets, settings.active_ipreset);
    }

    //========================================================================
    // preset_button_pressed()
    //------------------------------------------------------------------------
    function preset_button_pressed(e) {
        var settings = quiz.settings;
        var tablePresets = settings.tablePresets;
        var ref = e.currentTarget.attributes.ref.value;
        var action = e.currentTarget.attributes.action.value;
        var selected = Number(settings['active_'+ref]);
        var presets = settings[ref+'s'];
        var elem = $('#Item_Inspector_active_'+ref);

        var dflt;
        dflt = {name:'<untitled>', content:$.extend(true, {}, ipre_defaults)};

        switch (action) {
            case 'new':
                presets.push(dflt);
                tablePresets.push(table_defaults);
                selected = presets.length - 1;
                settings[ref+'s'] = presets;
                settings['active_'+ref] = selected;
                settings.tablePresets = tablePresets;
                populate_presets(elem, presets, selected);
                refresh_table_presets(selected)
                quiz.settings_dialog.refresh();
                $('#Item_Inspector_'+ref.slice(0,4)+'_name').focus().select();
                break;

            case 'up':
                if (selected <= 0) break;
                presets = [].concat(presets.slice(0, selected-1), presets[selected], presets[selected-1], presets.slice(selected+1));
                tablePresets = [].concat(tablePresets.slice(0, selected-1), tablePresets[selected], tablePresets[selected-1], tablePresets.slice(selected+1));
                selected--;
                settings[ref+'s'] = presets;
                settings['active_'+ref] = selected;
                settings.tablePresets = tablePresets;
                populate_presets(elem, presets, selected);
                break;

            case 'down':
                if (selected >= presets.length-1) break;
                presets = [].concat(presets.slice(0, selected), presets[selected+1], presets[selected], presets.slice(selected+2));
                tablePresets = [].concat(tablePresets.slice(0, selected), tablePresets[selected+1], tablePresets[selected], tablePresets.slice(selected+2));
                selected++;
                settings[ref+'s'] = presets;
                settings['active_'+ref] = selected;
                settings.tablePresets = tablePresets;
                populate_presets(elem, presets, selected);
                break;

            case 'delete':
                presets = presets.slice(0, selected).concat(presets.slice(selected+1));
                tablePresets = tablePresets.slice(0, selected).concat(tablePresets.slice(selected+1));
                selected = Math.max(0, selected-1);
                settings[ref+'s'] = presets;
                settings['active_'+ref] = selected;
                settings.tablePresets = tablePresets;
                populate_presets(elem, presets, selected);
                quiz.settings_dialog.refresh();
                break;
        }
    }

    //========================================================================
    // init_settings()
    //------------------------------------------------------------------------
    var table_defaults;
    function init_settings() {
        var idx;
        // Merge some defaults
        var defaults = {
        };
        var settings = $.extend(true, {}, defaults, wkof.settings.Item_Inspector);
        wkof.settings.Item_Inspector = quiz.settings = settings;


        let ipresets_defaults = [
                {name:'Leeches', content:{wk_items:{enabled:true,filters:{srs:{enabled:true,value:{appr1:true,appr2:true,appr3:true,appr4:true,guru1:true,guru2:true,mast:true}}
                                                                          ,additionalFilters_leechTraining:{enabled:true,value:1}}},
                                         tableContents:{currentItem:0,table_data:"Leech",sort1:"Default",sort2:"Default",tooltip1:"Meaning_Full",tooltip2:"Reading_Full",tooltip3:"None",tooltip4:"None",randomSelection:0,leechStreakLimit:0}}},
                {name:'Failed Last Review', content:{wk_items:{enabled:true,filters:{additionalFilters_failedLastReview:{enabled:true,value:24}}},
                                                     tableContents:{currentItem:0,table_data:"Leech",sort1:"Default",sort2:"Default",tooltip1:"Meaning_Full",tooltip2:"Reading_Full",tooltip3:"None",tooltip4:"None",randomSelection:0,leechStreakLimit:0}}},
                {name:'Current Level SRS', content:{wk_items:{enabled:true,filters:{level:{enabled:true,value:"+0"},srs:{enabled:true,value:{appr1:true,appr2:true,appr3:true,appr4:true,guru1:true,guru2:true,mast:true,enli:true}}}},
                                                    tableContents:{currentItem:0,table_data:"Srs",sort1:"Default",sort2:"Default",tooltip1:"Review_Date",tooltip2:"Review_Wait",tooltip3:"None",tooltip4:"None",randomSelection:0,leechStreakLimit:0}}},
                {name:'Previous Level SRS', content:{wk_items:{enabled:true,filters:{level:{enabled:true,value:"-1"},srs:{enabled:true,value:{appr1:true,appr2:true,appr3:true,appr4:true,guru1:true,guru2:true,mast:true,enli:true}}}},
                                                     tableContents:{currentItem:0,table_data:"Srs",sort1:"Default",sort2:"Default",tooltip1:"Review_Date",tooltip2:"Review_Wait",tooltip3:"None",tooltip4:"None",randomSelection:0,leechStreakLimit:0}}},
            ];
        table_defaults = {currentItem:0,table_data:"Leech",sort1:"Default",sort2:"Default",tooltip1:"Meaning_Full",tooltip2:"Reading_Full",tooltip3:"None",tooltip4:"None",randomSelection:0,leechStreakLimit:0,};
        // define defaults if presets not yet defined
        if (settings.ipresets === undefined) {
            settings.ipresets = ipresets_defaults;
            settings.active_ipreset = 0;
            settings.tablePresets = init_table_presets(ipresets_defaults);
        };
        // restore defaults when ipresets list is empty
        if (!settings.ipresets.length) {
            settings.ipresets = ipresets_defaults;
            settings.active_ipreset = 0;
            settings.tablePresets = init_table_presets(ipresets_defaults);
        };
        if (ipre_defaults) {
            for (idx in settings.ipresets) {
                settings.ipresets[idx].content = $.extend(true, {}, ipre_defaults, settings.ipresets[idx].content);
            }
        }

        if (settings.table_data) {refresh_table_presets(settings.active_ipreset)};


        function init_table_presets(ipresets_defaults){
            var x = [];
            for (var y of ipresets_defaults){x.push(y.content.tableContents)};
            return x;
        }
    }

    //========================================================================
    // populate_items_config()
    //------------------------------------------------------------------------
    var ipre_defaults;
    function populate_items_config(config) {
        var ipre_srcs = config.settings.pg_items.content.grp_ipre.content.ipre_srcs.content;
        var srcs = wkof.ItemData.registry.sources;
        ipre_defaults = {};
        let src_name = 'wk_items';
        var src = srcs[src_name];
        var pg_content = {};
        ipre_srcs['pg_'+src_name] = {type:'page',label:'Filters',content:pg_content};
        var settings = {};
        ipre_defaults[src_name] = settings;

        // Add 'Filters' section.
        if (src.filters && Object.keys(src.filters).length > 0) {
            settings.filters = {};
            var flt_content = {};
            pg_content['grp_'+src_name+'_filters'] = {type:'group',label:'',content:flt_content};
            for (var flt_name in src.filters) {
                var flt = src.filters[flt_name];
                settings.filters[flt_name] = {enabled:false, value:flt.default};
                switch (flt.type) {
                    case 'checkbox':
                        flt_content[src_name+'_flt_'+flt_name] = {
                            type:'checkbox',
                            label:flt.label,
                            default:flt.default,
                            path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
                            hover_tip:flt.hover_tip
                        }
                        break;
                    case 'multi':
                        var dflt = flt.default;
                        if (typeof flt.filter_value_map === 'function') dflt = flt.filter_value_map(dflt);
                        flt_content[src_name+'_flt_'+flt_name] = {
                            type:'list',
                            multi:true,
                            size:Math.min(4,Object.keys(flt.content).length),
                            label:flt.label,
                            content:flt.content,
                            default:dflt,
                            path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
                            hover_tip:flt.hover_tip
                        }
                        settings.filters[flt_name].value = dflt;
                        break;
                    case 'text':
                    case 'number':
                    case 'input':
                        flt_content[src_name+'_flt_'+flt_name] = {
                            type:flt.type,
                            label:flt.label,
                            placeholder:flt.placeholder,
                            default:flt.default,
                            path:'@ipresets[@active_ipreset].content["'+src_name+'"].filters["'+flt_name+'"].value',
                            hover_tip:flt.hover_tip
                        }
                        break;
                    case 'button':
                        flt_content[src_name+'_flt_'+flt_name] = {
                            type:flt.type,
                            label:flt.label,
                            on_click:flt.on_click,
                            hover_tip:flt.hover_tip
                        }
                        break;
                }
            }
        }
    }

    //========================================================================
    // toggle_filter()
    //------------------------------------------------------------------------
    function toggle_filter(e) {
        var row = $(e.delegateTarget);
        var panel = row.closest('[role="tabpanel"]');
        var source = panel.attr('id').match(/^Item_Inspector_pg_(.*)$/)[1];
        var enabled = row.find('.enable input[type="checkbox"]').prop('checked');
        var preset = quiz.settings.ipresets[quiz.settings.active_ipreset].content;
        var filter_name = row.find('.setting').attr('name').slice((source+'_flt_').length);

        if (enabled) {
            row.addClass('checked');
        } else {
            row.removeClass('checked');
        }
        try {
            preset[source].filters[filter_name].enabled = enabled;
        } catch(e) {}
    }

    //========================================================================
    // populate_presets()
    //------------------------------------------------------------------------
    function populate_presets(elem, presets, active_preset) {
        var html = '';
        for (var idx in presets) {
            var preset = presets[idx];
            var name = preset.name.replace(/</g,'&lt;').replace(/>/g,'&gt;');
            html += '<option name="'+idx+'">'+name+'</option>';
        }
        var elem_name = elem.attr('id')
        if (elem_name === 'Item_Inspector_source' && quiz.custom.has_ipreset) {
            html += '<option name="custom">('+quiz.custom.ipreset.name+')</option>';
            if (quiz.custom.using_ipreset) active_preset = presets.length;
        }
        elem.html(html);
        elem.children().eq(active_preset).prop('selected', true);
        refresh_table_presets(active_preset);

    }

    //========================================================================
    // refresh_table_presets()
    //------------------------------------------------------------------------
    function refresh_table_presets(active_preset){

        // Convert tablePresets to new Review_Date name and add the fouth tooltip, sort keys, randomSelection, leechStreakLimit, currentItem
        let tablePresets = quiz.settings.tablePresets;
        for (var i = 0; i < tablePresets.length; i++){
            for (var settingName in tablePresets[i]){
                var setting = tablePresets[i][settingName];
                if (setting == 'Review_Date12' || setting == 'Review_Date24' ) {tablePresets[i][settingName] = 'Review_Date'};
            };
            if (tablePresets[i].sort1 == undefined){tablePresets[i].sort1 = 'Default'};
            if (tablePresets[i].sort2 == undefined){tablePresets[i].sort2 = 'Default'};
            if (tablePresets[i].tooltip4 == undefined){tablePresets[i].tooltip4 = 'None'};
            if (tablePresets[i].randomSelection == undefined){tablePresets[i].randomSelection = 0};
            if (tablePresets[i].leechStreakLimit == undefined){tablePresets[i].leechStreakLimit = 0};
            if (tablePresets[i].currentItem == undefined){tablePresets[i].currentItem = 0};
        }

        // change the settings the framework wants to use and let the framework
        // update the UI.
        let presets = quiz.settings.tablePresets[active_preset];
        quiz.settings.table_data = presets.table_data;
        quiz.settings.sort1 = presets.sort1;
        quiz.settings.sort2 = presets.sort2;
        quiz.settings.tooltip1 = presets.tooltip1;
        quiz.settings.tooltip2 = presets.tooltip2;
        quiz.settings.tooltip3 = presets.tooltip3;
        quiz.settings.tooltip4 = presets.tooltip4;
        quiz.settings.randomSelection = presets.randomSelection;
        quiz.settings.leechStreakLimit = presets.leechStreakLimit;
    }

    //========================================================================
    // refresh_table_on_preset_click()
    //------------------------------------------------------------------------
    function refresh_table_on_preset_click(){
        // change the settings the framework wants to use and let the framework
        // update the UI.
        let active_preset = $('#Item_Inspector_active_ipreset').prop('selectedIndex');
        let presets = quiz.settings.tablePresets[active_preset];
        quiz.settings.table_data = presets.table_data;
        quiz.settings.sort1 = presets.sort1;
        quiz.settings.sort2 = presets.sort2;
        quiz.settings.tooltip1 = presets.tooltip1;
        quiz.settings.tooltip2 = presets.tooltip2;
        quiz.settings.tooltip3 = presets.tooltip3;
        quiz.settings.tooltip4 = presets.tooltip4;
        quiz.settings.randomSelection = presets.randomSelection;
        quiz.settings.leechStreakLimit = presets.leechStreakLimit;
    }

    //========================================================================
    // update_table_presets()
    //------------------------------------------------------------------------
    function update_table_presets(){
        // copy the settings the framework wants to use and let the framework
        // manage the UI.
        let active_preset = $('#Item_Inspector_active_ipreset').prop('selectedIndex');
        let presets = quiz.settings.tablePresets[active_preset];
        presets.table_data = quiz.settings.table_data;
        presets.sort1 = quiz.settings.sort1;
        presets.sort2 = quiz.settings.sort2;
        presets.tooltip1 = quiz.settings.tooltip1;
        presets.tooltip2 = quiz.settings.tooltip2;
        presets.tooltip3 = quiz.settings.tooltip3;
        presets.tooltip4 = quiz.settings.tooltip4;
        presets.randomSelection = quiz.settings.randomSelection;
        presets.leechStreakLimit = quiz.settings.leechStreakLimit;
    }

    //########################################################################
    // QUIZ DIALOG
    //########################################################################

    //========================================================================
    // install_css()
    //------------------------------------------------------------------------
    function install_css() {
        $('head').append(
            '<style id="Item_Inspector_css" type="text/css">'+

            //--[ Settings dialog ]-------------------------------------------
            '#wkof_ds div[role="dialog"][aria-describedby="wkofs_Item_Inspector"] {z-index:12002;}'+

            '#wkofs_Item_Inspector.wkof_settings .pre_list_btn_grp {width:60px;float:left;margin-right:2px;}'+
            '#wkofs_Item_Inspector.wkof_settings .pre_list_btn_grp button {width:100%; padding:2px 0;}'+
            '#wkofs_Item_Inspector.wkof_settings .pre_list_btn_grp button:not(:last-child) {margin-bottom:2px;}'+
            '#wkofs_Item_Inspector.wkof_settings .pre_list_wrap {display:flex;}'+
            '#wkofs_Item_Inspector.wkof_settings .pre_list_wrap .right {flex:1;}'+
            '#wkofs_Item_Inspector.wkof_settings .pre_list_wrap .list {overflow:auto;height:100%;}'+

            '#wkofs_Item_Inspector.wkof_settings .filters .row {border-top:1px solid #ccc; padding:6px 4px; margin-bottom:0;}'+
            '#wkofs_Item_Inspector.wkof_settings .filters .row:not(.checked) {padding-top:0px;padding-bottom:0px;}'+
            '#wkofs_Item_Inspector .filters .row .enable input[type="checkbox"] {margin:0;}'+
            '#wkofs_Item_Inspector.narrow .filters .row.checked .right input[type="checkbox"]:after {content:"⇐yes?";margin-left:28px;line-height:30px;}'+
            '#wkofs_Item_Inspector .filters .row.checked {background-color:#f7f7f7;}'+
            '#wkofs_Item_Inspector .filters .row:not(.checked) {opacity:0.5;}'+
            '#wkofs_Item_Inspector .filters .row .enable {display:inline; margin:0; float:left;}'+
            '#wkofs_Item_Inspector:not(.narrow) .filters .left {width:170px;}'+

            '#wkofs_Item_Inspector .filters .row .enable input[type="checkbox"] {margin:0 4px 0 0;}'+
            '#wkofs_Item_Inspector .filters .row:not(.checked) .right {display:none;}'+
            '#wkofs_Item_Inspector .filters .row:not(.checked) .left label {text-align:left;}'+
            '#wkofs_Item_Inspector.narrow .filters .row .left {width:initial;}'+
            '#wkofs_Item_Inspector.narrow .filters .row .left label {line-height:30px;}'+
            '#wkofs_Item_Inspector #Item_Inspector_ipre_srcs .src_enable .left {width:initial;}'+
            '#wkofs_Item_Inspector #Item_Inspector_ipre_srcs .src_enable .left label {text-align:left;width:initial;line-height:30px;}'+
            '#wkofs_Item_Inspector #Item_Inspector_ipre_srcs .src_enable .right {float:left; margin:0 4px;width:initial;}'+
            //----------------------------------------------------------------

            '</style>'
        );
    }

    //========================================================================
    // fetch_items()
    //------------------------------------------------------------------------
    function fetch_items() {
        var settings = quiz.settings;
        var ipreset = settings.ipresets[settings.active_ipreset].content;

        //set_mode('loading');
        var config = {};
        for (var src_name in ipreset) {
            var src_preset = ipreset[src_name];
            //if (!src_preset.enabled) continue;
            if (!wkof.ItemData.registry.sources[src_name]) continue;
            var src_cfg = {};
            config[src_name] = src_cfg;
            src_cfg.filters = {};
            //if (src_name === 'wk_items') src_cfg.options = {study_materials: true};
            if (src_name === 'wk_items') src_cfg.options = {};
            var ipre_filters = src_preset.filters;
            for (var flt_name in ipre_filters) {
                var ipre_flt = ipre_filters[flt_name];
                if (!ipre_flt.enabled) continue;
                if (!wkof.ItemData.registry.sources[src_name].filters[flt_name]) continue;
                src_cfg.filters[flt_name] = {value: ipre_flt.value};
                if (ipre_flt.invert === true) src_cfg.filters[flt_name].invert = true;
            }
        }

        let presets = quiz.settings.tablePresets[settings.active_ipreset];
        for (var settingName in presets){
            var setting = presets[settingName];
            if ((settingName != 'randomSelection') && (settingName != 'leechStreakLimit') && (setting != 'None') &&
                (setting != 'Default') && (settingName != 'currentItem')) {
                     src_cfg.options[metadata[setting].endPoint] = true;
            };
        }
        if (presets.leechStreakLimit != 0) {src_cfg.options.review_statistics = true}

        return wkof.ItemData.get_items(config)
        .then(function(items){
            quiz.items = items;
        });
    }


    //------------------------------
	// Calculating displayed data
	//------------------------------

    var theFuture = Date.parse("01 Jan 2090 00:00:00 GMT");
    var srsName = ["Initiate","Apprentice I","Apprentice II","Apprentice III","Apprentice IV","Guru I","Guru II","Master","Enlightened","Burned"];

    var metadata = {'Meaning_Brief': {'exists': ((item) => {return true}), 'label': 'EN: ',
                                      'tableEntry': meaningsBrief,
                                      'tooltipEntry': meaningsBrief,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                       },
                    'Meaning_Full': {'exists': ((item) => {return true}), 'label': 'EN: ',
                                      'tableEntry': meaningsFullTable,
                                      'tooltipEntry': meaningsFull,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                       },
                    'Reading_Brief': {'exists': ((item) => {return item.data.readings}), 'label': 'JP: ',
                                      'tableEntry': readingsBrief,
                                      'tooltipEntry': readingsBrief,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                       },
                    'Reading_Full': {'exists': ((item) => {return item.data.readings}), 'label': 'JP: ',
                                      'tableEntry': readingsFull,
                                      'tooltipEntry': readingsFull,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                       },
                    'Leech': {'exists': ((item) => {return true}), 'label': 'Leech: ',
                                      'tableEntry': leechScore,
                                      'tooltipEntry': leechScore,
                                      'sortkey': leechScore,
                                      'sortOrder': 'Descending',
                                      'sortkey2': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct : 101)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Percentage_Correct': {'exists': ((item) => {return true}), 'label': '%Total: ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct+'%' : 'None')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct+'%' : 'None')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct : 101)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Meaning_Correct': {'exists': ((item) => {return true}), 'label': '%Meaning: ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? MeaningCorrect(item)+'%' : 'None')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? MeaningCorrect(item)+'%' : 'None')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? MeaningCorrect(item) : 101)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Reading_Correct': {'exists': ((item) => {return item.data.readings}), 'label': '%Reading: ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? ReadingCorrect(item)+'%' : 'None')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? ReadingCorrect(item)+'%' : 'None')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? ReadingCorrect(item) : 101)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Meaning_Current_Streak': {'exists': ((item) => {return true}), 'label': 'Meaning Current Streak: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.meaning_current_streak - 1)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.meaning_current_streak -1)}),
                                      'sortkey': ((item) => {return (item.review_statistics.meaning_current_streak)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Reading_Current_Streak': {'exists': ((item) => {return true}), 'label': 'Reading Current Streak: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.reading_current_streak - 1)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.reading_current_streak - 1)}),
                                      'sortkey': ((item) => {return (item.review_statistics.reading_current_streak)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Level': {'exists': ((item) => {return true}), 'label': 'Level: ',
                                      'tableEntry': ((item) => {return item.data.level}),
                                      'tooltipEntry': ((item) => {return item.data.level}),
                                      'sortkey': ((item) => {return item.data.level}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                       },
                    'Srs': {'exists': ((item) => {return true}), 'label': 'SRS: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.srs_stage != undefined ? srsName[item.assignments.srs_stage] : 'Locked') : 'Locked')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.srs_stage != undefined ? srsName[item.assignments.srs_stage] : 'Locked') : 'Locked')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.srs_stage != undefined ? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? Date.parse(item.assignments.available_at) : theFuture) : theFuture)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                       },
                    'Review_Date': {'exists': ((item) => {return true}), 'label': 'Review: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? makeDate(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? makeDate(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? trimDate(Date.parse(item.assignments.available_at)) : theFuture) : theFuture)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments ? (item.assignments.srs_stage != undefined? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                       },
                    'Review_Wait': {'exists': ((item) => {return true}), 'label': 'Wait: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? reviewWait(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? reviewWait(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.available_at != undefined ? trimDate(Date.parse(item.assignments.available_at)) : theFuture) : theFuture)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments ? (item.assignments.srs_stage != undefined? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                       },
                    'Passed_Date': {'exists': ((item) => {return true}), 'label': 'Passed: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.passed_at != undefined ? makePassedDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.passed_at != undefined ? makePassedDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.passed_at != undefined ? trimDate(Date.parse(item.assignments.passed_at)) : 0) : 0)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments ? (item.assignments.srs_stage != undefined ? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                       },
                    'Burned_Date': {'exists': ((item) => {return true}), 'label': 'Burned: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.burned_at != undefined ? makeBurnedDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.burned_at != undefined ? makeBurnedDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.burned_at != undefined ? trimDate(Date.parse(item.assignments.burned_at)) : 0) : 0)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return item.data.level}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                       },
                    'Lesson_Date': {'exists': ((item) => {return true}), 'label': 'Lesson: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.started_at != undefined ? makeLessonDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.started_at != undefined ? makeLessonDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.started_at ? trimDate(Date.parse(item.assignments.started_at)) : theFuture) : theFuture)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return item.data.level}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                       },
    }


    function MeaningCorrect(item){
        let stats = item.review_statistics;
        let denominator = stats.meaning_correct + stats.meaning_incorrect;
        if (denominator == 0){
            return 100
        } else {
            return Math.floor(100 * stats.meaning_correct / denominator)
        }
    }

    function ReadingCorrect(item){
        let stats = item.review_statistics;
        let denominator = stats.reading_correct + stats.reading_incorrect;
        if (denominator == 0){
            return 100
        } else {
            return Math.floor(100 * stats.reading_correct / denominator)
        }
    }

    function leechScore(item){
		let reviewStats = item.review_statistics;
        if (reviewStats){
		    let meaningScore = getLeechScore(reviewStats.meaning_incorrect, reviewStats.meaning_current_streak);
		    let readingScore = getLeechScore(reviewStats.reading_incorrect, reviewStats.reading_current_streak);

            return Math.max(meaningScore, readingScore);
        } else {return 0}
    }

	function getLeechScore(incorrect, currentStreak) {
        //get incorrect number than lessen it using the user's correctStreak
        let leechScore = incorrect / Math.pow((currentStreak || 0.5), 1.5); // '||' => if currentstreak zero make 0.5 instead (prevents dividing by zero)
        leechScore = Math.round(leechScore * 100) / 100; //round to two decimals
		return leechScore;
	}

   function reviewWait(item){
         return (Date.parse(item.assignments.available_at) < Date.now() ? 'Now' : s_to_dhm((Date.parse(item.assignments.available_at)-Date.now())/1000))
   }

    // Converts seconds to days, hours, and minutes
    function s_to_dhm(s) {
        var d = Math.floor(s/60/60/24);
        var h = Math.floor(s%(60*60*24)/60/60);
        var m = Math.ceil(s%(60*60*24)%(60*60)/60);
        return (d>0?d+'d ':'')+(h>0?h+'h ':'')+(m>0?m+'m':'1m');
    }

    function makeDate(item){
    	return formatDate(new Date(item.assignments.available_at), true, /* is_next_date */);
    }

    function makePassedDate(item){
    	return formatDate(new Date(item.assignments.passed_at), false, /* is_next_date */);
    }

    function makeBurnedDate(item){
    	return formatDate(new Date(item.assignments.burned_at), false, /* is_next_date */);
    }

    function makeLessonDate(item){
    	return formatDate(new Date(item.assignments.started_at), false, /* is_next_date */);
    }

    //========================================================================
    // Print date in pretty format.
    //-------------------------------------------------------------------
    function formatDate(d, is_next_date){
        var s = '';
        var now = new Date();
        var YY = d.getFullYear(),
            MM = d.getMonth(),
            DD = d.getDate(),
            hh = d.getHours(),
            mm = d.getMinutes(),
            one_day = 24*60*60*1000;

        if (is_next_date && d < now) return "Available Now";
        var same_day = ((YY == now.getFullYear()) && (MM == now.getMonth()) && (DD == now.getDate()) ? 1 : 0);

        //    If today:  "Today 8:15pm"
        //    otherwise: "Wed, Apr 15, 8:15pm"
        if (same_day) {
            s += 'Today ';
        } else {
            s += ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()]+', '+
                ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][MM]+' '+DD+', ';
        }
        if (quiz.settings.hoursFormat === '24hours') {
            s += ('0'+hh).slice(-2)+':'+('0'+mm).slice(-2);
        } else {
            s += (((hh+11)%12)+1)+':'+('0'+mm).slice(-2)+['am','pm'][Math.floor(d.getHours()/12)];
        }

        return s;
    }

    //========================================================================
    // Reduce date in milliseconds to minutes boundary
    // This makes secondary sorting match the primary sort data seen by the user
    //------------------------------------------------------------------------
    function trimDate(d){
       return Math.floor((d/1000)/60)*1000*60;
    }

    function meaningsFullTable(item){
        var meanings = meaningsFull(item);
        if (meanings.length > 20){meanings = meanings.slice(0, 20) + '...'}
        return meanings;
    }

/*    function meaningsFull(item){
        var meanings = item.data.meanings[0].meaning;
        for (var k = 1; k < item.data.meanings.length; k++){
            if(item.data.meanings[k].primary){
               meanings = item.data.meanings[k].meaning + ', ' + meanings;
             } else {
               meanings += ', ' + item.data.meanings[k].meaning};
             }
        return meanings;
    }
*/
    function meaningsFull(item){
        // make sure meanings are returned in the order WK provides them, but primary comes first
        var meanings = "";
        var meaningsPrimary = "";
        for (var k = 0; k < item.data.meanings.length; k++){
            if(item.data.meanings[k].primary){
               if (meaningsPrimary.length != 0){
                   meaningsPrimary += ', ' + item.data.meanings[k].meaning;
                } else {
                   meaningsPrimary = item.data.meanings[k].meaning;
                }
             } else {
                 if (meanings.length != 0){
                     meanings += ', ' + item.data.meanings[k].meaning;
                 } else {
                     meanings = item.data.meanings[k].meaning;
                 }
             }
        }
        if (meanings.length != 0){meaningsPrimary = meaningsPrimary + ', ' + meanings}
        return meaningsPrimary;
    }


    function meaningsBrief(item){
        var meanings = item.data.meanings[0].meaning;
        for (var k = 0; k < item.data.meanings.length; k++){if (item.data.meanings[k].primary){return item.data.meanings[k].meaning}};
        return meanings;
    }

    function readingsFull(item){
        // make sure readings are returned in the order WK provides them, but primary comes first
        var readings = "";
        var readingsPrimary = "";
        for (var k = 0; k < item.data.readings.length; k++){
            if(item.data.readings[k].primary){
               if (readingsPrimary.length != 0){
                   readingsPrimary += ', ' + item.data.readings[k].reading;
                } else {
                   readingsPrimary = item.data.readings[k].reading;
                }
             } else {
                 if (readings.length != 0){
                     readings += ', ' + item.data.readings[k].reading;
                 } else {
                     readings = item.data.readings[k].reading;
                 }
             }
        }
        if (readings.length != 0){readingsPrimary = readingsPrimary + ', ' + readings}
        return readingsPrimary;
    }

    function readingsBrief(item){
        var readings = item.data.readings[0].reading;
        for (var k = 0; k < item.data.readings.length; k++){if (item.data.readings[k].primary){return item.data.readings[k].reading}};
        return readings;
    }

    function shuffle(items){
        for(let i = items.length - 1; i > 0; i--){
            const j = Math.floor(Math.random() * i);
            const temp = items[i];
            items[i] = items[j];
            items[j] = temp;
        }
        return items
    }

	function updatePage() {
        let leechStreakLimit = quiz.settings.tablePresets[quiz.settings.active_ipreset].leechStreakLimit;
        if (leechStreakLimit != 0) {
            quiz.items = quiz.items.filter((item => {return (item.review_statistics.meaning_current_streak < (leechStreakLimit+1)) || (item.review_statistics.reading_current_streak < (leechStreakLimit+1))}));
        };

        let randomSelection = quiz.settings.tablePresets[quiz.settings.active_ipreset].randomSelection;
        if (randomSelection != 0){
            quiz.items = shuffle(quiz.items).slice(0, randomSelection);
            };

        let tableKey = quiz.settings.tablePresets[quiz.settings.active_ipreset].table_data;
        let sort1 = quiz.settings.tablePresets[quiz.settings.active_ipreset].sort1;
        let sort2 = quiz.settings.tablePresets[quiz.settings.active_ipreset].sort2;
        let sortKey, sortOrder;
        let sortKey2, sortOrder2;
        if ((sort1 == "Default") && (sort2 == 'Default')){
            sortKey = metadata[tableKey].sortkey;
            sortOrder = metadata[tableKey].sortOrder;
            sortKey2 = metadata[tableKey].sortkey2;
            sortOrder2 = metadata[tableKey].sortOrder2;
        } else if ((sort1 == "Default") && (sort2 != "Default")){
            sortKey = metadata[tableKey].sortkey;
            sortOrder = metadata[tableKey].sortOrder;
            sortKey2 = metadata[sort2].sortkey;
            sortOrder2 = metadata[sort2].sortOrder;
        } else if ((sort1 != "Default") && (sort2 == "Default")){
            sortKey = metadata[sort1].sortkey;
            sortOrder = metadata[sort1].sortOrder;
            sortKey2 = metadata[sort1].sortkey2;
            sortOrder2 = metadata[sort1].sortOrder2;
        } else {
            sortKey = metadata[sort1].sortkey;
            sortOrder = metadata[sort1].sortOrder;
            sortKey2 = metadata[sort2].sortkey;
            sortOrder2 = metadata[sort2].sortOrder;
        }
        quiz.items.forEach(((item) => {item.sortKey = sortKey(item); item.sortKey2 = sortKey2(item)}));

        if ((sortOrder == 'Descending') && (sortOrder2 == 'Descending')){
            quiz.items = quiz.items.sort(function(a, b){return b.sortKey == a.sortKey ? b.sortKey2 - a.sortKey2 : b.sortKey - a.sortKey });
        } else if ((sortOrder == 'Descending') && (sortOrder2 == 'Ascending')){
            quiz.items = quiz.items.sort(function(a, b){return b.sortKey == a.sortKey ? a.sortKey2 - b.sortKey2 : b.sortKey - a.sortKey });
        } else if ((sortOrder == 'Ascending') && (sortOrder2 == 'Descending')){
            quiz.items = quiz.items.sort(function(a, b){return a.sortKey == b.sortKey ? b.sortKey2 - a.sortKey2 : a.sortKey - b.sortKey });
        } else {
            quiz.items = quiz.items.sort(function(a, b){return(a.sortKey == b.sortKey ? a.sortKey2 - b.sortKey2 : a.sortKey - b.sortKey)});
        };

        createTopLeechTables(quiz.items);
	}

    function itemsCharacterCallback (itemsData){
        //check if an item has characters. Kanji and vocabulary will always have these but wk-specific radicals (e.g. gun, leaf, stick) use images instead
        if(itemsData.characters!= null) {
            return itemsData.characters;
        } else if (itemsData.character_images!= null){
            let imgUrl = '';
            for (let i = 0; i < itemsData.character_images.length; i++) {
                if (itemsData.character_images[i].content_type == "image/png"){
                    imgUrl = itemsData.character_images[i].url;
                }
            }
            return '<img class="radical-'+itemsData.slug+' radicalCharacterImgSize" src="'+imgUrl+'"></img>';
        } else {
            //if both characters and character_images are somehow absent try using slug instead
            return itemsData.slug;
        }
    }

    function makeTableEntry(item, selectedTable){
        let tableData = metadata[quiz.settings.tablePresets[selectedTable].table_data];
        return (tableData.exists(item) ? tableData.tableEntry(item) : '')
    }

    function makeTooltips(item, selectedTable){
        let html = '';
        let presets = quiz.settings.tablePresets[selectedTable]

        let tableName = presets.tooltip1;
        if (tableName != "None") {
            let tableData = metadata[tableName];
            if (tableData.exists(item)) {
                html += '<tr><td class="WkisLabel">'+tableData.label+'</td><td class="WkisTipValue">'+tableData.tooltipEntry(item)+'</td></tr>'
                };
        };

        tableName = presets.tooltip2;
        if (tableName != "None") {
            let tableData = metadata[tableName];
            if (tableData.exists(item)) {
                html += '<tr><td class="WkisLabel">'+tableData.label+'</td><td class="WkisTipValue">'+tableData.tooltipEntry(item)+'</td></tr>'
                };
        };

        tableName = presets.tooltip3;
        if (tableName != "None") {
            let tableData = metadata[tableName];
            if (tableData.exists(item)) {
                html += '<tr><td class="WkisLabel">'+tableData.label+'</td><td class="WkisTipValue">'+tableData.tooltipEntry(item)+'</td></tr>'
                };
        };

        tableName = presets.tooltip4;
        if (tableName != "None" && tableName != undefined) {
            let tableData = metadata[tableName];
            if (tableData.exists(item)) {
                html += '<tr><td class="WkisLabel">'+tableData.label+'</td><td class="WkisTipValue">'+tableData.tooltipEntry(item)+'</td></tr>'
                };
        };

        if (html != '') {
            html = '<div class="WkisTooltipContent"><table><tbody>'+html+'</tbody></table></div>';
        };
        return html;
    }

    /* control variables for the currently displayed tables*/
    var currentItem = 0;
    var nbItems = 0;

    function initCurrentItem(){
        currentItem = quiz.settings.tablePresets[quiz.settings.active_ipreset].currentItem;
    }

    /* Callbacks for buttons */
    function clickedBackward(event) {
        //test prevents multiple clicks
        if (!event.detail || event.detail == 1) {
            if ((currentItem - nbDisplayedItems) >= 0) {
                currentItem -= nbDisplayedItems;
                quiz.settings.tablePresets[quiz.settings.active_ipreset].currentItem = currentItem;
                quiz.settings_dialog.save();
            };
            /* refresh the page */
            createTopLeechTables(quiz.items);
        };
     }

    function clickedForward(event) {
        //test prevents multiple clicks
        if (!event.detail || event.detail == 1) {
            if ((currentItem + nbDisplayedItems) < nbItems) {
                currentItem += nbDisplayedItems;
                quiz.settings.tablePresets[quiz.settings.active_ipreset].currentItem = currentItem;
                quiz.settings_dialog.save();
            };
            /* refresh the page */
            createTopLeechTables(quiz.items);
         };
     }

    function selectTable(event) {
        quiz.settings.active_ipreset = $('#WkitTableSelector').prop('selectedIndex');
        currentItem = 0;
        quiz.settings.tablePresets[quiz.settings.active_ipreset].currentItem = currentItem;
        quiz.settings_dialog.save();

        fetch_items()
            .then(function(){
                     updatePage()
                    })

    }

    const numberOfTables = 3;
    const leechesPerTable = 11;
    const nbDisplayedItems = numberOfTables * leechesPerTable;

    function createTopLeechTables(items) {
        let sectionContents = "";
        let sectionContainer = "";
        let topBlock = "";
        let numberPerTable = leechesPerTable;
        let startnumberTable = currentItem;
        let endNumberTable = currentItem + numberPerTable;
        let itemsLength = items.length;
        let nrOfTables = numberOfTables;
        let totalNumberOfLeeches = numberPerTable * nrOfTables;
        let meanings = "";
        let readings = "";
        nbItems = items.length;
        let activeTable = quiz.settings.active_ipreset;



        //make sure we don't create empty tables if there are too few leeches
        if(items.length == 0){ //if no leeches
            nrOfTables = 0;
            sectionContents += `<div class="emptyMessage"><p><b>No items match your criteria. You may try relaxing them if possible.</b></p></div>`;
        } else if(items.length < totalNumberOfLeeches) { //if less leeches available then user requested
            var ratio = items.length / (numberPerTable*3);
            if(ratio <= 0.34){
                nrOfTables = 1;
            } else if(ratio <= 0.67){
                nrOfTables = 2;
            }
        } else if (numberPerTable >= totalNumberOfLeeches){ //if table capacity greater than user's requested amount of leeches
            nrOfTables = 1;
        }


        //Create leech tables
        for (var i = 0; i < nrOfTables; i++){
            //In case there are less than the requested amount of leeches
            if(items.length <= endNumberTable){
                endNumberTable = items.length;
                nrOfTables = i - 1;
            }
            sectionContents += `
                <div class="span4" style="width=290px;">
                    <section class="kotoba-table-list dashboard-sub-section" style="position: relative;">
                        <h3 class="small-caps">Items ${startnumberTable+1}-${endNumberTable} of ${itemsLength}</h3>
                            <table>
                                <tbody>`;
            for (var j = startnumberTable; j < endNumberTable; j++){
                let tableEntry = makeTableEntry(items[j], activeTable);
                let tooltip = makeTooltips(items[j], activeTable);
                sectionContents += `<tr class="${items[j].object}">` +
                                       '<td>' +
                                            `<div class="WkisTooltip"><a target="_blank" href="${items[j].data.document_url}"><span lang="ja">${itemsCharacterCallback(items[j].data)}</span></a>` +
                                               tooltip +
                                            '</div>' +
                                       '</td>' +
                                       '<td>' +
                                             '<div style="text-align: right"><a><span style="color: Gainsboro">'+tableEntry+'</span></a>' +
                                             '</div>' +
                                       '</td>' +
                                   '</tr>';
            }
            //preparing for next table
            startnumberTable += numberPerTable;
            endNumberTable += numberPerTable;

            sectionContents += '</tbody>' +
                           '</table>' +
                   '</section>' +
                '</div>';
        }

        /* build containers for the table elements */
        sectionContainer = `<section id="WkitTopBar" class="WkitTopBar"></section> `;
        topBlock = '<div id="WkitControlBar" class="WkitControlBar">' +
                      '<div class="WkitHeader">' +
                         '<button id="WkitBackwardButton" type="button" class="WkitButton">&#9668</button>' +
                         '<button id="WkitForwardButton" type="button" class="WkitButton">&#9658</button>' +
                         '<select id="WkitTableSelector" class="WkitSelector" title="Choose the table you want to display"></select>'+
                         '<p class="WkitTitle"><b>Wanikani Item Inspector</b></p>' +
                       '</div>' +
                   '</div>' +
                   '<div id="leech_table"><p>The table goes here</p></div>';


        //check if a leech table is already there, insert container and topBlock if not already there and then update the contents
        if(document.getElementById("leech_table")) {
             $('#leech_table').html(sectionContents);//replace existing table
        } else {
            // add containers since not there
            if ($('section.progression').length) {
                $('section.progression').after(sectionContainer);
            } else {
                $('section.srs-progress').after(sectionContainer);
            };
            // insert the top block - must be separate from the sectionContainer to work around a bug
            $('#WkitTopBar').append(topBlock);  // must be appended - someone else may be there due to the bug
            // update contents
            $('#leech_table').html(sectionContents);//replace existing list
         };

        // Populate the dropdown with the configured tables
        var tableList = '';
        var ipresets = quiz.settings.ipresets;
        for (var table of ipresets) {
            tableList += '<option>' + table.name.replace(/</g,'&lt;').replace(/>/g,'&gt;') +'</option>'
        };
        var dropdown = $('#WkitTableSelector');
        dropdown.html(tableList);
        $('#WkitTableSelector').prop('selectedIndex',activeTable);
    }

    function eventHandlers() {
        /* Define the button actions. Must be done when the DOC is completed */
        $("#WkitBackwardButton").click(clickedBackward);
        $("#WkitForwardButton").click(clickedForward);
        $('#WkitTableSelector').change(selectTable);

    };
/*
    function loadSettings() {
        wkof.Settings.load('Item_Inspector')
            .then(function(){
                     quiz_setup_state = 'ready';
                     var defaults = {
                     };
                     var settings = $.extend(true, {}, defaults, wkof.settings.Item_Inspector);
                     wkof.settings.Item_Inspector = quiz.settings = settings;
                  })
    }
*/
    //------------------------------------------
    // Starting the program
    // at the end to ensure the global variables are defined
    //------------------------------------------
    wkof.include('ItemData, Menu, Settings');
	wkof.ready('ItemData, Menu, Settings')
        .then(install_css)
        .then(install_menu)
        .then(init_settings)
        .then(setup_quiz_settings)
        .then(function(){return wkof.Settings.load('Item_Inspector')})
        .then(function(){
                     quiz_setup_state = 'ready';
                     var defaults = {};
                     var settings = $.extend(true, {}, defaults, wkof.settings.Item_Inspector);
                     wkof.settings.Item_Inspector = quiz.settings = settings;
                     if (settings.table_data) {refresh_table_presets(settings.active_ipreset)};
              })
        .then(fetch_items)
        .then(function(){
                     initCurrentItem();
                     updatePage()
                    })
        .then(eventHandlers);


})();