您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds the section "User Synonyms" to WaniKani lessons.
// ==UserScript== // @name WaniKani Lesson User Synonyms 3 // @namespace https://greasyfork.org/users/317813 // @version 1.9 // @description Adds the section "User Synonyms" to WaniKani lessons. // @author Sinyaven // @license MIT-0 // @match https://www.wanikani.com/lesson/session // @match https://preview.wanikani.com/lesson/session // @homepageURL https://community.wanikani.com/t/54399 // @require https://greasyfork.org/scripts/430565-wanikani-item-info-injector/code/WaniKani%20Item%20Info%20Injector.user.js?version=1111117 // @grant none // ==/UserScript== (function() { "use strict"; /* global WaniKani, wkof, wkItemInfo, $ */ if (!window.wkof) { if (confirm(`WaniKani Lesson User Synonyms 3 script requires Wanikani Open Framework.\nShow installation instructions?`)) { location.href = `https://community.wanikani.com/t/28549`; } return; } addCss(); wkof.include(`Apiv2`); wkItemInfo.on(`lesson,lessonQuiz`).under(`meaning`).notifyWhenVisible(addUserSynonymSection); async function downloadUserSynonyms(itemId) { await wkof.ready(`Apiv2`); let response = await wkof.Apiv2.fetch_endpoint(`study_materials`, {filters: {subject_ids: [itemId]}, disable_progress_dialog: true}); return response.data[0]?.data.meaning_synonyms || []; } function uploadUserSynonyms(itemId, itemType, synonyms) { let data = {study_material: {subject_type: itemType, subject_id: itemId, meaning_synonyms: synonyms}}; fetch(`/study_materials/${itemId}`, { method: `PUT`, headers: { "Content-Type": `application/json; charset=utf-8`, "X-CSRF-Token": document.querySelector(`meta[name=csrf-token]`).content }, body: JSON.stringify(data) }); } function updateUserSynonymsInLessonQueue(itemId, synonyms) { let activeQueue = $.jStorage.get(`l/activeQueue`); let item = activeQueue.find(a => a.id === itemId); if (!item) return; item.syn = synonyms; $.jStorage.set(`l/activeQueue`, activeQueue); } function addUserSynonymSection(injectorState) { injectorState.injector.appendSideInfo(`User Synonyms`, userSynonymSection(injectorState)).classList.add(`user-synonyms`); } function userSynonymSection(injectorState) { let blacklist = $.jStorage.get(injectorState.on === `lesson` ? `l/currentLesson` : `l/currentQuizItem`).auxiliary_meanings.filter(m => m.type === `blacklist`).map(m => m.meaning.toLowerCase()); let ul = document.createElement(`ul`); insertUserSynonyms(ul, injectorState, blacklist); let addSynonym = document.createElement(`li`); addSynonym.title = `Add your own synonym`; addSynonym.classList.add(`user-synonyms-add-btn`); addSynonym.addEventListener(`click`, e => clickOnAddSynonym(e, injectorState, blacklist)); ul.append(addSynonym); return ul; } async function insertUserSynonyms(ul, injectorState, blacklist) { let userSynonyms = await downloadUserSynonyms(injectorState.id); ul.prepend(...userSynonyms.map(u => createSynonymElement(u, injectorState, blacklist))); updateUserSynonymsInLessonQueue(injectorState.id, userSynonyms); } function createSynonymElement(synonym, injectorState, blacklist) { let li = document.createElement(`li`); li.title = `Click to remove synonym`; li.textContent = synonym; li.style.maxWidth = `100%`; li.style.overflowWrap = `break-word`; li.addEventListener(`click`, e => clickOnSynonym(e, injectorState)); if (blacklist.includes(synonym.toLowerCase())) li.classList.add(`on-blacklist`); return li; } function clickOnSynonym(event, injectorState) { let parent = event.currentTarget.parentElement; event.currentTarget.remove(); readUserSynonymsFromHtmlAndUploadThem(parent, injectorState); } function clickOnAddSynonym(event, injectorState, blacklist) { let li = document.createElement(`li`); let form = document.createElement(`form`); let input = document.createElement(`input`); let bSubmit = document.createElement(`button`); let bCancel = document.createElement(`button`); let icon = document.createElement(`i`); li.classList.add(`user-synonyms-add-form`); input.type = `text`; input.autocapitalize = `none`; input.autocomplete = `off`; input.autocorrect = `off`; input.spellcheck = `false`; input.maxLength = 64; bCancel.type = `button`; bSubmit.type = `submit`; bSubmit.textContent = `Add`; icon.classList.add(`fa`, `fa-times`); compatibilityModeFix(form, input, injectorState, blacklist); form.addEventListener(`submit`, e => submitSynonym(e, injectorState, blacklist)); input.addEventListener(`keydown`, e => e.stopPropagation()); bCancel.addEventListener(`click`, clickOnCancel); bCancel.append(icon); form.append(input, bSubmit, bCancel); li.append(form); event.currentTarget.before(li); input.focus(); } function compatibilityModeFix(form, input, injectorState, blacklist) { if (!WaniKani.wanikani_compatibility_mode) return; let delaySubmit = false; form.addEventListener(`submit`, e => { if (delaySubmit) e.stopImmediatePropagation(); e.preventDefault(); delaySubmit = false; }); input.addEventListener(`keydown`, e => { if (e.code === `Enter`) delaySubmit = true; }); input.addEventListener(`keyup`, e => { if (e.code === `Enter`) addSynonym(e.currentTarget.parentElement, injectorState, blacklist); e.stopPropagation(); }); } function clickOnCancel(event) { event.currentTarget.parentElement.parentElement.remove(); } function submitSynonym(event, injectorState, blacklist) { addSynonym(event.currentTarget, injectorState, blacklist); event.preventDefault(); } function addSynonym(form, injectorState, blacklist) { let synonym = form[0].value.trim(); let ul = form.parentElement.parentElement; if (!synonym || readUserSynonymsFromHtml(ul).includes(synonym)) return; let synonymElement = createSynonymElement(synonym, injectorState, blacklist); form.parentElement.before(synonymElement); form.parentElement.remove(); readUserSynonymsFromHtmlAndUploadThem(ul, injectorState); } function readUserSynonymsFromHtmlAndUploadThem(ul, injectorState) { let userSynonyms = readUserSynonymsFromHtml(ul); uploadUserSynonyms(injectorState.id, injectorState.type, userSynonyms); updateUserSynonymsInLessonQueue(injectorState.id, userSynonyms); } function readUserSynonymsFromHtml(ul) { return [...ul.children].filter(c => !c.classList.contains(`user-synonyms-add-btn`) && !c.classList.contains(`user-synonyms-add-form`)).map(c => c.textContent); } function addCss() { let style = document.createElement(`style`); style.textContent = ` .user-synonyms ul { margin: 0; padding: 0 } .user-synonyms ul li { display: inline-block; line-height: 1.5em } .user-synonyms ul li:not(.user-synonyms-add-btn):not(.user-synonyms-add-form) { cursor: pointer; vertical-align: middle } .user-synonyms ul li:not(.user-synonyms-add-btn):not(.user-synonyms-add-form):after { content: '\\f00d'; margin-left: 0.5em; margin-right: 1.5em; padding: 0.15em 0.3em; color: #a2a2a2; background-color: #eee; font-size: 0.5em; font-family: FontAwesome; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-transition: background-color 0.3s linear, color 0.3s linear; -moz-transition: background-color 0.3s linear, color 0.3s linear; -o-transition: background-color 0.3s linear, color 0.3s linear; transition: background-color 0.3s linear, color 0.3s linear; vertical-align: middle } .user-synonyms ul li:not(.user-synonyms-add-btn):not(.user-synonyms-add-form):hover:after { background-color: #f03; color: #fff } .user-synonyms ul li.user-synonyms-add-btn { cursor: pointer; display: block; font-size: 0.75em; margin-top: 0.25em } .user-synonyms ul li.user-synonyms-add-btn:after { content: '' } .user-synonyms ul li.user-synonyms-add-btn:before { content: '+ ADD SYNONYM'; margin-right: 0.5em; padding: 0.15em 0.3em; background-color: #eee; color: #a2a2a2; -webkit-transition: background-color 0.3s linear, color 0.3s linear; -moz-transition: background-color 0.3s linear, color 0.3s linear; -o-transition: background-color 0.3s linear, color 0.3s linear; transition: background-color 0.3s linear, color 0.3s linear; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px } .user-synonyms ul li.user-synonyms-add-btn:hover:before { background-color: #a2a2a2; color: #fff } .user-synonyms ul li.user-synonyms-add-form { display: block } .user-synonyms ul li.user-synonyms-add-form form { display: block; margin: 0; padding: 0 } .user-synonyms ul li.user-synonyms-add-form form input,.user-synonyms ul li.user-synonyms-add-form form button { line-height: 1em } .user-synonyms ul li.user-synonyms-add-form form input { outline: none; display: block; width: 100%; margin: 0; padding: 0; border: 0; border-bottom: 1px solid #a2a2a2 } .user-synonyms ul li.user-synonyms-add-form form button { outline: none; background-color: #eee; color: #a2a2a2; font-size: 0.75em; border: none; -webkit-transition: background-color 0.3s linear, color 0.3s linear; -moz-transition: background-color 0.3s linear, color 0.3s linear; -o-transition: background-color 0.3s linear, color 0.3s linear; transition: background-color 0.3s linear, color 0.3s linear; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px } .user-synonyms ul li.user-synonyms-add-form form button:hover { background-color: #a2a2a2; color: #fff } .user-synonyms ul li.user-synonyms-add-form form button:disabled { cursor: default; background-color: red; color: #fff } .user-synonyms ul li.user-synonyms-add-form form button[type=button] { margin-left: 0.25em; padding-left: 0.3em; padding-right: 0.3em } .user-synonyms ul li.user-synonyms-add-form form button[type=button]:hover { background-color: red; color: #fff } .user-synonyms .user-synonyms-add-form + .user-synonyms-add-btn { display: none } .item-info-injector.user-synonyms li + li + li + li + li + li + li + li + * { display: none } .user-synonyms .on-blacklist { color: red } `; // except for the last three rules, the CSS is copied from the WK review page. document.head.appendChild(style); } })();