WaniKani JJ External Definition

Get JJ External Definition from Weblio, Kanjipedia

  1. // ==UserScript==
  2. // @name WaniKani JJ External Definition
  3. // @namespace http://www.wanikani.com
  4. // @version 1.4.2
  5. // @description Get JJ External Definition from Weblio, Kanjipedia
  6. // @author polv
  7. // @author NicoleRauch
  8. // @match *://www.wanikani.com/*
  9. // @match *://preview.wanikani.com/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=weblio.jp
  11. // @license MIT
  12. // @require https://unpkg.com/dexie@3/dist/dexie.js
  13. // @require https://gf.qytechs.cn/scripts/430565-wanikani-item-info-injector/code/WaniKani%20Item%20Info%20Injector.user.js?version=1276163
  14. // @grant GM_xmlhttpRequest
  15. // @connect kanjipedia.jp
  16. // @connect weblio.jp
  17. // @homepage https://github.com/patarapolw/wanikani-userscript/blob/master/userscripts/external-definition.user.js
  18. // ==/UserScript==
  19.  
  20. // @ts-check
  21. /// <reference path="./types/item-info.d.ts" />
  22. /// <reference path="./types/gm.d.ts" />
  23. (function () {
  24. 'use strict';
  25.  
  26. const AFTER_EN_MEANING = false;
  27.  
  28. /** @type {number | undefined} */
  29. const MAX_ENTRIES = 3;
  30. /** @type {number | undefined} */
  31. const HTML_MAX_CHAR = 10000;
  32.  
  33. const entryClazz = 'wkexternaldefinition';
  34.  
  35. const style = document.createElement('style');
  36. style.appendChild(
  37. document.createTextNode(/* css */ `
  38.  
  39. .${entryClazz} {
  40. --kanji-variant-size: 64px;
  41. }
  42.  
  43. .${entryClazz} details {
  44. margin-top: 1em;
  45. }
  46.  
  47. .${entryClazz} details summary {
  48. display: revert;
  49. margin-bottom: 1em;
  50. cursor: pointer;
  51. }
  52.  
  53. .${entryClazz} .spoiler:not(:hover), .${entryClazz} .spoiler:not(:hover) * {
  54. background-color: #ccc;
  55. color: #ccc;
  56. text-shadow: none;
  57. }
  58.  
  59. .${entryClazz} .keep-10em {
  60. display: inline-block;
  61. width: 10em;
  62. min-width: fit-content;
  63. max-width: 100%;
  64. }
  65.  
  66. /* Weblio fixes */
  67. .${entryClazz} p {
  68. margin-bottom: 0.5em;
  69. }
  70. .${entryClazz} a.crosslink {
  71. color: #023e8a;
  72. }
  73. .${entryClazz} a {
  74. text-decoration: none;
  75. }
  76. .${entryClazz} a.external {
  77. text-decoration: underline;
  78. }
  79. .${entryClazz} ol {
  80. list-style: revert;
  81. padding: revert;
  82. }
  83. .${entryClazz} ul {
  84. list-style: revert;
  85. padding: revert;
  86. }
  87. .${entryClazz} .wnryjNotice {
  88. border: #b5b6b5 solid 1px;
  89. font-size: 0.8em;
  90. line-height: 1.32em;
  91. margin: 16px 0 0 0;
  92. padding: 10px;
  93. width: auto;
  94. }
  95. .${entryClazz} .SgkdjImg img {
  96. width: 40%;
  97. height: 40%;
  98. }
  99. .${entryClazz} .synonymsUnderDictWrp {
  100. margin-top: 1em;
  101. }
  102. .${entryClazz} .synonymsUnderDict {
  103. background-color: #f7f7f7;
  104. clear: both;
  105. margin: 0 0 0 8px;
  106. padding: 2px 8px;
  107. }
  108. .${entryClazz} .synonymsUnderDict a {
  109. padding-right: 1em;
  110. }
  111. .${entryClazz} .tssmjC {
  112. background-color: #f0f0f0;
  113. border: #666666 solid 1px;
  114. color: #363636;
  115. font-size: 0.9em;
  116. line-height: 1.0em;
  117. margin-right: 5px;
  118. padding: 1px;
  119. }
  120.  
  121. /* Kanjipedia fixes */
  122. .${entryClazz}-kanjipedia-reading-horizontal * {
  123. display: inline-block;
  124. }
  125. .${entryClazz}-kanjipedia-reading-horizontal li {
  126. margin-right: 1em;
  127. }
  128. .${entryClazz}-kanjipedia-reading-horizontal .kanji-variant-header {
  129. display: none;
  130. }
  131.  
  132. .${entryClazz} .kanji-variant {
  133. display: flex;
  134. flex-direction: row;
  135. align-items: center;
  136. justify-content: center;
  137. width: 100%;
  138. font-size: var(--kanji-variant-size, 64px);
  139. font-family: "HiraMinProN-W3", "Hiragino Mincho ProN W3", "Hiragino Mincho ProN", "ヒラギノ明朝 ProN W3", "游明朝", YuMincho, "HG明朝E", "MS P明朝", "MS PMincho", "MS 明朝", "MS Mincho", serif; /* Font list from Jisho.org */
  140. margin-top: 0;
  141. margin-bottom: 0;
  142. }
  143. .${entryClazz} .kanji-variant img {
  144. height: var(--kanji-variant-size, 64px);
  145. }
  146. .${entryClazz} .kanji-variant + .kanji-variant {
  147. margin-left: 1em;
  148. }
  149. .${entryClazz} .okurigana {
  150. color: #ab9b96;
  151. }
  152. @media only screen and (min-width: 768px) {
  153. .subject-readings__reading {
  154. flex: 1;
  155. }
  156. }
  157. `),
  158. );
  159. document.head.appendChild(style);
  160.  
  161. const radicalMap = {
  162. Gun: ['𠂉'],
  163. Leaf: ['丆'],
  164. Beggar: ['丂'],
  165. Spikes: ['业'],
  166. Kick: ['𧘇'],
  167. Viking: ['𤇾', '𦥯'],
  168. Cape: ['𠃌'],
  169. Hills: [],
  170. Gladiator: ['龹'],
  171. Pope: [],
  172. Spring: ['𡗗'],
  173. Squid: ['㑒', '僉'],
  174. Yurt: [],
  175. Chinese: ['𦰩', '堇'],
  176. Bear: ['㠯'],
  177. Blackjack: ['龷'],
  178. Trash: ['𠫓'],
  179. Tofu: [],
  180. Creeper: [],
  181. Bar: ['㦮', '戔'],
  182. Saw: ['巩'],
  183. Zombie: ['袁'],
  184. Explosion: [],
  185. Morning: ['𠦝', '龺'],
  186. 'Death Star': ['俞'],
  187. Comb: [],
  188. Elf: [],
  189. Coral: ['丞'],
  190. Cactus: [],
  191. Satellite: ['䍃'],
  192. Psychopath: ['鬯'], // Except this one; but it's a smaller radical not elsewhere described.
  193. };
  194.  
  195. ///////////////////////////////////////////////////////////////////////////////////////////////////
  196.  
  197. // @ts-ignore
  198. const _Dexie = /** @type {typeof import('dexie').default} */ (Dexie);
  199. /**
  200. * @typedef {{ id: string; url: string; definition: string; reading: string; variant: string }} EntryKanjipedia
  201. * @typedef {{ id: string; url: string; definitions: string[] }} EntryWeblio
  202. */
  203.  
  204. class Database extends _Dexie {
  205. /** @type {import('dexie').Table<EntryKanjipedia, string>} */
  206. kanjipedia;
  207.  
  208. /** @type {import('dexie').Table<EntryWeblio, string>} */
  209. weblio;
  210.  
  211. constructor() {
  212. super(entryClazz);
  213. this.version(1).stores({
  214. kanjipedia: 'id,url',
  215. weblio: 'id,url',
  216. });
  217. }
  218. }
  219.  
  220. const db = new Database();
  221.  
  222. ///////////////////////////////////////////////////////////////////////////////////////////////////
  223. // Updating the kanji and vocab we are looking for
  224. /** @type {string | undefined} */
  225. let kanji;
  226. /** @type {string | undefined} */
  227. let vocab;
  228.  
  229. let isSuru = false;
  230.  
  231. let isSuffix = false;
  232. /** @type {string[]} */
  233. let reading = [];
  234.  
  235. let kanjipediaDefinition;
  236. let weblioDefinition;
  237. let kanjipediaReading;
  238.  
  239. let qType = '';
  240. let sType = '';
  241.  
  242. window.addEventListener('willShowNextQuestion', (e) => {
  243. // First, remove any already existing entries to avoid displaying entries for other items:
  244. document.querySelectorAll('.' + entryClazz).forEach((el) => el.remove());
  245. kanji = undefined;
  246. vocab = undefined;
  247. reading = [];
  248. qType = '';
  249.  
  250. kanjipediaDefinition = undefined;
  251. kanjipediaReading = undefined;
  252. weblioDefinition = undefined;
  253.  
  254. if ('detail' in e) {
  255. const { subject, questionType } = /** @type {any} */ (e.detail);
  256. qType = questionType;
  257. sType = subject.subject_category || subject.type;
  258. if (sType === 'Vocabulary') {
  259. vocab = fixVocab(subject.characters);
  260. reading = subject.readings
  261. ? subject.readings.map((r) => r.reading)
  262. : [subject.characters];
  263. } else {
  264. kanji =
  265. typeof subject.characters === 'string'
  266. ? subject.characters
  267. : getRadicalKanji(subject.meanings);
  268. }
  269. }
  270.  
  271. updateInfo();
  272. });
  273.  
  274. /**
  275. *
  276. * @param {string} v
  277. * @returns
  278. */
  279. function fixVocab(v) {
  280. const suru = 'する';
  281. isSuru = v.endsWith(suru) && v !== suru;
  282. if (isSuru) {
  283. v = v.substring(0, v.length - suru.length);
  284. reading = reading.map((r) => r.replace(new RegExp(suru + '$'), ''));
  285. }
  286.  
  287. const extMark = '〜';
  288. isSuffix = v.startsWith(extMark);
  289. if (isSuffix) {
  290. v = v.substring(extMark.length);
  291. }
  292.  
  293. return v.replace(/(.)々/g, '$1$1');
  294. }
  295.  
  296. ///////////////////////////////////////////////////////////////////////////////////////////////////
  297. /**
  298. * Loading the information and updating the webpage
  299. *
  300. * @returns {Promise<void>}
  301. */
  302. async function updateInfo() {
  303. /**
  304. *
  305. * @param {string} definition
  306. * @param {string} full_url
  307. * @param {string} name
  308. * @returns {string}
  309. */
  310. function insertDefinition(definition, full_url, name) {
  311. const output = document.createElement('div');
  312. output.className = entryClazz;
  313. output.lang = 'ja';
  314. output.innerHTML = definition;
  315.  
  316. if (full_url) {
  317. const a = document.createElement('a');
  318. a.className = 'external';
  319. a.innerText = 'Click for full entry';
  320. a.href = full_url;
  321.  
  322. const p = document.createElement('p');
  323. p.style.marginTop = '0.5em';
  324. p.append(a);
  325. output.append(p);
  326. }
  327.  
  328. output.querySelectorAll('a').forEach((a) => {
  329. a.target = '_blank';
  330. a.rel = 'noopener noreferrer';
  331. });
  332.  
  333. if (name === 'Kanjipedia') {
  334. kanjipediaDefinition = output;
  335. kanjipediaInserter.renew();
  336. } else {
  337. weblioDefinition = output;
  338. weblioInserter.renew();
  339. }
  340.  
  341. return output.outerHTML;
  342. }
  343.  
  344. /**
  345. *
  346. * @param {string} kanji
  347. * @returns {Promise<string>}
  348. */
  349. async function searchKanjipedia(kanji) {
  350. /**
  351. *
  352. * @param {EntryKanjipedia} r
  353. */
  354. const setContent = (r) => {
  355. kanjipediaReading = r.reading;
  356.  
  357. let htmlVar = '';
  358.  
  359. if (r.variant) {
  360. r.variant = r.variant.trim();
  361. if (!r.variant.startsWith('<')) {
  362. r.variant = `<div>${r.variant}</div>`;
  363. }
  364.  
  365. const el = document.createElement('div');
  366. el.innerHTML = r.variant;
  367. el.querySelectorAll('img').forEach((it) => {
  368. it.removeAttribute('style');
  369. });
  370.  
  371. htmlVar = [
  372. '<li class="kanji-variant-header">異体字</li>',
  373. `<div class="kanji-variant">${el.innerHTML}</div>`,
  374. ].join('\n');
  375.  
  376. kanjipediaReading += htmlVar;
  377. }
  378.  
  379. kanjipediaReadingInserter.renew();
  380.  
  381. return insertDefinition(
  382. (qType === 'meaning' && sType !== 'Radical'
  383. ? htmlVar
  384. : `<ul class="${entryClazz}-kanjipedia-reading-horizontal">${kanjipediaReading}</ul>`) +
  385. r.definition
  386. .split('<br>')
  387. .map((s) => `<p>${s}</p>`)
  388. .join('\n'),
  389. r.url,
  390. 'Kanjipedia',
  391. );
  392. };
  393.  
  394. const r = await db.kanjipedia.get(kanji);
  395. if (r) {
  396. return setContent(r);
  397. }
  398.  
  399. const kanjipediaUrlBase = 'https://www.kanjipedia.jp/';
  400. const regexImgSrc = /img src="/g;
  401. const replacementImgSrc = 'img width="16px" src="' + kanjipediaUrlBase;
  402. const regexTxtNormal = /class="txtNormal">/g;
  403. const replacementTxtNormal = '>.';
  404. const regexSpaceBeforeCircledNumber = / ([\u2460-\u2473])/g;
  405.  
  406. return new Promise((resolve, reject) => {
  407. function onerror(e) {
  408. (window.unsafeWindow || window).console.error(arguments);
  409. reject(e);
  410. }
  411.  
  412. GM_xmlhttpRequest({
  413. method: 'GET',
  414. url: kanjipediaUrlBase + 'search?k=' + kanji + '&kt=1&sk=leftHand',
  415. onerror,
  416. onload: function (data) {
  417. const div = document.createElement('div');
  418. div.innerHTML = data.responseText.replace(
  419. regexImgSrc,
  420. replacementImgSrc,
  421. );
  422.  
  423. const firstResult = /** @type {HTMLAnchorElement} */ (
  424. div.querySelector('#resultKanjiList a')
  425. );
  426. if (!firstResult) {
  427. resolve('');
  428. return;
  429. }
  430.  
  431. const rawKanjiURL = firstResult.href;
  432. const kanjiPageURL = kanjipediaUrlBase + rawKanjiURL.slice(25);
  433. GM_xmlhttpRequest({
  434. method: 'GET',
  435. url: kanjiPageURL,
  436. onerror,
  437. onload: function (data) {
  438. const rawResponseNode = document.createElement('div');
  439. rawResponseNode.innerHTML = data.responseText
  440. .replace(regexImgSrc, replacementImgSrc)
  441. .replace(regexTxtNormal, replacementTxtNormal)
  442. .replace(regexSpaceBeforeCircledNumber, '<br/>$1');
  443.  
  444. const readingNode = rawResponseNode.querySelector(
  445. '#kanjiLeftSection #onkunList',
  446. );
  447. if (!readingNode) return;
  448.  
  449. // Okurigana dot removal, so that it can be read as a vocabulary with Yomichan
  450. readingNode.querySelectorAll('span').forEach((it) => {
  451. const text = it.innerText;
  452. if (text[0] === '.') {
  453. it.innerText = text.substring(1);
  454. it.classList.add('okurigana');
  455. it.style.color = '#ab9b96';
  456. }
  457. });
  458.  
  459. const r = {
  460. id: kanji,
  461. url: kanjiPageURL,
  462. reading: readingNode.innerHTML,
  463. definition: Array.from(
  464. rawResponseNode.querySelectorAll('#kanjiRightSection p'),
  465. )
  466. .map((p) => p.innerHTML)
  467. .join('\n'),
  468. variant: (() => {
  469. const vs = [
  470. ...rawResponseNode.querySelectorAll('#kanjiOyaji'),
  471. ...rawResponseNode.querySelectorAll('.subKanji'),
  472. ].filter(
  473. (n) => n.textContent !== decodeURIComponent(kanji || ''),
  474. );
  475.  
  476. if (!vs.length) return '';
  477.  
  478. vs.map((v) => {
  479. v.classList.add('kanji-variant');
  480. v.querySelectorAll('img').forEach((img) => {
  481. img.removeAttribute('width');
  482. });
  483. });
  484.  
  485. return vs.map((v) => v.innerHTML).join('\n');
  486. })(),
  487. };
  488.  
  489. db.kanjipedia.add(r);
  490. resolve(setContent(r));
  491. },
  492. });
  493. },
  494. });
  495. });
  496. }
  497.  
  498. /**
  499. *
  500. * @param {string} vocab
  501. * @returns {Promise<string>}
  502. */
  503. async function searchWeblio(vocab) {
  504. /**
  505. *
  506. * @param {EntryWeblio} [r]
  507. */
  508. const setContent = (r) => {
  509. if (!r || !r.definitions.length) {
  510. if (kanji) {
  511. return insertDefinition(
  512. `No entries found. Try <a class="external" href="https://en.wiktionary.org/wiki/${kanji}" target="_blank" rel="noopener noreferrer">${kanji} - Wiktionary</a>`,
  513. '',
  514. 'Wiktionary',
  515. );
  516. }
  517.  
  518. return '';
  519. }
  520. const reYomi = /(読み方:)([\p{sc=Katakana}\p{sc=Hiragana}ー]+)/gu;
  521. const makeYomiSpoiler = (s) =>
  522. qType === 'meaning' && sType !== 'Radical'
  523. ? s
  524. .replace(reYomi, '$1<span class="spoiler keep-10em">$2</span>')
  525. .replace(/<p(>.*?[[音訓]].*?<\/p>)/s, '<p class="spoiler"$1')
  526. : s;
  527.  
  528. const sortedDef = r.definitions
  529. .sort((t1, t2) => {
  530. /**
  531. *
  532. * @param {string} t
  533. * @returns {number}
  534. */
  535. const fn = (t) => {
  536. let isKanji = /[[音訓]]/.exec(t);
  537. if (kanji && isKanji) return -10;
  538.  
  539. reYomi.lastIndex = 0;
  540. const m = reYomi.exec(t);
  541. if (m) {
  542. if (!reading.length) return 0;
  543. if (isKanji) return reading.length;
  544.  
  545. let readingIdx = reading.indexOf(m[2]);
  546. if (readingIdx === -1) return 100;
  547.  
  548. if (isSuffix && t.includes('接尾')) {
  549. readingIdx -= 0.5;
  550. }
  551.  
  552. if (isSuru && t.includes('スル')) {
  553. readingIdx -= 0.5;
  554. }
  555.  
  556. return readingIdx;
  557. }
  558.  
  559. return 1000;
  560. };
  561. return fn(t1) - fn(t2);
  562. })
  563. .map((html) => {
  564. if (!HTML_MAX_CHAR || html.length < HTML_MAX_CHAR) {
  565. return makeYomiSpoiler(html);
  566. }
  567.  
  568. const div = document.createElement('div');
  569. div.innerHTML = makeYomiSpoiler(html.substring(0, HTML_MAX_CHAR));
  570.  
  571. const mark = document.createElement('mark');
  572. mark.style.cursor = 'pointer';
  573. mark.setAttribute('data-html', html);
  574. mark.textContent = '...';
  575.  
  576. html = div.outerHTML.replace(
  577. /<\/div>$/,
  578. mark.outerHTML.replace(
  579. /^<mark /,
  580. '$&' +
  581. 'onclick="parentElement.innerHTML=getAttribute(\'data-html\')" ',
  582. ) + '$&',
  583. );
  584. div.remove();
  585.  
  586. return html;
  587. });
  588.  
  589. let vocabDefinition = sortedDef.splice(0, MAX_ENTRIES).join('<hr>');
  590.  
  591. if (sortedDef.length) {
  592. vocabDefinition += `<details><summary>Show more</summary>${sortedDef.join(
  593. '<hr>',
  594. )}</details>`;
  595. }
  596.  
  597. return insertDefinition(vocabDefinition, r.url, 'Weblio');
  598. };
  599.  
  600. const r = await db.weblio.get(vocab);
  601. if (r) {
  602. return setContent(r);
  603. }
  604.  
  605. const vocabPageURL = 'https://www.weblio.jp/content/' + vocab;
  606.  
  607. return new Promise((resolve, reject) => {
  608. function onerror(e) {
  609. (window.unsafeWindow || window).console.error(arguments);
  610. setContent();
  611. reject(e);
  612. }
  613.  
  614. GM_xmlhttpRequest({
  615. method: 'GET',
  616. url: vocabPageURL,
  617. onerror,
  618. onload: function (data) {
  619. if (!data.responseText) {
  620. resolve(setContent());
  621. return;
  622. }
  623.  
  624. const div = document.createElement('div');
  625. div.innerHTML = data.responseText;
  626. const definitions = Array.from(div.querySelectorAll('.kiji'))
  627. .flatMap((el) => {
  628. return Array.from(el.children).filter(
  629. (el) => el instanceof HTMLDivElement,
  630. );
  631. })
  632. .map((el) => {
  633. if (el instanceof HTMLElement) {
  634. if (el.querySelector('script')) return '';
  635. return el.innerHTML;
  636. }
  637. return '';
  638. })
  639. .filter((s) => s);
  640. div.remove();
  641.  
  642. if (!definitions.length) {
  643. resolve(setContent());
  644. return;
  645. }
  646.  
  647. const r = {
  648. id: vocab,
  649. url: vocabPageURL,
  650. definitions,
  651. };
  652.  
  653. db.weblio.add(r);
  654. resolve(setContent(r));
  655. },
  656. });
  657. });
  658. }
  659.  
  660. if (kanji) {
  661. await Promise.allSettled([searchKanjipedia(kanji), searchWeblio(kanji)]);
  662. } else if (vocab) {
  663. await searchWeblio(vocab);
  664. }
  665. }
  666.  
  667. ///////////////////////////////////////////////////////////////////////////////////////////////////
  668. // Triggering updates on lessons and reviews
  669.  
  670. const kanjipediaInserter = wkItemInfo
  671. .on('lesson,lessonQuiz,review,extraStudy,itemPage')
  672. .forType('kanji,radical')
  673. .under('meaning')
  674. .spoiling('meaning')
  675. .notify((state) => {
  676. if (
  677. !(
  678. kanji &&
  679. (kanji === state.characters ||
  680. kanji === getRadicalKanji(state.meaning))
  681. )
  682. ) {
  683. return;
  684. }
  685.  
  686. if (!kanjipediaDefinition) return;
  687.  
  688. const title = 'Kanjipedia Explanation';
  689. if (
  690. AFTER_EN_MEANING ||
  691. state.on === 'itemPage' ||
  692. (state.type === 'radical' && state.on === 'lesson')
  693. ) {
  694. state.injector.append(title, kanjipediaDefinition);
  695. } else {
  696. state.injector.appendAtTop(title, kanjipediaDefinition);
  697. }
  698. });
  699.  
  700. const weblioInserter = wkItemInfo
  701. .on('lesson,lessonQuiz,review,extraStudy,itemPage')
  702. .under('meaning')
  703. .spoiling('meaning')
  704. .notify((state) => {
  705. if (state.on === 'itemPage') {
  706. qType = '';
  707. }
  708.  
  709. const isVocabulary = state.type
  710. .toLocaleLowerCase()
  711. .endsWith('vocabulary');
  712.  
  713. let fixedCharacters = state.characters;
  714. if (isVocabulary) {
  715. fixedCharacters = fixVocab(state.characters);
  716. }
  717.  
  718. if (state.on === 'itemPage') {
  719. if (isVocabulary) {
  720. kanji = '';
  721. if (vocab !== fixedCharacters) {
  722. reading = state.reading;
  723. vocab = fixedCharacters;
  724.  
  725. updateInfo();
  726. return;
  727. }
  728. } else {
  729. vocab = '';
  730. reading = [];
  731. const newKanji = state.characters || getRadicalKanji(state.meaning);
  732.  
  733. if (kanji !== newKanji) {
  734. kanji = newKanji;
  735. updateInfo();
  736. return;
  737. }
  738.  
  739. if (!kanji) return;
  740. }
  741. } else {
  742. if (isVocabulary) {
  743. if (fixedCharacters !== vocab) return;
  744. } else if (kanji) {
  745. if (
  746. typeof state.characters === 'string'
  747. ? kanji !== state.characters
  748. : kanji !== getRadicalKanji(state.meaning)
  749. )
  750. return;
  751. }
  752. }
  753.  
  754. if (!weblioDefinition) return;
  755.  
  756. const title = 'Weblio Explanation';
  757. if (
  758. AFTER_EN_MEANING ||
  759. state.on === 'itemPage' ||
  760. (state.type === 'radical' && state.on === 'lesson')
  761. ) {
  762. state.injector.append(title, weblioDefinition);
  763. } else {
  764. state.injector.appendAtTop(title, weblioDefinition);
  765. }
  766. });
  767.  
  768. let kanjipediaReadingPanelInterval = 0;
  769.  
  770. const kanjipediaReadingInserter = wkItemInfo
  771. .on('lesson,lessonQuiz,review,extraStudy,itemPage')
  772. .forType('kanji')
  773. .under('reading')
  774. .notify((state) => {
  775. if (!(kanji && kanji === state.characters)) {
  776. return;
  777. }
  778.  
  779. if (!kanjipediaReading) return;
  780. clearInterval(kanjipediaReadingPanelInterval);
  781.  
  782. if (state.on === 'itemPage') {
  783. document
  784. .querySelectorAll(`.${entryClazz}-reading`)
  785. .forEach((el) => el.remove());
  786.  
  787. const dst = document.querySelector('.subject-readings');
  788.  
  789. if (dst) {
  790. const el = document.createElement('div');
  791. el.className = `subject-readings__reading subject-readings__reading--primary ${entryClazz} ${entryClazz}-reading`;
  792.  
  793. const h = document.createElement('h3');
  794. h.className = 'subject-readings__reading-title';
  795. h.innerText = 'Kanjipedia';
  796.  
  797. const content = document.createElement('div');
  798. content.className = 'subject-readings__reading-items';
  799. content.lang = 'ja';
  800. content.innerHTML = kanjipediaReading;
  801.  
  802. el.append(h, content);
  803. dst.append(el);
  804. }
  805. } else {
  806. kanjipediaReadingPanelInterval = setInterval(() => {
  807. const node = document.querySelector('.subject-readings');
  808. if (node) {
  809. if (node.querySelector(`.${entryClazz}`)) {
  810. return clearInterval(kanjipediaReadingPanelInterval);
  811. }
  812. node.insertAdjacentHTML(
  813. 'beforeend',
  814. '<div class="subject-readings__reading subject-readings__reading--primary ' +
  815. entryClazz +
  816. ' ' +
  817. entryClazz +
  818. '-reading' +
  819. '"><h3 class="subject-readings__reading-title">Kanjipedia</h3>' +
  820. `<p class="subject-readings__reading-items" lang="ja">${kanjipediaReading}</p>` +
  821. '</div>',
  822. );
  823. return clearInterval(kanjipediaReadingPanelInterval);
  824. }
  825. }, 100);
  826. }
  827. });
  828.  
  829. /**
  830. *
  831. * @param {string[]} meanings
  832. * @returns {string | undefined}
  833. */
  834. function getRadicalKanji(meanings) {
  835. const [en] = meanings;
  836. if (!en) return;
  837. const ks = radicalMap[en];
  838. if (!ks) return;
  839. console.log(
  840. `${entryClazz}: ${
  841. ks.length
  842. ? `converted ${en} to ${ks.join(', ')}`
  843. : `cannot convert ${en} to Kanji`
  844. }`,
  845. );
  846. return ks[0];
  847. }
  848. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址