您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show pitch accent data on WaniKani
// ==UserScript== // @name WaniKani Pitch Accent // @namespace https://greasyfork.org/users/649 // @version 1.0.1 // @description Show pitch accent data on WaniKani // @author Adrien Pyke // @match *://www.wanikani.com/vocabulary/* // @match *://www.wanikani.com/level/*/vocabulary/* // @match *://www.wanikani.com/review/session // @match *://www.wanikani.com/lesson/session // @require https://cdn.jsdelivr.net/gh/IllDepence/SVG_pitch@295af214b1e3c8add03a31cf022e28033495da08/accdb.js // @require https://cdn.jsdelivr.net/gh/fuzetsu/userscripts@ec863aa92cea78a20431f92e80ac0e93262136df/wait-for-elements/wait-for-elements.js // @grant none // ==/UserScript== /* global acc_dict */ (() => { 'use strict'; const Util = { q: (query, context = document) => context.querySelector(query), qq: (query, context = document) => Array.from(context.querySelectorAll(query)), toMoraArray: kana => kana.match(/.[ゃゅょぁぃぅぇぉャュョァィゥェォ]?/gu), getAccentData: (kanji, reading) => { const [kana, pitch] = (acc_dict[kanji] && acc_dict[kanji].find(([r]) => r === reading)) || []; if (!kana) return []; return [Util.toMoraArray(kana), [...pitch.replace(/[lh]/gu, '')]]; } }; const Draw = { textGeneric: (x, text, color = '#666') => `<text x="${x}" y="67.5" style="font-size: 20px; fill: ${color};">${text}</text>`, text: (x, mora) => mora.length === 1 ? Draw.textGeneric(x, mora) : Draw.textGeneric(x - 5, mora[0]) + Draw.textGeneric(x + 12, mora[1]), circle: (x, y, empty, color = '#000', emptyColor = '#eee') => ` <circle r="5" cx="${x}" cy="${y}" style="opacity: 1; fill: ${color};" /> ` + (empty ? `<circle r="3.25" cx="${x}" cy="${y}" style="opacity: 1; fill: ${emptyColor};" />` : ''), path: (x, y, type, stepWidth, color = '#000') => ` <path d="m ${x},${y} ${stepWidth},${ { s: 0, u: -25, d: 25 }[type] }" style="fill: none; stroke: ${color}; stroke-width: 1.5;" /> `, svg: (kanji, reading) => { const [mora, pitch] = Util.getAccentData(kanji, reading); if (!mora) return; const stepWidth = 35; const marginLr = 16; const positions = Math.max(mora.length, pitch.length); const svgWidth = Math.max(0, (positions - 1) * stepWidth + marginLr * 2); const getXCenter = step => marginLr + step * stepWidth; const getYCenter = type => (type === 'H' ? 5 : 30); const chars = mora .map((kana, i) => Draw.text(getXCenter(i) - 11, kana)) .join(''); const paths = pitch .slice(1) .map((type, i) => ({ prevXCenter: getXCenter(i), prevYCenter: getYCenter(pitch[i]), yCenter: getYCenter(type) })) .map(({ prevXCenter, prevYCenter, yCenter }) => Draw.path( prevXCenter, prevYCenter, prevYCenter < yCenter ? 'd' : prevYCenter > yCenter ? 'u' : 's', stepWidth ) ) .join(''); const circles = pitch .map((type, i) => Draw.circle(getXCenter(i), getYCenter(type), i >= mora.length) ) .join(''); return ` <svg width="${svgWidth}px" height="75px" viewBox="0 0 ${svgWidth} 74"> ${chars + paths + circles} </svg> `.trim(); } }; const addSvgToGroup = (group, kanji, marginTop) => { const svg = Draw.svg( kanji, Util.q('.pronunciation-variant', group).textContent ); if (!svg) return; const div = document.createElement('div'); div.style.marginTop = marginTop; div.innerHTML = svg; group.appendChild(div); }; const isLesson = window.location.pathname.includes('/lesson/'); const isReview = window.location.pathname.includes('/review/'); const isVocab = !isLesson && !isReview; waitForElems({ sel: '.pronunciation-group', onmatch: group => addSvgToGroup( group, Util.q( isVocab ? '.vocabulary-icon' : isLesson ? '#character' : '#character > span' ).textContent, isVocab ? 0 : '10px' ) }); })();