WaniKani Item Inspector

Inspect Items in Tabular Format

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

您需要先安装一个扩展,例如 篡改猴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.6.1
// @include       https://www.wanikani.com/dashboard
// @include       https://www.wanikani.com/
// @grant         none
// ==/UserScript==

(function() {
    'use strict';
    //------------------------------
	// Wanikani Framework
	//------------------------------
	if (!window.wkof) {
        var script_name = 'Wanikani Item Inspector';
        var response = confirm(script_name + ' 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 scriptId = 'Item_Inspector';
    var scriptTitle = 'Item Inspector';

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


    //------------------------------
	// Styling
	//------------------------------
    function table_css(){
        let is_dark = is_dark_theme();
        var leechTableCss = `
            /* Control Bar */

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

            /* to prevent rendering problems */
            #WkitTopBar {
                  color: #333;
                  display: inline-block;
                  width: 103.2%;
                  margin-left: -29px;
             }

            #WkitTopBar .WkitSmallCaps {
                  font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
                  text-rendering: optimizelegibility;
                  margin-block-start: 1em;
                  margin-block-end: 1em;
                  margin-inline-start: 0px;
                  margin-inline-end: 0px;
                  margin: 0;
                  padding: 7.5px 30px;
                  background-color: #d5d5d5;
                  text-align: center;
                  border-radius: 5px 5px 0 0;
                  font-size: 11px;
                  font-weight: bold;
                  letter-spacing: 0;
                  line-height: 20px;
                  color: #555;
                  text-transform: uppercase;
                  text-shadow: 0 1px 0 #fff;
                  ${is_dark ? 'align-items: center;' : ''}
                  ${is_dark ? 'background-color: #1c1e21;' : ''}
                  ${is_dark ? 'box-shadow: 0 1px 1px rgba(0, 0, 0, 0.7), 0 2px 2px rgba(0, 0, 0, 0.7);' : ''}
                  ${is_dark ? 'color: #bcbcbc;' : ''}
                  ${is_dark ? 'display: flex;' : ''}
                  ${is_dark ? 'margin-bottom:;' : ''}
                  ${is_dark ? 'max-height: 60px;' : ''}
                  ${is_dark ? 'padding-bottom: 3px;' : ''}
                  ${is_dark ? 'padding-top: 3px;' : ''}
                  ${is_dark ? 'text-align: left;' : ''}
            }

            #WkitTopBar .WkitTableList {
                  position: relative;
                  margin: 0 0 30px;
            }

            #WkitTopBar .WkitTableList table{
                  width: 100%;
                  max-width: 100%;
                  background-color: transparent;
                  border-collapse: collapse;
                  border-spacing: 0;
            }

            #WkitTopBar .WkitTableList table{
                  width: 100%;
                  line-height: 1em;
                  color: #fff;
            }

            #WkitTopBar .WkitTableList table a {
                  display: block;
                  padding: 0.7em 1em;
                  color: #fff;
                  text-decoration: none;
                  -webkit-transition: text-shadow ease-out 0.3s;
                  -moz-transition: text-shadow ease-out 0.3s;
                  -o-transition: text-shadow ease-out 0.3s;
                  transition: text-shadow ease-out 0.3s;
            }

            #WkitTopBar .WkitTableList table tr{
                  border-top: 0;
                  border-bottom: 0;
                  border-left: 0;
                  text-shadow: 0 1px 0 rgba(0,0,0,0.2);
            }

            #WkitTopBar .WkitTableList table td{
                  -webkit-box-sizing: border-box;
                  -moz-box-sizing: border-box;
                  box-sizing: border-box;
            }

            #WkitTopBar .WkitTableList table tr[class=vocabulary]{
                  background-color: #a100f1;
                  background-image: -moz-linear-gradient(top, #a0f, #9300dd);
                  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#a0f), to(#9300dd));
                  background-image: -webkit-linear-gradient(top, #a0f, #9300dd);
                  background-image: -o-linear-gradient(top, #a0f, #9300dd);
                  background-image: linear-gradient(to bottom, #a0f, #9300dd);
                  background-repeat: repeat-x;
                  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFAA00FF', endColorstr='#FF9300DD', GradientType=0);
                  border-top: 1px solid #c655ff;
                  border-bottom: 1px solid #8800cc;
                  border-left: 1px solid #c655ff;        }

            #WkitTopBar .WkitTableList table tr[class=kanji]{
                  background-color: #f100a1;
                  background-image: -moz-linear-gradient(top, #f0a, #dd0093);
                  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f0a), to(#dd0093));
                  background-image: -webkit-linear-gradient(top, #f0a, #dd0093);
                  background-image: -o-linear-gradient(top, #f0a, #dd0093);
                  background-image: linear-gradient(to bottom, #f0a, #dd0093);
                  background-repeat: repeat-x;
                  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFF00AA', endColorstr='#FFDD0093', GradientType=0);
                  border-top: 1px solid #f6c;
                  border-bottom: 1px solid #cc0088;
                  border-left: 1px solid #f6c;
            }

            #WkitTopBar .WkitTableList table tr[class=radical]{
                  background-color: #00a1f1;
                  background-image: -moz-linear-gradient(top, #0af, #0093dd);
                  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0af), to(#0093dd));
                  background-image: -webkit-linear-gradient(top, #0af, #0093dd);
                  background-image: -o-linear-gradient(top, #0af, #0093dd);
                  background-image: linear-gradient(to bottom, #0af, #0093dd);
                  background-repeat: repeat-x;
                  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF00AAFF', endColorstr='#FF0093DD', GradientType=0);
                  border-top: 1px solid #88d7ff;
                  border-bottom: 1px solid #069;
                  border-left: 1px solid #88d7ff;
            }

            #WkitTopBar .WkitHeader {
                  background-color: ${is_dark ? '#232629' : 'LightGray'};
                  height: 40px;
                  border-radius: 5px;
             }

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

            #WkitTopBar .WkitButton {
                   display: inline;
                   float: left;
                   border-width: 1px;
                   border-radius: 3px;
                   width: 28px;
                   height: 28px;
                   margin: 3px;
                   margin-top: 6px
            }

           #WkitTopBar .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;
            }

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

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

            /* Pop ups, aka TOOL TIPS */

            #WkitTopBar .WkitTooltip {
                  position: relative;
                  display: inline-block;
            }

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

            #WkitTopBar .WkitTooltip:hover div.WkitTooltipContent {
                  visibility: visible;
                  display: inline-block;
                  z-index: 50;
            }

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

             #WkitTopBar .WkitTooltip2 {
                  position: relative;
                  display: inline-block;
                  width: 30px;
            }

            #WkitTopBar .WkitTooltip2 .WkitEnlargedTooltip {
                  display: none;
                  visibility: hidden;
                  background-color: black;
                  color: #fff;
                  font-size: 100%;
                  width: max-content;
                  border-radius: 3px;
                  position: absolute;
                  bottom: 30px;
                  left: 0%;
                  padding: 2px;
                  z-index: 1;
            }

            #WkitTopBar .WkitTooltip2:hover div.WkitEnlargedTooltip {
                  visibility: visible;
                  display: inline-block;
                  z-index: 50;
            }

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

            #WkitTopBar .WkitTooltip .radical {
                  background: #0096e7;
                  order: 0;
            }

            #WkitTopBar .WkitTooltip .kanji {
                  background: #ff00aa;
                  order: 1;
            }

            #WkitTopBar .WkitTooltip .vocabulary {
                  background: #9800e8;
                  order: 2;
            }

            #WkitTopBar .WkitTooltip2 .radical {
                  padding: 5px;
                  background: #0096e7;
                  order: 0;
             }

            #WkitTopBar .WkitTooltip2 .kanji {
                  padding: 5px;
                  background: #ff00aa;
                  order: 1;
            }

            #WkitTopBar .WkitTooltip2 .vocabulary {
                  padding: 5px;
                  background: #9800e8;
                  order: 2;
            }

            #WkitTopBar .WkitTooltip2 img{
                 width: 80px;
                 height: 80px;
                 align-items: center;
             }

           #WkitTopBar .radical img {
                 filter: invert(1);
           }

           #WkitTopBar .left p {
                  font-size: 35px;
                  line-height: 40px;
                  display: block;
                  align-items: center;
                  text-align: center;
                  border-radius: 3px;
                  margin: 5px;
                  padding: 5px;
           }

           #WkitTopBar .WkitEnlargedTooltip span {
                  font-size: 60px;
                  line-height: 58px;
                  display: block;
                  border-radius: 3px;
                  margin: 5px;
           }

           #WkitTopBar .right  {
                  padding-left: 7px;
                  padding-right: 7px;
                  padding-bottom: 7px;
           }

           #WkitTopBar .right table td:first-child {
                  padding-right: 10px;
                  font-weight: bold;
           }

           #WkitTopBar .right table td {
                  padding-top: 3px;
           }

           #WkitTopBar .WkitLabel, {
                  padding: 4px;
           }`

        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: scriptId,
            name: scriptId,
            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';
    var exportedInfo = ['export1','export2','export3','export4','export5','export6','export7','export8','export9','export10',
                        'export11','export12','export13','export14','export15','export16','export17','export18','export19','export20',
                        'export21','export22','export23','export24','export25','export26','export27','export28','export29',];
    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'};
        let wordCloudContents = {'No Repeat': 'Don\'t Repeat', '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',
                                   };

        let exportElementContents = {'None': 'Not Exported', 'Item':'Item', 'Type':'Item Type (Rad, Kan, Voc)', 'Export_Date': 'Export Date',
                                   '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',
                                   '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: scriptId,
            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"},
                               },
                    numberOfLines: {type:'dropdown',label:'Number of Lines',hover_tip:'The number of lines that will be displayed in a table.', default:11,
                                    content:{8:"8", 9:"9", 10:"10", 11:"11", 12:"12", 13:"13", 14:"14", 15:"15",},
                                   },
                sect_tbl_cnts:{type:'section',label:'Items Export to Clipboard'},
                    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',},
                    exportLimit: {type: 'number', label:'Export Limit', default: 0, hover_tip: 'Maximum number of items exported\n0 means no limit',},
                    repeatWordCloud: {type: 'dropdown', label:'Repeat for Word Cloud', default: 'No Repeat', hover_tip: 'Repeat the items according this number',
                                     content:wordCloudContents,},
                }},
                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 Popups'},
                                  tooltip1: {type:'dropdown',label:'First Popup Element',hover_tip:'The first line of data that will be displayed on the popup.',
                                              on_change:update_table_presets, default:'None',
                                              content:dataElementContents,
                                            },
                                  tooltip2: {type:'dropdown',label:'Second Popup Element',hover_tip:'The second line of data that will be displayed on the popup.',
                                              on_change:update_table_presets, default:'None',
                                              content:dataElementContents,
                                            },
                                  tooltip3: {type:'dropdown',label:'Third Popup Element',hover_tip:'The third line of  data that will be displayed on the popup.',
                                              on_change:update_table_presets, default:'None',
                                              content:dataElementContents,
                                            },
                                  tooltip4: {type:'dropdown',label:'Fourth Popup Element',hover_tip:'The fourth line of  data that will be displayed on the popup.',
                                              on_change:update_table_presets, default:'None',
                                              content:dataElementContents,
                                            },
                                  tooltip5: {type:'dropdown',label:'Fifth Popup Element',hover_tip:'The fifth line of  data that will be displayed on the popup.',
                                              on_change:update_table_presets, default:'None',
                                              content:dataElementContents,
                                            },
                                  tooltip6: {type:'dropdown',label:'Sixth Popup Element',hover_tip:'The sixth line of  data that will be displayed on the popup.',
                                              on_change:update_table_presets, default:'None',
                                              content:dataElementContents,
                                            },
                                  sect_tbl_settings:{type:'section',label:'Other Settings'},
                                  enlargingTooltip: {type: 'checkbox', label:'Popup for Enlarged Items', hover_tip:'Adds a popup at the right of the item to show an enlarged version of the item',
                                                     on_change:update_table_presets, default: false, },
                                  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
                                                   },
                             },
                        },
                            table_export:{type:'page',label:'Export',hover_tip:'Define the exported items of your table',content:{
                                  sect_tbl_export:{type:'section',label:'Exported Columns'},
                                  includeTitle: {type: 'checkbox', label:'Include a title line', default: false, hover_tip: 'Adds a title line before the exported items',
                                                 on_change:update_table_presets, },
                                  export1: {type:'dropdown',label:'Exported Column no 1',hover_tip:'The 1st column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export2: {type:'dropdown',label:'Exported Column no 2',hover_tip:'The 2nd column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export3: {type:'dropdown',label:'Exported Column no 3',hover_tip:'The 3rd column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export4: {type:'dropdown',label:'Exported Column no 4',hover_tip:'The 4th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export5: {type:'dropdown',label:'Exported Column no 5',hover_tip:'The 5th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export6: {type:'dropdown',label:'Exported Column no 6',hover_tip:'The 6th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export7: {type:'dropdown',label:'Exported Column no 7',hover_tip:'The 7th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export8: {type:'dropdown',label:'Exported Column no 8',hover_tip:'The 8th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export9: {type:'dropdown',label:'Exported Column no 9',hover_tip:'The 9th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export10: {type:'dropdown',label:'Exported Column no 10',hover_tip:'The 10th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export11: {type:'dropdown',label:'Exported Column no 11',hover_tip:'The 11th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export12: {type:'dropdown',label:'Exported Column no 12',hover_tip:'The 12th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export13: {type:'dropdown',label:'Exported Column no 13',hover_tip:'The 13th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export14: {type:'dropdown',label:'Exported Column no 14',hover_tip:'The 14th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export15: {type:'dropdown',label:'Exported Column no 15',hover_tip:'The 15th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export16: {type:'dropdown',label:'Exported Column no 16',hover_tip:'The 16th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export17: {type:'dropdown',label:'Exported Column no 17',hover_tip:'The 17th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export18: {type:'dropdown',label:'Exported Column no 18',hover_tip:'The 18th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export19: {type:'dropdown',label:'Exported Column no 19',hover_tip:'The 19th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export20: {type:'dropdown',label:'Exported Column no 20',hover_tip:'The 20th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export21: {type:'dropdown',label:'Exported Column no 21',hover_tip:'The 21th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export22: {type:'dropdown',label:'Exported Column no 22',hover_tip:'The 22th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export23: {type:'dropdown',label:'Exported Column no 23',hover_tip:'The 23th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export24: {type:'dropdown',label:'Exported Column no 24',hover_tip:'The 24th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export25: {type:'dropdown',label:'Exported Column no 25',hover_tip:'The 25th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export26: {type:'dropdown',label:'Exported Column no 26',hover_tip:'The 26th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export27: {type:'dropdown',label:'Exported Column no 27',hover_tip:'The 27th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export28: {type:'dropdown',label:'Exported Column no 28',hover_tip:'The 28th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                                  export29: {type:'dropdown',label:'Exported Column no 29',hover_tip:'The 29th column of exported information', default:'None',
                                              on_change:update_table_presets, content:exportElementContents,
                                            },
                              },
                        }},
                    }},
                }},
            },
        }};

        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(scriptId)
            .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('table')
            .then(function(){
                     initCurrentItem();
                     setNumberOfLines();
                     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",enlargingTooltip:true,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",enlargingTooltip:true,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",enlargingTooltip:false,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",enlargingTooltip:false,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",
                          enlargingTooltip:false,randomSelection:0,leechStreakLimit:0,};
        for (var i = 0; i < exportedInfo.length; i++){
            table_defaults[exportedInfo[i]] = 'None';
        };
        // 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].enlargingTooltip == undefined){tablePresets[i].enlargingTooltip = false};
            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};

        };
        if (quiz.settings.repeatWordCloud === "Don\'t Reteat"){quiz.settings.repeatWordCloud = 'No Repeat'}; //fixing the consequence of a bug

        // 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.enlargingTooltip = presets.enlargingTooltip;
        quiz.settings.randomSelection = presets.randomSelection;
        quiz.settings.leechStreakLimit = presets.leechStreakLimit;
        quiz.settings.includeTitle = presets.includeTitle;
        exportedInfo.forEach(function (exp){quiz.settings[exp] = presets[exp]});
    }

    //========================================================================
    // 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.enlargingTooltip = presets.enlargingTooltip;
        quiz.settings.randomSelection = presets.randomSelection;
        quiz.settings.leechStreakLimit = presets.leechStreakLimit;
        quiz.settings.includeTitle = presets.includeTitle;
        exportedInfo.forEach(function (exp){quiz.settings[exp] = presets[exp]});
    }

    //========================================================================
    // 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.enlargingTooltip = quiz.settings.enlargingTooltip;
        presets.randomSelection = quiz.settings.randomSelection;
        presets.leechStreakLimit = quiz.settings.leechStreakLimit;
        presets.includeTitle = quiz.settings.includeTitle;
        exportedInfo.forEach(function (exp){presets[exp] = quiz.settings[exp]});
    }

    //########################################################################
    // 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(required) {
        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 ((setting != 'None') && (setting != 'Default')) {
                     if (metadata[setting] != undefined){
                         if (required === 'export'){
                             src_cfg.options[metadata[setting].endPoint] = true;
                         } else if (!exportedInfo.includes(settingName)){
                             src_cfg.options[metadata[setting].endPoint] = true;
                         };
                     };
            };
        }
        if (presets.leechStreakLimit != 0) {src_cfg.options.review_statistics = true};
        if (required === 'wordCloud') {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': 'Meaning ',
                                      'tableEntry': meaningsBrief,
                                      'tooltipEntry': meaningsBrief,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                                      'title': 'Meaning',
                                      'export': ((item)=>('"'+meaningsBrief(item)+'"'))
                       },
                    'Meaning_Full': {'exists': ((item) => {return true}), 'label': 'Meaning ',
                                      'tableEntry': meaningsFullTable,
                                      'tooltipEntry': meaningsFull,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                                      'title': 'Meaning',
                                      'export': ((item)=>('"'+meaningsFull(item)+'"'))
                       },
                    'Reading_Brief': {'exists': ((item) => {return item.data.readings}), 'label': 'Reading ',
                                      'tableEntry': readingsBrief,
                                      'tooltipEntry': readingsBrief,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                                      'title': 'Reading',
                                      'export': ((item)=>('"'+readingsBrief(item)+'"'))
                       },
                    'Reading_Full': {'exists': ((item) => {return item.data.readings}), 'label': 'Reading ',
                                      'tableEntry': readingsFull,
                                      'tooltipEntry': readingsFull,
                                      'sortkey': ((item) => {return 0}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return 0}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                                      'title': 'Reading',
                                      'export': ((item)=>('"'+readingsFull(item)+'"'))
                       },
                    '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',
                                      'wordCloud': ((item) => {return Math.round(leechScore(item))}),
                                      'title': 'Leech',
                                      'export': ((item)=>'"'+leechScore(item).toLocaleString()+'"'),
                       },
                    'Total_Incorrect_Answers': {'exists': ((item) => {return true}), 'label': 'Total&nbsp;Incor. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'sortOrder': 'Descending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'title': '"Total Incor."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect : 'unavailable')})
                       },
                    'Total_Correct_Answers': {'exists': ((item) => {return true}), 'label': 'Total&nbsp;Cor. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct + item.review_statistics.reading_correct : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct + item.review_statistics.reading_correct : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct + item.review_statistics.reading_correct : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct + item.review_statistics.reading_correct : 'unavailable')}),
                                      'title': '"Total Cor."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct + item.review_statistics.reading_correct : 'unavailable')})
                       },
                    'Percentage_Correct': {'exists': ((item) => {return true}), 'label': '%Total ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct+'%' : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct+'%' : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct : 101)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct : 0)}),
                                      'title': '%Total',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.percentage_correct+'%' : 'unavailable')})
                       },
                    'Meaning_Correct': {'exists': ((item) => {return true}), 'label': '%Meaning ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? MeaningCorrect(item)+'%' : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? MeaningCorrect(item)+'%' : 'None')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? MeaningCorrect(item) : 101)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'title': '%Meaning',
                                      'export': ((item) => {return (item.review_statistics ? MeaningCorrect(item)+'%' : 'unavailable')})
                       },
                    'Reading_Correct': {'exists': ((item) => {return item.data.readings}), 'label': '%Reading ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? ReadingCorrect(item)+'%' : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? ReadingCorrect(item)+'%' : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? ReadingCorrect(item) : 101)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? ReadingCorrect(item) : 0)}),
                                      'title': '%Reading',
                                      'export': ((item) => {return (item.review_statistics ? ReadingCorrect(item)+'%' : 'unavailable')})
                        },
                    'Meaning_Incorrect_Answers': {'exists': ((item) => {return true}), 'label': 'Meaning&nbsp;Incor. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect : 'unavailable')}),
                                      'sortOrder': 'Descending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect : 'unavailable')}),
                                      'title': '"Meaning Incor."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_incorrect : 'unavailable')})
                       },
                    'Meaning_Correct_Answers': {'exists': ((item) => {return true}), 'label': 'Meaning&nbsp;Cor. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct : 'unavailable')}),
                                      'title': '"Meaning Cor."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_correct : 'unavailable')})
                       },
                    'Meaning_Current_Streak': {'exists': ((item) => {return true}), 'label': 'Meaning&nbsp;Cur.&nbsp;Str. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_current_streak - 1 : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.meaning_current_streak -1 : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_current_streak : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_current_streak - 1 : 'unavailable')}),
                                      'title': '"Meaning Cur. Str."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_current_streak - 1 : 'unavailable')})
                       },
                    'Meaning_Max_Streak': {'exists': ((item) => {return true}), 'label': 'Meaning&nbsp;Max.&nbsp;Str. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_max_streak - 1 : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.meaning_max_streak -1 : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_max_streak : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_max_streak - 1 : 'unavailable')}),
                                      'title': '"Meaning Max. Str."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.meaning_max_streak - 1 : 'unavailable')})
                       },
                    'Reading_Incorrect_Answers': {'exists': ((item) => {return true}), 'label': 'Reading&nbsp;Incor. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'sortOrder': 'Descending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.reading_incorrect : 'unavailable')}),
                                      'title': '"Reading Incor."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.reading_incorrect : 'unavailable')})
                       },
                    'Reading_Correct_Answers': {'exists': ((item) => {return true}), 'label': 'Reading&nbsp;Cor. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.reading_correct : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.reading_correct : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.reading_correct : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.reading_correct : 'unavailable')}),
                                      'title': '"Reading Cor."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.reading_correct : 'unavailable')})
                       },
                     'Reading_Current_Streak': {'exists': ((item) => {return true}), 'label': 'Reading&nbsp;Cur.&nbsp;Str. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.reading_current_streak - 1 : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.reading_current_streak - 1 : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.reading_current_streak : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.reading_current_streak - 1 : 'unavailable')}),
                                      'title': '"Reading Cur. Str."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.reading_current_streak - 1 : 'unavailable')})
                       },
                     'Reading_Max_Streak': {'exists': ((item) => {return true}), 'label': 'Reading&nbsp;Max.&nbsp;Str. ',
                                      'tableEntry': ((item) => {return (item.review_statistics ? item.review_statistics.reading_max_streak - 1 : 'unavailable')}),
                                      'tooltipEntry':  ((item) => {return (item.review_statistics ? item.review_statistics.reading_max_streak - 1 : 'unavailable')}),
                                      'sortkey': ((item) => {return (item.review_statistics ? item.review_statistics.reading_max_streak : 'unavailable')}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': leechScore,
                                      'sortOrder2': 'Descending',
                                      'endPoint' : 'review_statistics',
                                      'wordCloud': ((item) => {return (item.review_statistics ? item.review_statistics.reading_max_streak - 1 : 'unavailable')}),
                                      'title': '"Reading Max. Str."',
                                      'export': ((item) => {return (item.review_statistics ? item.review_statistics.reading_max_streak - 1 : 'unavailable')})
                       },
                    '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',
                                      'title': 'Level',
                                      'export': ((item) => {return item.data.level})
                       },
                    'Srs': {'exists': ((item) => {return true}), 'label': 'SRS ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined ? srsName[item.assignments.srs_stage] : 'Locked') : 'Locked')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined ? srsName[item.assignments.srs_stage] : 'Locked') : 'Locked')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined ? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? Date.parse(item.assignments.available_at) : theFuture) : theFuture)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                                      'title': 'SRS',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined ? srsName[item.assignments.srs_stage] : 'Locked') : 'Locked')})
                       },
                    'Review_Date': {'exists': ((item) => {return true}), 'label': 'Review ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? makeDate(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? makeDate(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? trimDate(Date.parse(item.assignments.available_at)) : theFuture) : theFuture)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                                      'title': '"Review Date"',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? forExportDate(item.assignments.available_at) : 'Unscheduled') : 'Unscheduled')})
                       },
                    'Review_Wait': {'exists': ((item) => {return true}), 'label': 'Wait ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? reviewWait(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? reviewWait(item) : 'Unscheduled') : 'Unscheduled')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (item.assignments.available_at != undefined ? trimDate(Date.parse(item.assignments.available_at)) : theFuture) : theFuture)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                                      'title': 'Wait',
                                      'export': ((item) => 'None')
                       },
                    'Passed_Date': {'exists': ((item) => {return true}), 'label': 'Passed ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.passed_at != undefined ? makePassedDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.passed_at != undefined ? makePassedDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (item.assignments.passed_at != undefined ? trimDate(Date.parse(item.assignments.passed_at)) : 0) : 0)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return (item.assignments != undefined ? (item.assignments.srs_stage != undefined ? item.assignments.srs_stage : 10) : 10)}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                                      'title': '"Passed Guru"',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.passed_at != undefined ? forExportDate(item.assignments.passed_at) : '"Not yet"') : '"Not yet"')})
                        },
                    'Burned_Date': {'exists': ((item) => {return true}), 'label': 'Burned ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.burned_at != undefined ? makeBurnedDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.burned_at != undefined ? makeBurnedDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (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',
                                      'title': 'Burned',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.burned_at != undefined ? forExportDate(item.assignments.burned_at) : '"Not yet"') : '"Not yet"')})
                       },
                    'Resurrected_Date': {'exists': ((item) => {return true}), 'label': 'Resurrected ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.resurrected_at != undefined ? makeResurrectedDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.resurrected_at != undefined ? makeResurrectedDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (item.assignments.resurrected_at != undefined ? trimDate(Date.parse(item.assignments.resurrected_at)).toDateString() : 0) : 0)}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2': ((item) => {return item.data.level}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                                      'title': 'Resurrected',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.resurrected_at != undefined ? forExportDate(item.assignments.resurrected_at) : '"Not yet"') : '"Not yet"')})
                       },
                    'Lesson_Date': {'exists': ((item) => {return true}), 'label': 'Lesson ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.started_at != undefined ? makeLessonDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.started_at != undefined ? makeLessonDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (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',
                                      'title': 'Lesson',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.started_at != undefined ? forExportDate(item.assignments.started_at) : '"Not yet"') : '"Not yet"')})
                       },
                     'Unlock_Date': {'exists': ((item) => {return true}), 'label': 'Unlock ',
                                      'tableEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.unlocked_at != undefined ? makeUnlockDate(item) : 'Not yet') : 'Not yet')}),
                                      'tooltipEntry': ((item) => {return (item.assignments != undefined ? (item.assignments.unlocked_at != undefined ? makeUnlockDate(item) : 'Not yet') : 'Not yet')}),
                                      'sortkey': ((item) => {return (item.assignments != undefined ? (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',
                                      'title': 'Unlock',
                                      'export': ((item) => {return (item.assignments != undefined ? (item.assignments.unlocked_at != undefined ? forExportDate(item.assignments.unlocked_at) : '"Not yet"') : '"Not yet"')})
                       },
                      'Item': {'exists': ((item) => {return true}), 'label': 'Item ',
                                      'tableEntry': ((item) => {return 'None'}),
                                      'tooltipEntry':  ((item) => {return 'None'}),
                                      'sortkey':  ((item) => {return 'None'}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2':  ((item) => {return 'None'}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                                      'title': 'Item',
                                      'export': ((item) => {return (item.data.characters ? item.data.characters : item.data.slug)})
                       },
                      'Type': {'exists': ((item) => {return true}), 'label': 'Type ',
                                      'tableEntry':  ((item) => {return 'None'}),
                                      'tooltipEntry':  ((item) => {return 'None'}),
                                      'sortkey':  ((item) => {return 'None'}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2':  ((item) => {return 'None'}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'assignments',
                                      'title': 'Type',
                                      'export': ((item) => {return (item.assignments != undefined ? item.assignments.subject_type : 'unavailable')})
                       },
                       'Export_Date': {'exists': ((item) => {return true}), 'label': 'Export&nbsp;Date',
                                      'tableEntry':  ((item) => {return 'None'}),
                                      'tooltipEntry':  ((item) => {return 'None'}),
                                      'sortkey':  ((item) => {return 'None'}),
                                      'sortOrder': 'Ascending',
                                      'sortkey2':  ((item) => {return 'None'}),
                                      'sortOrder2': 'Ascending',
                                      'endPoint' : 'subjects',
                                      'title': '"Export Date"',
                                      'export': ((item) => {return today()})
                       },
    }

    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){
        if (item.review_statistics != undefined){
		    let reviewStats = item.review_statistics;
		    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 */);
    }

    function forExportDate(date) {
        var d = new Date(date),
            month = '' + (d.getMonth() + 1),
            day = '' + d.getDate(),
            year = d.getFullYear();

        if (month.length < 2) month = '0' + month;
        if (day.length < 2) day = '0' + day;
        return [year, month, day].join('-');
    }

    function today() {
        var d = new Date(),
            month = '' + (d.getMonth() + 1),
            day = '' + d.getDate(),
            year = d.getFullYear();

        if (month.length < 2) month = '0' + month;
        if (day.length < 2) day = '0' + day;
        return [year, month, day].join('-');
    }

    //========================================================================
    // 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(noCreateTable) {

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

        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)});
        };
        if (!noCreateTable){
            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 = itemsData.character_images.find(a=>a.content_type=="image/png").url;
            let imgUrl = itemsData.character_images.find(a=>a.content_type=="image/svg+xml"&&a.metadata.inline_styles).url;
            return '<img height=14px width=14px 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="WkitLabel">'+tableData.label+'</td><td class="WkitTipValue">'+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);

        var info = {
            type: item.object,
            characters: item.data.characters,
            url: item.data.document_url,
            svg: (item.data.characters === null ? item.data.character_images.find(a=>a.content_type=="image/svg+xml"&&a.metadata.inline_styles).url : null),
        };

        if (html != '') {
            html = '<div class="WkitTooltipContent">'+
                      '<div class="left">'+
                          '<p class="'+info.type+'">'+(info.characters === null ? '<img src="'+info.svg+'">' : info.characters)+'</p>'+
                      '</div>'+
                      '<div class="right"><table class="WkitTablePopup"><tbody>'+html+'</tbody></table></div>'+
                   '</div>';
        };
        return html;
    }

    function makeEnlargingTooltip (item, selectedTable){
        let html;
        if (quiz.settings.tablePresets[selectedTable].enlargingTooltip === true){
            html = '<div class="WkitEnlargedTooltip">'+
                      '<span class="'+item.object+'">'+(item.data.characters === null ? '<img src="'+item.data.character_images.find(a=>{ return a.content_type=="image/svg+xml"&&a.metadata.inline_styles}).url+'"' : item.data.characters)+'</span>'+
                   '</div>';
        } else {
            html = '';
        };
        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('table')
            .then(function(){
                     updatePage()
                    })

    }

    const numberOfTables = 3;
    var leechesPerTable;
    var nbDisplayedItems;
    function setNumberOfLines (){
        if (quiz.settings.numberOfLines === undefined){
            leechesPerTable = 11;
        } else {
            leechesPerTable = Number(quiz.settings.numberOfLines);
        };
        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;">
                    <div class="WkitTableList" style="position: relative;">
                        <h3 class="WkitSmallCaps">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);
                let tooltip2 = makeEnlargingTooltip(items[j], activeTable);
                sectionContents += `<tr class="${items[j].object}">` +
                                       '<td>' +
                                            `<div class="WkitTooltip"><a target="_blank" href="${items[j].data.document_url}"><span class='WkitItem' lang="ja">${itemsCharacterCallback(items[j].data)}</span></a>` +
                                                 tooltip +
                                            '</div>' +
                                            `<div class="WkitTooltip2"><p><span> </span></p>` +
                                                 tooltip2 +
                                            '</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>' +
                   '</div>' +
                '</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
   }

    function fillClipboard(){
        // make sure the repeat field endpoint is available in the items
        if (quiz.settings.repeatWordCloud != 'No Repeat'){
            fetch_items('wordCloud');
            updatePage(true);
        };
        let items = quiz.items;
        let text = '';
        let noLatin = quiz.settings.noLatin;
        if (noLatin === undefined){noLatin = false};
        let oneItemPerLine = quiz.settings.oneItemPerLine;
        if (oneItemPerLine === undefined){oneItemPerLine = false};
        let exportLimit = quiz.settings.exportLimit;
        if (exportLimit === undefined){exportLimit = 0};
        let exportCount = 0;
        let repeatWordCloud = quiz.settings.repeatWordCloud;
        let repeatCount;

        for (var i = 0; i < items.length; i++){
            let itemsData = items[i].data;
            if (repeatWordCloud != 'No Repeat'){
                    repeatCount = metadata[repeatWordCloud].wordCloud(items[i]);
                } else {
                    repeatCount = 1;
                };
            for (var j = 0; j < repeatCount; j++){
                if(itemsData.characters!= null) {
                    text += itemsData.characters+' ';
                    if (oneItemPerLine){text += '\n'};
                } else if (!noLatin){
                    text += itemsData.slug+' ';
                    if (oneItemPerLine){text += '\n'};
                };
             };
             if (repeatCount != 0){exportCount += 1;};
             if (exportLimit != 0 && exportCount >= exportLimit){break};
        };

        let textarea = '<textarea id="WkitTextArea" class="WkitClipBoard">';
        $('#leech_table').html(textarea);//replace existing table
        $('#WkitTextArea').val(text);
        $('#WkitTextArea').select();
        document.execCommand('copy');
        alert(exportCount+' items have been copied in the clipboard.');

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

    function exportTable(){
        let currentPreset = quiz.settings.tablePresets[quiz.settings.active_ipreset];
        let currentColumn = currentPreset[exportedInfo[0]];
        if (currentColumn === undefined || currentColumn == 'None' ){
            alert('The first column is not exported.\nPlease configure your export settings.');
            return;
        };

        // make sure all export data is available in the items
        fetch_items('export');
        updatePage(true);
        let items = quiz.items;
        let text = '';
        let exportCount = 0;


        let firstElement = true;
        if (currentPreset.includeTitle){
            for (var i = 0; i < exportedInfo.length; i++){
                currentColumn = currentPreset[exportedInfo[i]];
                if (currentColumn !='None' && currentColumn != undefined){
                    if (firstElement){
                        text += metadata[currentColumn].title;
                        firstElement = false;
                    } else {
                        text += ', '+metadata[currentColumn].title;
                    };
                };
            };
            text += '\n';
        };
        for (i = 0; i < items.length; i++){
            firstElement = true;
            for (var j = 0; j < exportedInfo.length; j++){
                let currentColumn = currentPreset[exportedInfo[j]];
                if (currentColumn !='None' && currentColumn != undefined){
                    if (firstElement){
                        text += (metadata[currentColumn].exists(items[i]) ? metadata[currentColumn].export(items[i]) : '');
                        firstElement = false;
                    } else {
                        text += ', '+(metadata[currentColumn].exists(items[i]) ? metadata[currentColumn].export(items[i]) : '');
                    };
                };
            };
            text += '\n';
            exportCount += 1;
        };

        let textarea = '<textarea id="WkitTextArea" class="WkitClipBoard">';
        $('#leech_table').html(textarea);//replace existing table
        $('#WkitTextArea').val(text);
        $('#WkitTextArea').select();
        document.execCommand('copy');
        alert(exportCount+' items have been exported to the clipboard.');

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

    function insertContainer(){
        /* build containers for the table elements */
        let sectionContainer = '<div id="WkitTopBar" class="WkitTopBar"></div> ';
        let topBlock = '<div id="WkitControlBar" class="WkitControlBar">' +
                      '<div class="WkitHeader">' +
                         '<button id="WkitBackwardButton" type="button" class="WkitButton" style="margin-left: 6px">&#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" style="font-size:12px;">&#9925;</button>' +
                         '<button id="WkitExport" type="button" class="WkitButton" title="Export to format csv in clipboard\nPaste in your software of choice" style="font-size:20px;">&#8686;</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);
        $("#WkitExport").click(exportTable);

    };

    //------------------------------------------
    // Starting the program
    // at the end to ensure the global variables are defined
    //------------------------------------------
    var notAdditionalFilters = false;
    function check_additional_filters(){
        wkof.include('ItemData, Menu, Settings');
        if (!window.wkof.ItemData.registry.sources.wk_items.filters.additionalFilters_leechTraining) {
            script_name = 'Wanikani Item Inspector';
            response = confirm(script_name + ' requires  WaniKani Open Framework Additional Filters.\nIf you have already installed it please enable the filters in the settings.\n Click "OK" to be forwarded to installation instructions.');
            if (response) {
                window.location.href = 'https://community.wanikani.com/t/userscript-wanikani-open-framework-additional-filters-recent-lessons-leech-training-related-items-and-more/30512';
            };

            notAdditionalFilters = true;
        };
    };

	wkof.ready('ItemData, Menu, Settings')
        .then(check_additional_filters)
        .then(table_css)
        .then(install_css)
        .then(install_menu)
        .then(init_settings)
        .then(setup_quiz_settings)
        .then(function(){return wkof.Settings.load(scriptId)})
        .then(init_settings)
        .then(fetch_items)
        .then(function(){
                 if (!notAdditionalFilters){
                     initCurrentItem();
                     setNumberOfLines();
                     insertContainer();
                     updatePage();
                     eventHandlers();
                  }
              })
    if (notAdditionalFilters){return};

    // Handy little function that rfindley wrote. Checks whether the theme is dark.
  function is_dark_theme() {
      // Grab the <html> background color, average the RGB.  If less than 50% bright, it's dark theme.
      return $('body').css('background-color').match(/\((.*)\)/)[1].split(',').slice(0,3).map(str => Number(str)).reduce((a, i) => a+i)/(255*3) < 0.5;
  }


})();