NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Cotsu-Tools
// @namespace mybearworld
// @match *://cotsu.de/*
// @grant GM.xmlHttpRequest
// @version 1.16.5
// @license MIT
// @author mybearworld
// @description Userscript für https://cotsu.de.
// @updateURL https://openuserjs.org/meta/mybearworld/Cotsu-Tools.meta.js
// @downloadURL https://openuserjs.org/install/mybearworld/Cotsu-Tools.user.js
// ==/UserScript==
"use strict";
(() => {
// src/lib/isSceneChange.ts
var isSceneChange = (records) => records.some(
(record) => record.target instanceof HTMLElement && record.target.id === "gatsby-focus-wrapper" || record.addedNodes.length > 0 && record.addedNodes[0] instanceof HTMLElement && record.addedNodes[0].id === "gatsby-focus-wrapper"
);
// src/lib/element.ts
var element = (a) => {
if (!(a instanceof HTMLElement)) throw new Error("Not an element");
return a;
};
var text = (a) => {
if (!(a instanceof Text)) throw new Error("Not an element");
return a;
};
// src/lib/gmfetch.ts
var gmfetch = (options) => {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
...options,
onload: (response) => {
if (response.status < 200 || response.status > 299) {
reject();
}
resolve(response.response);
},
onerror: () => {
reject();
}
});
});
};
// src/lib/wadokuInformation.ts
var parser = new DOMParser();
var wadokuSearchResultCache = /* @__PURE__ */ new Map();
var wadokuInformationCache = /* @__PURE__ */ new Map();
var TILDE = /[~〜]/g;
var getWadokuInformation = async (kanji, reading, bulk = [kanji]) => {
const shouldHaveEllipsis = kanji.includes("\u2026");
kanji = kanji.replace(TILDE, "\u2026");
reading = reading.replace(TILDE, "");
bulk = bulk.map((s) => s.replace(TILDE, "\u2026"));
if (reading.includes("\u30FB")) {
const results = await Promise.all(
reading.split("\u30FB").map(
(individualReading) => getWadokuInformation(kanji, individualReading, bulk)
)
);
const nonNullResult = results.find((result) => result !== null);
if (nonNullResult === void 0) return null;
return {
pitchAccent: results.filter((information) => information !== null).map((information) => information.pitchAccent).join("\u30FB"),
meaning: nonNullResult.meaning,
definition: nonNullResult.definition
};
}
const cacheKey = `${kanji}/${reading}`;
const cachedInformation = wadokuInformationCache.get(cacheKey);
if (cachedInformation) {
if (cachedInformation instanceof Promise) {
return await cachedInformation;
}
return cachedInformation;
}
let done;
wadokuInformationCache.set(
cacheKey,
new Promise((resolve) => {
done = resolve;
})
);
const doReturn = (information) => {
wadokuInformationCache.set(cacheKey, information);
done(information);
return information;
};
let suboptimalResult = {
segmentedPitchAccent: null,
noPitchAccent: null,
hasAlternateSpellings: null,
alternateSpelling: null,
undesirableDefinition: null,
ellipsis: null
};
try {
const cachedSearchResponseDocument = wadokuSearchResultCache.get(kanji);
let searchResponseDocument;
if (cachedSearchResponseDocument) {
searchResponseDocument = await cachedSearchResponseDocument;
} else {
const res = [];
bulk.forEach((kanji2) => {
wadokuSearchResultCache.set(
kanji2,
new Promise((resolve) => res.push(resolve))
);
});
const searchResponse = await gmfetch({
// ¡ is a separator character that's not used in an entry and probably
// won't be at any point.
url: `https://wadoku.de/search/${bulk.join("\xA1")}`,
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
data: "searchType=JAPANESE&matchType=EXACT"
});
searchResponseDocument = parser.parseFromString(
searchResponse,
"text/html"
);
res.forEach((resolve) => resolve(searchResponseDocument));
}
for (const resultLine of searchResponseDocument.querySelectorAll(
".resultline"
)) {
const readingRow = resultLine.querySelector(".accent") ?? resultLine.querySelector(".reading");
if (!readingRow || readingRow.textContent.trim().replace(/\uffe8|・|~|…/g, "") !== reading) {
continue;
}
const senses = element(
resultLine.querySelector(".senses")?.cloneNode(true)
);
let isUndesirableDefinition = false;
const fillElement = (elementToFill, currentNode) => {
if (currentNode instanceof Text) {
if (currentNode.textContent === " " && (elementToFill.lastChild?.textContent === " " || elementToFill.lastChild?.textContent === "\xA0")) {
return;
}
elementToFill.append(currentNode.textContent);
return;
}
if (!(currentNode instanceof Element))
throw new Error("currentElement is not an element");
if (currentNode.classList.contains("genus") || currentNode.classList.contains("season") || currentNode.matches(".transcr:has(+ .jap)") || currentNode.classList.contains("klammer") && currentNode.classList.contains("global") || currentNode.classList.contains("badge")) {
return;
}
let nextElementToFillClass = null;
let wrap = null;
if (currentNode.classList.contains("indexnr")) {
nextElementToFillClass = "cotsu-tools-definition-index-number";
wrap = ["[", "]"];
} else if (currentNode.classList.contains("reg") || currentNode.classList.contains("usage")) {
nextElementToFillClass = "cotsu-tools-definition-context";
} else if (currentNode.classList.contains("dom")) {
if (currentNode.textContent === "Buchtitel" || currentNode.textContent === "weibl. Name" || currentNode.textContent === "m\xE4nnl. Name" || currentNode.textContent === "Familienn." || currentNode.textContent === "Beng\u014D") {
isUndesirableDefinition = true;
}
nextElementToFillClass = "cotsu-tools-definition-context";
} else if (currentNode.classList.contains("klammer") || currentNode.classList.contains("etym")) {
nextElementToFillClass = "cotsu-tools-definition-paren";
} else if (currentNode.classList.contains("descr")) {
nextElementToFillClass = "cotsu-tools-definition-paren";
wrap = ["(", ")"];
} else if (currentNode.classList.contains("reflink") && !currentNode.classList.contains("other")) {
if (elementToFill.lastChild instanceof Text && elementToFill.lastChild.textContent === "; ") {
elementToFill.lastChild.remove();
}
return;
}
let nextElementToFill = elementToFill;
if (nextElementToFillClass) {
nextElementToFill = document.createElement("span");
nextElementToFill.classList.add(nextElementToFillClass);
elementToFill.append(nextElementToFill);
}
if (wrap) nextElementToFill.append(wrap[0]);
for (const child of currentNode.childNodes) {
fillElement(nextElementToFill, child);
}
if (wrap) nextElementToFill.append(wrap[1]);
};
const definition = document.createElement("div");
fillElement(definition, senses);
const information = {
meaning: "",
definition,
pitchAccent: readingRow.classList.contains("accent") ? readingRow.innerHTML : null
};
let sense = element(resultLine.querySelector(".sense:not(.master)"));
sense = sense.querySelector(".prior1") ?? sense;
for (const childNode of sense.childNodes) {
if (childNode instanceof HTMLElement) {
if (childNode.classList.contains("rel")) {
break;
} else if (childNode.classList.contains("token")) {
information.meaning += childNode.firstChild?.textContent;
} else if (childNode.classList.contains("def")) {
information.meaning += childNode.textContent;
}
} else if (childNode.nodeType === Node.TEXT_NODE) {
information.meaning += childNode.textContent;
}
}
information.meaning = information.meaning.trim().replace(/\.$/, "");
const orth = element(resultLine.querySelector(".orth")).textContent.split(
"\uFF1B"
);
if (isUndesirableDefinition) {
suboptimalResult.undesirableDefinition = information;
} else if (orth[0] !== kanji) {
suboptimalResult.alternateSpelling ??= information;
} else if (!shouldHaveEllipsis && information.pitchAccent?.includes("\u2026")) {
suboptimalResult.ellipsis ??= information;
} else if (information.pitchAccent?.includes("\uFF65")) {
suboptimalResult.segmentedPitchAccent ??= information;
} else if (information.pitchAccent === null) {
suboptimalResult.noPitchAccent ??= information;
} else if (orth.length !== 1) {
suboptimalResult.hasAlternateSpellings ??= information;
} else {
return doReturn(information);
}
}
} catch (e) {
console.error(e);
return doReturn(null);
}
return doReturn(
suboptimalResult.hasAlternateSpellings ?? suboptimalResult.alternateSpelling ?? suboptimalResult.ellipsis ?? suboptimalResult.segmentedPitchAccent ?? suboptimalResult.undesirableDefinition ?? suboptimalResult.noPitchAccent
);
};
var pitchAccentElement = (kanji, reading, bulk) => {
const pitchAccentElement2 = document.createElement("span");
pitchAccentElement2.classList.add(
"cotsu-tools-pitch-accent",
"cotsu-tools-pitch-accent-loading"
);
pitchAccentElement2.textContent = reading;
getWadokuInformation(kanji, reading, bulk).then((information) => {
pitchAccentElement2.classList.remove("cotsu-tools-pitch-accent-loading");
if (information?.pitchAccent) {
pitchAccentElement2.innerHTML = information.pitchAccent.replace(/class/g, "data-cotsu-tools-pitch-accent-segment").replace(/…/g, "\u301C");
} else {
pitchAccentElement2.append(" (kein Pitch-Accent verf\xFCgbar)");
}
});
return pitchAccentElement2;
};
var meaningElement = (kanji, reading, bulk) => {
const meaningElement2 = document.createElement("span");
meaningElement2.classList.add("cotsu-tools-meaning");
meaningElement2.textContent = "l\xE4dt...";
getWadokuInformation(kanji, reading, bulk).then((information) => {
if (information?.meaning) {
meaningElement2.innerHTML = `${information.meaning} (Wadoku)`;
} else {
meaningElement2.textContent = "(keine Bedeutung verf\xFCgbar)";
}
});
return meaningElement2;
};
var definitionElement = (kanji, reading, options) => {
const wrapperElement = document.createElement("div");
wrapperElement.classList.add("cotsu-tools-definition");
const headerElement = document.createElement("div");
headerElement.classList.add("cotsu-tools-definition-header");
headerElement.textContent = "Definition von Wadoku";
wrapperElement.append(headerElement);
const definitionElement2 = document.createElement("div");
if (options?.collapsed) {
definitionElement2.classList.add("cotsu-tools-definition-collapsed");
}
definitionElement2.textContent = "l\xE4dt...";
wrapperElement.append(definitionElement2);
getWadokuInformation(kanji, reading, options?.bulk).then((information) => {
definitionElement2.replaceChildren(
information?.definition ?? "keine Definition verf\xFCgbar"
);
if (options?.collapsed) {
let currentButton = null;
let hasClickedButton = false;
const resizeListener = () => {
if (definitionElement2.scrollHeight === definitionElement2.clientHeight) {
if (!currentButton) return;
currentButton.remove();
currentButton = null;
} else {
if (currentButton || hasClickedButton) return;
currentButton = document.createElement("button");
currentButton.textContent = "volle Definition anzeigen";
currentButton.addEventListener("click", () => {
definitionElement2.classList.remove(
"cotsu-tools-definition-collapsed"
);
if (currentButton) currentButton.remove();
currentButton = null;
hasClickedButton = true;
});
headerElement.append(currentButton);
}
};
window.addEventListener("resize", resizeListener);
resizeListener();
}
});
return wrapperElement;
};
// src/lib/settings.ts
var SETTINGS = {
"cotsu-tools-katakana-mode": {
name: "Katakana statt Hiragana bei \xDCbungen nutzen",
acknowledgeable: false,
effect: (newSetting) => {
document.body.classList.toggle("cotsu-tools-katakana-mode", newSetting);
}
},
"cotsu-tools-shuffle-summary": {
name: "Am Ende einer \xDCbung die Vokabel\xFCbersicht mischen",
acknowledgeable: false,
effect: (newSetting) => {
document.body.classList.toggle("cotsu-tools-shuffle-summary", newSetting);
}
},
"cotsu-tools-hide-question-maturity": {
name: "Indikator des Fortschritts einer Vokabel w\xE4hrend einer \xDCbung verstecken",
acknowledgeable: false,
effect: (newSetting) => {
document.body.classList.toggle(
"cotsu-tools-hide-question-maturity",
newSetting
);
}
},
"cotsu-tools-cursive-font": {
name: "Handschriftliche Schriftart f\xFCr japanischen Text verwenden (Yuji Syuku)",
acknowledgeable: true,
effect: (newSetting) => {
document.body.classList.toggle("cotsu-tools-cursive-font", newSetting);
}
}
};
var SETTING_IDS = Object.keys(SETTINGS);
var STORAGE_KEY = "cotsu-tools";
var ACKNOWLEDGED_STORAGE_KEY = "cotsu-tools-acknowledged";
var loadedSettingsString = localStorage.getItem(STORAGE_KEY);
var loadedAcknowledgedString = localStorage.getItem(ACKNOWLEDGED_STORAGE_KEY);
var settings;
if (loadedSettingsString) {
settings = JSON.parse(loadedSettingsString);
} else {
settings = Object.fromEntries(SETTING_IDS.map((id) => [id, false]));
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
}
SETTING_IDS.forEach((id) => {
if (id in settings) {
SETTINGS[id].effect(settings[id]);
} else {
settings[id] = false;
SETTINGS[id].effect(false);
}
});
var acknowledged;
if (loadedAcknowledgedString) {
acknowledged = JSON.parse(loadedAcknowledgedString);
} else {
if (loadedSettingsString) {
acknowledged = SETTING_IDS.filter((id) => !SETTINGS[id].acknowledgeable);
} else {
acknowledged = SETTING_IDS;
}
localStorage.setItem(ACKNOWLEDGED_STORAGE_KEY, JSON.stringify(acknowledged));
}
var getSetting = (id) => settings[id];
var settingName = (id) => SETTINGS[id].name;
var setSetting = (id, value) => {
settings[id] = value;
SETTINGS[id].effect(value);
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
};
var doAcknowledgeableSettingsExist = () => SETTING_IDS.some(
(id) => SETTINGS[id].acknowledgeable && !acknowledged.includes(id)
);
var isAcknowledged = (id) => acknowledged.includes(id);
var acknowledgeSettings = () => {
acknowledged = SETTING_IDS;
localStorage.setItem(ACKNOWLEDGED_STORAGE_KEY, JSON.stringify(acknowledged));
};
// src/actions/handleSettings.ts
var createDialog = () => {
const dialog = document.createElement("dialog");
dialog.classList.add("cotsu-tools-dialog");
const closeDialog = () => {
dialog.close();
dialog.querySelectorAll(".cotsu-tools-new-badge").forEach((badge) => {
badge.remove();
});
};
const dialogWrapper = document.createElement("div");
dialogWrapper.classList.add("cotsu-tools-dialog-wrapper");
dialog.addEventListener("mousedown", (e) => {
if (!element(e.target).closest(".cotsu-tools-dialog-wrapper")) {
closeDialog();
}
});
dialog.append(dialogWrapper);
const verisonHeader = document.createElement("header");
const leftHeader = document.createElement("div");
const h2 = document.createElement("h2");
h2.textContent = "Cotsu-Tools";
leftHeader.append(h2);
const version = document.createElement("span");
version.textContent = `v${GM.info.script.version}`;
leftHeader.append(version);
verisonHeader.append(leftHeader);
const rightHeader = document.createElement("div");
const link = document.createElement("a");
link.classList.add("cotsu-tools-dialog-link");
link.textContent = "\u2197";
link.href = "https://openuserjs.org/scripts/mybearworld/Cotsu-Tools";
link.target = "_blank";
rightHeader.append(link);
verisonHeader.append(rightHeader);
dialogWrapper.append(verisonHeader);
const settingsHeader = document.createElement("h3");
settingsHeader.textContent = "Einstellungen";
dialogWrapper.append(settingsHeader);
SETTING_IDS.forEach((id) => {
const label = document.createElement("label");
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = getSetting(id);
checkbox.addEventListener("input", () => {
setSetting(id, checkbox.checked);
});
label.append(checkbox);
const description = document.createElement("span");
description.textContent = settingName(id);
if (!isAcknowledged(id)) {
const newBadge = document.createElement("span");
newBadge.textContent = "Neu";
newBadge.classList.add(
"cotsu-tools-new-badge",
"cotsu-tools-new-badge-dialog"
);
description.append(newBadge);
}
label.append(description);
dialogWrapper.append(label);
});
const debuggingDetails = document.createElement("details");
const debuggingSummary = document.createElement("summary");
const debuggingHeader = document.createElement("h3");
debuggingHeader.textContent = "Debugging";
debuggingSummary.append(debuggingHeader);
debuggingDetails.append(debuggingSummary);
const debuggingWadokuKanjiInput = document.createElement("input");
debuggingWadokuKanjiInput.placeholder = "Kanji";
const debuggingWadokuReadingInput = document.createElement("input");
debuggingWadokuReadingInput.placeholder = "Lesung";
const debuggingWadokuButton = document.createElement("button");
debuggingWadokuButton.textContent = "Wadoku-Informationen erhalten";
const debuggingWadoku = document.createElement("div");
debuggingWadokuButton.addEventListener("click", () => {
debuggingWadoku.innerHTML = "";
const kanji = debuggingWadokuKanjiInput.value;
const reading = debuggingWadokuReadingInput.value;
debuggingWadoku.append(pitchAccentElement(kanji, reading));
debuggingWadoku.append(definitionElement(kanji, reading));
});
debuggingDetails.append(
debuggingWadokuKanjiInput,
debuggingWadokuReadingInput,
debuggingWadokuButton,
debuggingWadoku
);
dialogWrapper.append(debuggingDetails);
const closeButton = document.createElement("button");
closeButton.textContent = "Schlie\xDFen";
closeButton.addEventListener("click", () => {
closeDialog();
});
dialogWrapper.append(closeButton);
document.body.append(dialog);
return dialog;
};
var handleSettings = async (records) => {
if (!isSceneChange(records) || document.querySelector("cotsu-tools-button"))
return;
const header = document.querySelector(
"[class^=MainLayout-module--header--] .MuiGrid-root"
);
if (!header) return;
let dialog = document.querySelector(".cotsu-tools-dialog") || createDialog();
if (!(dialog instanceof HTMLDialogElement))
throw new Error("Settings aren't a dialog");
const settingsButton = document.createElement("button");
settingsButton.classList.add("cotsu-tools-button");
settingsButton.textContent = "Cotsu-Tools";
let newBadge = null;
if (doAcknowledgeableSettingsExist()) {
newBadge = document.createElement("span");
newBadge.textContent = "Neu";
newBadge.classList.add(
"cotsu-tools-new-badge",
"cotsu-tools-new-badge-nav-bar"
);
settingsButton.append(newBadge);
}
settingsButton.addEventListener("click", () => {
dialog.showModal();
acknowledgeSettings();
newBadge?.remove();
});
element(header.lastChild).insertAdjacentElement("afterbegin", settingsButton);
};
// src/lib/interceptedFetch.ts
var stats;
var readingExercise;
var didntKnowQids = /* @__PURE__ */ new Set();
var DUMMY_QUESTION_ID = "cotsu-tools-dummy-question";
var authorization;
var _fetch = unsafeWindow.fetch;
var startInterceptingFetch = () => {
unsafeWindow.fetch = async (url, options) => {
if (options?.headers && "Authorization" in options?.headers) {
authorization = options.headers.Authorization;
}
let requestBody = options?.body;
if (typeof requestBody === "string" && url === "https://api.cotsu.de/user.php?r=update-progress") {
const parsedRequestBody = JSON.parse(requestBody);
parsedRequestBody.answers = parsedRequestBody.answers.flatMap(
(answer) => answer.qid === DUMMY_QUESTION_ID ? [] : {
qid: answer.qid,
correct: didntKnowQids.has(answer.qid) ? 0 : answer.correct
}
);
requestBody = JSON.stringify(parsedRequestBody);
}
const response = await _fetch(url, { ...options, body: requestBody });
let body = await response.text();
if (typeof url === "string") {
if (url === "https://api.cotsu.de/user.php?r=stats") {
stats = JSON.parse(body);
} else if (url.startsWith("https://api.cotsu.de/user.php?r=review-reading") || url.startsWith("https://api.cotsu.de/user.php?r=learn-reading")) {
const parsedBody = JSON.parse(body);
if (parsedBody.questions.length !== 0) {
parsedBody.questionCount = parsedBody.questions.length;
parsedBody.questions.push({
writing: "",
reading: "",
german: "",
sentence: "Das war's!",
sentence_german: "",
qtype: "3",
qid: DUMMY_QUESTION_ID,
kanjis: []
});
readingExercise = parsedBody;
didntKnowQids = /* @__PURE__ */ new Set();
}
body = JSON.stringify(parsedBody);
}
}
return new Response(body, response);
};
};
// src/lib/kanjiSearch.ts
var kanjiSearch = async (kanji) => {
const response = await gmfetch({
url: `https://api.cotsu.de/user.php?r=search&search=${kanji}`,
method: "GET",
headers: {
Authorization: authorization
}
});
return [
...JSON.parse(response).words,
...JSON.parse(response).kanjis
];
};
// src/lib/katakanaToHiragana.ts
var KATAKANA_TO_HIRAGANA = { "\u30A1": "\u3041", "\u30A2": "\u3042", "\u30A3": "\u3043", "\u30A4": "\u3044", "\u30A5": "\u3045", "\u30A6": "\u3046", "\u30A7": "\u3047", "\u30A8": "\u3048", "\u30A9": "\u3049", "\u30AA": "\u304A", "\u30AB": "\u304B", "\u30AC": "\u304C", "\u30AD": "\u304D", "\u30AE": "\u304E", "\u30AF": "\u304F", "\u30B0": "\u3050", "\u30B1": "\u3051", "\u30B2": "\u3052", "\u30B3": "\u3053", "\u30B4": "\u3054", "\u30B5": "\u3055", "\u30B6": "\u3056", "\u30B7": "\u3057", "\u30B8": "\u3058", "\u30B9": "\u3059", "\u30BA": "\u305A", "\u30BB": "\u305B", "\u30BC": "\u305C", "\u30BD": "\u305D", "\u30BE": "\u305E", "\u30BF": "\u305F", "\u30C0": "\u3060", "\u30C1": "\u3061", "\u30C2": "\u3062", "\u30C3": "\u3063", "\u30C4": "\u3064", "\u30C5": "\u3065", "\u30C6": "\u3066", "\u30C7": "\u3067", "\u30C8": "\u3068", "\u30C9": "\u3069", "\u30CA": "\u306A", "\u30CB": "\u306B", "\u30CC": "\u306C", "\u30CD": "\u306D", "\u30CE": "\u306E", "\u30CF": "\u306F", "\u30D0": "\u3070", "\u30D1": "\u3071", "\u30D2": "\u3072", "\u30D3": "\u3073", "\u30D4": "\u3074", "\u30D5": "\u3075", "\u30D6": "\u3076", "\u30D7": "\u3077", "\u30D8": "\u3078", "\u30D9": "\u3079", "\u30DA": "\u307A", "\u30DB": "\u307B", "\u30DC": "\u307C", "\u30DD": "\u307D", "\u30DE": "\u307E", "\u30DF": "\u307F", "\u30E0": "\u3080", "\u30E1": "\u3081", "\u30E2": "\u3082", "\u30E3": "\u3083", "\u30E4": "\u3084", "\u30E5": "\u3085", "\u30E6": "\u3086", "\u30E7": "\u3087", "\u30E8": "\u3088", "\u30E9": "\u3089", "\u30EA": "\u308A", "\u30EB": "\u308B", "\u30EC": "\u308C", "\u30ED": "\u308D", "\u30EE": "\u308E", "\u30EF": "\u308F", "\u30F0": "\u3090", "\u30F1": "\u3091", "\u30F2": "\u3092", "\u30F3": "\u3093", "\u30F4": "\u3094", "\u30F5": "\u3095", "\u30F6": "\u3096" };
var katakanaToHiragana = (kana) => [...kana].map(
(kana2) => kana2 in KATAKANA_TO_HIRAGANA ? KATAKANA_TO_HIRAGANA[kana2] : kana2
).join("");
// src/actions/handleExerciseWords.ts
var previousReadingExercise = null;
var handleExerciseWords = async (records) => {
for (const record of records) {
handleUpdatedWord(record);
handleUpdatedCardWord(record);
handleWrongAnswer(record);
handleSummary(record);
}
};
var currentQuestionId = () => {
const studyProgressTextElement = element(
document.querySelector("[class^=StudyProgress-module--study-progress--] p")
);
return Number(studyProgressTextElement.childNodes.item(1)?.textContent) - 1;
};
var handleUpdatedWord = (record) => {
const firstAddedNode = record.addedNodes[0];
if ((record.type !== "characterData" || record.target.parentElement?.tagName !== "P" || !record.target.parentElement?.parentElement?.className.startsWith(
"StudyProgress-module--study-progress--"
)) && (record.type !== "childList" || !(firstAddedNode instanceof HTMLElement) || !firstAddedNode.className.startsWith(
"StudyProgress-module--study-progress--"
)))
return;
const id = currentQuestionId();
if (previousReadingExercise !== readingExercise) {
previousReadingExercise = readingExercise;
}
const currentQuestion = readingExercise.questions[id];
element(
document.querySelector(
"[class^=StudyProgress-module--study-progress--] .MuiLinearProgress-bar"
)
).style.transform = `translateX(${-100 * (1 - (id + 1) / readingExercise.questionCount)}%)`;
if (currentQuestion.qid === DUMMY_QUESTION_ID) {
const questionContainer = element(
document.querySelector(
"[class^=QuestionContainer-module--question-container--]"
)
);
questionContainer.classList.add("cotsu-tools-dummy-question");
element(
document.querySelector(
"[class*=ReadingQuestionCard-module--action-check--]"
)
).textContent = "Zum Ende";
} else {
void getWadokuInformation(currentQuestion.writing, currentQuestion.reading);
}
};
var handleUpdatedCardWord = (record) => {
const firstAddedNode = record.addedNodes[0];
if ((record.type !== "childList" || !(firstAddedNode instanceof HTMLElement) || !firstAddedNode.className?.startsWith("StudyContainer")) && (record.type !== "characterData" || !record.target.parentElement?.classList?.contains("card-word") || record.target.nextSibling?.textContent !== " \u2192 "))
return;
const cardWord = record.type === "childList" ? element(firstAddedNode).querySelector(".card-word") : record.target.parentElement;
if (!cardWord) return;
cardWord.parentElement?.classList.remove(
"cotsu-tools-didnt-know-word-strike"
);
cardWord.nextSibling?.remove();
const cardWordClone = element(cardWord.cloneNode(true));
cardWordClone.classList.add("cotsu-tools-card-word-clone");
const hiraganaElement = text(
[...cardWordClone.childNodes].find(
(_node, i) => cardWordClone.childNodes[i - 1]?.textContent === " \u2192 "
)
);
const exercise = readingExercise.questions[currentQuestionId() - 1];
hiraganaElement.textContent = "";
cardWordClone.append(definitionElement(exercise.writing, exercise.reading));
hiraganaElement.after(pitchAccentElement(exercise.writing, exercise.reading));
const wasAnsweredCorrectly = !cardWordClone.querySelector(
"[class*=MaturityTallies-module--tally-icon_0--]"
);
if (wasAnsweredCorrectly) {
const didntKnowWord = document.createElement("label");
didntKnowWord.classList.add("cotsu-tools-didnt-know-word");
didntKnowWord.append("Doch nicht gewusst?");
const didntKnowWordCheckBox = document.createElement("input");
didntKnowWordCheckBox.type = "checkbox";
didntKnowWordCheckBox.addEventListener("input", () => {
const qid = readingExercise.questions[currentQuestionId() - 1].qid;
if (didntKnowWordCheckBox.checked) {
didntKnowQids.add(qid);
} else {
didntKnowQids.delete(qid);
}
cardWordClone.parentElement?.classList.toggle(
"cotsu-tools-didnt-know-word-strike",
didntKnowWordCheckBox.checked
);
});
didntKnowWord.append(didntKnowWordCheckBox);
cardWordClone.insertAdjacentElement("afterbegin", didntKnowWord);
}
cardWord.insertAdjacentElement("afterend", cardWordClone);
const studyCardContainer = cardWord.parentElement?.parentElement?.parentElement;
if (!studyCardContainer) return;
studyCardContainer.querySelectorAll("[class^=KanjiCard-module--row--]").forEach((row) => {
const previousOtherKanjiElement = row.querySelector(
".cotsu-tools-other-kanji"
);
if (previousOtherKanjiElement) {
previousOtherKanjiElement.remove();
}
const kanji = element(
row.querySelector("[class^=KanjiCard-module--kanjiBig--]")
).textContent;
const otherKanjiElement = document.createElement("div");
otherKanjiElement.className = element(row.lastChild).className;
otherKanjiElement.classList.add("cotsu-tools-other-kanji");
const button = document.createElement("button");
button.classList.add("cotsu-tools-other-kanji-init-button");
button.textContent = "andere W\xF6rter mit " + kanji;
button.addEventListener("click", async () => {
button.textContent = "l\xE4dt...";
const result = await kanjiSearch(kanji);
const relevantBulk = [];
const relevantWords = [];
result.forEach((word) => {
if (!word.maturity) return;
if (word.word === exercise.writing) return;
relevantBulk.push(word.word);
relevantWords.push(word);
});
button.remove();
otherKanjiElement.classList.add("cotsu-tools-other-kanji-loaded");
const heading = document.createElement("div");
otherKanjiElement.append(heading);
if (relevantWords.length === 0) {
heading.textContent = "Du kennst das Kanji noch in keinen anderen W\xF6rtern.";
return;
}
heading.textContent = "Du kennst das Kanji noch in diesen W\xF6rtern:";
const ul = document.createElement("ul");
otherKanjiElement.append(ul);
relevantWords.forEach((word) => {
const li = document.createElement("li");
const reading = katakanaToHiragana(word.reading);
const kanji2 = document.createElement("span");
kanji2.classList.add("cotsu-tools-other-kanji-kanji");
kanji2.append(word.word);
li.append(kanji2);
const moreButton = document.createElement("button");
moreButton.classList.add("cotsu-tools-other-kanji-more");
moreButton.textContent = "?";
moreButton.addEventListener("click", () => {
moreButton.remove();
li.append(
" (",
pitchAccentElement(word.word, reading),
") ",
word.word_de ?? meaningElement(word.word, reading)
);
});
li.append(" ", moreButton);
ul.append(li);
});
});
otherKanjiElement.append(button);
row.append(otherKanjiElement);
});
};
var handleWrongAnswer = (record) => {
const firstAddedNode = record.addedNodes[0];
if (!(firstAddedNode instanceof HTMLElement) || !firstAddedNode.className?.startsWith(
"ReadingQuestionCard-module--wrong-answer--"
))
return;
const currentQuestion = readingExercise.questions[currentQuestionId()];
firstAddedNode.innerHTML = "";
firstAddedNode.append(
"Die Antwort ist ",
pitchAccentElement(currentQuestion.writing, currentQuestion.reading)
);
};
var handleSummary = (record) => {
const firstAddedNode = record.addedNodes[0];
if (!(firstAddedNode instanceof HTMLElement) || !firstAddedNode.className.startsWith("SummaryCard-module--summary-card--"))
return;
if (readingExercise.questions.length === 0) return;
element(
document.querySelector(
"[class^=QuestionContainer-module--question-container--]"
)
).classList.remove("cotsu-tools-dummy-question");
const summaryCorrectText = element(
document.querySelector("[class^=SummaryCard-module--summary-text--] p")
);
const summaryCorrectTextMatch = summaryCorrectText.textContent.match(
/^(\d+) von (\d+) richtig$/
);
if (!summaryCorrectTextMatch) throw new Error("No X von Y richtig");
const [, correctExercises, totalExercises] = summaryCorrectTextMatch;
summaryCorrectText.textContent = `${Number(correctExercises) - 1} von ${Number(totalExercises) - 1} richtig`;
const wordSummary = document.createElement("div");
const incorrectKanjiHeading = document.createElement("h4");
incorrectKanjiHeading.textContent = "Falsch waren:";
const incorrectKanjiUnshuffled = document.createElement("div");
incorrectKanjiUnshuffled.classList.add("cotsu-tools-summary-unshuffled");
const incorrectKanjiShuffled = document.createElement("div");
incorrectKanjiShuffled.classList.add("cotsu-tools-summary-shuffled");
const correctKanjiHeading = document.createElement("h4");
correctKanjiHeading.textContent = "Richtig waren:";
const correctKanjiUnshuffled = document.createElement("div");
correctKanjiUnshuffled.classList.add("cotsu-tools-summary-unshuffled");
const correctKanjiShuffled = document.createElement("div");
correctKanjiShuffled.classList.add("cotsu-tools-summary-shuffled");
wordSummary.append(
incorrectKanjiHeading,
incorrectKanjiUnshuffled,
incorrectKanjiShuffled,
correctKanjiHeading,
correctKanjiUnshuffled,
correctKanjiShuffled
);
wordSummary.classList.add("word-summary");
const wrongKanji = /* @__PURE__ */ new Set();
const originalSummaryElement = firstAddedNode.querySelector("div:not([class])");
originalSummaryElement?.childNodes?.forEach((word) => {
const match = word.textContent?.match(/([^ ]+) → ([^ ]+)/);
if (!match) throw new Error("No XYZ \u2192 ABC in summary element");
const [, kanji, reading] = match;
wrongKanji.add(`${kanji}/${reading}`);
});
readingExercise.questions.forEach(({ writing: kanji, reading, qid }) => {
if (qid === DUMMY_QUESTION_ID) return;
const spoilerWrap = (toWrap) => {
const spoilerElement = document.createElement("button");
spoilerElement.classList.add("cotsu-tools-spoiler");
spoilerElement.ariaLabel = "L\xF6sung anzeigen";
const accessibilityWrapper = document.createElement("span");
accessibilityWrapper.ariaHidden = "true";
accessibilityWrapper.append(toWrap);
spoilerElement.append(accessibilityWrapper);
spoilerElement.addEventListener("click", () => {
spoilerElement.classList.add("cotsu-tools-spoiler-shown");
spoilerElement.ariaLabel = null;
spoilerElement.role = "none";
spoilerElement.disabled = true;
accessibilityWrapper.ariaHidden = "false";
});
return spoilerElement;
};
const isIncorrect = wrongKanji.has(`${kanji}/${reading}`) || didntKnowQids.has(qid);
const makeRow = () => {
const row = document.createElement("div");
const word = document.createElement("span");
word.lang = "ja";
word.textContent = kanji;
const solution = document.createElement("span");
solution.append(
pitchAccentElement(kanji, reading),
" ",
readingExercise.questions.find(
(question) => question.writing === kanji && question.reading === reading
)?.german ?? meaningElement(kanji, reading)
);
row.append(word, " \u2192 ", isIncorrect ? spoilerWrap(solution) : solution);
return row;
};
if (isIncorrect) {
incorrectKanjiUnshuffled.append(makeRow());
incorrectKanjiShuffled.append(makeRow());
} else {
correctKanjiUnshuffled.append(makeRow());
correctKanjiShuffled.append(makeRow());
}
});
const shuffle = (container) => {
const children = [...container.children];
let remaining = children.length;
while (remaining) {
const i = Math.floor(Math.random() * remaining);
remaining--;
[children[remaining], children[i]] = [children[i], children[remaining]];
}
container.innerHTML = "";
children.forEach((child) => {
container.append(child);
});
};
shuffle(correctKanjiShuffled);
shuffle(incorrectKanjiShuffled);
if (originalSummaryElement) {
element(document.querySelector("h3")).textContent = "Hier nochmal alle W\xF6rter";
originalSummaryElement.replaceWith(wordSummary);
} else {
const actions = element(
document.querySelector("[class^=SummaryCard-module--summary-actions--]")
);
const h3 = document.createElement("h3");
h3.textContent = "Hier nochmal alle W\xF6rter";
actions.append(h3);
actions.append(wordSummary);
}
const summaryText = element(
document.querySelector("[class^=SummaryCard-module--summary-text--] p")
);
const summaryTextMatch = summaryText.textContent.match(/^(\d+)(.*)$/);
if (!summaryTextMatch) throw new Error("No summaryTextMatch");
summaryText.textContent = (Number(summaryTextMatch[1]) - didntKnowQids.size).toString() + summaryTextMatch[2];
};
// src/lib/levels.ts
var LEVELS = ["N5", "N4", "N3", "N2", "N1"];
var LOWERCASE_LEVELS = {
N5: "n5",
N4: "n4",
N3: "n3",
N2: "n2",
N1: "n1"
};
// src/actions/showMoreStats.ts
var percent = (a, b) => `${Math.round(a / b * 100)}%`;
var showMoreStats = async (records) => {
if (!records.some(
(record) => record.target instanceof HTMLElement && record.target.className?.includes("MaturityTallies")
))
return;
const practiceCard = document.querySelector(
"div[class*=index-module--kanji-read-actions--] .MuiGrid-item:first-of-type [class*=index-module--action-card-normal--]"
);
if (practiceCard && stats.readyForEarlyReview !== "0" && stats.totalLearned !== 0) {
text(
practiceCard.querySelector("[class^=index-module--action-card-text--]")?.lastChild
).textContent = `Du kannst aber trotzdem noch ${Math.ceil(Number(stats.readyForEarlyReview) / 20)} mal \xFCben.`;
}
LEVELS.forEach((level) => {
const levelStats = stats.progress[LOWERCASE_LEVELS[level]];
const progressBarElement = element(
document.querySelector(`.MuiLinearProgress-root[label=${level}]`)?.parentElement
);
const levelNameElement = element(
progressBarElement.previousSibling?.firstChild
);
const progressElement = element(progressBarElement.nextSibling?.firstChild);
levelNameElement.textContent += ` (${levelStats.total})`;
progressElement.textContent = `${percent(
levelStats.learning,
levelStats.total
)} (${levelStats.learning})`;
if (levelStats.mature !== 0) {
const progressContainer = element(progressElement.parentElement);
const matureProgressContainer = element(
progressContainer.cloneNode(true)
);
const matureProgress = element(matureProgressContainer.firstChild);
matureProgress.classList.add("cotsu-tools-detail-green");
matureProgress.textContent = `${percent(
levelStats.mature,
levelStats.total
)} (${levelStats.mature})`;
element(progressContainer.parentElement).append(matureProgressContainer);
}
});
};
// src/actions/showExercisesLeftInKanjiLearningTab.ts
var showExercisesLeftInKanjiLearningTab = async (records) => {
if (!stats || !isSceneChange(records)) return;
document.querySelectorAll(".MuiSlider-markLabel").forEach((label) => {
const level = label.textContent.split(" ")[0];
if (!LEVELS.includes(level)) return;
const levelStats = stats.progress[LOWERCASE_LEVELS[level]];
const br = document.createElement("br");
if (levelStats.total !== levelStats.learning) {
label.append(
br,
`noch ${Math.ceil((levelStats.total - levelStats.learning) / 20)}`
);
}
});
};
// src/actions/fixFontInNewKanjiTab.ts
var fixFontInNewKanjiTab = async (records) => {
if (!isSceneChange(records)) return;
const search = document.querySelector("[class^=suche-module--search-field--");
if (!search) return;
search.setAttribute("lang", "ja");
};
// src/actions/handleKanjiTab.ts
var handleKanjiTab = async (records) => {
const items = [];
for (const record of records) {
const firstAddedNode = record.addedNodes[0];
if (record.target instanceof HTMLElement && record.target.parentElement?.parentElement?.className.startsWith(
"suche-module--container--"
) && record.target.classList.contains("MuiList-root") && firstAddedNode instanceof HTMLElement) {
const text2 = element(
firstAddedNode.querySelector(
".MuiListItemText-root .MuiTypography-root"
)
);
if (text2.dataset.cotsuToolsAddedWadokuInformation === "true") {
continue;
}
const match = text2.textContent.match(/^(.+?)((.+?)) (.+)?$/);
if (!match) throw new Error("Unexpected text format");
const [, kanji, kana, german] = match;
const reading = katakanaToHiragana(kana);
items.push({ kanji, reading, german, element: text2 });
}
}
if (items.length === 0) return;
const bulk = items.map(({ kanji }) => kanji);
items.forEach(({ element: element2, kanji, reading, german }) => {
element2.innerHTML = "";
element2.dataset.cotsuToolsAddedWadokuInformation = "true";
element2.append(
kanji,
"\uFF08",
pitchAccentElement(kanji, reading, bulk),
"\uFF09 ",
german || meaningElement(kanji, reading, bulk)
);
});
};
// src/lib/style.css
var style_default = `/* General */
@font-face {
font-family: "Noto Sans JP All Katakana";
src: url("https://mybearworld.github.io/japanese-fonts/out/NotoSansJPAllKatakana.ttf");
}
@font-face {
font-family: "Yuji Syuku";
src: url("https://mybearworld.github.io/japanese-fonts/src/YujiSyuku-Regular.ttf");
size-adjust: 120%;
font-weight: 400;
unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF10-FF19, U+FF66-FF9F;
}
@font-face {
font-family: "Yuji Syuku All Katakana";
src: url("https://mybearworld.github.io/japanese-fonts/out/YujiSyukuAllKatakana.ttf");
size-adjust: 120%;
font-weight: 400;
unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF10-FF19, U+FF66-FF9F;
}
input {
font-weight: 400 !important;
}
body {
--cotsu-tools-default-font: Noto Sans JP, Noto Sans Japanese, sans-serif;
--cotsu-tools-font: var(--cotsu-tools-default-font);
}
body.cotsu-tools-cursive-font {
--cotsu-tools-font: Yuji Syuku, var(--cotsu-tools-default-font);
}
a,
body,
button,
p,
span,
input {
font-family: var(--cotsu-tools-font) !important;
}
/* Dialog */
[class^="MainLayout-module--header--"] .MuiGrid-container .MuiGrid-item {
flex-basis: initial;
max-width: initial;
}
[class^="MainLayout-module--header--"]
.MuiGrid-container
.MuiGrid-item:last-child {
flex-grow: 1;
}
.cotsu-tools-button {
font: inherit;
border: none;
background: none;
color: #fff;
padding: 0;
margin-right: 20px;
height: 60px;
}
.cotsu-tools-button:hover {
cursor: pointer;
color: #ffdc79;
}
body:has(.cotsu-tools-dialog:open) {
overflow: hidden;
}
.cotsu-tools-new-badge {
background-color: #ff4e71;
color: white;
font-weight: bold;
padding: 0 0.5rem;
border-radius: 999px;
}
.cotsu-tools-new-badge.cotsu-tools-new-badge-nav-bar {
background-color: white;
color: #ff4e71;
margin-left: 0.5rem;
}
.cotsu-tools-new-badge.cotsu-tools-new-badge-dialog {
margin-left: 0.25rem;
}
.cotsu-tools-dialog {
border: 1px solid #f6f6f6;
border-radius: 20px;
width: min(calc(100% - 2rem), 30rem);
max-width: none;
box-sizing: border-box;
padding: 0;
margin-top: max(auto, 1rem);
max-height: 100vh;
}
.cotsu-tools-dialog-wrapper {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
max-height: calc(100vh - 4rem);
}
.cotsu-tools-dialog header {
display: flex;
justify-content: space-between;
align-items: center;
}
.cotsu-tools-dialog header > div {
display: flex;
align-items: end;
gap: 0.5rem;
}
.cotsu-tools-dialog h2 {
margin: 0;
font-family: inherit;
}
.cotsu-tools-dialog h3 {
font-size: 1rem;
font-weight: 700;
margin: 0;
margin-top: 0.5rem;
}
.cotsu-tools-dialog summary {
margin-top: 0.5rem;
}
.cotsu-tools-dialog summary h3 {
margin-top: 0;
display: inline-block;
}
.cotsu-tools-dialog header span {
font-size: 1rem;
font-weight: 700;
}
.cotsu-tools-dialog-link {
font-size: 1.5rem;
font-weight: 700;
}
.cotsu-tools-dialog label {
display: flex;
gap: 0.5rem;
align-items: baseline;
}
.cotsu-tools-dialog input {
margin: 0;
}
.cotsu-tools-dialog button {
align-self: end;
}
/* Pitch accent */
.cotsu-tools-pitch-accent {
display: inline-table;
border-collapse: collapse;
}
.cotsu-tools-pitch-accent,
.cotsu-tools-pitch-accent span {
font-weight: 400;
}
.cotsu-tools-pitch-accent span {
border: 0px solid currentcolor;
--border-width: 1px;
display: table-cell;
padding: 0 3px;
}
.cotsu-tools-pitch-accent.cotsu-tools-pitch-accent-loading {
letter-spacing: 3px;
opacity: 0.5;
margin-top: var(--border-width);
}
.card-header .cotsu-tools-pitch-accent,
.card-header .cotsu-tools-pitch-accent span {
--border-width: 2px;
font-weight: 700;
}
.cotsu-tools-pitch-accent span:empty {
display: none;
}
.cotsu-tools-pitch-accent [data-cotsu-tools-pitch-accent-segment~="divider"] {
display: none;
}
.cotsu-tools-pitch-accent [data-cotsu-tools-pitch-accent-segment~="b"] {
border-bottom-width: var(--border-width);
}
.cotsu-tools-pitch-accent [data-cotsu-tools-pitch-accent-segment~="t"] {
border-top-width: var(--border-width);
}
.cotsu-tools-pitch-accent [data-cotsu-tools-pitch-accent-segment~="l"] {
border-left-width: var(--border-width);
}
.cotsu-tools-pitch-accent [data-cotsu-tools-pitch-accent-segment~="r"] {
border-right-width: var(--border-width);
}
div.card-word {
height: auto;
min-height: 29px;
display: none;
}
div.card-word.cotsu-tools-card-word-clone {
display: block;
}
/* Meaning */
.cotsu-tools-meaning {
padding: 0;
border: 0;
background-color: transparent;
font-size: inherit;
font-weight: inherit;
}
.cotsu-tools-meaning,
.cotsu-tools-meaning-summary,
.cotsu-tools-meaning-definition {
font-family: inherit;
}
.cotsu-tools-meaning-definition {
display: none;
}
.cotsu-tools-meaning.cotsu-tools-meaning-shown-definition
.cotsu-tools-meaning-summary {
display: none;
}
.cotsu-tools-meaning.cotsu-tools-meaning-shown-definition
.cotsu-tools-meaning-definition {
display: inline-block;
}
/* Definition */
.cotsu-tools-definition {
font-weight: 300;
}
.cotsu-tools-definition-header {
font-weight: 700;
font-size: 12px;
line-height: 1.2;
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: center;
}
.cotsu-tools-definition-collapsed {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
}
.cotsu-tools-definition-index-number {
margin-left: 0.3rem;
}
.cotsu-tools-definition-paren {
color: #555;
}
.cotsu-tools-definition-context {
font-variant-caps: all-small-caps;
letter-spacing: 0.02rem;
}
/* Dummy question */
.cotsu-tools-dummy-question
[class^="ReadingQuestionCard-module--section-action--"] {
height: auto;
}
.cotsu-tools-dummy-question
[class*="ReadingQuestionCard-module--action-check--"] {
margin: 0;
transition: margin 0s;
}
.cotsu-tools-dummy-question
[class^="ReadingQuestionCard-module--input-field--"],
.cotsu-tools-dummy-question
[class^="ReadingQuestionCard-module--newWordHint--"],
.cotsu-tools-dummy-question [class^="StudyProgress-module--study-progress--"] {
display: none;
}
/* Word summary */
.word-summary div,
.word-summary span {
font-weight: 400;
}
.word-summary div {
margin-bottom: 4px;
}
.cotsu-tools-spoiler {
border-radius: 5px;
border: 0;
background-color: currentcolor;
padding: 0;
color: #444;
padding: 1px 5px;
}
.cotsu-tools-spoiler:not(.cotsu-tools-spoiler-shown):hover {
cursor: pointer;
color: #555;
}
.cotsu-tools-spoiler.cotsu-tools-spoiler-shown {
background-color: #eee;
transition:
background-color 0.5s,
color 0.5s;
user-select: text;
color: inherit;
}
.cotsu-tools-summary-shuffled {
display: none;
}
.cotsu-tools-shuffle-summary .cotsu-tools-summary-shuffled {
display: block;
}
.cotsu-tools-shuffle-summary .cotsu-tools-summary-unshuffled {
display: none;
}
/* Stats */
div[class^="index-module--action-card-text--"] {
height: auto;
min-height: 74px;
}
[class^="MaturityTallies-module--maturity-tallies--"] + h4 + p + div {
display: table;
border-spacing: 10px 0;
}
[class^="MaturityTallies-module--maturity-tallies--"] + h4 + p + div > div {
display: table-row;
}
[class^="MaturityTallies-module--maturity-tallies--"]
+ h4
+ p
+ div
> div
> div {
display: table-cell;
min-width: 0px !important;
white-space: nowrap;
}
.cotsu-tools-detail-green {
color: #40d5ac;
font-weight: bold;
}
/* Katakana mode */
body {
--cotsu-tools-kana-font: ;
}
body.cotsu-tools-katakana-mode {
--cotsu-tools-kana-font:
Noto Sans JP All Katakana, Noto Sans JP, Noto Sans Japanese, sans-serif;
}
body.cotsu-tools-cursive-font {
--cotsu-tools-kana-font:
Yuji Syuku, Noto Sans JP, Noto Sans Japanese, sans-serif;
}
body.cotsu-tools-cursive-font.cotsu-tools-katakana-mode {
--cotsu-tools-kana-font:
Yuji Syuku All Katakana, Noto Sans JP, Noto Sans Japanese, sans-serif;
}
[class^="ReadingQuestionCard-module--input-field--"],
.cotsu-tools-pitch-accent,
.cotsu-tools-pitch-accent span,
.commonReading,
.commonReading b {
font-family: var(--cotsu-tools-kana-font) !important;
}
/* Other words with Kanji */
.cotsu-tools-other-kanji-init-button {
white-space: nowrap;
}
.cotsu-tools-other-kanji {
flex: 1;
display: flex;
flex-direction: column;
align-items: end;
padding: 0;
text-align: left;
}
.cotsu-tools-other-kanji li {
margin-bottom: 0.25rem;
}
.cotsu-tools-other-kanji.cotsu-tools-other-kanji-loaded {
align-items: start;
}
.cotsu-tools-other-kanji-kanji {
font-weight: 400;
}
.cotsu-tools-other-kanji-more {
margin-left: 0.25rem;
}
/* Didn't know word */
.cotsu-tools-didnt-know-word {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
user-select: none;
}
.cotsu-tools-didnt-know-word-strike .card-level-up {
text-decoration: line-through;
}
.cotsu-tools-didnt-know-word-strike
[class^="MaturityTallies-module--tally-icon--"] {
filter: grayscale(1);
position: relative;
}
.cotsu-tools-didnt-know-word-strike
[class^="MaturityTallies-module--tally-icon--"]::before {
position: absolute;
top: 50%;
left: -2px;
rotate: -45deg;
width: 20px;
height: 1px;
display: inline-block;
background-color: black;
content: "";
}
/* Hide question maturity */
.cotsu-tools-hide-question-maturity
[class^="ReadingQuestionCard-module--icons--"] {
display: none;
}
`;
// src/lib/style.ts
var insertCSS = () => {
const styleElement = document.createElement("style");
styleElement.innerHTML = style_default;
document.head.append(styleElement);
};
// src/index.ts
insertCSS();
startInterceptingFetch();
new MutationObserver(async (records) => {
handleSettings(records);
handleExerciseWords(records);
showMoreStats(records);
showExercisesLeftInKanjiLearningTab(records);
fixFontInNewKanjiTab(records);
handleKanjiTab(records);
}).observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
})();