// ==UserScript==
// @name WaniKani Open Framework Kanjidic2 and Traditional Radicals Filters
// @namespace https://www.wanikani.com
// @description Kanjidic2 and traditional radicals filters for the WaniKani Open Framework
// @author prouleau
// @version 1.4.2
// @match https://www.wanikani.com/*
// @license GPLV3; https://www.gnu.org/licenses/gpl-3.0.en.html and MIT; http://opensource.org/licenses/MIT --- with an exception described in comments
// @grant none
// ==/UserScript==
// ===============================================================
// Software Licence
//
// The license is GPLV3 or later because this script includes @acm2010 code and databases licenced under GPLV3 or later
// --- for Keisei Semantic-Phonetic Composition
// --- for Niai Visual Similarity Data
//
// Code borrowed from WKOF is licensed under MIT --- http://opensource.org/licenses/MIT
//
// You may use Item Inspector code under either the GPLV3 or MIT license with these restrictions.
// --- The GPLV3 or later code and database borrowed from @acm2010 must remain licensed under GPLV3 or later in all cases.
// --- If you use @acm2010 code and/or databases you work as a whole must be licensed under GPLV3 or later to comply with @acm2010 license.
// --- The MIT code borrowed from WKOF must remain licensed under MIT in all cases.
// These restrictions are required because we can't legally change the license for someone else's code and database without their permission.
// Not even if we modify their code.
//
// ===============================================================
/* globals $, _, LZMA */
/* eslint-disable no-eval */
/* eslint-disable no-multi-spaces */
/* eslint-disable curly */
/* eslint-disable no-return-assign */
var advSearchFilters = {};
(function(wkof) {
'use strict';
//=================================================================
// These filters are button type filters
// They need a path to be defined in the config parameter of the on_click handler to locate where to store the filter parameters.
// The exception is Self Study Quiz because the path is automatically figured out upon detection that the on_click function is called from Self Study Quiz
//
// They support the callable dialog protocol, meaning that the on_click handler is callable directly without having to click an actual button
// The support of this protocol is indicated by the flag callable_dialog: true in the registry entry for the filter
//
// Callable on_click handlers support in their config parameter three callbacks and require one parameter
//
// on_save: called when the settings are saved
// on_cancel: called when the settings are cancelled
// on_close: called when the dialog is closed
// script_id: (required when called without clicking a button) must be the script id used to store wkof settings. (wkof.settings[script_id])
//
//=================================================================
var wkofMinimumVersion = '1.0.52';
if (!wkof) {
var response = confirm('WaniKani Open Framework Date Filters 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;
}
// --------------------------------------
// File names for resources to be loaded.
// Prerequisite scripts
const lodash_file = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/lodash.min.js';
const lzma_file = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/lzma.js';
const lzma_shim_file = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/lzma.shim.js';
// Stroke count for Wanikani radicals
const Wkit_StrokeCountforRadicals = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/WK_radicals.json.compressed';
// Traditional radicals items
const traditionaRadicalsFile = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/trad_rad.json.compressed';
// Kanjidic 2 data
const kanjidic2File = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/kanjidic2.json.compressed';
// Keisei Semantic-Phonetic Composition databases
const kanji_db = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/kanji_esc.json.compressed';
const phonetic_db = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/phonetic_esc.json.compressed';
const wk_kanji_db = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/wk_kanji_esc.json.compressed';
// Lars Yencken visually similar kanji database - also required for Niai visual similarity
const visuallySimilarFilename = 'https://raw.githubusercontent.com/rouleaup88/item-inspector/main/stroke_edit_dist_esc.json.compressed';
// Niai Visually similar databases
const from_keisei_db_filename ='https://raw.githubusercontent.com/rouleaup88/item-inspector/main/from_keisei_esc.json.compressed';
const old_script_db_filename ='https://raw.githubusercontent.com/rouleaup88/item-inspector/main/old_script_esc.json.compressed';
const wk_niai_noto_db_filename ='https://raw.githubusercontent.com/rouleaup88/item-inspector/main/wk_niai_noto_esc.json.compressed';
const yl_radical_db_filename ='https://raw.githubusercontent.com/rouleaup88/item-inspector/main/yl_radical_esc.json.compressed';
// END of filenames
//-------------------
var settingsScriptId = 'advSearchFilters';
var settingsTitle = 'Advanced Search Filters';
var needToRegisterFilters = true;
var filterNamePrefix = 'advSearchFilters_';
var advancedSearchFilterName = filterNamePrefix + 'advancedSearch';
var relatedSearchFilterName = filterNamePrefix + 'relatedSearch';
var extensiveSearchFilterName = filterNamePrefix + 'extensiveSearch';
var strokeCountGteqFilterName = filterNamePrefix + 'strokeCountGteq';
var strokeCountLteqFilterName = filterNamePrefix + 'strokeCountLteq';
var explicitListFilterName = filterNamePrefix + 'explicitList';
var explicitBlockFilterName = filterNamePrefix + 'explicitBlock';
var supportedFilters = [advancedSearchFilterName, relatedSearchFilterName, extensiveSearchFilterName, strokeCountGteqFilterName, strokeCountLteqFilterName,
explicitListFilterName, explicitBlockFilterName, ];
function startupSequence(){
wkof.set_state(settingsScriptId, 'loading');
var config = {wk_items: {filters:{}, options:{'subjects': true,}}};
wkof.include('ItemData, Settings');
loadPrerequisiteScripts()
.then(function(){return wkof.ready('ItemData, Settings')})
.then(function(){return wkof.Settings.load(settingsScriptId)})
.then(ageCache)
.then(updateFiltersWhenReady)
};
var componentIndexKan = {};
var componentIndexVoc = {};
var usedInIndexRad = {};
var usedInIndexKan = {};
var WKvisuallySimilarIndex = {};
function prepareIndex(items){
for (var item of items){
if (item.object === 'kanji'){
componentIndexKan[item.data.slug] = item.data.component_subject_ids;
usedInIndexKan[item.data.slug] = item.data.amalgamation_subject_ids;
WKvisuallySimilarIndex[item.data.slug] = item.data.visually_similar_subject_ids;
};
if (item.object === 'radical'){
usedInIndexRad[item.data.slug] = item.data.amalgamation_subject_ids;
if (item.data.characters !== null) usedInIndexRad[item.data.characters] = item.data.amalgamation_subject_ids;
};
if (item.object !== 'vocabulary'){
componentIndexVoc[item.data.slug] = item.data.amalgamation_subject_ids;
};
};
};
function ageCache(){
// periodically ages the cache
let ageingTime = 1000*60*60*24*30*2; // two months
var now = Date.now();
if (wkof.settings[settingsScriptId] === undefined) wkof.settings[settingsScriptId] = {};
if (wkof.settings[settingsScriptId].lastTime === undefined){
wkof.settings[settingsScriptId].lastTime = now;
wkof.Settings.save(settingsScriptId);
}
let lastTime = wkof.settings[settingsScriptId].lastTime;
if (now > lastTime + ageingTime){
deleteCache();
wkof.settings[settingsScriptId].lastTime = now;
wkof.Settings.save(settingsScriptId);
};
};
function deleteCache(){
deleteKeiseiCache();
deleteVisuallySimilarCache();
deleteNiaiCache();
kanjidic2_cacheDelete();
WkStrokeCount_cacheDelete()
trad_rad_cacheDelete();
wkof.file_cache.delete(lodash_file);
wkof.file_cache.delete(lzma_file);
wkof.file_cache.delete(lzma_shim_file);
};
function updateFiltersWhenReady() {
// register the filters
needToRegisterFilters = true;
return waitForItemDataRegistry().then(registerFilters);
};
function waitForItemDataRegistry() {
return wkof.wait_state('wkof.ItemData.registry', 'ready');
};
function registerFilters() {
if (!needToRegisterFilters) {
return;
};
supportedFilters.forEach(function(filterName) {
delete wkof.ItemData.registry.sources.wk_items.filters[filterName];
});
registerTraditionalRadicals(); // must be first
registerAdvancedSearchFilter();
registerRelatedSearchFilter();
registerExtensiveSearchFilter();
registerStrokeCountGteqilter();
registerStrokeCountLteqilter();
registerExplicitListFilter();
registerExplicitBlockFilter();
needToRegisterFilters = false;
wkof.set_state(settingsScriptId, 'ready');
};
// Unicode for Japanese characters
// reference https://stackoverflow.com/questions/15033196/using-javascript-to-check-whether-a-string-contains-japanese-characters-includi
//
// regexes for splitting stings into words, one to get composed words that may have ' and - and one for subwords separated by ' or -
const breakTheWords = /[^\-'a-zA-Z0-9\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf\u3400-\u4dbf]/;
const breakTheSubwords = /[\-']/;
const noHtml = /<[^>]*>/g;
// constants for searches
const all = '*';
const none = '!*'
// ------------------
// helping functions
// split_list() is borrowed from @rfindley -- must be licensed under MIT
function split_list(str) {return str.replace(/、/g,',')
.replace(/[\s ]+/g,' ')
.replace(/!/g, '!')
.replace(/*/g, '*')
.trim()
.replace(/ *, */g, ',')
.replace(/ *, */g, ',')
.toLowerCase()
.split(',')
.filter(function(name) {return (name.length > 0);});
};
// detailed path parsing to make sure it is a real path and not something else -- much needed to keep eval safe to use
// there is no alternative to eval in this context
// function set_value() and get_value is borrowed from wkof Settings module -- must be under MIT license
function set_value(path, value) {
var depth=0, new_path='', param, c;
for (var idx = 0; idx < path.length; idx++) {
c = path[idx];
if (c === '[') {
if (depth++ === 0) {
new_path += '[';
param = '';
} else {
param += '[';
}
} else if (c === ']') {
if (--depth === 0) {
new_path += JSON.stringify(eval(param)) + ']';
} else {
param += ']';
}
} else {
if (c === '@') c = 'base.';
if (depth === 0)
new_path += c;
else
param += c;
};
};
eval(new_path + '=value');
};
function get_value(path) {
var depth=0, new_path='', param, c;
for (var idx = 0; idx < path.length; idx++) {
c = path[idx];
if (c === '[') {
if (depth++ === 0) {
new_path += '[';
param = '';
} else {
param += '[';
};
} else if (c === ']') {
if (--depth === 0) {
new_path += JSON.stringify(eval(param)) + ']';
} else {
param += ']';
};
} else {
if (c === '@') c = 'base.';
if (depth === 0)
new_path += c;
else
param += c;
};
};
return eval(new_path);
};
function katakana2hiragana(string){
const baseHiragana = 12352 // start of unicode hiragana block
const endHiragana = 12447 // end of unicode hiragana block
const baseKatakana = 12448 // start of unicode katakana block
const endKatakana = 12543 // end of unicode katakana block
const delta = baseKatakana - baseHiragana;
let result = []
for (let idx in string){
let x = string.charCodeAt(idx);
if (x > baseKatakana && x <= endKatakana) x -= delta;
x = String.fromCodePoint(x)
result.push(x);
};
return result.join('');
};
function reportSearchResult(item, match, place){
item.report = 'Search term '+match+' matches at '+place;
};
function prepareKanjidic2(filterValue){
dataRequired.kanjidic2 = true;
return loadMissingData();
};
function prepareKanjidic2AndWkRad(filterValue) {
dataRequired.WkStrokeCountData = true;
return prepareKanjidic2(filterValue);
};
// BEGIN Stroke Count GTEQ
let strokeCountGteqHover_tip = 'Kanji and traditional radicals whose stroke count >= value';
function registerStrokeCountGteqilter() {
const registration = {
type: 'number',
label: 'Stroke Count >=',
default: 0,
filter_func: strokeOrderGteqSearchFilter,
filter_value_map: (x) => Number(x),
prepare: prepareKanjidic2AndWkRad,
set_options: function(options) { options.subjects = true;},
hover_tip: strokeCountGteqHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[strokeCountGteqFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.wk_items.filters[strokeCountGteqFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[strokeCountGteqFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[strokeCountGteqFilterName].main_source = 'wk_items';
};
function strokeOrderGteqSearchFilter(filterValue, item){
if (item.data === undefined) {
return false;
};
let itemType = item.object;
if (itemType === 'kanji' && (item.data.slug in kanjidic2Data)) {
return Number(kanjidic2Data[item.data.slug].stroke_count) >= filterValue;
} else if (itemType === 'radical') {
return WkStrokeCountData[item.id].stroke_count >= filterValue;
} else if (itemType === 'trad_rad') {
if (typeof item.data.stroke_count === 'string'){
return Number(item.data.stroke_count) >= filterValue;
};
};
return false;
};
// END Stroke Count GTEQ
// BEGIN Stroke Count LTEQ
let strokeCountLteqHover_tip = 'Kanji and traditional radicals whose stroke count <= value';
function registerStrokeCountLteqilter() {
const registration = {
type: 'number',
label: 'Stroke Count <=',
default: 100,
filter_func: strokeOrderLteqSearchFilter,
filter_value_map: (x) => Number(x),
prepare: prepareKanjidic2AndWkRad,
set_options: function(options) { options.subjects = true;},
hover_tip: strokeCountLteqHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[strokeCountLteqFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.wk_items.filters[strokeCountLteqFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[strokeCountLteqFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[strokeCountLteqFilterName].main_source = 'wk_items';
};
function strokeOrderLteqSearchFilter(filterValue, item){
if (item.data === undefined) {
return false;
};
let itemType = item.object;
if (itemType === 'kanji' && item.data.characters in kanjidic2Data) {
return Number(kanjidic2Data[item.data.characters].stroke_count) <= filterValue;
} else if (itemType === 'radical') {
return WkStrokeCountData[item.id].stroke_count <= filterValue;
} else if (itemType === 'trad_rad') {
if (typeof item.data.stroke_count === 'string'){
return Number(item.data.stroke_count) <= filterValue;
};
};
return false;
};
// END Stroke Count LTEQ
// BEGIN Extensive Search
let extensiveSearchHover_tip = 'Search for terms in meanings, readings and kanji.\nKanjidic2 meaning and readings are searched.\nYou may use latin, kana and kanji.';
function registerExtensiveSearchFilter() {
const registration = {
type: 'text',
label: 'Extensive Search',
default: '',
filter_func: extensiveSearchFilter,
filter_value_map: prepareExtensiveFilterValue,
prepare: prepareKanjidic2,
set_options: function(options) { options.subjects = true;},
hover_tip: extensiveSearchHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[extensiveSearchFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.wk_items.filters[extensiveSearchFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[extensiveSearchFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[extensiveSearchFilterName].main_source = 'wk_items';
};
function prepareExtensiveFilterValue(filterValue){
return split_list(filterValue);
};
function extensiveSearchFilter(filterValue, item){
if (item.data === undefined) {
return false;
};
let itemType = item.object;
for (var searchTerm of filterValue){
if (item.data.characters !== null && item.data.characters.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'characters'); return true;};
if (itemType === 'radical' && item.data.slug.indexOf(searchTerm.replace(' ', '-')) >= 0){reportSearchResult(item, searchTerm, 'characters'); return true;};
for (var meaning of item.data.meanings){
let term = meaning.meaning.toLowerCase();
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'meanings'); return true;};
};
if (itemType !== 'radical' && itemType !== 'kana_vocabulary'){
for (let reading of item.data.readings){
if (reading.reading.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'readings'); return true;};
};
};
if (itemType === 'kanji' && item.data.characters in kanjidic2Data){
for (let meaning of kanjidic2Data[item.data.characters].meanings){
let term = meaning.toLowerCase();
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 meaning'); return true;};
};
for (let onyomi of kanjidic2Data[item.data.characters].onyomi){
let term = katakana2hiragana(onyomi);
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 onyomi'); return true;};
};
for (let kunyomi of kanjidic2Data[item.data.characters].kunyomi){
let term = kunyomi
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi'); return true;};
};
for (let nanori of kanjidic2Data[item.data.characters].nanori){
let term = nanori
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 nanori'); return true;};
};
};
};
return false;
};
// END Extensive Search
// BEGIN Advanced Search
let advancedSearchHover_tip = 'Search for terms in multiple locations of your choice.\nYou may use latin, kana and kanji.\nMultiple search options permit to tailor your search.';
function registerAdvancedSearchFilter() {
const registration = {
type: 'button',
label: 'Advanced Search',
default: advancedSearchDefaults,
callable_dialog: true,
on_click: advancedSearchDialog,
filter_func: advancedSearchFilter,
filter_value_map: prepareFilterValue,
prepare: prepareAdvancedSearch,
set_options: function(options) { options.subjects = true; options.study_materials = true; },
hover_tip: advancedSearchHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[advancedSearchFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.wk_items.filters[advancedSearchFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[advancedSearchFilterName] = $.extend({}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[advancedSearchFilterName].main_source = 'wk_items';
};
let advancedSearchDefaults = {itemType: {radical: true, kanji: true, vocabulary: true, kana_vocabulary: true, trad_rad: true,},
exactMatch: {characters: true, meanings: false, readings: true,
allow: false, block: false,
mMnemonics: false, mHints: false, rMnemonics: false, rHints: false, contextSentences: false,
mNotes: false, rNotes: false, synonyms: false,
Kanjidic2_Meaning: false, Kanjidic2_Onyomi: false,
Kanjidic2_Kunyomi: false, Kanjidic2_Nanori: false},
acceptedAnswer: true,
searchIn: {characters: true, meanings: true, readings: true, onyomi: true, kunyomi: true, nanori: true,
pos: false, allow: false, block: false,
mMnemonics: false, mHints: false, rMnemonics: false, rHints: false, contextSentences: false,
mNotes: false, rNotes: false, synonyms: false,
Kanjidic2_Meaning: false, Kanjidic2_Onyomi: false,
Kanjidic2_Kunyomi: false, Kanjidic2_Nanori: false},
keisei: {phonetic: false, nonPhonetic: false, compound: false,
notCompound: false, obscure: false, notInDB: false},
searchTerms: ''};
function advancedSearchDialog(name, config, on_change){
let $originalDialog = $('#wkof_ds').find('[role="dialog"]');
let areaId = settingsScriptId+'_advSearch';
let scriptId = config.script_id || $originalDialog.attr("aria-describedby").slice(6);
let path;
if (config.path){
path = config.path.replaceAll('@', 'wkof.settings["'+scriptId+'"].');
} else if (scriptId === 'ss_quiz') {
// Self Study Quiz doesn't define the path but we know what it is provided we find the source
let row = $(this.delegateTarget);
let panel = row.closest('[role="tabpanel"]');
let source = panel.attr('id').match(/^ss_quiz_pg_(.*)$/)[1];
path = 'wkof.settings.ss_quiz.ipresets[wkof.settings.ss_quiz.active_ipreset].content.'+source+'.filters.advSearchFilters_advancedSearch.value'
// initialize in case it is not already initialized
let v = $.extend(true, {}, get_value(path));
set_value(path, v)
} else {
throw 'config.path is not defined';
};
let value = $.extend(true, {}, advancedSearchDefaults, get_value(path));
set_value(path, value)
wkof.settings[settingsScriptId] = wkof.settings[settingsScriptId] || {};
wkof.settings[settingsScriptId].advSearch = $.extend(true, {}, advancedSearchDefaults, get_value(path));
let html = '<textarea id="'+areaId+'" rows="2"></textarea>';
let searchTermHovertip = ''+
'List your search terms separated by commas. Latin, hiragana and kanji accepted\n\n'+
'* selects items where "Search In" information is PRESENT *and* NON EMPTY.\n'+
'!* selects items where "Search In" information is ABSENT *or* EMPTY.\n\n'+
'Valid parts of speech are:\n'+
'adjective adverb conjunction counter expression godan verb ichican verb\n'+
'interjection intransitive verb noun numeral prefix pronoun proper noun suffix\n'+
'transitive verb い adjective な adjective の adjective する verb';
let dialogConfig = {
script_id: settingsScriptId,
title: 'Advanced Search',
on_save: on_save,
on_cancel: on_cancel,
on_close: on_close,
no_bkgd: true,
settings: {itemType: {type: 'list', multi: true, label: 'Item Type', path: '@advSearch.itemType',
hover_tip: 'The type of items that must match the search terms.',
default: {radical: true, kanji: true, vocabulary: true, kana_vocabulary: true, trad_rad: true},
content: {radical: 'Radical', kanji: 'Kanji', vocabulary: 'Vocabulary', kana_vocabulary: 'Kana Vocabulary',
trad_rad:'Traditional Radical'},},
exactMatch: {type: 'list', multi: true, label: 'Searches With Exact Match', size: 5, path: '@advSearch.exactMatch',
hover_tip: 'Selected searches requires the whole word matches.\nSubstring match is the default\nPart of Speech always use exact match.\n\nYou must select the search in Search In\nfor this parameter to take effect.',
default: {characters: true, meanings: false, readings: true,
allow: false, block: false,
mMnemonics: false, mHints: false, rMnemonics: false, rHints: false, contextSentences: false,
mNotes: false, rNotes: false, synonyms: false,
Kanjidic2_Meaning: false, Kanjidic2_Onyomi: false,
Kanjidic2_Kunyomi: false, Kanjidic2_Nanori: false},
content: {characters: 'Characters', meanings: 'Meanings', readings: 'Readings',
allow: 'Allow List', block: 'Block list',
mMnemonics: 'Meaning mnemonics', mHints: 'Meaning hints',
rMnemonics: 'Reading mnemonics', rHints: 'Reading hints',
contextSentences: 'Context Sentences',
mNotes: 'Meaning notes', rNotes: 'Reading notes', synonyms: 'User synonyms',
Kanjidic2_Meaning: 'Kanjidict2 Meaning', Kanjidic2_Onyomi: 'Kanjidict2 Onyomi',
Kanjidic2_Kunyomi: 'Kanjidict2 Kunyomi', Kanjidic2_Nanori: 'Kanjidict2 Nanori'},},
acceptedAnswer: {type: 'checkbox', label: 'Only Accepted Answers', default: true, path: '@advSearch.acceptedAnswer',
hover_tip: 'Match only meanings and readings marked\nas accepted answers by Wanikani.\nUnchecked, matches both accepted\nand unaccepted answers.',},
searchIn: {type: 'list', multi: true, label: 'Search In', size: 6, path: '@advSearch.searchIn',
hover_tip: 'Where to search to find a search term match.\nAny reading applies to both kanji and vocabulary.\nOther readings apply only to kanji.',
default: {characters: true, meanings: true, readings: true,
onyomi: true, kunyomi: true, nanori: true,
pos: false, allow: false, block: false,
mMnemonics: false, mHints: false, rMnemonics: false, rHints: false, contextSentences: false,
mNotes: false, rNotes: false, synonyms: false,
Kanjidic2_Meaning: false, Kanjidic2_Onyomi: false,
Kanjidic2_Kunyomi: false, Kanjidic2_Nanori: false},
content: {characters: 'Characters', meanings: 'Meanings', readings: 'Any Reading',
onyomi: 'Readings Onyomi', kunyomi: 'Readings Kunyomi', nanori: 'Readings Nanori',
pos: 'Part of Speech', allow: 'Allow List', block: 'Block List',
mMnemonics: 'Meaning Mnemonics', mHints: 'Meaning Hints',
rMnemonics: 'Reading Mnemonics', rHints: 'Reading Hints',
contextSentences: 'Context Sentences',
mNotes: 'Meaning Notes', rNotes: 'Reading Notes', synonyms: 'User Synonyms',
Kanjidic2_Meaning: 'Kanjidict2 Meaning', Kanjidic2_Onyomi: 'Kanjidict2 Onyomi',
Kanjidic2_Kunyomi: 'Kanjidict2 Kunyomi', Kanjidic2_Nanori: 'Kanjidict2 Nanori',},},
keisei: {type: 'list', multi: true, label: 'Search Keisei Database', size: 4, path: '@advSearch.keisei',
hover_tip: 'Selected items must have one of the selected Keisei Semantic-Phonetic properties.\n\nThis search criterion is not used if no property is selected.',
default: {phonetic: false, nonPhonetic: false, compound: false,
notCompound: false, obscure: false, notInDB: false},
content: {phonetic: 'Is a Phonetic Mark', nonPhonetic: 'Is Not a Phonetic Mark',
compound: 'Is a Phonetic Compound',
notCompound: 'Not a Phonetic Compound', obscure: 'Obscure or Contested Origin',
notInDB: 'Not in Keisei Database'},},
divider: {type: 'divider'},
searchTerms: {type: 'html', label: 'Search Terms',
html: html,},
},
};
let dialog = new wkof.Settings(dialogConfig);
dialog.open();
// ui issue: do not let the calling dialog be visible
let originalDisplay = $originalDialog.css('display');
$originalDialog.css('display', 'none');
// work around some framework limitations regarding html types
let $searchTerms = $('#'+areaId);
$searchTerms.val(wkof.settings[settingsScriptId].advSearch.searchTerms);
$searchTerms.change(textareaChanged);
let $label = $searchTerms.closest('form').children('.left');
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', searchTermHovertip);
function on_close(){
$originalDialog.css('display', originalDisplay);
if (typeof config.on_close === 'function') config.on_close();
};
function on_save(){
set_value(path, get_value('wkof.settings.'+settingsScriptId+'.advSearch'))
if (typeof config.on_save === 'function') config.on_save();
};
function on_cancel(){
if (typeof config.on_cancel === 'function') config.on_cancel();
};
function textareaChanged(e){
wkof.settings[settingsScriptId].advSearch.searchTerms = $searchTerms.val();
};
};
function prepareAdvancedSearch(filterValueOriginal){
let searchIn = filterValueOriginal.searchIn;
dataRequired.kanjidic2 = (searchIn.Kanjidic2_Meaning || searchIn.Kanjidic2_Onyomi || searchIn.Kanjidic2_Kunyomi || searchIn.Kanjidic2_Nanori);
let keisei = filterValueOriginal.keisei;
dataRequired.keisei = (keisei.phonetic || keisei.nonPhonetic || keisei.compound || keisei.notCompound || keisei.obscure || keisei.notInDB);
return loadMissingData();
};
function prepareFilterValue(filterValueOriginal){
// Don't modify the original because it is a pointer to the filter settings - modifying it will accumulate trash in the settings
var filterValue = $.extend({}, filterValueOriginal);
filterValue.hasSearchIn = Object.values(filterValue.searchIn).reduce(((acc,cur) => acc || cur), false);
filterValue.searchIn.keisei = Object.values(filterValue.keisei).reduce(((acc, cur) => acc || cur), false);
filterValue.searchTermsArray = split_list(filterValue.searchTerms);
return filterValue;
};
function advancedSearchFilter(filterValue, item) {
if (item.data === undefined) {
return false;
};
if (!filterValue.itemType[item.object]) return false;
let searchIn = filterValue.searchIn;
let exactMatch = filterValue.exactMatch;
let keisei = filterValue.keisei;
let hasSearchIn = filterValue.hasSearchIn;
let acceptedAnswer = filterValue.acceptedAnswer;
let itemType = item.object;
for (var searchTerm of filterValue.searchTermsArray){
// must match keisei if searched in AND match ANY ONE of the other searched in locations
if (searchIn.keisei){
let result = false;
if (itemType === 'radical'){
if (searchTerm !== none) {
if (keiseiDB.checkPhonetic(keiseiDB.mapWKRadicalToPhon(item.data.slug))) {
if (keisei.phonetic) result = true;
} else {
if (keisei.nonPhonetic) result = true;
};
};
} else if (itemType === 'kanji'){
if (searchTerm !== none) {
let kan = item.data.slug;
if (!keiseiDB.checkKanji(kan)){
if (keisei.notInDB) result = true;
} else if (!keiseiDB.checkPhonetic(kan) && (keiseiDB.getKType(kan) !== keiseiDB.KTypeEnum.comp_phonetic)){
let typeKan = keiseiDB.getKType(kan);
if (keisei.nonPhonetic || keisei.notCompound) {
result = true;
} if (!(typeKan in Object.values(keiseiDB.KTypeEnum))) {
if (keisei.notInDB) result = true;
} else if (typeKan === keiseiDB.KTypeEnum.unknown) {
if (keisei.obscure) result = true;
};
} else {
if (keiseiDB.checkPhonetic(kan)) {
if (keisei.phonetic || keisei.notCompound) result = true;
} else if ((keisei.compound || (keisei.nonPhonetic))){
result = true;
};
};
};
};
if (!result) return false;
if (!hasSearchIn) return result; // if no other location is searched in we are done with the result
};
if (searchIn.characters){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'characters'); return true;};
if (searchTerm !== none) {
if (exactMatch.characters){
if (item.data.characters !== null && item.data.characters === searchTerm) {reportSearchResult(item, searchTerm, 'characters'); return true;};
if (itemType === 'radical' && item.data.slug === searchTerm.replace(' ', '-')) {reportSearchResult(item, searchTerm, 'characters'); return true;};
} else {
if (item.data.characters !== null && item.data.characters.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'characters'); return true;};
if (itemType === 'radical' && item.data.slug.indexOf(searchTerm.replace(' ', '-')) >= 0){reportSearchResult(item, searchTerm, 'characters'); return true;};
};
};
};
if (searchIn.meanings) {
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'meanings'); return true;};
if (searchTerm !== none) {
for (var meaning of item.data.meanings){
if (acceptedAnswer && !meaning.accepted_answer) continue;
let term = meaning.meaning.toLowerCase();
if (exactMatch.meanings){
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'meanings'); return true;};
let words = term.split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'meanings'); return true;};
};
} else {
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'meanings'); return true;};
};
};
};
};
if (searchIn.readings){
if (itemType !== 'radical' && itemType !== 'kana_vocabulary'){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'readings'); return true;};
if (exactMatch.readings){
for (let reading of item.data.readings){
if (acceptedAnswer && !reading.accepted_answer) continue;
if (reading.reading === searchTerm && reading.accepted_answer) {reportSearchResult(item, searchTerm, 'readings'); return true;};
};
} else {
for (let reading of item.data.readings){
if (acceptedAnswer && !reading.accepted_answer) continue;
if (reading.reading.indexOf(searchTerm) >= 0 && reading.accepted_answer) {reportSearchResult(item, searchTerm, 'readings'); return true;};
};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'readings');}
};
};
if (searchIn.onyomi || searchIn.kunyomi || searchIn.nanori){
if (itemType === 'kanji') {
let notSeen = {onyomi: true, kunyomi: true, nanori: true};
let seen = {onyomi: false, kunyomi: false, nanori: false};
if (exactMatch.readings){
for (let reading of item.data.readings){
if (acceptedAnswer && !reading.accepted_answer) continue;
notSeen[reading.type] = false;
seen[reading.type] = true;
if (reading.reading === searchTerm && searchIn[reading.type]) {reportSearchResult(item, searchTerm, reading.type); return true;};
};
} else {
for (let reading of item.data.readings){
if (acceptedAnswer && !reading.accepted_answer) continue;
notSeen[reading.type] = false;
seen[reading.type] = true;
if (reading.reading.indexOf(searchTerm) >= 0 && searchIn[reading.type]) {reportSearchResult(item, searchTerm, reading.type); return true;};
};
};
if (searchTerm === none){
if (searchIn.onyomi && notSeen.onyomi) {reportSearchResult(item, searchTerm, 'onyomi'); return true;};
if (searchIn.kunyomi && notSeen.kunyomi) {reportSearchResult(item, searchTerm, 'kunyomi'); return true;};
if (searchIn.nanori && notSeen.nanori) {reportSearchResult(item, searchTerm, 'nanori'); return true;};
};
if (searchTerm === all){
if (searchIn.onyomi && seen.onyomi) {reportSearchResult(item, searchTerm, 'onyomi'); return true;};
if (searchIn.kunyomi && seen.kunyomi) {reportSearchResult(item, searchTerm, 'kunyomi'); return true;};
if (searchIn.nanori && seen.nanori) {reportSearchResult(item, searchTerm, 'nanori'); return true;};
};
} else {
let reading;
if (searchIn.nanori) reading = 'nanori';
if (searchIn.kunyomi) reading = 'kunyomi';
if (searchIn.onyomi) reading = 'onyomi';
if (searchTerm === none) {reportSearchResult(item, searchTerm, reading); return true;};
};
};
if (searchIn.pos){
if (itemType === 'vocabulary' || itemType === 'kana_vocabulary'){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'part of speech'); return true;};
for (let pos of item.data.parts_of_speech){
if (searchTerm === pos) {reportSearchResult(item, searchTerm, 'part of speech'); return true;};
let words = pos.split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'part of speech'); return true;};
};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'part of speech'); return true;};
};
};
if (searchIn.allow || searchIn.block) {
if (itemType !== 'trad_rad'){
let seenAllow = false;
let seenBlock = false;
for (let list of item.data.auxiliary_meanings){
if (searchIn.allow && list.type === 'whitelist'){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'allow list'); return true;};
seenAllow = true;
if (exactMatch.allow){
if (searchTerm === list.meaning.toLowerCase()) {reportSearchResult(item, searchTerm, 'allow list'); return true;};
let words = list.meaning.toLowerCase().split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'allow list'); return true;};
};
} else {
if (list.meaning.toLowerCase().indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'allow list'); return true;};
};
} else if(searchIn.block && list.type === 'blacklist') {
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'block list'); return true;};
seenBlock = true;
if (exactMatch.block){
if (searchTerm === list.meaning.toLowerCase()) {reportSearchResult(item, searchTerm, 'block list'); return true;};
let words = list.meaning.toLowerCase().split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'block list'); return true;};
};
} else {
if (list.meaning.toLowerCase().indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'block list'); return true;};
};
};
};
if (searchTerm === none){
if (searchIn.allow && !seenAllow) {reportSearchResult(item, searchTerm, 'allow list'); return true;};
if (searchIn.block && !seenBlock) {reportSearchResult(item, searchTerm, 'block list'); return true;};
};
} else {
if (searchTerm === none){
if (searchIn.allow) {reportSearchResult(item, searchTerm, 'allow list'); return true;};
if (searchIn.block) {reportSearchResult(item, searchTerm, 'block list'); return true;};
};
};
};
if (searchIn.mMnemonics) {
if (item.data.meaning_mnemonic){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
let mnemonic = item.data.meaning_mnemonic.toLowerCase().replace(noHtml, ' ');
if (searchTerm === "onyomi" && mnemonic.indexOf("on'yomi") >= 0) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
if (searchTerm === "kunyomi" && mnemonic.indexOf("kun'yomi") >= 0) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
if (exactMatch.mMnemonics){
let words = mnemonic.toLowerCase().split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
};
};
} else {
if (mnemonic.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'meaning mnemonic'); return true;};
};
};
if (searchIn.mHints){
if (item.data.meaning_hint){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
let hint = item.data.meaning_hint.toLowerCase().replace(noHtml, ' ');
if (searchTerm === "onyomi" && hint.indexOf("on'yomi") >= 0) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
if (searchTerm === "kunyomi" && hint.indexOf("kun'yomi") >= 0) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
if (exactMatch.mHints){
let words = hint.split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subwords) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
}
};
} else {
if (hint.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'meaning hint'); return true;};
};
};
if (searchIn.rMnemonics) {
if (item.data.reading_mnemonic){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
let mnemonic = item.data.reading_mnemonic.toLowerCase().replace(noHtml, ' ');
if (searchTerm === "onyomi" && mnemonic.indexOf("on'yomi") >= 0) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
if (searchTerm === "kunyomi" && mnemonic.indexOf("kun'yomi") >= 0) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
if (exactMatch.rMnemonics){
let words = mnemonic.split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
}
};
} else {
if (mnemonic.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'reading mnemonic'); return true;};
};
};
if (searchIn.rHints) {
if (item.data.reading_hint){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
let hint = item.data.reading_hint.toLowerCase().replace(noHtml, ' ');
if (searchTerm === "onyomi" && hint.indexOf("on'yomi") >= 0) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
if (searchTerm === "kunyomi" && hint.indexOf("kun'yomi") >= 0) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
if (exactMatch.rHints){
let words = hint.split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
}
};
} else {
if (hint.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'reading hint'); return true;};
};
};
if (searchIn.contextSentences){
if (item.object === 'vocabulary'){
if (item.data.context_sentences.length !== 0){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
for (let sentences of item.data.context_sentences){
let ja = sentences.ja;
let en = sentences.en;
if (exactMatch.contextSentences){
let words = ja.toLowerCase().split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
};
words = en.toLowerCase().split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
};
};
} else {
if (ja.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
if (en.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'context sentences'); return true;};
};
};
if (searchIn.mNotes){
if (item.study_materials){
if (item.study_materials.meaning_note){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
let note = item.study_materials.meaning_note.toLowerCase().replace(noHtml, ' ');
if (searchTerm === "onyomi" && note.indexOf("on'yomi") >= 0) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
if (searchTerm === "kunyomi" && note.indexOf("kun'yomi") >= 0) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
if (exactMatch.mNotes){
let words = note.split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
};
};
} else {
if (note.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'meaning note'); return true;};
};
};
if (searchIn.rNotes){
if (item.study_materials){
if (item.study_materials.reading_note){
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
let note = item.study_materials.reading_note.toLowerCase().replace(noHtml, ' ');
if (searchTerm === "onyomi" && note.indexOf("on'yomi") >= 0) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
if (searchTerm === "kunyomi" && note.indexOf("kun'yomi") >= 0) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
if (exactMatch.rNotes){
let words = note.split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
}
};
} else {
if (note.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'reading note'); return true;};
};
};
if (searchIn.synonyms){
if (item.study_materials){
let synonyms = item.study_materials.meaning_synonyms;
if (synonyms.length === 0){
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
} else {
if (searchTerm === all) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
for (let synonym of synonyms){
synonym = synonym.toLowerCase();
if (searchTerm === synonym) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
if (exactMatch.synonyms){
let words = synonym.split(breakTheWords).filter(function(name) {return (name.length > 0);});
for (var word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
};
let subwords = word.split(breakTheSubwords);
if (subwords.length !== 1){
for (let subword of subwords) if (searchTerm === subword) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
}
} else {
if (synonym.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
};
};
};
} else {
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'user synonyms'); return true;};
};
};
if (searchIn.Kanjidic2_Meaning) {
if (item.object !== 'kanji'){
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'kanjidic2 meaning'); return true;};
} else {
if (searchTerm === all) {
if (item.data.characters in kanjidic2Data && kanjidic2Data[item.data.characters].meanings.length !== 0){
reportSearchResult(item, searchTerm, 'kanjidic2 meaning');
return true;
};
} else if (searchTerm === none) {
if (!(item.data.characters in kanjidic2Data) || kanjidic2Data[item.data.characters].meanings.length === 0){
reportSearchResult(item, searchTerm, 'kanjidic2 meaning');
return true;
};
} else if (item.data.characters in kanjidic2Data){
for (let meaning of kanjidic2Data[item.data.characters].meanings){
let term = meaning.toLowerCase();
if (exactMatch.Kanjidic2_Meaning){
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 meaning'); return true;};
let words = term.split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'kanjidic2 meaning'); return true;};
};
} else {
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 meaning'); return true;};
};
};
};
};
};
if (searchIn.Kanjidic2_Onyomi) {
if (item.object !== 'kanji'){
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'kanjidic2 onyomi'); return true;};
} else {
if (searchTerm === all) {
if (item.data.characters in kanjidic2Data && kanjidic2Data[item.data.characters].onyomi.length !== 0){
reportSearchResult(item, searchTerm, 'kanjidic2 onyomi');
return true;
};
} else if (searchTerm === none) {
if (!(item.data.characters in kanjidic2Data) || kanjidic2Data[item.data.characters].onyomi.length === 0){
reportSearchResult(item, searchTerm, 'kanjidic2 onyomi');
return true;
};
} else if (item.data.characters in kanjidic2Data){
for (let onyomi of kanjidic2Data[item.data.characters].onyomi){
let term = katakana2hiragana(onyomi);
if (exactMatch.Kanjidic2_Onyomi){
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 onyomi'); return true;};
let words = term.split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'kanjidic2 onyomi'); return true;};
if (word.endsWith('-') || word.endsWith('ー') || word.endsWith('ー') || word.endsWith('-') || word.endsWith('‐')){
term = word.slice(0, -1)
};
if (word.startsWith('-') || word.startsWith('ー') || word.startsWith('ー') || word.startsWith('-') || word.startsWith('‐')){
term = word.slice(1)
};
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 onyomi'); return true;};
};
} else {
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 onyomi'); return true;};
};
};
};
};
};
if (searchIn.Kanjidic2_Kunyomi) {
if (item.object !== 'kanji'){
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi'); return true;};
} else {
if (searchTerm === all) {
if (item.data.characters in kanjidic2Data && kanjidic2Data[item.data.characters].kunyomi.length !== 0){
reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi');
return true;
};
} else if (searchTerm === none) {
if (!(item.data.characters in kanjidic2Data) || kanjidic2Data[item.data.characters].kunyomi.length === 0){
reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi');
return true;
};
} else if (item.data.characters in kanjidic2Data){
for (let kunyomi of kanjidic2Data[item.data.characters].kunyomi){
let term = kunyomi
if (exactMatch.Kanjidic2_Kunyomi){
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi'); return true;};
let words = term.split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi'); return true;};
if (word.endsWith('-') || word.endsWith('ー') || word.endsWith('ー') || word.endsWith('-') || word.endsWith('‐')){
term = word.slice(0, -1)
};
if (word.startsWith('-') || word.startsWith('ー') || word.startsWith('ー') || word.startsWith('-') || word.startsWith('‐')){
term = word.slice(1)
};
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi'); return true;};
};
} else {
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 kunyomi'); return true;};
};
};
};
};
};
if (searchIn.Kanjidic2_Nanori) {
if (item.object !== 'kanji'){
if (searchTerm === none) {reportSearchResult(item, searchTerm, 'kanjidic2 nanori'); return true;};
} else {
if (searchTerm === all) {
if (item.data.characters in kanjidic2Data && kanjidic2Data[item.data.characters].nanori.length !== 0){
reportSearchResult(item, searchTerm, 'kanjidic2 nanori');
return true;
};
} else if (searchTerm === none) {
if (!(item.data.characters in kanjidic2Data) || kanjidic2Data[item.data.characters].nanori.length === 0){
reportSearchResult(item, searchTerm, 'kanjidic2 nanori');
return true;
};
} else if (item.data.characters in kanjidic2Data){
for (let nanori of kanjidic2Data[item.data.characters].nanori){
let term = nanori
if (exactMatch.Kanjidic2_Nanori){
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 nanori'); return true;};
let words = term.split(' ').filter(function(name) {return (name.length > 0);});
for (let word of words) {
if (searchTerm === word) {reportSearchResult(item, searchTerm, 'kanjidic2 nanori'); return true;};
if (word.endsWith('-') || word.endsWith('ー') || word.endsWith('ー') || word.endsWith('-') || word.endsWith('‐')){
term = word.slice(0, -1)
};
if (word.startsWith('-') || word.startsWith('ー') || word.startsWith('ー') || word.startsWith('-') || word.startsWith('‐')){
term = word.slice(1)
};
if (searchTerm === term) {reportSearchResult(item, searchTerm, 'kanjidic2 nanori'); return true;};
};
} else {
if (term.indexOf(searchTerm) >= 0) {reportSearchResult(item, searchTerm, 'kanjidic2 nanori'); return true;};
};
};
};
};
};
};
return false;
};
// END Advanced Search
// BEGIN Related Search
let relatedSearchHover_tip = 'Find items related to search terms.\n\nRelationships include:\n* Components of items\n* Items where components are used\n* Visually similar kanjis\n* Semantic-phonetic composition relationships';
function registerRelatedSearchFilter() {
const registration = {
type: 'button',
label: 'Related Search',
default: relatedSearch_defaults,
callable_dialog: true,
on_click: relatedSearchDialog,
prepare: prepareRelatedSearch,
filter_value_map: prepareRelatedFilterValue,
filter_func: relatedSearchFilter,
set_options: function(options) { options.subjects = true; },
hover_tip: relatedSearchHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[relatedSearchFilterName] = $.extend(true, {}, registration);
wkof.ItemData.registry.sources.wk_items.filters[relatedSearchFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[relatedSearchFilterName] = $.extend(true, {}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[relatedSearchFilterName].main_source = 'wk_items';
};
let relatedSearch_defaults = {returns: {matchedItem: true, components: false, usedIn: true,
WKvisuallySimilar: false, LYvisuallySimilar: false, NiaiVisuallySimilar: false, NiaiAlternate: 0.3,
phonetic: false, nonPhonetic: false, keiseiRelated: false,
tradRadInKanji: false, kanjiwithTradRad: false},
similarityTreshold: 0,
searchTerms: '',
itemType: {radical: true, kanji: true, vocabulary: true, trad_rad: true,},
};
function relatedSearchDialog(name, config, on_change){
let $originalDialog = $('#wkof_ds').find('[role="dialog"]');
let areaId = settingsScriptId+'_relSearch';
let scriptId = config.script_id || $originalDialog.attr("aria-describedby").slice(6);
let path;
if (config.path){
path = config.path.replaceAll('@', 'wkof.settings["'+scriptId+'"].');
} else if (scriptId === 'ss_quiz') {
// Self Study Quiz doesn't define the path but we know what it is provided we find the source
let row = $(this.delegateTarget);
let panel = row.closest('[role="tabpanel"]');
let source = panel.attr('id').match(/^ss_quiz_pg_(.*)$/)[1];
path = 'wkof.settings.ss_quiz.ipresets[wkof.settings.ss_quiz.active_ipreset].content.'+source+'.filters.advSearchFilters_relatedSearch.value'
// initialize in case it is not already initialized
let v = $.extend(true, {}, get_value(path));
set_value(path, v)
} else {
throw 'config.path is not defined';
};
let value = $.extend(true, {}, relatedSearch_defaults, get_value(path));
set_value(path, value);
wkof.settings[settingsScriptId] = wkof.settings[settingsScriptId] || {};
wkof.settings[settingsScriptId].relSearch = $.extend(true, {}, relatedSearch_defaults, get_value(path));
let html = '<textarea id="'+areaId+'" rows="3"></textarea>';
let searchTermHovertip = ''+
'List your search terms separated by commas. Latin, kana and kanji accepted.\n'+
'Your search terms must match the characters for the item exactly.\n'+
'English name of radicals and traditional radicals are accepted.';
let dialogConfig = {
script_id: settingsScriptId,
title: 'Related Search',
on_save: on_save,
on_cancel: on_cancel,
on_close: on_close,
no_bkgd: true,
settings: {returns: {type: 'list', multi: true, label: 'Items Returned By The Search', size: 4, path: '@relSearch.returns',
hover_tip: 'Choose which items are being searched.\nWK Visially similar means according to Wanikani data.\nLY Visually similar means according to Lars Yencken data.',
default: {matchedItem: true, components: false, usedIn: true,
WKvisuallySimilar: false, LYvisuallySimilar: false, NiaiVisuallySimilar: false, NiaiAlternate: 0.3,
phonetic: false, nonPhonetic: false, keiseiRelated: false,
tradRadInKanji: false, kanjiwithTradRad: false},
content: {matchedItem: 'Matched Items', components: 'Components of Matched Items', usedIn: 'Items Where Matches Are Used',
WKvisuallySimilar: 'WK Visually Similar To Matches', LYvisuallySimilar: 'LY Visually Similar To Matches',
NiaiVisuallySimilar: 'Niai Visually Similar To Matches',
phonetic: 'Keisei Phonetic Compounds', nonPhonetic: 'Non Phonetic Compounds',
keiseiRelated: 'Related Phonetic Marks',
tradRadInKanji: 'Traditional Radicals in Kanji', kanjiwithTradRad: 'Kanji Using Traditional Radicals'},},
LYsimilarityTreshold: {type: 'number', label: 'LY Similarity Threshold', min: 0.0, max: 1.0, default: 0, path: '@relSearch.LYsimilarityTreshold',
hover_tip: 'A number between 0 and 1 for the degree\nof similaity of LY Visually Similar Kanjis.',},
NiaiSimilarityTreshold: {type: 'number', label: 'Niai Similarity Threshold', min: 0.0, max: 1.0, default: 0, path: '@relSearch.NiaiSimilarityTreshold',
hover_tip: 'A number between 0 and 1 for the degree\nof similaity of LY Visually Similar Kanjis.',},
NiaiAlternate: {type:'checkbox',label:'Use Niai Alternate Sources',hover_tip:'Add alternate sources to Niai visually similar\ndata to the main sources.\nThis will return more similar kanji.',
path: '@relSearch.NiaiAlternate', default:0.3, min: 0.0, max: 1.0,
},
divider: {type: 'divider'},
searchTerms: {type: 'html', label: 'Search terms', hover_tip: searchTermHovertip, path: '@relSearch.searchTerms',
html: html,},
itemType: {type: 'list', multi: true, label: 'Item Type of Search Terms', path: '@relSearch.itemType',
hover_tip: 'Further specify the search terms to be of the given types.',
default: {radical: true, kanji: true, vocabulary: true, trad_rad: true},
content: {radical: 'Radical', kanji: 'Kanji', vocabulary: 'Vocabulary', trad_rad: 'Traditional Radical'},},
},
};
let dialog = new wkof.Settings(dialogConfig);
dialog.open();
// ui issue: do not let the calling dialog be visible
let originalDisplay = $originalDialog.css('display');
$originalDialog.css('display', 'none');
// work around some framework limitations regarding html types
let $searchTerms = $('#'+areaId);
$searchTerms.val(wkof.settings[settingsScriptId].relSearch.searchTerms);
$searchTerms.change(textareaChanged);
let $label = $searchTerms.closest('form').children('.left');
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', searchTermHovertip);
function on_close(){
$originalDialog.css('display', originalDisplay);
if (typeof config.on_close === 'function') config.on_close();
};
function on_save(){
set_value(path, get_value('wkof.settings.'+settingsScriptId+'.relSearch'));
if (typeof config.on_save === 'function') config.on_save();
};
function on_cancel(){
if (typeof config.on_cancel === 'function') config.on_cancel();
};
function textareaChanged(e){
wkof.settings[settingsScriptId].relSearch.searchTerms = $searchTerms.val();
};
};
function prepareRelatedSearch(filterValue){
let returns = filterValue.returns;
dataRequired.items = returns.components || returns.usedIn || returns.WKvisuallySimilar || returns.NiaiVisuallySimilar;
dataRequired.larsYencken = returns.LYvisuallySimilar || (returns.NiaiVisuallySimilar && filterValue.NiaiAlternate);
dataRequired.niai = returns.NiaiVisuallySimilar;
dataRequired.keisei = (returns.phonetic || returns.nonPhonetic || returns.keiseiRelated);
return loadMissingData().then(function(){prepareRelatedFilterValueMap(filterValue)});
function prepareRelatedFilterValueMap(filterValue){
let validItemTypes = filterValue.itemType;
for (let searchTerm of filterValue.searchTermsArray){
let idx = filterValue.searchData[searchTerm];
idx.components = {};
if (searchTerm in componentIndexKan && validItemTypes.kanji) idx.components.radical = componentIndexKan[searchTerm];
if (searchTerm in componentIndexVoc && validItemTypes.vocabulary) idx.components.kanji = componentIndexVoc[searchTerm];
idx.usedIn = {};
if (searchTerm in usedInIndexRad && validItemTypes.radical) idx.usedIn.kanji = usedInIndexRad[searchTerm];
if (searchTerm in usedInIndexKan && validItemTypes.kanji) idx.usedIn.vocabulary = usedInIndexKan[searchTerm];
idx.WKvisuallySimilar = {};
if (searchTerm in WKvisuallySimilarIndex && validItemTypes.kanji) idx.WKvisuallySimilar.kanji = WKvisuallySimilarIndex[searchTerm];
idx.keiseiCompounds = {};
if (returns.phonetic && searchTerm in keiseiCompoundIndex && (validItemTypes.kanji || validItemTypes.radical)){
idx.keiseiCompounds.radical = keiseiCompoundIndex[searchTerm];
idx.keiseiCompounds.kanji = keiseiCompoundIndex[searchTerm];
};
idx.keiseiNonCompounds = {};
if (returns.nonPhonetic && searchTerm in keiseiNonCompoundIndex && (validItemTypes.kanji || validItemTypes.radical)){
idx.keiseiNonCompounds.radical = keiseiNonCompoundIndex[searchTerm];
idx.keiseiNonCompounds.kanji = keiseiNonCompoundIndex[searchTerm];
};
idx.keiseiRelated = {};
if (returns.keiseiRelated && searchTerm in keiseiRelatedMarksIndex && (validItemTypes.kanji || validItemTypes.radical)){
idx.keiseiRelated.radical = keiseiRelatedMarksIndex[searchTerm];
idx.keiseiRelated.kanji = keiseiRelatedMarksIndex[searchTerm];
};
idx.LYvisuallySimilar = {};
if (returns.LYvisuallySimilar && searchTerm in visuallySimilarData && validItemTypes.kanji){
idx.LYvisuallySimilar.kanji = [];
let threshold = filterValue.LYsimilarityTreshold;
for (let value of visuallySimilarData[searchTerm]){
if (value.score >= threshold && (value.kan in componentIndexKan)) idx.LYvisuallySimilar.kanji.push(value.kan);
};
};
idx.NiaiVisuallySimilar = {};
if (returns.NiaiVisuallySimilar && validItemTypes.kanji){
idx.NiaiVisuallySimilar.kanji = makeKanjiListNiai(searchTerm);
};
idx = filterValue.searchData[searchTerm];
idx.tradRadInKanji = {};
if (searchTerm in tradRadInKanji && validItemTypes.kanji){
idx.tradRadInKanji.trad_rad = tradRadInKanji[searchTerm];
};
idx.kanjiwithTradRad = {};
if (searchTerm in traditionalRadicals && validItemTypes.trad_rad){
idx.kanjiwithTradRad.kanji = traditionalRadicals[searchTerm].data.kanji;
};
if (searchTerm in kanjiwithTradRadbyName && validItemTypes.trad_rad){
idx.kanjiwithTradRad.kanji = kanjiwithTradRadbyName[searchTerm];
};
};
};
function makeKanjiListNiai(kanji){
const sources = [
{"id": "noto_db", "base_score": 0.1},
{"id": "keisei_db", "base_score": 0.65},
];
const alt_sources = [
{"id": "old_script_db", "base_score": 0.4},
{"id": "yl_radical_db", "base_score": -0.2},
{"id": "stroke_dist_db", "base_score": -0.2},
];
let selectedSources, score, old_score, similarData;
let alternate = filterValue.NiaiAlternate;
if (alternate){
selectedSources = [...alt_sources, ...sources];
} else {
selectedSources = sources;
};
let similar_kanji = {};
for (let source of selectedSources ){
let threshold = filterValue.NiaiSimilarityTreshold;
switch (source.id){
case 'noto_db': // has scores
if (!(kanji in wk_niai_noto_db)) continue;
similarData = wk_niai_noto_db[kanji];
break;
case 'keisei_db': // no scores
if (!(kanji in from_keisei_db)) continue;
similarData = from_keisei_db[kanji];
break;
case 'old_script_db': // no scores
if (!(kanji in old_script_db)) continue;
similarData = old_script_db[kanji];
break;
case 'yl_radical_db':; // has scores
if (!(kanji in yl_radical_db)) continue;
similarData = yl_radical_db[kanji];
break;
case 'stroke_dist_db': // has scrores
if (!(kanji in visuallySimilarData)) continue;
similarData = visuallySimilarData[kanji];
break;
};
switch (source.id){
case 'noto_db': // has scores
case 'yl_radical_db': // has scores
case 'stroke_dist_db': // has scrores
for (let similar of similarData){
score = similar.score + source.base_score;
old_score = (similar.kan in similar_kanji ? similar_kanji[similar.kan].score : 0.0);
if ((score > threshold || (score > 0.0 && old_score > 0.0)) && (typeof componentIndexKan[similar.kan]) === 'object'){
similar_kanji[similar.kan] = {kan: similar.kan, score: score};
} else if (score < 0) {
delete similar_kanji[similar.kan];
};
};
break;
case 'keisei_db': // no scores
case 'old_script_db': // no scores
score = source.base_score;
for (let similar of similarData){
//old_score = (similar in similar_kanji ? similar_kanji[similar].score : 0.0);
if ((typeof componentIndexKan[similar]) === 'object'){
similar_kanji[similar] = {kan: similar, score: score};
} else if (score < 0) {
delete similar_kanji[similar];
};
};
break;
};
};
let acc = Object.values(similar_kanji);
acc = acc.map((a) => a.kan)
return acc;
};
};
function prepareRelatedFilterValue(filterValueOriginal){
// Don't modify the original because it is a pointer to the filter settings - modifying it will accumulate trash in the settings
var filterValue = $.extend({}, filterValueOriginal);
filterValue.searchTermsArray = split_list(filterValue.searchTerms);
filterValue.searchData = {};
for (let searchTerm of filterValue.searchTermsArray) filterValue.searchData[searchTerm] = {};
return filterValue;
};
function reportRelatedResult(item, itemType, match, place){
item.report = 'Relates to '+itemType+' '+match+' as '+place;
};
function relatedSearchFilter(filterValue, item) {
if (item.data === undefined) {
return false;
};
let compMapType = {radical: 'kanji', kanji: 'vocabulary'};
let usedMapType = {vocabulary: 'kanji', kanji: 'radical'};
let itemType = item.object;
let returns = filterValue.returns;
let charKey = (item.data.characters !== null ? item.data.characters : item.data.slug);
for (var searchTerm of filterValue.searchTermsArray) {
let result = false;
let searchData = filterValue.searchData[searchTerm];
if (returns.matchedItem){
if (filterValue.itemType[itemType]) {
if (item.data.characters !== null && item.data.characters === searchTerm) {
reportRelatedResult(item, itemType, searchTerm, 'being the item.');
return true;
};
if (itemType === 'radical' && item.data.slug === searchTerm.replace(/ /g, '-')) {
reportRelatedResult(item, itemType, searchTerm, 'being the item.');
return true;
};
if (itemType === 'trad_rad') {
for (let meaningData of item.data.meanings){
if (meaningData.meaning === searchTerm){
reportRelatedResult(item, itemType, searchTerm, 'being the item.');
return true;
};
};
};
};
};
if (returns.components) {
if (itemType in searchData.components && searchData.components[itemType].indexOf(item.id) >= 0) {
reportRelatedResult(item, compMapType[itemType], searchTerm, 'a component.');
return true;
};
};
if (returns.WKvisuallySimilar) {
if (itemType in searchData.WKvisuallySimilar && searchData.WKvisuallySimilar[itemType].indexOf(item.id) >= 0) {
reportRelatedResult(item, itemType, searchTerm, 'WK visually similar.');
return true;
};
};
if (returns.usedIn) {
if (itemType in searchData.usedIn && searchData.usedIn[itemType].indexOf(item.id) >= 0) {
reportRelatedResult(item, usedMapType[itemType], searchTerm, 'an item where it is used.');
return true;
};
};
if (returns.phonetic) {
if (itemType in searchData.keiseiCompounds && searchData.keiseiCompounds[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, itemType, searchTerm, 'a keisei phonetic compound.');
return true;
};
};
if (returns.nonPhonetic) {
if (itemType in searchData.keiseiNonCompounds && searchData.keiseiNonCompounds[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, itemType, searchTerm, 'a non phonetic compound.');
return true;
};
};
if (returns.keiseiRelated) {
if (itemType in searchData.keiseiRelated && searchData.keiseiRelated[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, itemType, searchTerm, 'a related phonetic mark.');
return true;
};
};
if (returns.LYvisuallySimilar) {
if (itemType in searchData.LYvisuallySimilar && searchData.LYvisuallySimilar[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, itemType, searchTerm, 'a visually similar kanji according to Lars Yencken.');
return true;
};
};
if (returns.NiaiVisuallySimilar) {
if (itemType in searchData.NiaiVisuallySimilar && searchData.NiaiVisuallySimilar[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, itemType, searchTerm, 'a visually similar kanji according to Niai.');
return true;
};
};
if (returns.tradRadInKanji) {
if (itemType in searchData.tradRadInKanji && searchData.tradRadInKanji[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, 'kanji', searchTerm, 'a component of the kanji.');
return true;
};
};
if (returns.kanjiwithTradRad) {
if (itemType in searchData.kanjiwithTradRad && searchData.kanjiwithTradRad[itemType].indexOf(charKey) >= 0) {
reportRelatedResult(item, 'traditional radical', searchTerm, 'a kanji where it is used.'); return true;
};
};
};
return false;
};
// END Related Search
// BEGIN Explicit List
let explicitListHover_tip = 'Enter explicit list of items.';
function registerExplicitListFilter() {
const registration = {
type: 'button',
label: 'Explicit List',
default: explicitList_defaults,
callable_dialog: true,
on_click: explicitListDialog,
filter_value_map: prepareValueExplicitList,
filter_func: explicitListFilter,
set_options: function(options) { options.subjects = true; },
hover_tip: explicitListHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[explicitListFilterName] = $.extend(true, {}, registration);
wkof.ItemData.registry.sources.wk_items.filters[explicitListFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[explicitListFilterName] = $.extend(true, {}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[explicitListFilterName].main_source = 'wk_items';
};
let explicitList_defaults = {explicitList_radical: '',
explicitList_kanji: '',
explicitList_vocabulary: '',
explicitList_kana_vocabulary: '',
explicitList_trad_rad: '',
};
function explicitListDialog(name, config, on_change){
let $originalDialog = $('#wkof_ds').find('[role="dialog"]');
let scriptId = config.script_id || $originalDialog.attr("aria-describedby").slice(6);
let path;
if (config.path){
path = config.path.replaceAll('@', 'wkof.settings["'+scriptId+'"].');
} else if (scriptId === 'ss_quiz') {
// Self Study Quiz doesn't define the path but we know what it is provided we find the source
let row = $(this.delegateTarget);
let panel = row.closest('[role="tabpanel"]');
let source = panel.attr('id').match(/^ss_quiz_pg_(.*)$/)[1];
path = 'wkof.settings.ss_quiz.ipresets[wkof.settings.ss_quiz.active_ipreset].content.'+source+'.filters.advSearchFilters_explicitList.value'
// initialize in case it is not already initialized
let v = $.extend(true, {}, get_value(path));
set_value(path, v)
} else {
throw 'config.path is not defined';
};
let value = $.extend(true, {}, explicitList_defaults, get_value(path));
set_value(path, value);
wkof.settings[settingsScriptId] = wkof.settings[settingsScriptId] || {};
wkof.settings[settingsScriptId].explicitList = $.extend(true, {}, explicitList_defaults, get_value(path));
let areaIdRadical = settingsScriptId+'_radical';
let html_radical = '<textarea id="'+areaIdRadical+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdKanji = settingsScriptId+'_kanji';
let html_kanji = '<textarea id="'+areaIdKanji+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdVocabulary = settingsScriptId+'_vocabulary';
let html_vocabulary = '<textarea id="'+areaIdVocabulary+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdKana_Vocabulary = settingsScriptId+'_kana_vocabulary';
let html_kana_vocabulary = '<textarea id="'+areaIdKana_Vocabulary+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdTrad_rad = settingsScriptId+'_trad_rad';
let html_trad_rad = '<textarea id="'+areaIdTrad_rad+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let downloadText = '<button><a download="Filter Item List.txt" id="'+settingsScriptId+'_itemList_link" style="text-decoration:none;color:#000000;">Download Configured Items</a></button>';
let inputFile = '<input id="'+settingsScriptId+'_inputFile" type="file" title="Select a file for uploading items with the button above.">';
let dialogConfig = {
script_id: settingsScriptId,
title: 'Explicit List',
on_save: on_save,
on_cancel: on_cancel,
on_close: on_close,
no_bkgd: true,
settings: {explicitList_radical:{type: "html", html: html_radical, label: 'Radicals',},
explicitList_kanji:{type: "html", html: html_kanji, label: 'Kanji',},
explicitList_vocabulary:{type: "html", html: html_vocabulary, label: 'Kanji Vocabulary',},
explicitList_kana_vocabulary:{type: "html", html: html_kana_vocabulary, label: 'Kana Only Vocabulary',},
explicitList_trad_rad:{type: "html", html: html_trad_rad, label: 'Traditional Radicals',},
explicitList_divider:{type: 'divider'},
explicitList_download:{type: "html", label: ' ', html: downloadText,},
explicitList_divider2:{type: 'divider'},
explicitList_upload:{type: "button", label: 'Set Items From Selected File', on_click: uploadFile,
hover_tip: 'Upload your items from a previously downloaded file.',},
explicitList_input:{type: 'html', html: inputFile,},
},
};
let dialog = new wkof.Settings(dialogConfig);
dialog.open();
// ui issue: do not let the calling dialog be visible
let originalDisplay = $originalDialog.css('display');
$originalDialog.css('display', 'none');
// work around some framework limitations regarding html types
let $explicitList_radical = $('#'+areaIdRadical);
$explicitList_radical.val(wkof.settings[settingsScriptId].explicitList.explicitList_radical);
$explicitList_radical.change(radicalChanged);
let $label = $explicitList_radical.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
let radicalHovertip = 'List your items separated by commas\nYour search terms must match the characters for the item exactly.\nEnglish name of radicals are accepted.';
$label.attr('title', radicalHovertip);
let $explicitList_kanji = $('#'+areaIdKanji);
$explicitList_kanji.val(wkof.settings[settingsScriptId].explicitList.explicitList_kanji);
$explicitList_kanji.change(kanjiChanged);
$label = $explicitList_kanji.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
let itemlHovertip = 'List your items separated by commas.\nYour search terms must match the characters for the item exactly.';
$label.attr('title', itemlHovertip);
let $explicitList_vocabulary = $('#'+areaIdVocabulary);
$explicitList_vocabulary.val(wkof.settings[settingsScriptId].explicitList.explicitList_vocabulary);
$explicitList_vocabulary.change(vocabularyChanged);
$label = $explicitList_vocabulary.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', itemlHovertip);
let $explicitList_kana_vocabulary = $('#'+areaIdKana_Vocabulary);
$explicitList_kana_vocabulary.val(wkof.settings[settingsScriptId].explicitList.explicitList_kana_vocabulary);
$explicitList_kana_vocabulary.change(kana_vocabularyChanged);
$label = $explicitList_kana_vocabulary.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', itemlHovertip);
let $explicitList_trad_rad = $('#'+areaIdTrad_rad);
$explicitList_trad_rad.val(wkof.settings[settingsScriptId].explicitList.explicitList_trad_rad);
$explicitList_trad_rad.change(trad_radChanged);
$label = $explicitList_trad_rad.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', radicalHovertip);
let $download = $('#advSearchFilters_itemList_link');
$download.attr('title', 'Download Your Configured Items In a File');
setDownloadLink();
function on_close(){
$originalDialog.css('display', originalDisplay);
if (typeof config.on_close === 'function') config.on_close();
};
function on_save(){
set_value(path, get_value('wkof.settings.'+settingsScriptId+'.explicitList'));
if (typeof config.on_save === 'function') config.on_save();
};
function on_cancel(){
if (typeof config.on_cancel === 'function') config.on_cancel();
};
function radicalChanged(e){
wkof.settings[settingsScriptId].explicitList.explicitList_radical = $explicitList_radical.val();
setDownloadLink()
};
function kanjiChanged(e){
wkof.settings[settingsScriptId].explicitList.explicitList_kanji = $explicitList_kanji.val();
setDownloadLink()
};
function vocabularyChanged(e){
wkof.settings[settingsScriptId].explicitList.explicitList_vocabulary = $explicitList_vocabulary.val();
setDownloadLink()
};
function kana_vocabularyChanged(e){
wkof.settings[settingsScriptId].explicitList.explicitList_kana_vocabulary = $explicitList_kana_vocabulary.val();
setDownloadLink()
};
function trad_radChanged(e){
wkof.settings[settingsScriptId].explicitList.explicitList_trad_rad = $explicitList_trad_rad.val();
setDownloadLink()
};
};
//function setDownloadLink(name, value, config){
function setDownloadLink(){
let radicalElem = $('#'+settingsScriptId+'_radical');
let kanjiElem = $('#'+settingsScriptId+'_kanji');
let vocabularyElem = $('#'+settingsScriptId+'_vocabulary');
let kana_vocabularyElem = $('#'+settingsScriptId+'_kana_vocabulary');
let trad_radElem = $('#'+settingsScriptId+'_trad_rad');
let radicals = radicalElem.val();
let kanji = kanjiElem.val();
let vocabulary = vocabularyElem.val();
let kana_vocabulary = kana_vocabularyElem.val();
let trad_rad = trad_radElem.val();
let encoded = makeEncode(radicals, kanji, vocabulary, kana_vocabulary, trad_rad);
let downloadElem = $('#'+settingsScriptId+'_itemList_link');
downloadElem.attr("href", "data:text/plain; charset=utf-8,"+encoded);
};
function makeEncode(radicals, kanji, vocabulary, kana_vocabulary, trad_rad){
let list = [];
list.push('radicals');
list.push(radicals);
list.push('kanji');
list.push(kanji);
list.push('vocabulary');
list.push(vocabulary);
list.push('kana_vocabulary');
list.push(kana_vocabulary);
list.push('traditional radicals');
list.push(trad_rad);
let text = list.join('\n');
return encodeURI("\uFEFF"+text);
};
function uploadFile(name, config, on_change){
let buttons = $(this.target).closest('.right');
buttons.find('.note').remove();
let fileElem = $("#advSearchFilters_inputFile");
let filenames = fileElem.prop('files');
if (filenames.length === 0){
buttons.append('<div class="note error">Plese select a file</div>');
return;
};
let filename = filenames[0];
let reader = new FileReader();
reader.onload = validateReception;
reader.readAsText(filename);
function validateReception(event){
let result = receiveText(event);
if (typeof result === 'string'){
buttons.find('.note').remove();
buttons.append('<div class="note error">'+result+'</div>');
};
};
function receiveText(event){
let text = event.target.result;
let radicals, kanji, vocabulary, kana_vocabulary, trad_rad;
let errorMsg = 'Invalid file content';
text = text.replaceAll('\n','');
let start = text.indexOf('radicals');
if (start !== 0) return errorMsg;
start = start + 'radicals'.length;
let end = text.indexOf('kanji');
if (end < start) return errorMsg+' radicals';
radicals = text.slice(start, end);
start = end + 'kanji'.length;
end = text.indexOf('vocabulary');
if (end < start) return errorMsg+' kanji';
kanji = text.slice(start, end);
start = end + 'vocabulary'.length;
// to support old versions from befroe the introduction of the kana_vocabulary object type
end = text.indexOf('kana_vocabulary') === -1 ? text.indexOf('traditional radicals') : text.indexOf('kana_vocabulary');
if (end < start) return errorMsg+' vocabulary';
vocabulary = text.slice(start, end);
start = end + 'kana_vocabulary'.length;
end = text.indexOf('traditional radicals');
// to support old versions from befroe the introduction of the kana_vocabulary object type
if (start < end) {kana_vocabulary = text.slice(start, end)} else {kana_vocabulary = ''};
start = end + 'traditional radicals'.length;
trad_rad = text.slice(start);
let elem = $('#'+settingsScriptId+'_radical');
elem.val(radicals);
elem.change();
elem = $('#'+settingsScriptId+'_kanji');
elem.val(kanji);
elem.change();
elem = $('#'+settingsScriptId+'_vocabulary');
elem.val(vocabulary);
elem.change();
elem = $('#'+settingsScriptId+'_kana_vocabulary');
elem.val(kana_vocabulary);
elem.change();
elem = $('#'+settingsScriptId+'_trad_rad');
elem.val(trad_rad);
elem.change();
return true;
};
};
function prepareValueExplicitList(filterValue){
let renamed = {};
renamed.radical = split_list(filterValue.explicitList_radical).map((str) => str.replace(/ /g, '-'));
renamed.kanji = split_list(filterValue.explicitList_kanji);
renamed.vocabulary = split_list(filterValue.explicitList_vocabulary);
renamed.kana_vocabulary = split_list(filterValue.explicitList_kana_vocabulary);
renamed.trad_rad = split_list(filterValue.explicitList_trad_rad);
return renamed;
};
function explicitListFilter(filterValue, item) {
let type = item.object;
if (type === 'radical') if (filterValue.radical.indexOf(item.data.slug.toLowerCase()) >= 0) return true;
if (type === 'radical') if (item.data.characters === null) return false;
if (type === 'trad_rad'){
for (let mm of item.data.meanings){
if (filterValue.trad_rad.indexOf(mm.meaning.toLowerCase()) >= 0) return true;
};
};
return filterValue[type].indexOf(item.data.characters) >= 0;
};
// END Explicit List
// BEGIN Explicit Block
let explicitBlockHover_tip = 'Enter explicit list of items to be barred from displaying.';
function registerExplicitBlockFilter() {
const registration = {
type: 'button',
label: 'Explicit Block',
default: explicitBlock_defaults,
callable_dialog: true,
on_click: explicitBlockDialog,
filter_value_map: prepareValueExplicitBlock,
filter_func: explicitBlockFilter,
set_options: function(options) { options.subjects = true; },
hover_tip: explicitBlockHover_tip,
};
wkof.ItemData.registry.sources.wk_items.filters[explicitBlockFilterName] = $.extend(true, {}, registration);
wkof.ItemData.registry.sources.wk_items.filters[explicitBlockFilterName].alternate_sources = ['trad_rad'];
wkof.ItemData.registry.sources.trad_rad.filters[explicitBlockFilterName] = $.extend(true, {}, registration);
wkof.ItemData.registry.sources.trad_rad.filters[explicitBlockFilterName].main_source = 'wk_items';
};
let explicitBlock_defaults = {explicitBlock_radical: '',
explicitBlock_kanji: '',
explicitBlock_vocabulary: '',
explicitBlock_kana_vocabulary: '',
explicitBlock_trad_rad: '',
};
function explicitBlockDialog(name, config, on_change){
let $originalDialog = $('#wkof_ds').find('[role="dialog"]');
let scriptId = config.script_id || $originalDialog.attr("aria-describedby").slice(6);
let path;
if (config.path){
path = config.path.replaceAll('@', 'wkof.settings["'+scriptId+'"].');
} else if (scriptId === 'ss_quiz') {
// Self Study Quiz doesn't define the path but we know what it is provided we find the source
let row = $(this.delegateTarget);
let panel = row.closest('[role="tabpanel"]');
let source = panel.attr('id').match(/^ss_quiz_pg_(.*)$/)[1];
path = 'wkof.settings.ss_quiz.ipresets[wkof.settings.ss_quiz.active_ipreset].content.'+source+'.filters.advSearchFilters_explicitBlock.value'
// initialize in case it is not already initialized
let v = $.extend(true, {}, get_value(path));
set_value(path, v)
} else {
throw 'config.path is not defined';
};
let value = $.extend(true, {}, explicitBlock_defaults, get_value(path));
set_value(path, value);
wkof.settings[settingsScriptId] = wkof.settings[settingsScriptId] || {};
wkof.settings[settingsScriptId].explicitBlock = $.extend(true, {}, explicitBlock_defaults, get_value(path));
// The width styling attribute must be in-line in textareas otherwise the dialog box won't be positionned properly
// at the center of the screen. In that case the save button will be outsie the visible area forcing the user to
// manually replace the dialog to access this button.
let areaIdRadical = settingsScriptId+'_radical';
let html_radical = '<textarea id="'+areaIdRadical+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdKanji = settingsScriptId+'_kanji';
let html_kanji = '<textarea id="'+areaIdKanji+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdVocabulary = settingsScriptId+'_vocabulary';
let html_vocabulary = '<textarea id="'+areaIdVocabulary+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdKana_Vocabulary = settingsScriptId+'_kana_vocabulary';
let html_kana_vocabulary = '<textarea id="'+areaIdKana_Vocabulary+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let areaIdTrad_rad = settingsScriptId+'_trad_rad';
let html_trad_rad = '<textarea id="'+areaIdTrad_rad+'" rows="2" style="width: calc(100% - 5px)"></textarea>';
let downloadText = '<button><a download="Filter Item List.txt" id="'+settingsScriptId+'_itemList_link" style="text-decoration:none;color:#000000;">Download Configured Items</a></button>';
let inputFile = '<input id="'+settingsScriptId+'_inputFile" type="file" title="Select a file for uploading items with the button above.">';
let dialogConfig = {
script_id: settingsScriptId,
title: 'Explicit List',
on_save: on_save,
on_cancel: on_cancel,
on_close: on_close,
no_bkgd: true,
settings: {explicitBlock_radical:{type: "html", html: html_radical, label: 'Radicals',},
explicitBlock_kanji:{type: "html", html: html_kanji, label: 'Kanji',},
explicitBlock_vocabulary:{type: "html", html: html_vocabulary, label: 'Vocabulary',},
explicitBlock_kana_vocabulary:{type: "html", html: html_kana_vocabulary, label: 'Kana Vocabulary',},
explicitBlock_trad_rad:{type: "html", html: html_trad_rad, label: 'Traditional Radicals',},
explicitBlock_divider:{type: 'divider'},
explicitBlock_download:{type: "html", label: ' ', html: downloadText,},
explicitBlock_divider2:{type: 'divider'},
explicitBlock_upload:{type: "button", label: 'Set Items From Selected File', on_click: uploadFileBlock,
hover_tip: 'Upload your items from a previously downloaded file.',},
explicitBlock_input:{type: 'html', html: inputFile,},
},
};
let dialog = new wkof.Settings(dialogConfig);
dialog.open();
// ui issue: do not let the calling dialog be visible
let originalDisplay = $originalDialog.css('display');
$originalDialog.css('display', 'none');
// work around some framework limitations regarding html types
let $explicitBlock_radical = $('#'+areaIdRadical);
$explicitBlock_radical.val(wkof.settings[settingsScriptId].explicitBlock.explicitBlock_radical);
$explicitBlock_radical.change(radicalChanged);
let $label = $explicitBlock_radical.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
let radicalHovertip = 'List your items separated by commas\nYour search terms must match the characters for the item exactly.\nEnglish name of radicals are accepted.';
$label.attr('title', radicalHovertip);
let $explicitBlock_kanji = $('#'+areaIdKanji);
$explicitBlock_kanji.val(wkof.settings[settingsScriptId].explicitBlock.explicitBlock_kanji);
$explicitBlock_kanji.change(kanjiChanged);
$label = $explicitBlock_kanji.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
let itemlHovertip = 'List your items separated by commas.\nYour search terms must match the characters for the item exactly.';
$label.attr('title', itemlHovertip);
let $explicitBlock_vocabulary = $('#'+areaIdVocabulary);
$explicitBlock_vocabulary.val(wkof.settings[settingsScriptId].explicitBlock.explicitBlock_vocabulary);
$explicitBlock_vocabulary.change(vocabularyChanged);
$label = $explicitBlock_vocabulary.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', itemlHovertip);
let $explicitBlock_kana_vocabulary = $('#'+areaIdKana_Vocabulary);
$explicitBlock_kana_vocabulary.val(wkof.settings[settingsScriptId].explicitBlock.explicitBlock_kana_vocabulary);
$explicitBlock_kana_vocabulary.change(kana_vocabularyChanged);
$label = $explicitBlock_kana_vocabulary.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', itemlHovertip);
let $explicitBlock_trad_rad = $('#'+areaIdTrad_rad);
$explicitBlock_trad_rad.val(wkof.settings[settingsScriptId].explicitBlock.explicitBlock_trad_rad);
$explicitBlock_trad_rad.change(trad_radChanged);
$label = $explicitBlock_trad_rad.prev();
$label.css('width', 'calc(100% - 5px)');
$label.children('label').css('text-align', 'left');
$label.attr('title', radicalHovertip);
let $download = $('#advSearchFilters_itemList_linkBlock');
$download.attr('title', 'Download Your Configured Items In a File');
setDownloadLinkBlock();
function on_close(){
$originalDialog.css('display', originalDisplay);
if (typeof config.on_close === 'function') config.on_close();
};
function on_save(){
set_value(path, get_value('wkof.settings.'+settingsScriptId+'.explicitBlock'));
if (typeof config.on_save === 'function') config.on_save();
};
function on_cancel(){
if (typeof config.on_cancel === 'function') config.on_cancel();
};
function radicalChanged(e){
wkof.settings[settingsScriptId].explicitBlock.explicitBlock_radical = $explicitBlock_radical.val();
setDownloadLinkBlock()
};
function kanjiChanged(e){
wkof.settings[settingsScriptId].explicitBlock.explicitBlock_kanji = $explicitBlock_kanji.val();
setDownloadLinkBlock()
};
function vocabularyChanged(e){
wkof.settings[settingsScriptId].explicitBlock.explicitBlock_vocabulary = $explicitBlock_vocabulary.val();
setDownloadLinkBlock()
};
function kana_vocabularyChanged(e){
wkof.settings[settingsScriptId].explicitBlock.explicitBlock_kana_vocabulary = $explicitBlock_kana_vocabulary.val();
setDownloadLinkBlock()
};
function trad_radChanged(e){
wkof.settings[settingsScriptId].explicitBlock.explicitBlock_trad_rad = $explicitBlock_trad_rad.val();
setDownloadLinkBlock()
};
};
function setDownloadLinkBlock(){
let radicalElem = $('#'+settingsScriptId+'_radicalBlock');
let kanjiElem = $('#'+settingsScriptId+'_kanjiBlock');
let vocabularyElem = $('#'+settingsScriptId+'_vocabularyBlock');
let kana_vocabularyElem = $('#'+settingsScriptId+'_kana_vocabularyBlock');
let trad_radElem = $('#'+settingsScriptId+'_trad_radBlock');
let radicals = radicalElem.val();
let kanji = kanjiElem.val();
let vocabulary = vocabularyElem.val();
let kana_vocabulary = kana_vocabularyElem.val();
let trad_rad = trad_radElem.val();
let encoded = makeEncode(radicals, kanji, vocabulary, kana_vocabulary, trad_rad);
let downloadElem = $('#'+settingsScriptId+'_itemList_linkBlock');
downloadElem.attr("href", "data:text/plain; charset=utf-8,"+encoded);
};
function uploadFileBlock(name, config, on_change){
let buttons = $(this.target).closest('.right');
buttons.find('.note').remove();
let fileElem = $("#advSearchFilters_inputFileBlock");
let filenames = fileElem.prop('files');
if (filenames.length === 0){
buttons.append('<div class="note error">Plese select a file</div>');
return;
};
let filename = filenames[0];
let reader = new FileReader();
reader.onload = validateReception;
reader.readAsText(filename);
function validateReception(event){
let result = receiveText(event);
if (typeof result === 'string'){
buttons.find('.note').remove();
buttons.append('<div class="note error">'+result+'</div>');
};
};
function receiveText(event){
let text = event.target.result;
let radicals, kanji, vocabulary, kana_vocabulary, trad_rad;
let errorMsg = 'Invalid file content';
text = text.replaceAll('\n','');
let start = text.indexOf('radicals');
if (start !== 0) return errorMsg;
start = start + 'radicals'.length;
let end = text.indexOf('kanji');
if (end < start) return errorMsg+' radicals';
radicals = text.slice(start, end);
start = end + 'kanji'.length;
end = text.indexOf('vocabulary');
if (end < start) return errorMsg+' kanji';
kanji = text.slice(start, end);
start = end + 'vocabulary'.length;
// to support old versions from befroe the introduction of the kana_vocabulary object type
end = (text.indexOf('kana_vocabulary') === -1) ? (text.indexOf('traditional radicals')) : (text.indexOf('kana_vocabulary'));
if (end < start) return errorMsg+' vocabulary';
vocabulary = text.slice(start, end);
start = end + 'kana_vocabulary'.length;
end = text.indexOf('traditional radicals');
// to support old versions from befroe the introduction of the kana_vocabulary object type
if (start < end) kana_vocabulary = text.slice(start, end);
start = end + 'traditional radicals'.length;
trad_rad = text.slice(start);
let elem = $('#'+settingsScriptId+'_radicalBlock');
elem.val(radicals);
elem.change();
elem = $('#'+settingsScriptId+'_kanjiBlock');
elem.val(kanji);
elem.change();
elem = $('#'+settingsScriptId+'_vocabularyBlock');
elem.val(vocabulary);
elem.change();
elem = $('#'+settingsScriptId+'_kana_vocabularyBlock');
elem.val(kana_vocabulary);
elem.change();
elem = $('#'+settingsScriptId+'_trad_radBlock');
elem.val(trad_rad);
elem.change();
return true;
};
};
function prepareValueExplicitBlock(filterValue){
let renamed = {};
renamed.radical = split_list(filterValue.explicitBlock_radical).map((str) => str.replace(/ /g, '-'));
renamed.kanji = split_list(filterValue.explicitBlock_kanji);
renamed.vocabulary = split_list(filterValue.explicitBlock_vocabulary);
renamed.kana_vocabulary = split_list(filterValue.explicitBlock_kana_vocabulary);
renamed.trad_rad = split_list(filterValue.explicitBlock_trad_rad);
return renamed;
};
function explicitBlockFilter(filterValue, item) {
let type = item.object;
if (type === 'radical') if (filterValue.radical.indexOf(item.data.slug.toLowerCase()) >= 0) return false;
if (type === 'radical') if (item.data.characters === null) return true;
if (type === 'trad_rad'){
for (let mm of item.data.meanings){
if (filterValue.trad_rad.indexOf(mm.meaning.toLowerCase()) >= 0) return false;
};
};
return filterValue[type].indexOf(item.data.characters) < 0;
};
// END Explicit Block
// BEGIN Keisei Semantic-Phonetic Composition
// Source @acm2010 at the Keisei Semantic Phonetic composition script
// https://community.wanikani.com/t/userscript-keisei-%E5%BD%A2%E5%A3%B0-semantic-phonetic-composition/21479
//================================================
// BEGIN code by @acm2010 released under GPL V3 or later license
//================================================
// Wrapper object for Keisei database interactions
// #############################################################################
function KeiseiDB()
{
this.KTypeEnum = Object.freeze({
unknown: 0,
hieroglyph: 1, // 象形: type of character representing pictures
indicative: 2, // 指事: indicative (kanji whose shape is based on logical representation of an abstract idea)
comp_indicative: 3, // 会意: kanji made up of meaningful parts (e.g. "mountain pass" is up + down + mountain)
comp_phonetic: 4, // 形声: kanji in which one element suggests the meaning, the other the pronunciation
derivative: 5, // 転注: applying an extended meaning to a kanji
rebus: 6, // 仮借: borrowing a kanji with the same pronunciation to convey an unrelated term
kokuji: 7, // kanji originating from Japan
shinjitai: 8, // Character was simplified from a different form (seiji)
unprocessed: 9 // not yet visited
}
);
this.wkradical_to_phon = {};
}
// #############################################################################
// #############################################################################
(function() {
"use strict";
KeiseiDB.prototype = {
constructor: KeiseiDB,
// #####################################################################
init: function()
{
this.genWKRadicalToPhon();
this.mapPhoneticsToKan(); // added by prouleau for Item Inspector
this.findSemanticForKan(); // added by prouleau for Item Inspector
},
// #####################################################################
// #####################################################################
kdata: function(kanji)
{
return this.kanji_db[kanji];
},
// #####################################################################
// #####################################################################
checkKanji: function(kanji)
{
return Boolean(this.kanji_db && (kanji in this.kanji_db));
},
// #####################################################################
// #####################################################################
checkPhonetic: function(phon)
{
return Boolean(this.phonetic_db && (phon in this.phonetic_db));
},
// #####################################################################
// #####################################################################
checkRadical: function(phon)
{
return Boolean(this.phonetic_db && this.phonetic_db[phon] && this.phonetic_db[phon][`wk-radical`]);
},
// #####################################################################
// #####################################################################
getKType: function(kanji)
{
return this.KTypeEnum[this.kanji_db[kanji].type];
},
// #####################################################################
// #####################################################################
getKItem: function(kanji)
{
if (kanji in this.kanji_db)
return this.kanji_db[kanji];
else
return {};
},
// #####################################################################
// #####################################################################
getWKItem: function(kanji)
{
if (kanji in this.wk_kanji_db)
return this.wk_kanji_db[kanji];
else
return {"level": "N/A"};
},
// #####################################################################
// #####################################################################
getKReadings: function(kanji)
{
let result = [];
if (!(kanji in this.kanji_db))
result = this.phonetic_db[kanji].readings;
else
result = this.kanji_db[kanji].readings;
if (!result.length)
return this.getWKOnyomi(kanji);
else
return result;
},
// #####################################################################
// #####################################################################
getKPhonetic: function(kanji)
{
if (kanji in this.kanji_db)
return this.kanji_db[kanji].phonetic;
else
return null;
},
// #####################################################################
// Phonetic DB functions
// #####################################################################
getPCompounds: function(phon)
{
if (phon in this.phonetic_db)
return this.phonetic_db[phon].compounds;
else
{
console.log(`The following phonetic is not in the DB!`, phon);
return [];
}
},
// #####################################################################
// added by prouleau for Item Inspector
// The phonetic field is used by @acm2010 code but it seems is not initialized
// anywhere in the code. I had to reverse engineer the initialization
// #####################################################################
mapPhoneticsToKan: function()
{
Object.keys(this.kanji_db).forEach(
(kan) => {
this.kanji_db[kan].phonetic = [];
});
Object.keys(this.phonetic_db).forEach(
(phon) => {
this.phonetic_db[phon].compounds.forEach(
(kan) => {
if (this.kanji_db[kan].phonetic.indexOf(phon) === -1 && this.checkPhonetic(phon)) this.kanji_db[kan].phonetic.push(phon);
})
})
},
// #####################################################################
// added by prouleau for Item Inspector
// The semantic field is used by @acm2010 code but it seems is not initialized
// anywhere in the code. I had to reverse engineer the initialization
// #####################################################################
findSemanticForKan: function()
{
/* Object.keys(this.kanji_db).forEach(
(kan) => {
if (!(kan in kanjiIndex)) return;
kanjiIndex[kan].data.component_subject_ids.forEach((id) => {
let rad = subjectIndexRad[id].data.characters;
if (rad === null) return;
if (this.kanji_db[kan].phonetic.indexOf(rad) === -1) this.kanji_db[kan].semantic = rad;
return})
})*/
Object.keys(this.phonetic_db).forEach(
(phon) => {
Object.keys(this.getPNonCompounds(phon)).forEach(
(kan) => {
if (this.kanji_db[kan] && this.kanji_db[kan].phonetic && this.kanji_db[kan].phonetic.indexOf(phon) === -1) this.kanji_db[kan].semantic = phon;
})
})
},
// #####################################################################
// #####################################################################
getPReadings: function(phon)
{
if (phon in this.phonetic_db)
return this.phonetic_db[phon].readings;
else
return [];
},
// #####################################################################
// Sort the readings by their importance (main readings first, then the
// readings that are at least possible, and finally readings unused in
// jouyou. Also add styles that reflect their importance.
// #####################################################################
getPReadings_style: function(phon)
{
let result = [];
const rnd_length = this.phonetic_db[phon].readings.length;
if (phon in this.phonetic_db)
{
this.phonetic_db[phon].readings.forEach(
function(reading, i)
{
let rnd_count = 0;
[phon, ...this.phonetic_db[phon].compounds].forEach(
function(compound, j)
{
if (!(compound in this.kanji_db))
return;
// Give a bonus to earlier readings in the phonetic's list
if (this.kanji_db[compound].readings[0] === reading)
rnd_count += 10 + (rnd_length-i);
else if (this.kanji_db[compound].readings.includes(reading))
rnd_count += 2 + (rnd_length-i);
},
this
);
if (!rnd_count)
result.push([rnd_count, `<span class="keisei_obscure_reading">${reading}</span>`]);
else if (rnd_count < 10)
result.push([rnd_count, `<span class="keisei_alternative_reading">${reading}</span>`]);
else
result.push([rnd_count, `<span class="keisei_main_reading">${reading}</span>`]);
},
this
);
}
return result.sort((a,b)=>b[0]-a[0]).map((d)=>d[1]);
},
// #####################################################################
// #####################################################################
getPXRefs: function(phon)
{
if (phon in this.phonetic_db)
return this.phonetic_db[phon].xrefs;
else
return [];
},
// #####################################################################
// #####################################################################
getPNonCompounds: function(phon)
{
if (phon in this.phonetic_db)
return this.phonetic_db[phon].non_compounds;
else
return [];
},
// #####################################################################
genWKRadicalToPhon: function()
{
Object.keys(this.phonetic_db).forEach(
function(phon) {
const data = this.phonetic_db[phon];
this.wkradical_to_phon[data[`wk-radical`]] = phon;
this.wkradical_to_phon[phon] = phon;
},
this
);
},
// #####################################################################
mapWKRadicalToPhon: function(radical)
{
if (this.wkradical_to_phon && radical in this.wkradical_to_phon)
return this.wkradical_to_phon[radical];
else
return null;
},
// #####################################################################
getWKRadical: function(phon)
{
return this.phonetic_db[phon][`wk-radical`];
},
// #####################################################################
getWKRadicalPP: function(phon)
{
if (this.phonetic_db && this.phonetic_db[phon][`wk-radical`])
return _.startCase(this.phonetic_db[phon][`wk-radical`]);
else
return `Not in WK`;
},
// #####################################################################
// #####################################################################
isKanjiInWK: function(kanji)
{
return Boolean(this.wk_kanji_db && (kanji in this.wk_kanji_db));
},
// #####################################################################
// #####################################################################
isFirstReadingInWK: function(kanji)
{
if (this.isKanjiInWK(kanji))
{
const onyomi = this.getWKOnyomi(kanji);
return (onyomi.includes(this.getKReadings(kanji)[0]));
}
else
return true;
},
// #####################################################################
// Check if a kanji is considered obscure in regard to its phonetic,
// ie, the kanji doesn't look like it has the phonetic at all.
// #####################################################################
isPObscure: function(kanji)
{
return Boolean(this.kanji_db && (`obscure` in this.kanji_db[kanji]));
},
// #####################################################################
getWKOnyomi: function(kanji)
{
if (this.isKanjiInWK(kanji))
{
if (this.wk_kanji_db[kanji].onyomi !== null)
return _.map(this.wk_kanji_db[kanji].onyomi.split(`,`), _.trim);
else
return [];
}
else
return [`*DB Error*`];
},
// #####################################################################
getWKKMeanings: function(kanji)
{
let result = [];
if (this.isKanjiInWK(kanji))
result = _.map(this.wk_kanji_db[kanji].meaning.split(`,`), _.trim);
else
{
if (this.kanji_db[kanji] === undefined)
{
console.log(`The kanji ${kanji} is not found in the DB!`);
result = [];
}
else
result = this.kanji_db[kanji].meanings;
}
return result.map(_.startCase);
},
// #####################################################################
// #####################################################################
deRendaku: function(reading)
{
let result = reading;
const rendaku = `がぎぐげござじずぜぞだぢちづでどばびぶべぼぱぴぷぺぽ`;
const vanilla = `かきくけこさしすせそたちしつてとはひふへほはひふへほ`;
const pattern = _.zip(_.split(rendaku, ``), _.split(vanilla, ``));
pattern.push([`じょ`, `よ`]);
pattern.push([`じゃ`, `や`]);
pattern.push([`じゅ`, `ゆ`]);
_.forEach(pattern,
([from_kana, to_kana]) =>
result = result.replace(from_kana, to_kana)
);
return result;
},
// #####################################################################
// #####################################################################
// Support for additional pages listing all tone mark
// #####################################################################
// #####################################################################
getPhoneticsByCompCount: function()
{
let result = new Map();
_.forEach(this.phonetic_db,
(p_item, phon) => {
const comp_len = p_item.compounds.length;
if (result.has(comp_len))
result.get(comp_len).push(phon);
else
result.set(comp_len, [phon]);
}
);
result.forEach(
(phons, count) => {
result.set(count, phons.sort(
(a,b)=>a.localeCompare(b, "ja-u-co-unihan")));
}
);
return new Map([...result.entries()].sort((a,b)=>b[0]-a[0]));
},
// #####################################################################
// #####################################################################
getPhoneticsByHeader: function()
{
const initials_d = new Map([
[`あ`, `あいうえお`],
[`か`, `かきくけこがぎぐげご`],
[`さ`, `さしすせそざじずぜぞ`],
[`た`, `たちつてとだぢづでど`],
[`な`, `なにぬねの`],
[`は`, `はひふへほばびぶべぼ`],
[`ま`, `まみむめも`],
[`ら`, `らりるれろ`],
[`や`, `やゆよ`],
[`わ`, `わを`]
]);
let result = new Map();
initials_d.forEach(
(subheaders, header) => {
result.set(header, new Map());
_.forEach(subheaders,
(subheader) => {
const sub_result = [];
_.forEach(this.phonetic_db,
(p_item, phon) => {
if (!p_item.readings.length)
return;
if (subheader.match(p_item.readings[0][0]))
sub_result.push([2*p_item.compounds.length +
(p_item["wk-radical"]?1:0),
phon]);
}
);
result.get(header).set(subheader,
sub_result
.sort((a,b)=>b[0]-a[0])
.map((d)=>d[1]));
}
);
},
this
);
return result;
}
};
// #########################################################################
}
());
// #############################################################################
//================================================
// END code by @acm2010 released under GPL V3 or later license
//================================================
let nonPhoneticTypes = {};
function initNonPhoneticTypes() {
nonPhoneticTypes[keiseiDB.KTypeEnum.hieroglyph] = true;
nonPhoneticTypes[keiseiDB.KTypeEnum.indicative] = true;
nonPhoneticTypes[keiseiDB.KTypeEnum.comp_indicative] = true;
nonPhoneticTypes[keiseiDB.KTypeEnum.rebus] = true;
nonPhoneticTypes[keiseiDB.KTypeEnum.kokuji] = true;
nonPhoneticTypes[keiseiDB.KTypeEnum.shinjitai] = true;
};
let keiseiDB;
// function to be invoked in the startup sequence
function loadKeiseiDatabase(){
if (typeof advSearchFilters.kanji_db === 'object'){
KeiseiDB.prototype.kanji_db = advSearchFilters.kanji_db;
KeiseiDB.prototype.phonetic_db = advSearchFilters.phonetic_db;
KeiseiDB.prototype.wk_kanji_db = advSearchFilters.wk_kanji_db;
keiseiDB = new KeiseiDB();
keiseiDB.init();
prepareKeiseiIndexes();
return Promise.resolve();
} else {
let promiseList = [];
promiseList.push(load_file(kanji_db, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessKanjiDB(data);}));
promiseList.push(load_file(phonetic_db, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessPhoneticDB(data);}));
promiseList.push(load_file(wk_kanji_db, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessWkKanjiDB(data);}));
return Promise.all(promiseList).then(x => {keiseiDB = new KeiseiDB(); keiseiDB.init(); prepareKeiseiIndexes()});
};
};
function lzmaDecompressAndProcessKanjiDB(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
KeiseiDB.prototype.kanji_db = JSON.parse(string);
advSearchFilters.kanji_db = KeiseiDB.prototype.kanji_db;
};
function lzmaDecompressAndProcessPhoneticDB(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
KeiseiDB.prototype.phonetic_db = JSON.parse(string);
advSearchFilters.phonetic_db = KeiseiDB.prototype.phonetic_db;
};
function lzmaDecompressAndProcessWkKanjiDB(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
KeiseiDB.prototype.wk_kanji_db = JSON.parse(string);
advSearchFilters.wk_kanji_db = KeiseiDB.prototype.wk_kanji_db;
};
function deleteKeiseiCache(){
wkof.file_cache.delete(kanji_db);
wkof.file_cache.delete(phonetic_db);
wkof.file_cache.delete(wk_kanji_db);
};
var keiseiCompoundIndex = {}
var keiseiNonCompoundIndex = {}
var keiseiRelatedMarksIndex = {}
function prepareKeiseiIndexes(){
let phonetic = KeiseiDB.prototype.phonetic_db;
for (let kanji in componentIndexKan){
if ((typeof KeiseiDB.prototype.phonetic_db[kanji]) === 'object'){
keiseiCompoundIndex[kanji] = phonetic[kanji].compounds;
keiseiNonCompoundIndex[kanji] = phonetic[kanji].non_compounds;
keiseiRelatedMarksIndex[kanji] = phonetic[kanji].xrefs;
};
};
for (let radical in usedInIndexRad){
if ((typeof KeiseiDB.prototype.phonetic_db[radical]) === 'object'){
keiseiCompoundIndex[radical] = phonetic[radical].compounds;
keiseiNonCompoundIndex[radical] = phonetic[radical].non_compounds;
keiseiRelatedMarksIndex[radical] = phonetic[radical].xrefs;
};
};
};
// END Keisei Semantic-Phonetic Composition
// BEGIN Lars Yencken Visually Similar Kanji
var visuallySimilarData;
function get_visually_similar_data(){
if (typeof advSearchFilters.visuallySimilarData === 'object'){
visuallySimilarData = advSearchFilters.visuallySimilarData;
return Promise.resolve();
} else {
return load_file(visuallySimilarFilename, true, {responseType: "arraybuffer"})
.then(function(data){lzmaDecompressAndProcessVisuallySimilar(data)});
};
};
function lzmaDecompressAndProcessVisuallySimilar(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
visuallySimilarData = JSON.parse(string);
advSearchFilters.visuallySimilarData = visuallySimilarData;
};
function deleteVisuallySimilarCache(){
wkof.file_cache.delete(visuallySimilarFilename);
};
// END Lars Yencken Visually Similar Kanji
// BEGIN Niai Visually Similar Kanji
// Source @acm2010 at the Niai Visually Similar script
// https://community.wanikani.com/t/userscript-niai-%E4%BC%BC%E5%90%88%E3%81%84-visually-similar-kanji/23325
// https://github.com/mwil/wanikani-userscripts/tree/master/wanikani-similar-kanji
// Niai also uses Lars Yencken visually similar data
var from_keisei_db;
var old_script_db;
var wk_niai_noto_db;
var yl_radical_db;
function loadNiaiDatabase(){
if (typeof advSearchFilters.from_keisei_db === 'object'){
from_keisei_db = advSearchFilters.from_keisei_db;
old_script_db = advSearchFilters.old_script_db;
wk_niai_noto_db = advSearchFilters.wk_niai_noto_db;
yl_radical_db = advSearchFilters.yl_radical_db;
return Promise.resolve();
} else {
let promiseList = [];
promiseList.push(load_file(from_keisei_db_filename, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessNiai_from_keisei_db(data);}));
promiseList.push(load_file(old_script_db_filename, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessNiai_old_script_db(data);}));
promiseList.push(load_file(wk_niai_noto_db_filename, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessNiai_wk_niai_noto_db(data);}));
promiseList.push(load_file(yl_radical_db_filename, true, {responseType: "arraybuffer"}).then(data => {lzmaDecompressAndProcessNiai_yl_radical_db(data);}));
return Promise.all(promiseList);
};
};
function lzmaDecompressAndProcessNiai_from_keisei_db(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
from_keisei_db = JSON.parse(string);
advSearchFilters.from_keisei_db = from_keisei_db;
};
function lzmaDecompressAndProcessNiai_old_script_db(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
old_script_db = JSON.parse(string);
advSearchFilters.old_script_db = old_script_db;
};
function lzmaDecompressAndProcessNiai_wk_niai_noto_db(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
wk_niai_noto_db = JSON.parse(string);
advSearchFilters.wk_niai_noto_db = wk_niai_noto_db;
};
function lzmaDecompressAndProcessNiai_yl_radical_db(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
yl_radical_db = JSON.parse(string);
advSearchFilters.yl_radical_db = yl_radical_db;
};
function deleteNiaiCache(){
wkof.file_cache.delete(from_keisei_db_filename);
wkof.file_cache.delete(old_script_db_filename);
wkof.file_cache.delete(wk_niai_noto_db_filename);
wkof.file_cache.delete(yl_radical_db_filename);
};
// END Niai Visually Similar Kanji
//=============================================================================
// Begin code lifted from wkof core module and adapted to transfer binary data
// Must be licensed under MIT license
//=============================================================================
//------------------------------
// Load a file asynchronously, and pass the file as resolved Promise data.
//------------------------------
function promise(){var a,b,c=new Promise(function(d,e){a=d;b=e;});c.resolve=a;c.reject=b;return c;};
function load_file(url, use_cache, options) {
var fetch_promise = promise();
var no_cache = split_list(localStorage.getItem('wkof.load_file.nocache') || '');
if (no_cache.indexOf(url) >= 0 || no_cache.indexOf('*') >= 0) use_cache = false;
if (use_cache === true) {
return wkof.file_cache.load(url, use_cache).catch(fetch_url);
} else {
return fetch_url();
};
// Retrieve file from server
function fetch_url(){
var request = new XMLHttpRequest();
request.onreadystatechange = process_result;
request.open('GET', url, true);
if (options.responseType) request.responseType = options.responseType;
request.send();
return fetch_promise;
};
function process_result(event){
if (event.target.readyState !== 4) return;
if (event.target.status >= 400 || event.target.status === 0) return fetch_promise.reject(event.target.status);
if (use_cache) {
wkof.file_cache.save(url, event.target.response)
.then(fetch_promise.resolve.bind(null,event.target.response));
} else {
fetch_promise.resolve(event.target.response);
};
};
};
//===========================================================================
// End code lifted from wkof core module and adapted to transfer binary data
//===========================================================================
// ----------------------------------------------------------------------
// Kanjidic2 data
// ----------------------------------------------------------------------
var kanjidic2Data;
function loadKanjidic2(){
if (typeof advSearchFilters.kanjidic2Data === 'object'){
kanjidic2Data = advSearchFilters.kanjidic2Data;
return Promise.resolve();
} else {
return load_file(kanjidic2File, true, {responseType: "arraybuffer"})
.then(function(data){lzmaDecompressAndProcessKanjidic2(data)})
}
};
function lzmaDecompressAndProcessKanjidic2(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
kanjidic2Data = JSON.parse(string);
advSearchFilters.kanjidic2Data = kanjidic2Data;
};
function kanjidic2_cacheDelete(){
return wkof.file_cache.delete(kanjidic2File);
};
// converts the stream to a javascript string
// the native stream.toString() methof of the LZMA
// script is abysmally slow and a major source of latency
function streamToString(outStream){
var buffers = outStream.buffers, charList = [];
if (window.TextDecoder){
// TextDecoder supported - do it the fast way
var decoder = new TextDecoder();
for (let n = 0, nL = buffers.length; n < nL; n++) {
charList.push(decoder.decode(buffers[n]));
}
} else {
// TextDecoder unsupported - do it but a bit slower
var conv = String.fromCharCode;
for (let n = 0, nL = buffers.length; n < nL; n++) {
var buf = buffers[n];
for (var i = 0, iL = buf.length; i < iL; i++) {
charList.push(conv(buf[i]));
};
};
};
return charList.join('');
};
// ----------------------------------------------------------------------
// Stroke Count for Wanikani radicals
// ----------------------------------------------------------------------
var WkStrokeCountData;
function loadStrokeCount(){
// check if Item Inspector has already loaded the data
if (typeof (advSearchFilters) === 'object' && (WkStrokeCountData in advSearchFilters)){
WkStrokeCountData = advSearchFilters.WkStrokeCountData;
return Promise.resolve();
};
return load_file(Wkit_StrokeCountforRadicals, true, {responseType: "arraybuffer"})
.then(function(data){lzmaDecompressAndProcessWkStrokeCount(data)})
};
function lzmaDecompressAndProcessWkStrokeCount(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
WkStrokeCountData = JSON.parse(string);
// publish data to Item Inspectort
if (typeof advSearchFilters === 'object'){
advSearchFilters.WkStrokeCountData = WkStrokeCountData;
};
};
function WkStrokeCount_cacheDelete(){
return wkof.file_cache.delete(Wkit_StrokeCountforRadicals);
};
// ----------------------------------------------------------------------
// A series of functions to add traditional radicals as a source of items
// ----------------------------------------------------------------------
var traditionalRadicals;
var traditionalRadicalsItems;
function loadTraditionalRadicals(){
if (typeof advSearchFilters.traditionalRadicalsItems === 'object'){
traditionalRadicals = advSearchFilters.traditionalRadicals;
trad_rad_postLoadProcess();
return Promise.resolve();
} else {
return load_file(traditionaRadicalsFile, true, {responseType: "arraybuffer"})
.then(function(data){lzmaDecompressAndProcessTradRad(data);
advSearchFilters.traditionalRadicals = traditionalRadicals;
trad_rad_postLoadProcess();})
};
};
function lzmaDecompressAndProcessTradRad(data){
let inStream = new LZMA.iStream(data);
let outStream = LZMA.decompressFile(inStream);
let string = streamToString(outStream);
traditionalRadicals = JSON.parse(string);
};
function trad_rad_postLoadProcess(){
traditionalRadicalsItems = Object.values(traditionalRadicals);
prepareTradRadIndex();
};
function trad_rad_cacheDelete(){
return wkof.file_cache.delete(traditionaRadicalsFile);
};
function tradRadFetcher(){
return Promise.resolve(traditionalRadicalsItems);
};
function registerTraditionalRadicals(){
if (typeof wkof.ItemData.registry.sources.trad_rad === 'undefined'){
wkof.ItemData.registry.sources.trad_rad = {
description: 'Traditional Radicals',
fetcher: tradRadFetcher,
options: {},
filters: {},
};
};
return Promise.resolve();
};
var kanjiwithTradRadbyName = {};
var tradRadInKanji = {};
function prepareTradRadIndex(){
for (let item of traditionalRadicalsItems){
for (let mm of item.data.meanings){
let meaning = mm.meaning;
if (typeof kanjiwithTradRadbyName[meaning] !== 'object') kanjiwithTradRadbyName[meaning] = [];
kanjiwithTradRadbyName[meaning] = kanjiwithTradRadbyName[meaning].concat(item.data.kanji);
};
for (let kanji of item.data.kanji){
if (typeof tradRadInKanji[kanji] !== 'object') tradRadInKanji[kanji] = [];
tradRadInKanji[kanji].push(item.data.characters);
};
};
};
//-------------------------------------------
// Load prerequisite scripts
function loadPrerequisiteScripts(){
let promiseList = [];
promiseList.push(wkof.load_script(lodash_file, true).then(function(){return wkof.wait_state('Wkit_lodash','Ready')}));
promiseList.push(wkof.load_script(lzma_file, true).then(function(){return wkof.wait_state('Wkit_lzma','Ready')}));
promiseList.push(wkof.load_script(lzma_shim_file, true).then(function(){return wkof.wait_state('Wkit_lzma_shim','Ready')}));
return Promise.all(promiseList);
};
//------------------------------------------------------------------------------
// To reduce latency non Wanikani data is loaded on demand.
// Variables dataRequired and dataLoaded track which data is required and loaded
var dataRequired = {larsYencken: false, niai: false, keisei: false, kanjidic2: false, items: false, WkStrokeCountData: false,};
var dataLoaded = {larsYencken: false, niai: false, keisei: false, kanjidic2: false, items: false, WkStrokeCountData: false,};
function loadMissingData(){
let promiseList = [];
if (dataRequired.larsYencken && !dataLoaded.larsYencken) {
promiseList.push(get_visually_similar_data().then(function(){dataLoaded.larsYencken = true;}));
};
if (dataRequired.niai && !dataLoaded.niai) {
promiseList.push(loadNiaiDatabase().then(function(){dataLoaded.niai = true;}));
};
if (dataRequired.keisei && !dataLoaded.keisei) {
promiseList.push(loadKeiseiDatabase().then(function(){prepareKeiseiIndexes(); dataLoaded.keisei = true;}));
};
if (dataRequired.kanjidic2 && !dataLoaded.kanjidic2) {
promiseList.push(loadKanjidic2().then(function(){dataLoaded.kanjidic2 = true;}));
};
if (dataRequired.WkStrokeCountData && !dataLoaded.WkStrokeCountData) {
promiseList.push(loadStrokeCount().then(function(){dataLoaded.WkStrokeCountData = true;}));
};
// Items must always be loaded first because they are prerequisites to indexes
if (!dataLoaded.items) {
dataLoaded.items = true; // must be called first, otherwise the data will be loaded twice - once for each data source
let config = {wk_items: {filters:{}, options:{'subjects': true,}}};
return wkof.ItemData.get_items(config)
.then(prepareIndex)
.then(loadTraditionalRadicals)
.then(function(){return Promise.all(promiseList)})
.then(function(){wkof.set_state('advSearchFilters_items', 'ready')})
} else {
return wkof.wait_state('advSearchFilters_items', 'ready')
.then(function(){return Promise.all(promiseList)})
};
};
startupSequence();
})(window.wkof);