WaniKani Item Inspector

Inspect Items in Tabular Format

当前为 2020-09-12 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          WaniKani Item Inspector
// @namespace     wk-dashboard-item-inspector
// @description   Inspect Items in Tabular Format
// @author        prouleau
// @version       1.4.0
// @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;
         }

        .WkitClipBoard {
              display: inline-block;
              width: 96%;
              height: 420px;
              margin-left: 29px;
         }

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

        .WkitTitle {
               font-size: 150%;
               padding-right: 320px;
               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-right: 5px;
               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;

        let tableElementContents = {'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                   'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                   'Meaning_Correct_Answers': 'Meaning Correct Answers', 'Meaning_Incorrect_Answers': 'Meaning Incorrect Answers',
                                   'Reading_Correct_Answers': 'Reading Correct Answers', 'Reading_Incorrect_Answers': 'Reading Incorrect Answers',
                                   'Total_Correct_Answers': 'Total Correct Answers','Total_Incorrect_Answers': 'Total Incorrect Answers',
                                   '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',
                                   'Meaning_Max_Streak' : 'Meaning Maximum Streak', 'Reading_Max_Streak': 'Reading Maximum Streak',
                                   'Level':'Level',
                                   'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                   'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Resurrected_Date': 'Resurrected Date',
                                   'Lesson_Date':'Lesson Date', 'Unlock_Date': 'Unlock Date'};
        let dataElementContents = {'None':'None', 'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                   'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                   'Meaning_Correct_Answers': 'Meaning Correct Answers', 'Reading_Correct_Answers': 'Reading Correct Answers',
                                   'Meaning_Incorrect_Answers': 'Meaning Incorrect Answers', 'Reading_Incorrect_Answers': 'Reading Incorrect Answers',
                                   'Total_Correct_Answers': 'Total Correct Answers','Total_Incorrect_Answers': 'Total Incorrect Answers',
                                   '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',
                                   'Meaning_Max_Streak' : 'Meaning Maximum Streak', 'Reading_Max_Streak': 'Reading Maximum Streak',
                                   'Level':'Level',
                                   'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                   'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Resurrected_Date': 'Resurrected Date',
                                   'Lesson_Date':'Lesson Date', 'Unlock_Date': 'Unlock Date'};
        let sortElementContents = {'Default':'Default', 'Meaning_Brief':'Meaning Brief', 'Meaning_Full':'Meaning Full',
                                   'Reading_Brief':'Reading Brief', 'Reading_Full':'Reading Full', 'Leech':'Leach Value',
                                   'Meaning_Correct_Answers': 'Meaning Correct Answers', 'Reading_Correct_Answers': 'Reading Correct Answers',
                                   'Meaning_Incorrect_Answers': 'Meaning Incorrect Answers', 'Reading_Incorrect_Answers': 'Reading Incorrect Answers',
                                   'Total_Correct_Answers': 'Total Correct Answers','Total_Incorrect_Answers': 'Total Incorrect Answers',
                                   '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',
                                   'Meaning_Max_Streak' : 'Meaning Maximum Streak', 'Reading_Max_Streak': 'Reading Maximum Streak',
                                   'Level':'Level',
                                   'Srs':'SRS Stage', 'Review_Date':'Review Date', 'Review_Wait':'Review Wait Time',
                                   'Passed_Date':'Passed Guru Date', 'Burned_Date':'Burned Date', 'Resurrected_Date': 'Resurrected Date',
                                   'Lesson_Date':'Lesson Date', 'Unlock_Date': 'Unlock Date'};

        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',}
                                  },
                    position: {type: 'dropdown', label: 'Position', default: 2, hover_tip: 'Where on the dashboard to install Item Inspector',
                               content: {0: "Top", 1: "Below forecast", 2: "Below SRS", 3: "Below panels", 4: "Bottom"},
                               },
                    sect_tbl_cnts:{type:'section',label:'Word Export'},
                    noLatin: {type: 'checkbox', label:'No Latin Characters', default: false, hover_tip:'Radicals with latin characters not exported if set',},
                    oneItemPerLine: {type: 'checkbox', label:'One item per line', default: false, hover_tip: 'One item per line if set\nAll items in one paragraph otherwise',},
                }},
                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:tableElementContents,
                                              },
                                  sort1: {type:'dropdown',label:'Primary Sort Criterion',hover_tip:'Items will be sorted by this criterion.',
                                               on_change:update_table_presets,
                                               content:sortElementContents,
                                              },
                                  sortOrder1: {type:'dropdown',label:'Primary Sort Order',hover_tip:'Items will be sorted in this order.',
                                               on_change:update_table_presets,
                                               content:{'Default': 'Default', 'Ascending': 'Ascending', 'Descending': 'Descending',},
                                              },
                                  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:sortElementContents,
                                              },
                                  sortOrder2: {type:'dropdown',label:'Secondary Sort Order',hover_tip:'Items will be sorted in this order when the primary criterion is of equal values.',
                                               on_change:update_table_presets,
                                               content:{'Default': 'Default', 'Ascending': 'Ascending', 'Descending': 'Descending',},
                                              },
                                  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:dataElementContents,
                                            },
                                  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:dataElementContents,
                                            },
                                  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:dataElementContents,
                                            },
                                  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:dataElementContents,
                                            },
                                  tooltip5: {type:'dropdown',label:'Fifth Tooltip Element',hover_tip:'The fifth line of  data that will be displayed on the tooltip.',
                                              on_change:update_table_presets,
                                              content:dataElementContents,
                                            },
                                  tooltip6: {type:'dropdown',label:'Sixth Tooltip Element',hover_tip:'The sixth line of  data that will be displayed on the tooltip.',
                                              on_change:update_table_presets,
                                              content:dataElementContents,
                                            },
                                  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()
    //------------------------------------------------------------------------
    var old_position;
    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);
        old_position = quiz.settings.position;
        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();
                     populateDropdown();
                     if (old_position != quiz.settings.position){
                         if (document.getElementById("WkitTopBar")){
                             $('#WkitTopBar').empty();
                             $('#WkitTopBar').remove();
                         };
                         insertContainer();
                         eventHandlers();
                     };
                     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",sortOrder1:'Default',sort2:"Default",sortOrder2:'Default',
                                                        tooltip1:"Meaning_Full",tooltip2:"Reading_Full",tooltip3:"None",tooltip4:"None",tooltip5:"None",
                                                        tooltip6:"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",sortOrder1:'Default',sort2:"Default",sortOrder2:'Default',
                                                                    tooltip1:"Meaning_Full",tooltip2:"Reading_Full",tooltip3:"None",tooltip4:"None",tooltip5:"None",
                                                                    tooltip6:"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",sortOrder1:'Default',sort2:"Default",sortOrder2:'Default',
                                                                   tooltip1:"Review_Date",tooltip2:"Review_Wait",tooltip3:"None",tooltip4:"None",tooltip5:"None",
                                                                   tooltip6:"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",sortOrder1:'Default',sort2:"Default",sortOrder2:'Default',
                                                                    tooltip1:"Review_Date",tooltip2:"Review_Wait",tooltip3:"None",tooltip4:"None",tooltip5:"None",
                                                                    tooltip6:"None",randomSelection:0,leechStreakLimit:0}}},
            ];
        table_defaults = {currentItem:0,table_data:"Leech",sort1:"Default",sortOrder1:'Default',sort2:"Default",sortOrder2:'Default',
                          tooltip1:"Meaning_Full",tooltip2:"Reading_Full",tooltip3:"None",tooltip4:"None",tooltip5:"None",tooltip6:"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 add the settings that were added in new versions
        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].sortOrder1 == undefined){tablePresets[i].sortOrder1 = 'Default'};
            if (tablePresets[i].sort2 == undefined){tablePresets[i].sort2 = 'Default'};
            if (tablePresets[i].sortOrder2 == undefined){tablePresets[i].sortOrder2 = 'Default'};
            if (tablePresets[i].tooltip4 == undefined){tablePresets[i].tooltip4 = 'None'};
            if (tablePresets[i].tooltip5 == undefined){tablePresets[i].tooltip5 = 'None'};
            if (tablePresets[i].tooltip6 == undefined){tablePresets[i].tooltip6 = '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.sortOrder1 = presets.sortOrder1;
        quiz.settings.sort2 = presets.sort2;
        quiz.settings.sortOrder2 = presets.sortOrder2;
        quiz.settings.tooltip1 = presets.tooltip1;
        quiz.settings.tooltip2 = presets.tooltip2;
        quiz.settings.tooltip3 = presets.tooltip3;
        quiz.settings.tooltip4 = presets.tooltip4;
        quiz.settings.tooltip5 = presets.tooltip5;
        quiz.settings.tooltip6 = presets.tooltip6;
        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.sortOrder1 = presets.sortOrder1;
        quiz.settings.sort2 = presets.sort2;
        quiz.settings.sortOrder2 = presets.sortOrder2;
        quiz.settings.tooltip1 = presets.tooltip1;
        quiz.settings.tooltip2 = presets.tooltip2;
        quiz.settings.tooltip3 = presets.tooltip3;
        quiz.settings.tooltip4 = presets.tooltip4;
        quiz.settings.tooltip5 = presets.tooltip5;
        quiz.settings.tooltip6 = presets.tooltip6;
        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.sortOrder1 = quiz.settings.sortOrder1;
        presets.sort2 = quiz.settings.sort2;
        presets.sortOrder2 = quiz.settings.sortOrder2;
        presets.tooltip1 = quiz.settings.tooltip1;
        presets.tooltip2 = quiz.settings.tooltip2;
        presets.tooltip3 = quiz.settings.tooltip3;
        presets.tooltip4 = quiz.settings.tooltip4;
        presets.tooltip5 = quiz.settings.tooltip5;
        presets.tooltip6 = quiz.settings.tooltip6;
        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') && (settingName != 'sortOrder1') && (settingName != 'sortOrder2')) {
                     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',
                       },
                    'Total_Incorrect_Answers': {'exists': ((item) => {return true}), 'label': 'Total Incorrect: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect)}),
                                      'sortkey': ((item) => {return (item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect)}),
                                      'sortOrder': 'Descending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Total_Correct_Answers': {'exists': ((item) => {return true}), 'label': 'Total Correct: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.meaning_correct + item.review_statistics.reading_correct)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.meaning_correct + item.review_statistics.reading_correct)}),
                                      'sortkey': ((item) => {return (item.review_statistics.meaning_correct + item.review_statistics.reading_correct)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      '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_Incorrect_Answers': {'exists': ((item) => {return true}), 'label': 'Meaning Incorrect Answers: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.meaning_incorrect)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.meaning_incorrect)}),
                                      'sortkey': ((item) => {return (item.review_statistics.meaning_incorrect)}),
                                      'sortOrder': 'Descending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Meaning_Correct_Answers': {'exists': ((item) => {return true}), 'label': 'Meaning Correct Answers: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.meaning_correct)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.meaning_correct)}),
                                      'sortkey': ((item) => {return (item.review_statistics.meaning_correct)}),
                                      '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',
                       },
                    'Meaning_Max_Streak': {'exists': ((item) => {return true}), 'label': 'Meaning Max. Streak: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.meaning_max_streak - 1)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.meaning_max_streak -1)}),
                                      'sortkey': ((item) => {return (item.review_statistics.meaning_max_streak)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Reading_Incorrect_Answers': {'exists': ((item) => {return true}), 'label': 'Reading Incorrect: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.reading_incorrect)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.reading_incorrect)}),
                                      'sortkey': ((item) => {return (item.review_statistics.reading_incorrect)}),
                                      'sortOrder': 'Descending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                       },
                    'Reading_Correct_Answers': {'exists': ((item) => {return true}), 'label': 'Reading Correct: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.reading_correct)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.reading_correct)}),
                                      'sortkey': ((item) => {return (item.review_statistics.reading_correct)}),
                                      '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',
                       },
                     'Reading_Max_Streak': {'exists': ((item) => {return true}), 'label': 'Reading Max. Streak: ',
                                      'tableEntry': ((item) => {return (item.review_statistics.reading_max_streak - 1)}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics.reading_max_streak - 1)}),
                                      'sortkey': ((item) => {return (item.review_statistics.reading_max_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',
                       },
                    'Resurrected_Date': {'exists': ((item) => {return true}), 'label': 'Resurrected: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.resurrected_at != undefined ? makeResurrectedDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.resurrected_at != undefined ? makeResurrectedDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.resurrected_at != undefined ? trimDate(Date.parse(item.assignments.resurrected_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',
                       },
                     'Unlock_Date': {'exists': ((item) => {return true}), 'label': 'Unlock: ',
                                      'tableEntry': ((item) => {return (item.assignments ? (item.assignments.unlocked_at != undefined ? makeUnlockDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments ? (item.assignments.unlocked_at != undefined ? makeUnlockDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments ? (item.assignments.unlocked_at ? trimDate(Date.parse(item.assignments.unlocked_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 makeResurrectedDate(item){
    	return formatDate(new Date(item.assignments.resurrected_at), false, /* is_next_date */);
    }

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

    function makeUnlockDate(item){
    	return formatDate(new Date(item.assignments.unlocked_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){
        // 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 settings = quiz.settings.tablePresets[quiz.settings.active_ipreset];
        let sort1 = settings.sort1;
        let sort2 = settings.sort2;
        let sortKey, sortKey2;
        let sortOrder = settings.sortOrder1;
        let sortOrder2 = settings.sortOrder2;
        if ((sort1 == "Default") && (sort2 == 'Default')){
            sortKey = metadata[tableKey].sortkey;
            if (sortOrder === 'Default'){sortOrder = metadata[tableKey].sortOrder};
            sortKey2 = metadata[tableKey].sortkey2;
            if (sortOrder2 === 'Default'){sortOrder2 = metadata[tableKey].sortOrder2};
        } else if ((sort1 == "Default") && (sort2 != "Default")){
            sortKey = metadata[tableKey].sortkey;
            if (sortOrder === 'Default'){sortOrder = metadata[tableKey].sortOrder};
            sortKey2 = metadata[sort2].sortkey;
            if (sortOrder2 === 'Default'){sortOrder2 = metadata[sort2].sortOrder};
        } else if ((sort1 != "Default") && (sort2 == "Default")){
            sortKey = metadata[sort1].sortkey;
            if (sortOrder === 'Default'){sortOrder = metadata[sort1].sortOrder};
            sortKey2 = metadata[sort1].sortkey2;
            if (sortOrder2 === 'Default'){sortOrder2 = metadata[sort1].sortOrder2};
        } else {
            sortKey = metadata[sort1].sortkey;
            if (sortOrder === 'Default'){sortOrder = metadata[sort1].sortOrder};
            sortKey2 = metadata[sort2].sortkey;
            if (sortOrder === 'Default'){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 makeTooltipEntry(item, tableName){
        let html = '';
        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>'
                };
        };
        return html;
    };

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

        html += makeTooltipEntry(item, presets.tooltip1);
        html += makeTooltipEntry(item, presets.tooltip2);
        html += makeTooltipEntry(item, presets.tooltip3);
        html += makeTooltipEntry(item, presets.tooltip4);
        html += makeTooltipEntry(item, presets.tooltip5);
        html += makeTooltipEntry(item, presets.tooltip6);

        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>';
        }

        //check if a leech table is already there, insert container and topBlock if not already there and then update the contents
        $('#leech_table').html(sectionContents);//replace existing table
        // 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 fillClipboard(){
        let textarea = '<textarea id="WkitTextArea" class="WkitClipBoard">';
        $('#leech_table').html(textarea);//replace existing table
        let items = quiz.items;
        let text = '';
        let noLatin = quiz.settings.noLatin;
        if (noLatin === undefined){noLatin = false; quiz.settings.noLatin = false}
        let oneItemPerLine = quiz.settings.oneItemPerLine;
        if (oneItemPerLine === undefined){oneItemPerLine = false; quiz.settings.oneItemPerLine = false}

        for (var i = 0; i < items.length; i++){
            let itemsData = items[i].data;
            if(itemsData.characters!= null) {
                text += itemsData.characters+' ';
                if (oneItemPerLine){text += '\n'};
            } else if (!noLatin){
                text += itemsData.slug+' ';
                if (oneItemPerLine){text += '\n'};
            };
        };

        $('#WkitTextArea').val(text);
        $('#WkitTextArea').select();
        document.execCommand('copy');
        alert('The items have been copied in the clipboard.');

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

    function insertContainer(){
        /* build containers for the table elements */
        let sectionContainer = `<section id="WkitTopBar" class="WkitTopBar"></section> `;
        let 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>'+
                         '<button id="WkitWordExport" type="button" class="WkitButton" title="Place table items in clipboard\nPaste in your software of choice">&#9729;</button>' +
                         '<p class="WkitTitle"><b>Wanikani Item Inspector</b></p>' +
                       '</div>' +
                   '</div>' +
                   '<div id="leech_table"><p>The table goes here</p></div>';

        if (quiz.settings.position === undefined) {quiz.settings.position = 2};
        let position = [".progress-and-forecast", '.progress-and-forecast', '.srs-progress',  '.span12 .row', '.span12 .row:last-of-type',][quiz.settings.position];
        if (quiz.settings.position == 0){
           $(position).before(sectionContainer);
        } else {
           $(position).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
        populateDropdown();
    };

    function populateDropdown(){
        // Populate the dropdown with the configured tables
        let activeTable = quiz.settings.active_ipreset;
        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);
        $("#WkitWordExport").click(fillClipboard);

    };

    //------------------------------------------
    // 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();
                     insertContainer();
                     updatePage()
                    })
        .then(eventHandlers);


})();