Skip to content

Commit c795360

Browse files
authored
Merge pull request #126 from DenisValeev/codex/create-total-recall-app-for-notes
Add Total Recall note deck app
2 parents e1ecf56 + fc7954c commit c795360

3 files changed

Lines changed: 676 additions & 0 deletions

File tree

apps/total-recall/app.js

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
(function () {
2+
const notesInput = document.querySelector('[data-notes-input]');
3+
const entryEl = document.querySelector('[data-entry-text]');
4+
const placeholderEl = document.querySelector('[data-placeholder]');
5+
const counterEl = document.querySelector('[data-counter]');
6+
const prevButton = document.querySelector('[data-prev]');
7+
const nextButton = document.querySelector('[data-next]');
8+
const revealButton = document.querySelector('[data-reveal]');
9+
const shuffleButton = document.querySelector('[data-shuffle]');
10+
const saveButton = document.querySelector('[data-save]');
11+
const resetButton = document.querySelector('[data-reset]');
12+
const statusEl = document.querySelector('[data-status]');
13+
14+
if (
15+
!notesInput ||
16+
!entryEl ||
17+
!placeholderEl ||
18+
!counterEl ||
19+
!prevButton ||
20+
!nextButton ||
21+
!revealButton ||
22+
!shuffleButton ||
23+
!saveButton ||
24+
!resetButton ||
25+
!statusEl
26+
) {
27+
return;
28+
}
29+
30+
const STORAGE_KEY = 'total-recall-notes';
31+
const DEFAULT_NOTES = [
32+
"Why don't scientists trust atoms? Because they make up everything.",
33+
"I told my computer I needed a break, and it said 'No problem — I'll go to sleep.'",
34+
'Why did the scarecrow get a promotion? He was outstanding in his field.'
35+
].join('\n\n');
36+
37+
let entries = [];
38+
let deck = [];
39+
let index = 0;
40+
let isVisible = false;
41+
42+
function setStatus(message) {
43+
statusEl.textContent = message;
44+
}
45+
46+
function safeGetItem(key) {
47+
try {
48+
return localStorage.getItem(key);
49+
} catch (error) {
50+
console.error('Failed to access localStorage', error); // eslint-disable-line no-console
51+
return null;
52+
}
53+
}
54+
55+
function parseNotes(raw) {
56+
if (typeof raw !== 'string') {
57+
return [];
58+
}
59+
60+
return raw
61+
.split(/\r?\n\s*\r?\n+/)
62+
.map((entry) => entry.trim())
63+
.filter((entry) => entry.length > 0);
64+
}
65+
66+
function shuffle(list) {
67+
const copy = list.slice();
68+
for (let i = copy.length - 1; i > 0; i -= 1) {
69+
const j = Math.floor(Math.random() * (i + 1));
70+
[copy[i], copy[j]] = [copy[j], copy[i]];
71+
}
72+
return copy;
73+
}
74+
75+
function resetDeck() {
76+
if (!entries.length) {
77+
deck = [];
78+
index = 0;
79+
return;
80+
}
81+
82+
deck = shuffle(entries);
83+
index = 0;
84+
}
85+
86+
function ensureDeck() {
87+
if (!deck.length) {
88+
resetDeck();
89+
}
90+
}
91+
92+
function setEntryVisible(visible) {
93+
isVisible = visible;
94+
entryEl.classList.toggle('is-visible', visible);
95+
entryEl.setAttribute('aria-hidden', visible ? 'false' : 'true');
96+
placeholderEl.hidden = visible;
97+
revealButton.setAttribute('aria-pressed', visible ? 'true' : 'false');
98+
revealButton.textContent = visible ? 'Hide note' : 'Reveal note';
99+
}
100+
101+
function renderEmptyState() {
102+
counterEl.textContent = '0 of 0';
103+
isVisible = false;
104+
entryEl.textContent = '';
105+
entryEl.classList.remove('is-visible');
106+
entryEl.setAttribute('aria-hidden', 'true');
107+
placeholderEl.hidden = false;
108+
placeholderEl.textContent = 'No notes yet. Add entries below to start reviewing.';
109+
revealButton.disabled = true;
110+
revealButton.setAttribute('aria-pressed', 'false');
111+
revealButton.textContent = 'Reveal note';
112+
prevButton.disabled = true;
113+
nextButton.disabled = true;
114+
shuffleButton.disabled = true;
115+
}
116+
117+
function render() {
118+
if (!entries.length) {
119+
renderEmptyState();
120+
return;
121+
}
122+
123+
ensureDeck();
124+
const current = deck[index];
125+
126+
entryEl.textContent = current;
127+
counterEl.textContent = `${index + 1} of ${deck.length}`;
128+
placeholderEl.textContent = 'Ready when you are. Press “Reveal note” or tap space to check your recall.';
129+
130+
revealButton.disabled = false;
131+
const disableNav = deck.length <= 1;
132+
prevButton.disabled = disableNav;
133+
nextButton.disabled = disableNav;
134+
shuffleButton.disabled = deck.length <= 1;
135+
136+
setEntryVisible(false);
137+
}
138+
139+
function showNext() {
140+
if (!entries.length) {
141+
return;
142+
}
143+
ensureDeck();
144+
index = (index + 1) % deck.length;
145+
render();
146+
}
147+
148+
function showPrev() {
149+
if (!entries.length) {
150+
return;
151+
}
152+
ensureDeck();
153+
index = (index - 1 + deck.length) % deck.length;
154+
render();
155+
}
156+
157+
function toggleEntry() {
158+
if (!entries.length) {
159+
return;
160+
}
161+
setEntryVisible(!isVisible);
162+
}
163+
164+
function reshuffleDeck() {
165+
if (entries.length <= 1) {
166+
return;
167+
}
168+
deck = shuffle(entries);
169+
index = 0;
170+
render();
171+
setStatus('Deck reshuffled.');
172+
}
173+
174+
function updateEntriesFrom(raw) {
175+
entries = parseNotes(raw);
176+
resetDeck();
177+
render();
178+
}
179+
180+
function handleSave() {
181+
const raw = notesInput.value;
182+
try {
183+
localStorage.setItem(STORAGE_KEY, raw);
184+
} catch (error) {
185+
console.error('Failed to save notes to localStorage', error); // eslint-disable-line no-console
186+
}
187+
updateEntriesFrom(raw);
188+
const count = entries.length;
189+
if (count === 0) {
190+
setStatus('Saved. Add notes to build your deck.');
191+
} else if (count === 1) {
192+
setStatus('Saved 1 card.');
193+
} else {
194+
setStatus(`Saved ${count} cards.`);
195+
}
196+
}
197+
198+
function handleReset() {
199+
notesInput.value = DEFAULT_NOTES;
200+
handleSave();
201+
setStatus('Restored the default joke deck.');
202+
}
203+
204+
prevButton.addEventListener('click', showPrev);
205+
nextButton.addEventListener('click', showNext);
206+
revealButton.addEventListener('click', toggleEntry);
207+
shuffleButton.addEventListener('click', reshuffleDeck);
208+
saveButton.addEventListener('click', handleSave);
209+
resetButton.addEventListener('click', handleReset);
210+
211+
document.addEventListener('keydown', (event) => {
212+
if (event.defaultPrevented) {
213+
return;
214+
}
215+
216+
if (event.key === 'ArrowRight') {
217+
event.preventDefault();
218+
showNext();
219+
} else if (event.key === 'ArrowLeft') {
220+
event.preventDefault();
221+
showPrev();
222+
} else if (event.key === ' ' || event.key === 'Spacebar') {
223+
const active = document.activeElement;
224+
if (
225+
active &&
226+
(active.tagName === 'BUTTON' ||
227+
active.tagName === 'TEXTAREA' ||
228+
active.tagName === 'INPUT' ||
229+
active.tagName === 'A' ||
230+
active.tagName === 'SUMMARY' ||
231+
active.tagName === 'SELECT')
232+
) {
233+
return;
234+
}
235+
event.preventDefault();
236+
toggleEntry();
237+
}
238+
});
239+
240+
const storedNotes = safeGetItem(STORAGE_KEY);
241+
const initialNotes = storedNotes !== null ? storedNotes : DEFAULT_NOTES;
242+
notesInput.value = initialNotes;
243+
setStatus('');
244+
updateEntriesFrom(initialNotes);
245+
})();

0 commit comments

Comments
 (0)