Supercharges AnkiWeb. Currently Supported Functionality - Show card total, done and remaining count per session - Dark Mode
当前为
// ==UserScript==
// @name Super Anki
// @version 6
// @grant none
// @match https://*.ankiuser.net/**
// @match https://*.ankiweb.net/decks
// @description Supercharges AnkiWeb. Currently Supported Functionality - Show card total, done and remaining count per session - Dark Mode
// @namespace asleepysamurai.com
// @license BSD Zero Clause License
// ==/UserScript==
const key = 'super-anki-data'
function readLocalStorage(){
return JSON.parse(localStorage.getItem(key)) || {}
}
function writeLocalStorage(dataDiff = {}){
const data = {...readLocalStorage(), ...dataDiff}
localStorage.setItem(key, JSON.stringify(data))
return data
}
function initSpeechSynthesis(){
addSpeakButton()
const observer = new MutationObserver(() => {
addSpeakButton()
})
const qaNode = document.querySelector('#qa')
observer.observe(qaNode, { characterData: false, attributes: false, childList: true, subtree: false });
}
function addSpeakButton(){
const deVoice = window.speechSynthesis.getVoices().find(v=>v.lang==='de-DE')
if(!deVoice){
console.log('No German Support')
return false
}
const speakerIcon = String.fromCodePoint(0x1F508)
const speakingIcon = String.fromCodePoint(0x1F50A)
const say = function(text, button){
const utterThis = new SpeechSynthesisUtterance(text);
utterThis.voice = deVoice
utterThis.addEventListener('end', (evt) => {
button.textContent = speakerIcon
})
button.textContent = speakingIcon
window.speechSynthesis.speak(utterThis)
}
const word = document.querySelector('.word')
const ipa = document.querySelector('.ipa')
const speakButton = document.createElement('div')
speakButton.textContent = speakerIcon
speakButton.setAttribute('style', 'padding-right: 0.5rem;font-size: 2rem;cursor: pointer')
speakButton.addEventListener('click', ()=>{
say(word.textContent.trim(), speakButton)
})
const container = document.createElement('div')
const wordContainer = document.createElement('div')
container.appendChild(speakButton)
container.appendChild(wordContainer)
container.setAttribute('style', 'display: flex;justify-content: center;align-items: center;')
word.parentNode.insertBefore(container, word)
wordContainer.appendChild(word)
wordContainer.appendChild(ipa)
}
function getTodaysDoneCount(done){
const now = new Date()
const cutOffTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 4, 0, 0)
if(now.getHours() < 4){
cutOffTime.setTime(cutOffTime.getTime() - (1000 * 60 * 60 * 24))
}
const savedData = readLocalStorage()
if(done === 0 && savedData.doneCount !== undefined && savedData.lastDoneTime && savedData.lastDoneTime >= cutOffTime.getTime()){
done = savedData.doneCount
}
writeLocalStorage({doneCount: done, lastDoneTime: now.getTime()})
return done
}
function formatCounts(total, remaining){
const done = getTodaysDoneCount(total - remaining)
return `${remaining} Left + ${done} Done = ${total}`
}
function addTotalCount(){
const counts = Array.from(document.querySelectorAll('.count'))
const equals = document.createTextNode(' = ')
const totalCards = counts.reduce((total, thisCount) => total + parseInt(thisCount.innerText), 0)
const totalCount = counts[0].cloneNode(true)
totalCount.innerText = formatCounts(totalCards, totalCards)
totalCount.classList.remove('active', 'new', 'learn', 'review')
const countParent = counts[0].parentElement
countParent.appendChild(equals)
countParent.appendChild(totalCount)
const observer = new MutationObserver(() => {
const restCards = counts.reduce((total, thisCount) => total + parseInt(thisCount.innerText), 0)
totalCount.innerText = formatCounts(totalCards, restCards)
})
counts.forEach(countNode => observer.observe(countNode, { characterData: true, attributes: false, childList: true, subtree: true }));
}
function setupObserver(){
try{
init()
}catch(err){
setTimeout(() => {
setupObserver()
}, 100)
}
}
function enableDarkMode(){
const style = document.documentElement.getAttribute('style')
document.documentElement.setAttribute('style', `${style || ''}; filter: invert(0.9);`)
}
function updateBranding(){
document.querySelector('.navbar-brand > span').innerHTML = 'SuperAnki <small><small>v6</small></small>'
}
function init(){
updateBranding()
enableDarkMode()
if(window.location.pathname.toLowerCase().startsWith('/study')){
addTotalCount()
initSpeechSynthesis()
}
}
setupObserver()