-
Notifications
You must be signed in to change notification settings - Fork 2
/
twsr-sidebar-open.tid
321 lines (313 loc) · 13 KB
/
twsr-sidebar-open.tid
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
created: 20210106052443123
modified: 20210106052443123
module-type: macro
tags:
title: $:/plugins/tadeaspaule/twsr/twsr-sidebar-open
type: application/javascript
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
const STORY_TIDDLER_TITLE = "$:/StoryList";
exports.name = "twsr-sidebar-open";
exports.params = [];
exports.run = function() {
try {
// read decks and delays (and decrement if necessary) ----------------------------
console.log($tw)
var currentlyRevisingDeck = null
var currentlyRevising = null
var currentlyRevisingAt = -1
const configFilename = "$:/config/twsr-config"
const delaysFilename = "$:/config/twsr-delays"
const intervalsFilename = "$:/config/twsr-intervals"
const sidebarFilename = "$:/plugins/tadeaspaule/twsr/sidebar"
var allTiddlers = $tw.wiki.allTitles()
var configFields = Object.assign({},$tw.wiki.getTiddler(configFilename).fields)
var config = JSON.parse(configFields.text)
var fields = ["failMultiplier", "multiplier", "easyMultiplier", "minMemoryStrength"]
var defaults = [0,1,1.3,1300]
for (var i = 0; i < 4; i++) {
if (isNaN(configFields[fields[i]])) configFields[fields[i]] = defaults[i]
}
var currentTimestamp = (new Date()).toISOString().split('T')[0]
var lastTimestamp = config.lastTimestamp || currentTimestamp
const diffTime = Math.abs(new Date(currentTimestamp) - new Date(lastTimestamp));
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); // to decrease delays
const logPrefix = "TWSR: "
console.log(logPrefix + diffDays + " days since last revision")
var delays = JSON.parse($tw.wiki.getTiddler(delaysFilename).fields.text)
var intervals = JSON.parse($tw.wiki.getTiddler(intervalsFilename).fields.text)
var tiddler2details = {}
var collectionCard2Deck = {}
var deck2Tiddlers = {}
var totalToRevise = 0
for (var i = 0; i < allTiddlers.length; i++) {
var tiddler = $tw.wiki.getTiddler(allTiddlers[i])
if (tiddler && tiddler.fields && tiddler.fields.tags) {
var twsrTag = tiddler.fields.tags.find(t => t === "twsr" || t === "twsr-collection")
if (!twsrTag) continue
else if (twsrTag === "twsr-collection") {
var entries = tiddler.fields.text.split('\n')
console.log("getting sep")
var sep = tiddler.fields.sep || "="
console.log(tiddler.fields)
console.log(sep)
for (var e = 0; e < entries.length; e++) {
var entry = entries[e].trim()
console.log(entry)
if (entry.length == 0 || !entry.includes(sep)) continue
var parts = entry.split(sep)
processCard(parts[0],parts[1])
}
}
else processCard(tiddler.fields.title, tiddler.fields)
}
}
function processCard (cardName, details) {
if (!delays[cardName]) delays[cardName] = 0
else delays[cardName] = delays[cardName] - diffDays
if (delays[cardName] && delays[cardName] > 0) return // tiddler not yet due for revision
tiddler2details[cardName] = details
var tiddlerHasDeck = false
for (var d = 0; d < config.decks.length; d++) {
if (tiddler.fields.tags.includes(config.decks[d])) {
tiddlerHasDeck = true
if (deck2Tiddlers[config.decks[d]] == undefined) deck2Tiddlers[config.decks[d]] = []
deck2Tiddlers[config.decks[d]].push(cardName)
if (typeof details == "string") collectionCard2Deck[cardName] = config.decks[d]
}
}
if (tiddlerHasDeck) totalToRevise += 1
}
function saveJson (filename, tiddler) {
var obj = {title: filename, text: JSON.stringify(tiddler,null,"\t"), type: "application/json"}
if (filename === configFilename) obj = Object.assign(obj,configFields)
$tw.wiki.addTiddler(new $tw.Tiddler(obj))
}
config.lastTimestamp = currentTimestamp
saveJson(configFilename,config)
if (diffDays > 0) {
// delays were changed, update timestamp and delays tiddler
saveJson(delaysFilename,delays)
}
// setup UI and functionality ----------------------------------------------------
// add deck functionality
var addDeckInput = document.getElementById("twsr-add-deck").children[0];
addDeckInput.addEventListener("keyup", function(event) {
// Number 13 is the "Enter" key on the keyboard
if (event.keyCode === 13) {
event.preventDefault();
if (!config.decks.includes(addDeckInput.value)) {
config.decks.push(addDeckInput.value)
saveJson(configFilename,config)
addDeckButton(addDeckInput.value)
addDeckInput.value = ''
}
}
});
// revise anything
var revisingAnything = false
function reviseAnything() {
revisingAnything = true
var cardCount = 0
config.decks.forEach(d => { cardCount += deck2Tiddlers[d] ? deck2Tiddlers[d].length : 0 })
var i = Math.floor(Math.random() * cardCount)
for (var d = 0; d < config.decks.length; d++) {
if (!deck2Tiddlers[config.decks[d]]) continue
for (var idx = 0; idx < deck2Tiddlers[config.decks[d]].length; idx++) {
if (i == 0) {
setCurrentlyRevising(config.decks[d], idx)
return
}
i--
}
}
}
var reviseAnythingBtn = document.getElementById("twsr-revise-anything").children[0]
reviseAnythingBtn.addEventListener('click', reviseAnything)
// deck-specific revise
var twsrDecks = document.getElementById("twsr-decks")
function resetDeck (deckName) {
var deckElem = document.getElementById("twsr-deck-" + deckName)
deckElem.textContent = deckName + " (" + (deck2Tiddlers[deckName] || []).length + ")"
if (!deck2Tiddlers[deckName] || deck2Tiddlers[deckName].length == 0) deck2Elem[currentlyRevisingDeck].style.display = "none"
}
var deck2Elem = {}
function addDeckButton (deckName) {
var deckElem = document.getElementById("twsr-deck-" + deckName)
if (!deckElem) {
deckElem = document.createElement('button')
deckElem.id = "twsr-deck-" + deckName
deckElem.className = 'tc-tag-label tc-tag-list-item tc-btn-invisible'
deckElem.addEventListener('click', function () {
if (!deck2Tiddlers[deckName] || deck2Tiddlers[deckName].length == 0) return
revisingAnything = false
setCurrentlyRevising(deckName, Math.floor(Math.random()*deck2Tiddlers[deckName].length))
})
twsrDecks.appendChild(deckElem)
}
deckElem.textContent = deckName + " (" + (deck2Tiddlers[deckName] || []).length + ")"
deck2Elem[deckName] = deckElem
}
config.decks.forEach(d => {
if (deck2Tiddlers[d]) addDeckButton(d)
})
var numToReviseElem = document.getElementById("twsr-number-to-revise")
numToReviseElem.textContent = `${totalToRevise} cards to revise today`
var reviseOuterElem = document.getElementById("twsr-revise-outer")
if (totalToRevise == 0) {
reviseOuterElem.style.display = "none"
return
}
var revisingNameText = document.getElementById("twsr-revising-name")
var revisingShowLink = document.getElementById("twsr-revising-show")
var collectionResult = document.getElementById("twsr-collection-result")
revisingShowLink.addEventListener('click', showResultButtons)
var results = ['again','hard','good','easy']
var resultButtons = document.getElementById("twsr-res-btns")
function removeFromDeck(deckName, index) {
console.log(logPrefix + "Removing revised card from deck " + deckName)
deck2Tiddlers[deckName].splice(index,1)
resetDeck(deckName)
}
function finishOneRevision(newDelay, newMemoryStrength) {
// update delays, config, intervals DataTiddlers
delays[currentlyRevising] = newDelay
config.memoryStrength = newMemoryStrength
intervals[currentlyRevising] = newDelay
saveJson(configFilename,config)
saveJson(delaysFilename,delays)
saveJson(intervalsFilename,intervals)
// update deck UI and data
console.log(logPrefix + "new delay for \"" + currentlyRevising +"\" is " + newDelay)
if (newDelay > 0) {
totalToRevise--
if (typeof tiddler2details[currentlyRevising] == "string") {
var deck = collectionCard2Deck[currentlyRevising]
for (var i = 0; i < deck2Tiddlers[deck].length; i++) {
if (deck2Tiddlers[deck][i] == currentlyRevising) {
removeFromDeck(deck,i)
break
}
}
}
else tiddler2details[currentlyRevising].tags.forEach(t => {
if (deck2Tiddlers[t]) {
for (var i = 0; i < deck2Tiddlers[t].length; i++) {
if (deck2Tiddlers[t][i] == currentlyRevising) {
removeFromDeck(t,i)
break
}
}
}
})
}
numToReviseElem.textContent = `${totalToRevise} cards to revise today`
if (totalToRevise == 0) {
reviseOuterElem.style.display = "none"
return
}
// potentially set a different deck
if (revisingAnything) {
reviseAnything()
return
}
if (deck2Tiddlers[currentlyRevisingDeck].length > 0) {
// show new thing to revise ...
setCurrentlyRevising(currentlyRevisingDeck, Math.floor(Math.random()*deck2Tiddlers[currentlyRevisingDeck].length))
}
else {
// ... or reset to default
revisingNameText.textContent = 'Revising: '
revisingShowLink.textContent = ''
}
collectionResult.textContent = ""
}
for (var i = 0; i < 4; i++) {
const idx = i
var resBtn = document.getElementById("twsr-res-" + results[idx])
resBtn.addEventListener('click', function () {
resultButtonClicked(idx)
resultButtons.style.display = 'none'
})
}
// vars for results (setting new intervals and memoryStrength)
const {failMultiplier,multiplier,easyMultiplier,minMemoryStrength} = configFields
var m0 = parseFloat(failMultiplier) || 0, m = parseFloat(multiplier) || 1, m4 = parseFloat(easyMultiplier) || 1.3
var newDelays = [], newFs = []
function resultButtonClicked (index) {
// using Anki algorithms found described here: https://gist.github.com/fasiha/31ce46c36371ff57fdbc1254af424174
finishOneRevision(newDelays[index],newFs[index])
}
resultButtons.style.display = 'none'
function setCurrentlyRevising(deckName,index) {
var value = deck2Tiddlers[deckName][index]
if (!value) return
console.log(logPrefix + "Currently revising set to " + value)
currentlyRevising = value
currentlyRevisingDeck = deckName
currentlyRevisingAt = index
var prompt = value
if (typeof tiddler2details[currentlyRevising] == "object" && tiddler2details[currentlyRevising].prompt) {
prompt = tiddler2details[currentlyRevising].prompt
// check if Cloze prompt
var clozeParts = prompt.match(/c\[[^\]]+\]/g)
if (clozeParts && clozeParts.length > 0) {
var i = Math.floor(Math.random() * clozeParts.length)
// hide one of the parts
prompt = prompt.replace(clozeParts[i], "____")
// reveal the rest
for (var idx = 0; idx < clozeParts.length; idx++) {
prompt = prompt.replace(clozeParts[idx], clozeParts[idx].slice(2,clozeParts[idx].length-1))
}
}
}
revisingNameText.textContent = 'Revising: ' + prompt
revisingShowLink.textContent = 'show'
resultButtons.style.display = 'none'
// update look & values for result buttons
var f = Math.max(config.memoryStrength || 0, minMemoryStrength)
var i = intervals[currentlyRevising] || 0, d = isNaN(delays[currentlyRevising]) ? 0 : Math.abs(delays[currentlyRevising]),
i1 = m0 * i,
i2 = Math.max(i + 1, (i + d/4) * 1.2 * m),
i3 = Math.max(i2 + 1, (i + d/2) * (f / 1000) * m),
i4 = Math.max(i3 + 1, (i + d) * (f / 1000) * m * m4)
newDelays = [i1,i2,i3,i4]
newFs = [
Math.max(minMemoryStrength, f - 200),
Math.max(minMemoryStrength, f - 150),
f,
Math.max(minMemoryStrength, f + 150)
]
for (var idx = 0; idx < 4; idx++) {
resultButtons.children[idx].innerHTML = results[idx] + "<br/>(" + Math.ceil(newDelays[idx]) + "d)"
}
}
function showResultButtons () {
resultButtons.style.display = 'flex'
if (typeof tiddler2details[currentlyRevising] == "string") {
collectionResult.textContent = tiddler2details[currentlyRevising]
return
}
else collectionResult.textContent = ""
// add curerntlyRevising tiddler to the end of the story
const storyTiddler = $tw.wiki.getTiddler(STORY_TIDDLER_TITLE);
var newList;
if (document.getElementsByClassName("krystal-header").length > 0) newList = [sidebarFilename,currentlyRevising]
else newList = storyTiddler.fields.list.concat(currentlyRevising)
$tw.wiki.addTiddler(new $tw.Tiddler({title: STORY_TIDDLER_TITLE, list: newList}));
// dispatch a tm-scroll event (scroll new tiddler into view. should support plugins like Krystal)
setTimeout(() => {
const storyElem = document.getElementsByClassName("tc-story-river")[0]
$tw.rootWidget.dispatchEvent({
type: "tm-scroll",
target: storyElem.children[storyElem.children.length-2]
})
},50)
}
} catch (err) {
console.error(err.stack)
return "(ERROR: " + err.message + ") ";
}};
})();