您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tool to mark and track individual radicals/kanji/vocabulary
- // ==UserScript==
- // @name WaniKani Item Marker
- // @description Tool to mark and track individual radicals/kanji/vocabulary
- // @namespace irx.wanikani.marker
- // @include https://www.wanikani.com/*
- // @version 4.0
- // @copyright 2017, Ingo Radax
- // @license MIT; http://opensource.org/licenses/MIT
- // @grant none
- // @run-at document-start
- // ==/UserScript==
- var WaniKani = (function() {
- var local_storage_prefix = 'wk_toolkit_';
- var api_key = null;
- var radical_data = null;
- var kanji_data = null;
- var vocabulary_data = null;
- var radicals_with_image = [];
- function log(msg) {
- console.log(msg);
- }
- function is_on_wanikani() {
- return (window.location.host == 'www.wanikani.com');
- }
- function is_on_dashboard() {
- return is_on_wanikani() && ((window.location.pathname == '/dashboard') || (window.location.pathname == '/'));
- }
- function is_on_review_session_page() {
- return is_on_wanikani() && (window.location.pathname == '/review/session');
- }
- function is_on_review_page() {
- return is_on_wanikani() && (window.location.pathname == '/review');
- }
- function is_on_lesson_session_page() {
- return is_on_wanikani() && (window.location.pathname == '/lesson/session');
- }
- function is_on_lesson_page() {
- return is_on_wanikani() && (window.location.pathname == '/lesson');
- }
- function is_on_lattice_radicals_meaning() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/radicals/meaning');
- }
- function is_on_lattice_radicals_progress() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/radicals/progress');
- }
- function is_on_lattice_kanji_combined() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/combined');
- }
- function is_on_lattice_kanji_meaning() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/meaning');
- }
- function is_on_lattice_kanji_reading() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/reading');
- }
- function is_on_lattice_kanji_progress() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/kanji/status');
- }
- function is_on_lattice_vocabulary_combined() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/combined');
- }
- function is_on_lattice_vocabulary_meaning() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/meaning');
- }
- function is_on_lattice_vocabulary_reading() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/reading');
- }
- function is_on_lattice_vocabulary_progress() {
- return is_on_wanikani() && (window.location.pathname == '/lattice/vocabulary/status');
- }
- //-------------------------------------------------------------------
- // Try to parse the url and detect if it belongs to a single item.
- // e.g. 'https://www.wanikani.com/level/1/radicals/construction'
- // will be parsed as 'radicals' and 'construction'
- //-------------------------------------------------------------------
- function parse_item_url(url) {
- if ((url == null) || (url == undefined)) {
- return null;
- }
- if (url.indexOf('/lattice/') > -1) {
- return null;
- }
- url = decodeURI(url);
- var parsed = /.*\/(radicals|kanji|vocabulary)\/(.+)/.exec(url);
- if (parsed) {
- return {type:parsed[1], name:parsed[2]};
- }
- else {
- return null;
- }
- }
- function reset_radical_data(resetLocalStorage) {
- radical_data = null;
- if (resetLocalStorage) {
- localStorage.removeItem(local_storage_prefix + 'api_user_radicals');
- localStorage.removeItem(local_storage_prefix + 'api_user_radicals_fetch_time');
- }
- }
- function reset_kanji_data(resetLocalStorage) {
- kanji_data = null;
- if (resetLocalStorage) {
- localStorage.removeItem(local_storage_prefix + 'api_user_kanji');
- localStorage.removeItem(local_storage_prefix + 'api_user_kanji_fetch_time');
- }
- }
- function reset_vocabulary_data(resetLocalStorage) {
- vocabulary_data = null;
- if (resetLocalStorage) {
- localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary');
- localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary_fetch_time');
- }
- }
- function clear_local_storage() {
- localStorage.removeItem(local_storage_prefix + 'last_review_time');
- localStorage.removeItem(local_storage_prefix + 'next_review_time');
- localStorage.removeItem(local_storage_prefix + 'last_unlock_time');
- localStorage.removeItem(local_storage_prefix + 'api_key');
- localStorage.removeItem(local_storage_prefix + 'api_user_radicals');
- localStorage.removeItem(local_storage_prefix + 'api_user_radicals_fetch_time');
- localStorage.removeItem(local_storage_prefix + 'api_user_kanji');
- localStorage.removeItem(local_storage_prefix + 'api_user_kanji_fetch_time');
- localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary');
- localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary_fetch_time');
- }
- function track_times() {
- if (is_on_review_session_page()) {
- localStorage.setItem(local_storage_prefix + 'last_review_time', now());
- var lastUnlockTime = new Date($('.recent-unlocks time:nth(0)').attr('datetime'))/1000;
- localStorage.setItem(local_storage_prefix + 'last_unlock_time', now());
- }
- if (is_on_dashboard()) {
- var next_review = Number($('.review-status .timeago').attr('datetime'));
- // Workaround for "WaniKani Real Times" script, which deletes the element we were looking for above.
- if (isNaN(next_review)) {
- next_review = Number($('.review-status time1').attr('datetime'));
- // Conditional divide-by-1000, in case someone fixed this error in Real Times script.
- if (next_review > 10000000000) next_review /= 1000;
- }
- localStorage.setItem(local_storage_prefix + 'next_review_time', next_review);
- }
- }
- function get_last_review_time() {
- return Number(localStorage.getItem(local_storage_prefix + 'last_review_time') || 0);
- }
- function get_next_review_time() {
- return Number(localStorage.getItem(local_storage_prefix + 'next_review_time') || 0);
- }
- function get_last_unlock_time() {
- return Number(localStorage.getItem(local_storage_prefix + 'last_unlock_time') || 0);
- }
- function now() {
- return Math.floor(new Date() / 1000);
- }
- function ajax_retry(url, retries, timeout) {
- retries = retries || 2;
- timeout = timeout || 3000;
- function action(resolve, reject) {
- $.ajax({
- url: url,
- timeout: timeout
- })
- .done(function(data, status){
- if (status === 'success')
- resolve(data);
- else
- reject();
- })
- .fail(function(xhr, status, error){
- if (status === 'error' && --retries > 0)
- action(resolve, reject);
- else
- reject();
- });
- }
- return new Promise(action);
- }
- function query_page_radicals_with_images() {
- return new Promise(function(resolve, reject) {
- ajax_retry('/lattice/radicals/meaning').then(function(page) {
- if (typeof page !== 'string') {return reject();}
- page = $(page);
- radicals_with_image = [];
- page.find('li').each(function(i, item) {
- var attr = $(this).attr('id');
- if (attr && attr.startsWith('radical-')) {
- var radicalChar = $(this).text();
- var href = $(this).find('a').attr('href');
- var parsedHref = WaniKani.parse_item_url(href);
- var image = $(this).find('img');
- var imageSrc = '';
- if (image.length > 0) {
- imageSrc = image.attr('src');
- }
- radicals_with_image.push({ name: parsedHref.name, character: radicalChar, image: imageSrc });
- }
- });
- resolve();
- },function(result) {
- reject(new Error('Failed to query page!'));
- });
- });
- }
- function get_radicals_with_image() {
- return radicals_with_image;
- }
- function get_api_key() {
- return new Promise(function(resolve, reject) {
- api_key = localStorage.getItem(local_storage_prefix + 'api_key');
- if (typeof api_key === 'string' && api_key.length == 32) {
- log("Already having API key");
- return resolve();
- }
- log("Loading API key");
- ajax_retry('/account').then(function(page) {
- log("Loading API key ... SUCCESS");
- // --[ SUCCESS ]----------------------
- // Make sure what we got is a web page.
- if (typeof page !== 'string') {return reject();}
- // Extract the user name.
- page = $(page);
- // Extract the API key.
- api_key = page.find('#api-button').parent().find('input').attr('value');
- if (typeof api_key !== 'string' || api_key.length !== 32) {return reject();}
- localStorage.setItem(local_storage_prefix + 'api_key', api_key);
- resolve();
- },function(result) {
- log("Loading API key ... ERROR");
- // --[ FAIL ]-------------------------
- reject(new Error('Failed to fetch API key!'));
- });
- });
- }
- function call_api_user_radicals() {
- return new Promise(function(resolve, reject) {
- log("Calling API: User radicals");
- $.getJSON('/api/user/' + api_key + '/radicals/', function(json){
- if (json.error && json.error.code === 'user_not_found') {
- log("Calling API: User radicals ... ERROR")
- reset_radical_data(true);
- location.reload();
- reject();
- return;
- }
- log("Calling API: User radicals ... SUCCESS");
- localStorage.setItem(local_storage_prefix + 'api_user_radicals', JSON.stringify(json));
- localStorage.setItem(local_storage_prefix + 'api_user_radicals_fetch_time', now());
- radical_data = json;
- resolve();
- });
- });
- }
- function call_api_user_kanji() {
- return new Promise(function(resolve, reject) {
- log("Calling API: User kanji");
- $.getJSON('/api/user/' + api_key + '/kanji/', function(json){
- if (json.error && json.error.code === 'user_not_found') {
- log("Calling API: User kanji ... ERROR")
- reset_kanji_data(true);
- location.reload();
- reject();
- return;
- }
- log("Calling API: User kanji ... SUCCESS");
- localStorage.setItem(local_storage_prefix + 'api_user_kanji', JSON.stringify(json));
- localStorage.setItem(local_storage_prefix + 'api_user_kanji_fetch_time', now());
- kanji_data = json;
- resolve();
- });
- });
- }
- function call_api_user_vocabulary() {
- return new Promise(function(resolve, reject) {
- log("Calling API: User vocabulary");
- $.getJSON('/api/user/' + api_key + '/vocabulary/', function(json){
- if (json.error && json.error.code === 'user_not_found') {
- log("Calling API: User vocabulary ... ERROR")
- reset_vocabulary_data(true);
- location.reload();
- reject();
- return;
- }
- log("Calling API: User vocabulary ... SUCCESS");
- localStorage.setItem(local_storage_prefix + 'api_user_vocabulary', JSON.stringify(json));
- localStorage.setItem(local_storage_prefix + 'api_user_vocabulary_fetch_time', now());
- vocabulary_data = json;
- resolve();
- });
- });
- }
- function get_last_fetch_time_api_user_radicals() {
- return Number(localStorage.getItem(local_storage_prefix + 'api_user_radicals_fetch_time'));
- }
- function get_last_fetch_time_api_user_kanji() {
- return Number(localStorage.getItem(local_storage_prefix + 'api_user_kanji_fetch_time'));
- }
- function get_last_fetch_time_api_user_vocabulary() {
- return Number(localStorage.getItem(local_storage_prefix + 'api_user_vocabulary_fetch_time'));
- }
- function load_radical_data() {
- var next_review_time = get_next_review_time();
- var last_review_time = get_last_review_time();
- var last_unlock_time = get_last_unlock_time();
- var last_fetch_time = get_last_fetch_time_api_user_radicals();
- if ((last_fetch_time <= last_unlock_time) ||
- (last_fetch_time <= last_review_time) ||
- ((next_review_time < now()) && (last_fetch_time <= (now() - 3600)))) {
- log("Clearing previous fetched radical data");
- radical_data = null;
- localStorage.removeItem(local_storage_prefix + 'api_user_radicals');
- localStorage.removeItem(local_storage_prefix + 'api_user_radicals_fetch_time');
- }
- if (radical_data == null) {
- var stringified = localStorage.getItem(local_storage_prefix + 'api_user_radicals');
- if (stringified != null) {
- log("Radical data loaded from local storage");
- radical_data = JSON.parse(stringified);
- }
- }
- if (radical_data != null) {
- log("Radical data already loaded");
- return Promise.resolve();
- }
- return new Promise(function(resolve, reject) {
- get_api_key()
- .then(call_api_user_radicals)
- .then(function() {
- resolve();
- });
- });
- }
- function load_kanji_data() {
- var next_review_time = get_next_review_time();
- var last_review_time = get_last_review_time();
- var last_unlock_time = get_last_unlock_time();
- var last_fetch_time = get_last_fetch_time_api_user_kanji();
- if ((last_fetch_time <= last_unlock_time) ||
- (last_fetch_time <= last_review_time) ||
- ((next_review_time < now()) && (last_fetch_time <= (now() - 3600)))) {
- log("Clearing previous fetched kanji data");
- kanji_data = null;
- localStorage.removeItem(local_storage_prefix + 'api_user_kanji');
- localStorage.removeItem(local_storage_prefix + 'api_user_kanji_fetch_time');
- }
- if (kanji_data == null) {
- var stringified = localStorage.getItem(local_storage_prefix + 'api_user_kanji');
- if (stringified != null) {
- log("Kanji data loaded from local storage");
- kanji_data = JSON.parse(stringified);
- }
- }
- if (kanji_data != null) {
- log("Kanji data already loaded");
- return Promise.resolve();
- }
- return new Promise(function(resolve, reject) {
- get_api_key()
- .then(call_api_user_kanji)
- .then(function() {
- resolve();
- });
- });
- }
- function load_vocabulary_data() {
- var next_review_time = get_next_review_time();
- var last_review_time = get_last_review_time();
- var last_unlock_time = get_last_unlock_time();
- var last_fetch_time = get_last_fetch_time_api_user_vocabulary();
- if ((last_fetch_time <= last_unlock_time) ||
- (last_fetch_time <= last_review_time) ||
- ((next_review_time < now()) && (last_fetch_time <= (now() - 3600)))) {
- log("Clearing previous fetched vocabulary data");
- vocabulary_data = null;
- localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary');
- localStorage.removeItem(local_storage_prefix + 'api_user_vocabulary_fetch_time');
- }
- if (vocabulary_data == null) {
- var stringified = localStorage.getItem(local_storage_prefix + 'api_user_vocabulary');
- if (stringified != null) {
- log("Vocabulary data loaded from local storage");
- vocabulary_data = JSON.parse(stringified);
- }
- }
- if (vocabulary_data != null) {
- log("Vocabulary data already loaded");
- return Promise.resolve();
- }
- return new Promise(function(resolve, reject) {
- get_api_key()
- .then(call_api_user_vocabulary)
- .then(function() {
- resolve();
- });
- });
- }
- function get_radical_data() {
- return radical_data;
- }
- function get_kanji_data() {
- return kanji_data;
- }
- function get_vocabulary_data() {
- return vocabulary_data;
- }
- function find_radical(meaning) {
- if (radical_data == null) {
- return null;
- }
- var numRadicals = radical_data.requested_information.length;
- for (var i = 0; i < numRadicals; i++) {
- if (radical_data.requested_information[i].meaning == meaning) {
- return radical_data.requested_information[i];
- }
- }
- return null;
- }
- function find_kanji(character) {
- if (kanji_data == null) {
- return null;
- }
- var numKanji = kanji_data.requested_information.length;
- for (var i = 0; i < numKanji; i++) {
- if (kanji_data.requested_information[i].character == character) {
- return kanji_data.requested_information[i];
- }
- }
- return null;
- }
- function find_vocabulary(character) {
- if (vocabulary_data == null) {
- return null;
- }
- var numVocabulary = vocabulary_data.requested_information.general.length;
- for (var i = 0; i < numVocabulary; i++) {
- if (vocabulary_data.requested_information.general[i].character == character) {
- return vocabulary_data.requested_information.general[i];
- }
- }
- return null;
- }
- function find_item(type, name) {
- if (type == 'radicals') {
- return find_radical(name);
- }
- else if(type == 'kanji') {
- return find_kanji(name);
- }
- else if(type == 'vocabulary') {
- return find_vocabulary(name);
- }
- else {
- return null;
- }
- }
- return {
- is_on_wanikani: is_on_wanikani,
- is_on_dashboard: is_on_dashboard,
- is_on_review_session_page: is_on_review_session_page,
- is_on_review_page: is_on_review_page,
- is_on_lesson_session_page: is_on_lesson_session_page,
- is_on_lesson_page: is_on_lesson_page,
- is_on_lattice_radicals_meaning: is_on_lattice_radicals_meaning,
- is_on_lattice_radicals_progress: is_on_lattice_radicals_progress,
- is_on_lattice_kanji_combined: is_on_lattice_kanji_combined,
- is_on_lattice_kanji_meaning: is_on_lattice_kanji_meaning,
- is_on_lattice_kanji_reading: is_on_lattice_kanji_reading,
- is_on_lattice_kanji_progress: is_on_lattice_kanji_progress,
- is_on_lattice_vocabulary_combined: is_on_lattice_vocabulary_combined,
- is_on_lattice_vocabulary_meaning: is_on_lattice_vocabulary_meaning,
- is_on_lattice_vocabulary_reading: is_on_lattice_vocabulary_reading,
- is_on_lattice_vocabulary_progress: is_on_lattice_vocabulary_progress,
- query_page_radicals_with_images: query_page_radicals_with_images,
- get_radicals_with_image: get_radicals_with_image,
- parse_item_url: parse_item_url,
- reset_radical_data: reset_radical_data,
- reset_kanji_data: reset_kanji_data,
- reset_vocabulary_data: reset_vocabulary_data,
- clear_local_storage: clear_local_storage,
- track_times: track_times,
- get_last_review_time: get_last_review_time,
- get_next_review_time: get_next_review_time,
- load_radical_data: load_radical_data,
- get_radical_data: get_radical_data,
- find_radical: find_radical,
- load_kanji_data: load_kanji_data,
- get_kanji_data: get_kanji_data,
- find_kanji: find_kanji,
- load_vocabulary_data: load_vocabulary_data,
- get_vocabulary_data: get_vocabulary_data,
- find_vocabulary: find_vocabulary,
- find_item: find_item,
- };
- })();
- var UI = (function() {
- function addStyle(aCss) {
- var head, style;
- head = document.getElementsByTagName('head')[0];
- if (head) {
- style = document.createElement('style');
- style.setAttribute('type', 'text/css');
- style.textContent = aCss;
- head.appendChild(style);
- return style;
- }
- return null;
- }
- function initCss() {
- var css =
- '#item_marker {' +
- ' display:none;' +
- '}' +
- '#item_marker h3 {' +
- ' height: 24px;' +
- ' margin-top: 10px;' +
- ' margin-bottom: 0px;' +
- ' padding: 5px 20px;' +
- ' border-radius: 5px 5px 0 0;' +
- ' background-color: seagreen;' +
- ' color: white;' +
- ' text-shadow: none;' +
- '}' +
- '#item_marker section {' +
- ' background-color: lightgrey;' +
- '}' +
- '#item_marker table {' +
- '}' +
- '#item_marker td {' +
- ' padding: 2px 8px;' +
- '}' +
- '#item_marker .close_button {' +
- ' float: right;' +
- ' height: 24px;' +
- '}'
- ;
- addStyle(css);
- }
- function buildIdAttr(id) {
- if (id && id != '')
- return 'id="' + id + '"';
- return '';
- }
- function addOnChangeListener(id, listener) {
- $('#' + id).off('change');
- $('#' + id).on('change', listener);
- }
- function addOnClickListener(id, listener) {
- $('#' + id).off('click');
- $('#' + id).on('click', listener);
- }
- function buildWindow(id, title) {
- var html =
- '<div ' + buildIdAttr(id) + ' class="container">' +
- '<div class="row">' +
- '<div class="span12" >' +
- '<section id="' + id + '_body">' +
- '<h3>' +
- title +
- '<button class="close_button" " ' + buildIdAttr(id + '_close_btn') + '>Close</button>' +
- '</h3>' +
- '</section>' +
- '</div>' +
- '</div>' +
- '</div>'
- ;
- return html;
- }
- function buildTable(id) {
- var html =
- '<table ' + buildIdAttr(id) + '>' +
- '<tbody ' + buildIdAttr(id + '_body') + '>' +
- '</tbody>' +
- '</table>';
- return html;
- }
- function buildTableRow(id, column1, column2) {
- var html =
- '<tr' + buildIdAttr(id) + '>' +
- '<td>' + column1 + '</td>' +
- '<td>' + column2 + '</td>' +
- '</tr>';
- return html;
- }
- function buildSelection(id, tooltip) {
- var html =
- '<select ' + buildIdAttr(id) + ' class="input" name="' + id + '" title="' + tooltip + '" />';
- return html;
- }
- function addSelectionOption(selectId, value, label, selected) {
- $('#' + selectId).append(
- '<option value="' + value + '" ' + (selected ? 'selected' : '') + '>' +
- label +
- '</option>');
- }
- function buildCheckBox(id, checked) {
- var html =
- '<input ' + buildIdAttr(id) + ' type="checkbox" ' + (checked ? 'checked' : '') + '>';
- return html;
- }
- return {
- initCss: initCss,
- buildWindow: buildWindow,
- buildTable: buildTable,
- buildTableRow: buildTableRow,
- buildCheckBox: buildCheckBox,
- buildSelection: buildSelection,
- addSelectionOption: addSelectionOption,
- addOnChangeListener: addOnChangeListener,
- addOnClickListener: addOnClickListener,
- };
- })();
- (function(gobj) {
- var data = {
- lists: [
- {
- name: 'A',
- items:[],
- settings: {
- markedItemsBorderColor: 'black',
- toggleMarksWithLinks: false,
- showReviewWarning: false,
- addDelayBeforeAnswerIsPossible: false,
- }
- },
- {
- name: 'B',
- items:[],
- settings: {
- markedItemsBorderColor: 'red',
- toggleMarksWithLinks: false,
- showReviewWarning: false,
- addDelayBeforeAnswerIsPossible: false,
- }
- },
- {
- name: 'C',
- items:[],
- settings: {
- markedItemsBorderColor: 'orange',
- toggleMarksWithLinks: false,
- showReviewWarning: false,
- addDelayBeforeAnswerIsPossible: false,
- }
- },
- {
- name: 'D',
- items:[],
- settings: {
- markedItemsBorderColor: 'yellow',
- toggleMarksWithLinks: false,
- showReviewWarning: false,
- addDelayBeforeAnswerIsPossible: false,
- }
- },
- {
- name: 'E',
- items:[],
- settings: {
- markedItemsBorderColor: 'cyan',
- toggleMarksWithLinks: false,
- showReviewWarning: false,
- addDelayBeforeAnswerIsPossible: false,
- }
- }
- ],
- settings: {
- unmarkedItemsBorderColor: 'no_border',
- }};
- var dataVersion = 2;
- var settingsWindowAdded = false;
- var dropDownMenuExtended = false;
- var availableBorderColors = [
- { value: 'no_border', label: 'No border' },
- { value: 'black', label: 'Black' },
- { value: 'darkgrey', label: 'Dark Grey' },
- { value: 'lightgrey', label: 'Light Grey' },
- { value: 'white', label: 'White' },
- { value: 'red', label: 'Red' },
- { value: 'orange', label: 'Orange' },
- { value: 'yellow', label: 'Yellow' },
- { value: 'green', label: 'Green' },
- { value: 'blue', label: 'Blue' },
- { value: 'indigo', label: 'Indigo' },
- { value: 'violet', label: 'Violet' },
- { value: 'cyan', label: 'Cyan' }
- ];
- var localStoragePrefix = 'ItemMarker_';
- var pageUpdateIsLocked = false;
- var lastSeenItem = 0;
- var lastSeenQuestionType = '';
- //-------------------------------------------------------------------
- // Main function
- //-------------------------------------------------------------------
- function main() {
- console.log('START - WaniKani Item Marker');
- loadData();
- WaniKani.track_times();
- extendDropDownMenu();
- updatePage();
- console.log('END - WaniKani Item Marker');
- }
- window.addEventListener('load', main, false);
- //window.addEventListener('focus', main, false);
- function extendDropDownMenu() {
- if (dropDownMenuExtended) {
- return;
- }
- $('<li><a href="#item_marker">Item Marker</a></li>')
- .insertBefore($('.account .dropdown-menu .nav-header:eq(1)'))
- .on('click', toggleSettingsWindow);
- dropDownMenuExtended = true;
- }
- function buildSettingsWindow() {
- UI.initCss();
- var html;
- html = UI.buildWindow('item_marker', 'Item Marker');
- $(html).insertAfter($('.navbar'));
- html = UI.buildTable('item_marker_settings');
- console.log(html);
- $('#item_marker_body').append(html);
- html = UI.buildTableRow(
- '',
- '<b>General settings</b>',
- '');
- $('#item_marker_settings_body').append(html);
- html = UI.buildTableRow(
- '',
- 'Border color for unmarked items',
- UI.buildSelection(
- 'border_color_unmarked_items',
- 'Select the border color for unmarked items'));
- $('#item_marker_settings_body').append(html);
- for (var i = 0; i < availableBorderColors.length; i++) {
- UI.addSelectionOption('border_color_unmarked_items',
- availableBorderColors[i].value,
- availableBorderColors[i].label,
- availableBorderColors[i].value == data.settings.unmarkedItemsBorderColor);
- }
- UI.addOnChangeListener('border_color_unmarked_items',
- function()
- {
- loadData();
- data.settings.unmarkedItemsBorderColor = $('#border_color_unmarked_items' + listIndex).val();
- saveData();
- updateLinks();
- });
- $(data.lists).each(function(listIndex, list) {
- html = UI.buildTableRow(
- '',
- '<b>Settings of List ' + list.name + '</b>',
- '');
- $('#item_marker_settings_body').append(html);
- html = UI.buildTableRow(
- '',
- 'Border color for marked items',
- UI.buildSelection(
- 'border_color_marked_items' + listIndex,
- 'Select the border color for marked items'));
- $('#item_marker_settings_body').append(html);
- for (var i = 0; i < availableBorderColors.length; i++) {
- UI.addSelectionOption('border_color_marked_items' + listIndex,
- availableBorderColors[i].value,
- availableBorderColors[i].label,
- availableBorderColors[i].value == list.settings.markedItemsBorderColor);
- }
- html = UI.buildTableRow(
- '',
- 'Use links to mark/unmark items',
- UI.buildCheckBox(
- 'use_links_to_toggle_items' + listIndex,
- list.settings.toggleMarksWithLinks));
- $('#item_marker_settings_body').append(html);
- html = UI.buildTableRow(
- '',
- 'Show warning during reviews',
- UI.buildCheckBox(
- 'show_review_warning' + listIndex,
- list.settings.showReviewWarning));
- $('#item_marker_settings_body').append(html);
- html = UI.buildTableRow(
- '',
- 'Add 30 second delay before review answer is possible',
- UI.buildCheckBox(
- 'add_delay_before_answer_is_possible' + listIndex,
- list.settings.addDelayBeforeAnswerIsPossible));
- $('#item_marker_settings_body').append(html);
- UI.addOnChangeListener('border_color_marked_items' + listIndex,
- function()
- {
- loadData();
- data.lists[listIndex].settings.markedItemsBorderColor = $('#border_color_marked_items' + listIndex).val();
- saveData();
- updateLinks();
- });
- UI.addOnChangeListener('use_links_to_toggle_items' + listIndex,
- function()
- {
- loadData();
- data.lists[listIndex].settings.toggleMarksWithLinks = $('#use_links_to_toggle_items' + listIndex).is(':checked');
- saveData();
- updateLinks();
- });
- UI.addOnChangeListener('show_review_warning' + listIndex,
- function()
- {
- loadData();
- data.lists[listIndex].settings.showReviewWarning = $('#show_review_warning' + listIndex).is(':checked');
- saveData();
- });
- UI.addOnChangeListener('add_delay_before_answer_is_possible' + listIndex,
- function()
- {
- loadData();
- data.lists[listIndex].settings.addDelayBeforeAnswerIsPossible = $('#add_delay_before_answer_is_possible' + listIndex).is(':checked');
- saveData();
- });
- UI.addOnClickListener('item_marker_close_btn',
- function(e)
- {
- toggleSettingsWindow(e);
- });
- });
- settingsWindowAdded = true;
- }
- function toggleSettingsWindow(e) {
- if (e !== undefined) e.preventDefault();
- // Add the manager if not already.
- if (!settingsWindowAdded) buildSettingsWindow();
- $('#item_marker').slideToggle();
- $('html, body').animate({scrollTop: 0}, 800);
- }
- //-------------------------------------------------------------------
- // Update the current page and add the item marker features
- //-------------------------------------------------------------------
- function updatePage() {
- if (pageUpdateIsLocked) {
- return;
- }
- if (WaniKani.is_on_dashboard()) {
- updateDashboardPage();
- }
- else if (WaniKani.is_on_review_page() || WaniKani.is_on_lesson_page()) {
- updateReviewAndLessonPage();
- }
- else if (WaniKani.is_on_review_session_page()) {
- updateReviewSessionPage();
- }
- else {
- var location = decodeURI(window.location);
- var parsedUrl = WaniKani.parse_item_url(location);
- if (parsedUrl) {
- updateItemPage(parsedUrl.type, parsedUrl.name);
- }
- }
- updateLinks();
- }
- function updateLinks() {
- $('a').each(function(i, item) {
- var href = $(this).attr('href');
- updateLinkFunction(href, $(this));
- updateItemBorder(href, $(this));
- });
- $('ul.alt-character-list a').each(function(i, item) {
- var href = $(this).attr('href');
- var parsedUrl = WaniKani.parse_item_url(href);
- if (parsedUrl) {
- $(this).css('box-shadow', '');
- updateItemBorder(href, $(this).parent('li'));
- }
- });
- $(data.lists).each(function(listIndex, list) {
- $('#marked_items_list' + listIndex + ' a').each(function(i, item) {
- var href = $(this).attr('href');
- var parsedUrl = WaniKani.parse_item_url(href);
- if (parsedUrl) {
- $(this).css('box-shadow', '');
- updateItemBorder(href, $(this).children('span:nth(0)'));
- }
- });
- });
- }
- function updateLinkFunction(url, htmlElem) {
- var parsedUrl = WaniKani.parse_item_url(url);
- if (parsedUrl) {
- htmlElem.off('click');
- $(data.lists).each(function(listIndex, list) {
- if (list.settings.toggleMarksWithLinks) {
- htmlElem.on('click',
- function(e)
- {
- e.preventDefault();
- toggleItemByUri(listIndex, url);
- });
- }
- });
- }
- }
- function updateItemBorder(url, htmlElem) {
- var parsedUrl = WaniKani.parse_item_url(url);
- if (parsedUrl) {
- var marked = false;
- var boxShadow = '';
- var shadowWidth = 2;
- $(data.lists).each(function(listIndex, list) {
- var item_index = indexOf(listIndex, parsedUrl.type, parsedUrl.name);
- if (item_index !== -1) {
- marked = true;
- if (list.settings.markedItemsBorderColor != 'no_border') {
- if (boxShadow == '') {
- boxShadow = 'inset 0 0 0 ' + shadowWidth + 'px ' + list.settings.markedItemsBorderColor;
- }
- else {
- boxShadow = boxShadow + ', inset 0 0 0 ' + shadowWidth + 'px ' + list.settings.markedItemsBorderColor;
- }
- shadowWidth = shadowWidth + 2;
- }
- }
- });
- if (marked) {
- htmlElem.css('box-shadow', boxShadow);
- }
- else {
- if (data.settings.unmarkedItemsBorderColor != 'no_border') {
- htmlElem.css('box-shadow', 'inset 0 0 0 2px ' + data.settings.unmarkedItemsBorderColor);
- }
- else {
- htmlElem.css('box-shadow', '');
- }
- }
- }
- }
- //-------------------------------------------------------------------
- // Load item marker data from local storage
- //-------------------------------------------------------------------
- function loadData() {
- var storedData = localStorage.getItem(localStoragePrefix + 'markedItems_v' + data.version);
- if (storedData != null) {
- console.log('Read current version ' + data.version);
- storedData = JSON.parse(storedData);
- data = storedData;
- return;
- }
- storedData = localStorage.getItem(localStoragePrefix + 'markedItems'); // v1
- if (storedData != null) {
- console.log('Read old version 1');
- storedData = JSON.parse(storedData);
- data.lists[0].items = storedData.items;
- var storedSettings = localStorage.getItem(localStoragePrefix + 'settings');
- if (storedSettings) {
- storedSettings = JSON.parse(storedSettings);
- data.lists[0].settings = storedSettings;
- }
- return;
- }
- }
- //-------------------------------------------------------------------
- // Save item marker data to local storage
- //-------------------------------------------------------------------
- function saveData() {
- localStorage.setItem(localStoragePrefix + 'markedItems_v' + data.version, JSON.stringify(data));
- }
- //-------------------------------------------------------------------
- // Return the index of the given item in the list
- // returns -1 if item isn't in list
- //-------------------------------------------------------------------
- function indexOf(listIndex, type, name) {
- return data.lists[listIndex].items.findIndex(function(item) { return (item.type == type) && (item.name == name); });
- }
- //-------------------------------------------------------------------
- // Remove all marks
- //-------------------------------------------------------------------
- function unmarkAllItems(listIndex) {
- loadData();
- data.lists[listIndex].items = [];
- saveData();
- updatePage();
- }
- //-------------------------------------------------------------------
- // Force refresh of page and data
- //-------------------------------------------------------------------
- function forceRefresh() {
- WaniKani.reset_radical_data(true);
- $(data.lists).each(function(listIndex, list) {
- for (var i = 0; i < list.items.length; i++) {
- if (list.items[i].type == 'radicals') {
- list.items[i].radical_character = null;
- list.items[i].radical_image = null;
- }
- }
- });
- updatePage();
- }
- //-------------------------------------------------------------------
- // Unmark a single item
- //-------------------------------------------------------------------
- function unmarkItem(listIndex, type, name) {
- loadData();
- var index = indexOf(listIndex, type, name);
- if (index > -1) {
- data.lists[listIndex].items.splice(index, 1);
- saveData();
- }
- updatePage();
- }
- function toggleItemByUri(listIndex, uri) {
- var parsedUrl = WaniKani.parse_item_url(uri);
- if (parsedUrl) {
- var index = indexOf(listIndex, parsedUrl.type, parsedUrl.name);
- if (index > -1) {
- unmarkItem(listIndex, parsedUrl.type, parsedUrl.name);
- }
- else {
- markItem(listIndex, parsedUrl.type, parsedUrl.name);
- }
- }
- }
- //-------------------------------------------------------------------
- // Sort the list of marked items
- //-------------------------------------------------------------------
- function sortItems(listIndex) {
- loadData();
- data.lists[listIndex].items.sort(
- function(a, b) {
- if (a.type == 'radicals') {
- if (b.type != 'radicals') {
- return -1;
- }
- }
- else if (a.type == 'kanji') {
- if (b.type == 'radicals') {
- return 1;
- }
- else if (b.type == 'vocabulary') {
- return -1;
- }
- }
- else {
- if (b.type != 'vocabulary') {
- return 1;
- }
- }
- return a.name.localeCompare(b.name);
- });
- saveData();
- updatePage();
- }
- //-------------------------------------------------------------------
- // Mark a single item
- //-------------------------------------------------------------------
- function markItem(listIndex, type, name) {
- loadData();
- var index = indexOf(listIndex, type, name);
- if (index === -1) {
- data.lists[listIndex].items.push({type: type, name: name, radical_character: null, radical_image: null});
- saveData();
- }
- updatePage();
- }
- //-------------------------------------------------------------------
- // Copy all items from the source to the target list
- // aka marks them all in the target list
- //-------------------------------------------------------------------
- function copyAll(sourceListIndex, targetListIndex) {
- loadData();
- pageUpdateIsLocked = true;
- for (var i = 0; i < data.lists[sourceListIndex].items.length; i++) {
- var item = data.lists[sourceListIndex].items[i];
- markItem(targetListIndex, item.type, item.name);
- }
- pageUpdateIsLocked = false;
- updatePage();
- }
- //-------------------------------------------------------------------
- // Callback if the currentItem changes
- //-------------------------------------------------------------------
- function onCurrentItemChanged() {
- updatePage();
- }
- //-------------------------------------------------------------------
- // Extends the review session page
- // - Adds buttons to mark/unmark the current item
- //-------------------------------------------------------------------
- function updateReviewSessionPage() {
- $.jStorage.stopListening('currentItem', onCurrentItemChanged);
- $.jStorage.listenKeyChange('currentItem', onCurrentItemChanged);
- var currentItem = $.jStorage.get('currentItem');
- var name;
- var type;
- if (currentItem.hasOwnProperty('rad')) {
- type = 'radicals';
- name = currentItem.en[0].toLowerCase().replace(/\s/g, "-");
- }
- else if (currentItem.hasOwnProperty('kan')) {
- type = 'kanji';
- name = currentItem.kan;
- }
- else if (currentItem.hasOwnProperty('voc')) {
- type = 'vocabulary';
- name = currentItem.voc;
- }
- else {
- return;
- }
- //console.log('updateReviewSessionPage - type: ' + type);
- //console.log('updateReviewSessionPage - name: ' + name);
- var showReviewWarning = false;
- var addDelayBeforeAnswerIsPossible = false;
- $(data.lists).each(function(listIndex, list) {
- $('#option-mark' + listIndex).remove();
- $('#option-unmark' + listIndex).remove();
- var item_index = indexOf(listIndex, type, name);
- if (item_index === -1) {
- $('#additional-content ul').append('<li id="option-mark' + listIndex + '"><span title="Mark on list ' + list.name + '">Mark ' + list.name + '</span></li>');
- $('#option-mark' + listIndex).on('click', function() { markItem(listIndex, type, name); });
- }
- else {
- $('#additional-content ul').append('<li id="option-unmark' + listIndex + '"><span title="Unmark from list ' + list.name + '">Unmark ' + list.name + '</span></li>');
- $('#option-unmark' + listIndex).on('click', function() { unmarkItem(listIndex, type, name); });
- if (list.settings.showReviewWarning) {
- showReviewWarning = true;
- }
- if (list.settings.addDelayBeforeAnswerIsPossible) {
- addDelayBeforeAnswerIsPossible = true;
- }
- }
- });
- $('#warning1').remove();
- $('#warning2').remove();
- if (showReviewWarning) {
- $('#character').append('<span id="warning1"> !!!</span>');
- $('#character').prepend('<span id="warning2">!!! </span>');
- }
- var currentQuestionType = $('#question-type').attr('class');
- $('#reviewWarning').remove();
- if ((currentItem.id != lastSeenItem) || (currentQuestionType != lastSeenQuestionType)) {
- if (addDelayBeforeAnswerIsPossible) {
- $('#answer-form').prepend('<span id="reviewWarning"> <br />Beware, it\'s a marked item! Think for a moment about your answer.<br /> </span>');
- $('#answer-form form').hide();
- setTimeout(function updateReviewSessionPage() {
- $('#answer-form form').show();
- $('#answer-form input').focus();
- $('#reviewWarning').remove();
- }, 30000);
- }
- else {
- $('#answer-form form').show();
- $('#answer-form input').focus();
- }
- }
- calculateDynamicWidthForReviewPage();
- lastSeenItem = currentItem.id;
- lastSeenQuestionType = currentQuestionType;
- }
- //-------------------------------------------------------------------
- // Updates the dynamic with for the review page
- //-------------------------------------------------------------------
- function calculateDynamicWidthForReviewPage(){
- var liCount = $('#additional-content ul').children().size();
- var percentage = 100 / liCount;
- percentage -= 0.1;
- var cssDynamicWidth =
- '#additional-content ul li {' +
- ' width: ' + percentage + '% !important' +
- '} ';
- addStyle(cssDynamicWidth);
- }
- //-------------------------------------------------------------------
- // Adds a css to the page
- //-------------------------------------------------------------------
- function addStyle(aCss) {
- var head, style;
- head = document.getElementsByTagName('head')[0];
- if (head) {
- style = document.createElement('style');
- style.setAttribute('type', 'text/css');
- style.textContent = aCss;
- head.appendChild(style);
- return style;
- }
- return null;
- }
- //-------------------------------------------------------------------
- // Extends the review and lesson page
- // - Adds a black/white border around marked/unmarked items displayed on the page
- //-------------------------------------------------------------------
- function updateReviewAndLessonPage() {
- var query = $('div.active li');
- if (query.length == 0) { // Page not completly loaded yet
- setTimeout(updatePage, 1000);
- return;
- }
- updateLinks();
- }
- //-------------------------------------------------------------------
- // Extends the dashboard
- // - Adds a 'marked items' section
- // - Adds common buttons and marked items list to section
- //-------------------------------------------------------------------
- function updateDashboardPage() {
- var query = $('section.progression');
- if (query.length != 1) {
- return;
- }
- $('#marked_items').remove();
- $('section.progression').after('<section id="marked_items" />');
- $(data.lists).each(function(listIndex, list) {
- $('#marked_items').append('<h2>Marked items - List ' + list.name + '</h2>');
- $('#marked_items').append('<p id="marked_items_buttons' + listIndex + '" />');
- addCommonButtons(listIndex);
- addMarkedItemsList(listIndex);
- });
- }
- //-------------------------------------------------------------------
- // Extends the an item page
- // - Adds a 'marked items' section
- // - Adds common buttons and marked items list to section
- // - Adds buttons to mark/unmark the current item
- //-------------------------------------------------------------------
- function updateItemPage(type, name) {
- var query = $('section#information');
- if (query.length != 1) {
- return;
- }
- $('#marked_items').remove();
- $('section#information').after('<section id="marked_items" />');
- $(data.lists).each(function(listIndex, list) {
- $('#marked_items').append('<h2>Marked items - List ' + list.name + '</h2>');
- $('#marked_items').append('<p id="marked_items_buttons' + listIndex + '" />');
- var index = indexOf(listIndex, type, name);
- if (index === -1) {
- var button = $('<button>Mark "' + name + '"</button>');
- button.on('click', function() { markItem(listIndex, type, name); });
- $('#marked_items_buttons' + listIndex).append(button)
- }
- else {
- var button = $('<button>Unmark "' + name + '"</button>');
- button.on('click', function() { unmarkItem(listIndex, type, name); });
- $('#marked_items_buttons' + listIndex).append(button)
- }
- addCommonButtons(listIndex);
- addMarkedItemsList(listIndex);
- });
- }
- //-------------------------------------------------------------------
- // Adds common buttons that are used on multiple locations
- //-------------------------------------------------------------------
- function addCommonButtons(listIndex) {
- if (data.lists[listIndex].items.length > 0) {
- var button = $('<button>Unmark all</button>');
- button.on('click', function() { unmarkAllItems(listIndex); });
- $('#marked_items_buttons' + listIndex).append(button)
- }
- if (data.lists[listIndex].items.length > 0) {
- $(data.lists).each(function(otherListIndex, otherList) {
- if (otherListIndex != listIndex) {
- var button = $('<button>Copy all to List ' + otherList.name + '</button>');
- button.on('click', function() { copyAll(listIndex, otherListIndex); });
- $('#marked_items_buttons' + listIndex).append(button)
- }
- });
- }
- if (data.lists[listIndex].items.length > 0) {
- var button = $('<button>Sort</button>');
- button.on('click', function() { sortItems(listIndex); });
- $('#marked_items_buttons' + listIndex).append(button)
- }
- {
- var button = $('<button>Force refresh</button>');
- button.on('click', function() { forceRefresh(); });
- $('#marked_items_buttons' + listIndex).append(button)
- }
- }
- function buildItemList(listIndex) {
- detect_radical_characters();
- for (var i = 0; i < data.lists[listIndex].items.length; i++) {
- var item = data.lists[listIndex].items[i];
- var typeForClass = item.type;
- if (typeForClass == 'radicals')
- typeForClass = 'radical';
- var itemText = item.name;
- if (item.type == 'radicals') {
- if (item.radical_image && item.radical_image != '') {
- itemText = '<img src="' + item.radical_image + '"/>';
- }
- else if (item.radical_character != '') {
- itemText = item.radical_character;
- }
- else {
- itemText = '<i class="radical-' + item.name + '"></i>';
- }
- }
- $('#marked_items_list' + listIndex).append(
- '<a href="/' + item.type + '/' + item.name + '">' +
- ' <span class="' + typeForClass + '-icon" lang="ja">' +
- ' <span style="display: inline-block; margin-top: 0.1em;" class="japanese-font-styling-correction">' +
- itemText +
- ' </span>' +
- ' </span>' +
- '</a>');
- }
- updateLinks();
- };
- //-------------------------------------------------------------------
- // Adds the list of marked items to the current page
- //-------------------------------------------------------------------
- function addMarkedItemsList(listIndex) {
- $('#marked_items').append('<span id="marked_items_list' + listIndex + '" />')
- if (data.lists[listIndex].items.length == 0) {
- $('#marked_items_list' + listIndex).append('<p>No marked items</p>');
- }
- else {
- WaniKani.query_page_radicals_with_images().then(function() { buildItemList(listIndex); });
- }
- }
- //-------------------------------------------------------------------
- // Uses the radical info to determine if a radical uses a character
- // or an image.
- //-------------------------------------------------------------------
- function detect_radical_characters() {
- var radicals_with_images = WaniKani.get_radicals_with_image();
- $(data.lists).each(function(listIndex, list) {
- $(list.items).each(function(itemIndex, item) {
- if ((item.type == 'radicals') && (item.radical_character == null)) {
- var radical_character = null;
- var foundRadical = radicals_with_images.find(function(radical_with_image) {
- return radical_with_image.name == item.name;
- });
- if (!foundRadical) {
- return;
- }
- console.log('detect_radical_characters: ' + item.name + ' -> ' + foundRadical.character + ' / ' + foundRadical.image);
- list.items[itemIndex].radical_character = foundRadical.character;
- list.items[itemIndex].radical_image = foundRadical.image;
- }
- });
- });
- saveData();
- }
- }());