您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Replaces kanji in wanikani with hanzi writer. Licenses for kanji data are found at https://github.com/chanind/hanzi-writer-data-jp/
// ==UserScript== // @name Wanikani hanzi-writer addition // @namespace https://declanfodor.com // @description Replaces kanji in wanikani with hanzi writer. Licenses for kanji data are found at https://github.com/chanind/hanzi-writer-data-jp/ // @match https://www.wanikani.com/* // @version 0.0.4 // @author Declan Fodor // @resource kanjiJSON https://raw.githubusercontent.com/chanind/hanzi-writer-data-jp/master/data/all.json // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/hanzi-writer.js // @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2 // @license MIT // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_getValue // @grant GM_setvalue // ==/UserScript== (function () { 'use strict'; const WK_PAGE = Object.freeze({ REVIEW: Symbol("review_page"), LESSON: Symbol("lesson_page"), DASHBOARD: Symbol("dashboard_page"), LOADING: Symbol("other_page") // Or a page we haven't implemented behavior for yet }); class PageStatus { constructor(previousStatus) { this.page = this.whichPage(unsafeWindow.location.href); this.switched = previousStatus ? previousStatus.page !== this.page : true; } whichPage(url) { switch (url) { case "https://www.wanikani.com/": return WK_PAGE.DASHBOARD; case "https://www.wanikani.com/subjects/review": return WK_PAGE.REVIEW; default: return WK_PAGE.LOADING; } } } class PageObserver { constructor(wk_page, onPage, offPage) { this.status = new PageStatus(null); this.observer = new MutationObserver(() => { if (this.status.switched && this.status.page === wk_page) { onPage(); } else if (this.status.switched) { offPage(); } this.status = new PageStatus(this.status); }); this.observer.observe(document, { childList: true, subtree: true }); } } let kanji_json = JSON.parse(GM_getResourceText("kanjiJSON")); class ReviewPage { constructor() { this.kanji_elem = null; this.kanji = null; this.writer = null; this.container_div = null; } /** * Called whenever the kanji has switched. It creates the hanzi writer instance */ drawHanziWriter() { if (!this.writer) { let character_header = document.querySelector(".quiz .character-header"); this.container_div = document.createElement("div"); this.container_div.id = "wkhwa-container-div"; this.writer = HanziWriter.create(this.container_div, this.kanji, { showOutline: wkof.settings.wkhwa.showOutline, showCharacter: wkof.settings.wkhwa.showCharacter, width: wkof.settings.wkhwa.width, height: wkof.settings.wkhwa.height, padding: wkof.settings.wkhwa.padding, strokeAnimationSpeed: wkof.settings.wkhwa.strokeAnimationSpeed, strokeHighlightSpeed: wkof.settings.wkhwa.strokeHighlightSpeed, strokeFadeDuration: wkof.settings.wkhwa.strokeFadeDuration, delayBetweenStrokes: wkof.settings.wkhwa.delayBetweenStrokes, delayBetweenLoops: wkof.settings.wkhwa.delayBetweenLoops, strokeColor: wkof.settings.wkhwa.strokeColor, highlightColor: wkof.settings.wkhwa.highlightColor, outlineColor: wkof.settings.wkhwa.outlineColor, drawingColor: wkof.settings.wkhwa.drawingColor, drawingWidth: wkof.settings.wkhwa.drawingWidth, showHintAfterMisses: wkof.settings.wkhwa.showHintAfterMisses, quizStartStrokeNum: wkof.settings.wkhwa.quizStartStrokeNum, highlightOnComplete: wkof.settings.wkhwa.highlightOnComplete, charDataLoader: (char, on_load) => { on_load(kanji_json[char]); } }); character_header.append(this.container_div); } else { this.writer.setCharacter(this.kanji); } if (wkof.settings.wkhwa.quiz) { this.writer.quiz(); } else if (wkof.settings.wkhwa.animate) { if (wkof.settings.wkhwa.loop_animation) { this.writer.loopCharacterAnimation(); } else { this.writer.animateCharacter(); } } } onReviewPage() { this.observer = new MutationObserver(() => { if (this.refreshKanjiState()) { this.drawHanziWriter(); } }); this.kanji_elem = document.querySelector(".quiz .character-header .character-header__characters"); if (this.refreshKanjiState()) { this.drawHanziWriter(); } this.observer.observe(this.kanji_elem, { childList: true, subtree: true }); } showHanziWriter() { this.kanji_elem.hidden = true; if (this.container_div) { this.container_div.hidden = false; } } hideHanziWriter() { this.kanji_elem.hidden = false; if (this.container_div) { this.container_div.hidden = true; } } /** * Returns true if the kanji shown has switched. Returns false otherwise * This function also manages hiding and showing kanji * in the event that the characters shown are either a radical or vocabulary */ refreshKanjiState() { // CHANGEME shouldn't this return an enum and have the logic outside this function? if (document.querySelector(".quiz-input__question-category").innerText.toLowerCase() === "kanji" && kanji_json[this.kanji_elem.innerText]) { if (this.kanji_elem.innerText !== this.kanji) { // We have switched to a new kanji, mayhap away from vocabulary, so we need to set these to be shown this.kanji = this.kanji_elem.innerText; this.showHanziWriter(); return true; } else { return false; } } // The character content has switched to vocabulary or a radical this.kanji = null; this.hideHanziWriter(); return false; } /** * Cleans up various objects if we switch away from them. */ offReviewPage() { this.kanji = null; this.writer = null; this.container_div = null; if (this.observer) { this.observer.disconnect(); } } } // Loads styles GM_addStyle(`#wkhwa-container-div { position: relative; top: -32px; display: flex; align-items: center; justify-content: center; }`); let default_settings = { showOutline: true, showCharacter: true, width: 200, height: 200, padding: 20, strokeAnimationSpeed: 1, strokeHighlightSpeed: 2, strokeFadeDuration: 400, delayBetweenStrokes: 1000, delayBetweenLoops: 2000, strokeColor: "#555555", highlightColor: "#8899FF", outlineColor: "#FFFFFF", drawingColor: "#333333", drawingWidth: 20, showHintAfterMisses: 3, quizStartStrokeNum: 0, highlightOnComplete: false, quiz: true, animate: false, loop_animation: false }; let config = { script_id: "wkhwa", title: "Hanzi writer addition", content: { hanzi_writer: { type: "group", label: "Options:", content: { showOutline: { type: 'checkbox', label: 'showOutline', default: default_settings.showOutline }, showCharacter: { type: 'colorbox', label: 'showCharacter', default: default_settings.showCharacter }, width: { type: "number", label: "width", default: default_settings.width }, height: { type: "number", label: "height", default: default_settings.height }, padding: { type: "number", label: "padding", default: default_settings.padding }, strokeAnimationSpeed: { type: "number", label: "strokeAnimationSpeed", default: default_settings.strokeAnimationSpeed }, strokeHighlightSpeed: { type: "number", label: "strokeHighlightSpeed", default: default_settings.strokeHighlightSpeed }, strokeFadeDuration: { type: "number", label: "strokeFadeDuration", default: default_settings.strokeFadeDuration }, delayBetweenStrokes: { type: "number", label: "delayBetweenStrokes", default: default_settings.delayBetweenStrokes }, delayBetweenLoops: { type: "number", label: "delayBetweenLoops", default: default_settings.delayBetweenLoops }, StrokeColor: { type: 'color', label: 'strokeColor', default: '#555555' }, highlightColor: { type: 'color', label: 'highlightColor', default: '#8899FF' }, outlineColor: { type: 'color', label: 'outlineColor', default: '#FFFFFF' }, drawingColor: { type: 'color', label: 'drawingColor', default: '#333333' }, drawingWidth: { type: "number", label: "drawingWidth", default: default_settings.drawingWidth }, showHintAfterMisses: { type: "number", label: "showHintAfterMisses", default: default_settings.showHintAfterMisses }, quizStartStrokeNum: { type: "number", label: "quizStartStrokeNum", default: default_settings.quizStartStrokeNum }, highlightOnComplete: { type: 'checkbox', label: 'highlightOnComplete', default: default_settings.highlightOnComplete }, quiz: { type: 'checkbox', label: 'quiz', default: default_settings.quiz }, animate: { type: 'checkbox', label: 'animate', default: default_settings.animate }, loop_animation: { type: 'checkbox', label: 'loopAnimation', default: default_settings.loop_animation } } } } }; wkof.include('Menu, Settings'); wkof.ready('Menu, Settings').then(main); function main() { wkof.Settings.load('wkhwa', default_settings); wkof.Menu.insert_script_link({ name: 'wkhwa', submenu: 'wkhwa', title: 'Hanzi writer settings', on_click: new wkof.Settings(config).open }); let review_page = new ReviewPage(); new PageObserver(WK_PAGE.REVIEW, review_page.onReviewPage.bind(review_page), review_page.offReviewPage.bind(review_page)); } })();