返回首頁 

Greasy Fork is available in English.

WaniKani Stroke Order

Shows a kanji's stroke order on its page and during lessons and reviews.

// ==UserScript==// @name        WaniKani Stroke Order// @namespace   japanese// @version     1.1.22// @description Shows a kanji's stroke order on its page and during lessons and reviews.// @license     GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html// @match       https://www.wanikani.com/*// @match       https://preview.wanikani.com/*// @author      Looki, maintained by kind users on the forum// @grant       GM_xmlhttpRequest// @connect     jisho.org// @connect     cloudfront.net// @require     https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js// @require     https://greasyfork.org/scripts/430565-wanikani-item-info-injector/code/WaniKani%20Item%20Info%20Injector.user.js?version=1326536// ==/UserScript==/** Thanks a lot to ...* Wanikani Phonetic-Semantic Composition - Userscript* by ruipgpinheiro (LordGravewish)* ... for code showing me how to insert sections during kanji reviews.* The code heavily borrows from that script!* Also thanks to Halo for a loading bug fix!*/;(function () {/* global Snap *//** Helper Functions/Variables*/let wkItemInfo = unsafeWindow.wkItemInfo/** Global Variables/Objects/Classes*/const JISHO = 'https://jisho.org'const strokeOrderCss ='.stroke_order_diagram--bounding_box {fill: none; stroke: #ddd; stroke-width: 2; stroke-linecap: square; stroke-linejoin: square;}' +'.stroke_order_diagram--bounding_box {fill: none; stroke: #ddd; stroke-width: 2; stroke-linecap: square; stroke-linejoin: square;}' +'.stroke_order_diagram--existing_path {fill: none; stroke: #aaa; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;}' +'.stroke_order_diagram--current_path {fill: none; stroke: #000; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;}' +'.stroke_order_diagram--path_start {fill: rgba(255,0,0,0.7); stroke: none;}' +'.stroke_order_diagram--guide_line {fill: none; stroke: #ddd; stroke-width: 2; stroke-linecap: square; stroke-linejoin: square; stroke-dasharray: 5, 5;}'init()/** Main*/function init() {wkItemInfo.on('lesson').forType('kanji').under('composition').append('Stroke Order', loadDiagram)wkItemInfo.on('lessonQuiz, review, extraStudy, itemPage').forType('kanji').under('composition').appendAtTop('Stroke Order', loadDiagram)let style = document.createElement('style')style.textContent = strokeOrderCssdocument.head.appendChild(style)}function xmlHttpRequest(urlText) {return new Promise((resolve, reject) =>GM_xmlhttpRequest({method: 'GET',url: urlText,onload: (xhr) => {xhr.status === 200 ? resolve(xhr) : reject(xhr.responseText)},onerror: (xhr) => {reject(xhr.responseText)},}),)}/** Adds the diagram section element to the appropriate location*/async function loadDiagram(injectorState) {let xhr = await xmlHttpRequest(JISHO + '/search/' + encodeURI(injectorState.characters) + '%20%23kanji')let strokeOrderSvg = xhr.responseText.match(/var url = '\/\/(.+)';/)if (!strokeOrderSvg) return nullxhr = await xmlHttpRequest('https://' + strokeOrderSvg[1])let namespace = 'http://www.w3.org/2000/svg'let div = document.createElement('div')let svg = document.createElementNS(namespace, 'svg')svg.id = 'stroke_order'div.style = 'width: 100%; overflow: auto hidden;'new strokeOrderDiagram(svg,xhr.responseXML || new DOMParser().parseFromString(xhr.responseText, 'application/xml'),)div.append(svg)return div}/** Lifted from jisho.org, modified to allow multiple rows*/var strokeOrderDiagram = function (element, svgDocument) {var s = Snap(element)var diagramSize = 200var coordRe = '(?:\\d+(?:\\.\\d+)?)'var strokeRe = new RegExp('^[LMT]\\s*(' + coordRe + ')[,\\s](' + coordRe + ')', 'i')var f = Snap(svgDocument.getElementsByTagName('svg')[0])var allPaths = f.selectAll('path')var drawnPaths = []var framesPerRow = 10var rowCount = Math.floor((allPaths.length - 1) / framesPerRow) + 1var canvasWidth = (Math.min(framesPerRow, allPaths.length) * diagramSize) / 2var frameSize = diagramSize / 2var canvasHeight = frameSize * rowCountvar frameOffsetMatrix = new Snap.Matrix()frameOffsetMatrix.translate(-frameSize / 16 + 2, -frameSize / 16 + 2)// Set drawing areas.node.style.width = canvasWidth + 'px's.node.style.height = canvasHeight + 'px's.node.setAttribute('viewBox', '0 0 ' + canvasWidth + ' ' + canvasHeight)// Draw global guidesvar boundingBoxTop = s.line(1, 1, canvasWidth - 1, 1)var boundingBoxLeft = s.line(1, 1, 1, canvasHeight - 1)for (var i = 0; i < rowCount; i++) {var horizontalY = frameSize / 2 + i * frameSizevar horizontalGuide = s.line(0, horizontalY, canvasWidth, horizontalY)horizontalGuide.attr({ class: 'stroke_order_diagram--guide_line' })var boundingBoxBottom = s.line(1, frameSize * (i + 1) - 1, canvasWidth - 1, frameSize * (i + 1) - 1)boundingBoxBottom.attr({ class: 'stroke_order_diagram--bounding_box' })}boundingBoxTop.attr({ class: 'stroke_order_diagram--bounding_box' })boundingBoxLeft.attr({ class: 'stroke_order_diagram--bounding_box' })// Draw strokesvar pathNumber = 1allPaths.forEach(function (currentPath) {var effectivePathNumber = ((pathNumber - 1) % framesPerRow) + 1var effectiveY = Math.floor((pathNumber - 1) / framesPerRow) * frameSizevar moveFrameMatrix = new Snap.Matrix()moveFrameMatrix.translate(frameSize * (effectivePathNumber - 1) - 4, -4 + effectiveY)// Draw frame guidesvar verticalGuide = s.line(frameSize * effectivePathNumber - frameSize / 2,1,frameSize * effectivePathNumber - frameSize / 2,canvasHeight - 1,)var frameBoxRight = s.line(frameSize * effectivePathNumber - 1,1,frameSize * effectivePathNumber - 1,canvasHeight - 1,)verticalGuide.attr({ class: 'stroke_order_diagram--guide_line' })frameBoxRight.attr({ class: 'stroke_order_diagram--bounding_box' })// Draw previous strokesdrawnPaths.forEach(function (existingPath) {var localPath = existingPath.clone()localPath.transform(moveFrameMatrix)localPath.attr({ class: 'stroke_order_diagram--existing_path' })s.append(localPath)})// Draw current strokecurrentPath.transform(frameOffsetMatrix)currentPath.transform(moveFrameMatrix)currentPath.attr({ class: 'stroke_order_diagram--current_path' })s.append(currentPath)// Draw stroke start pointvar match = strokeRe.exec(currentPath.node.getAttribute('d'))var pathStartX = match[1]var pathStartY = match[2]var strokeStart = s.circle(pathStartX, pathStartY, 4)strokeStart.attr({ class: 'stroke_order_diagram--path_start' })strokeStart.transform(moveFrameMatrix)pathNumber++drawnPaths.push(currentPath.clone())})}})()