-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathwanikani-pitch-accent.user.js
128 lines (119 loc) · 4.21 KB
/
wanikani-pitch-accent.user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// ==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'
)
});
})();