Generate flashcards from Wikipedia articles and test your knowledge with interactive quizzes
Size
37.7 KB
Version
1.1.1
Created
Oct 23, 2025
Updated
1 day ago
1// ==UserScript==
2// @name Wikipedia Flashcard Generator and Quiz
3// @description Generate flashcards from Wikipedia articles and test your knowledge with interactive quizzes
4// @version 1.1.1
5// @match https://*.en.wikipedia.org/*
6// @icon https://en.wikipedia.org/static/favicon/wikipedia.ico
7// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
8// ==/UserScript==
9(function() {
10 'use strict';
11
12 // Utility function to debounce
13 function debounce(func, wait) {
14 let timeout;
15 return function executedFunction(...args) {
16 const later = () => {
17 clearTimeout(timeout);
18 func(...args);
19 };
20 clearTimeout(timeout);
21 timeout = setTimeout(later, wait);
22 };
23 }
24
25 // Extract article content from Wikipedia page
26 function extractArticleContent() {
27 const contentDiv = document.querySelector('#mw-content-text .mw-parser-output');
28 if (!contentDiv) {
29 console.error('Could not find article content');
30 return '';
31 }
32
33 // Get all paragraphs, excluding references and other non-content sections
34 const paragraphs = Array.from(contentDiv.querySelectorAll('p'))
35 .filter(p => {
36 const parent = p.closest('.reflist, .navbox, .infobox, .metadata');
37 return !parent;
38 })
39 .map(p => p.textContent.trim())
40 .filter(text => text.length > 50) // Only substantial paragraphs
41 .slice(0, 10); // Limit to first 10 paragraphs
42
43 return paragraphs.join('\n\n');
44 }
45
46 // Get article title
47 function getArticleTitle() {
48 const titleElement = document.querySelector('#firstHeading');
49 return titleElement ? titleElement.textContent : 'Wikipedia Article';
50 }
51
52 // Export flashcards to Anki .apkg format
53 async function exportToAnki(flashcards, deckName) {
54 console.log('Exporting flashcards to Anki format');
55
56 try {
57 const zip = new JSZip();
58
59 // Create a timestamp for unique IDs
60 const timestamp = Date.now();
61
62 // Create the collection.anki21 database structure
63 const deckId = timestamp;
64
65 // Build the Anki database SQL
66 let sql = `PRAGMA foreign_keys=OFF;
67BEGIN TRANSACTION;
68
69CREATE TABLE col (
70 id integer primary key,
71 crt integer not null,
72 mod integer not null,
73 scm integer not null,
74 ver integer not null,
75 dty integer not null,
76 usn integer not null,
77 ls integer not null,
78 conf text not null,
79 models text not null,
80 decks text not null,
81 dconf text not null,
82 tags text not null
83);
84
85CREATE TABLE notes (
86 id integer primary key,
87 guid text not null,
88 mid integer not null,
89 mod integer not null,
90 usn integer not null,
91 tags text not null,
92 flds text not null,
93 sfld text not null,
94 csum integer not null,
95 flags integer not null,
96 data text not null
97);
98
99CREATE TABLE cards (
100 id integer primary key,
101 nid integer not null,
102 did integer not null,
103 ord integer not null,
104 mod integer not null,
105 usn integer not null,
106 type integer not null,
107 queue integer not null,
108 due integer not null,
109 ivl integer not null,
110 factor integer not null,
111 reps integer not null,
112 lapses integer not null,
113 left integer not null,
114 odue integer not null,
115 odid integer not null,
116 flags integer not null,
117 data text not null
118);
119
120CREATE TABLE revlog (
121 id integer primary key,
122 cid integer not null,
123 usn integer not null,
124 ease integer not null,
125 ivl integer not null,
126 lastIvl integer not null,
127 factor integer not null,
128 time integer not null,
129 type integer not null
130);
131
132CREATE TABLE graves (
133 usn integer not null,
134 oid integer not null,
135 type integer not null
136);
137
138CREATE INDEX ix_notes_usn on notes (usn);
139CREATE INDEX ix_cards_usn on cards (usn);
140CREATE INDEX ix_revlog_usn on revlog (usn);
141CREATE INDEX ix_cards_nid on cards (nid);
142CREATE INDEX ix_cards_sched on cards (did, queue, due);
143CREATE INDEX ix_revlog_cid on revlog (cid);
144CREATE INDEX ix_notes_csum on notes (csum);
145
146`;
147
148 // Model (note type) definition
149 const modelId = timestamp + 1;
150 const models = {
151 [modelId]: {
152 id: modelId,
153 name: 'Basic',
154 type: 0,
155 mod: Math.floor(timestamp / 1000),
156 usn: -1,
157 sortf: 0,
158 did: deckId,
159 tmpls: [{
160 name: 'Card 1',
161 ord: 0,
162 qfmt: '{{Front}}',
163 afmt: '{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}',
164 bqfmt: '',
165 bafmt: '',
166 did: null,
167 bfont: '',
168 bsize: 0
169 }],
170 flds: [
171 { name: 'Front', ord: 0, sticky: false, rtl: false, font: 'Arial', size: 20 },
172 { name: 'Back', ord: 1, sticky: false, rtl: false, font: 'Arial', size: 20 }
173 ],
174 css: '.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n',
175 latexPre: '\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n',
176 latexPost: '\\end{document}',
177 latexsvg: false,
178 req: [[0, 'all', [0]]]
179 }
180 };
181
182 // Deck definition
183 const decks = {
184 1: {
185 id: 1,
186 mod: Math.floor(timestamp / 1000),
187 name: 'Default',
188 usn: 0,
189 lrnToday: [0, 0],
190 revToday: [0, 0],
191 newToday: [0, 0],
192 timeToday: [0, 0],
193 collapsed: false,
194 browserCollapsed: false,
195 desc: '',
196 dyn: 0,
197 conf: 1,
198 extendNew: 0,
199 extendRev: 0
200 },
201 [deckId]: {
202 id: deckId,
203 mod: Math.floor(timestamp / 1000),
204 name: deckName,
205 usn: -1,
206 lrnToday: [0, 0],
207 revToday: [0, 0],
208 newToday: [0, 0],
209 timeToday: [0, 0],
210 collapsed: false,
211 browserCollapsed: false,
212 desc: '',
213 dyn: 0,
214 conf: 1,
215 extendNew: 0,
216 extendRev: 0
217 }
218 };
219
220 // Deck config
221 const dconf = {
222 1: {
223 id: 1,
224 mod: 0,
225 name: 'Default',
226 usn: 0,
227 maxTaken: 60,
228 autoplay: true,
229 timer: 0,
230 replayq: true,
231 new: {
232 bury: false,
233 delays: [1, 10],
234 initialFactor: 2500,
235 ints: [1, 4, 0],
236 order: 1,
237 perDay: 20
238 },
239 rev: {
240 bury: false,
241 ease4: 1.3,
242 ivlFct: 1,
243 maxIvl: 36500,
244 perDay: 200,
245 hardFactor: 1.2
246 },
247 lapse: {
248 delays: [10],
249 leechAction: 0,
250 leechFails: 8,
251 minInt: 1,
252 mult: 0
253 }
254 }
255 };
256
257 // Collection config
258 const conf = {
259 nextPos: 1,
260 estTimes: true,
261 activeDecks: [1],
262 sortType: 'noteFld',
263 timeLim: 0,
264 sortBackwards: false,
265 addToCur: true,
266 curDeck: 1,
267 newBury: true,
268 newSpread: 0,
269 dueCounts: true,
270 curModel: modelId,
271 collapseTime: 1200
272 };
273
274 // Insert collection data
275 sql += `INSERT INTO col VALUES(
276 1,
277 ${Math.floor(timestamp / 1000)},
278 ${Math.floor(timestamp / 1000)},
279 ${Math.floor(timestamp / 1000)},
280 11,
281 0,
282 0,
283 0,
284 '${JSON.stringify(conf).replace(/'/g, '\'\'')}',
285 '${JSON.stringify(models).replace(/'/g, '\'\'')}',
286 '${JSON.stringify(decks).replace(/'/g, '\'\'')}',
287 '${JSON.stringify(dconf).replace(/'/g, '\'\'')}',
288 '{}'
289 );\n\n`;
290
291 // Add notes and cards
292 flashcards.forEach((card, index) => {
293 const noteId = timestamp + 100 + index;
294 const cardId = timestamp + 1000 + index;
295 const guid = `${noteId}${Math.random().toString(36).substring(2, 10)}`;
296
297 // Escape single quotes in content
298 const front = card.question.replace(/'/g, '\'\'');
299 const back = card.answer.replace(/'/g, '\'\'');
300 const fields = `${front}\x1f${back}`;
301
302 // Calculate checksum (simple version)
303 const checksum = front.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
304
305 sql += `INSERT INTO notes VALUES(
306 ${noteId},
307 '${guid}',
308 ${modelId},
309 ${Math.floor(timestamp / 1000)},
310 -1,
311 '',
312 '${fields}',
313 '${front}',
314 ${checksum},
315 0,
316 ''
317 );\n`;
318
319 sql += `INSERT INTO cards VALUES(
320 ${cardId},
321 ${noteId},
322 ${deckId},
323 0,
324 ${Math.floor(timestamp / 1000)},
325 -1,
326 0,
327 0,
328 ${index + 1},
329 0,
330 0,
331 0,
332 0,
333 0,
334 0,
335 0,
336 0,
337 ''
338 );\n`;
339 });
340
341 sql += 'COMMIT;';
342
343 // Add the database to the zip
344 zip.file('collection.anki21', sql);
345
346 // Create media file (empty for now)
347 zip.file('media', '{}');
348
349 // Generate the zip file
350 const blob = await zip.generateAsync({ type: 'blob' });
351
352 // Download the file
353 const url = URL.createObjectURL(blob);
354 const a = document.createElement('a');
355 a.href = url;
356 a.download = `${deckName.replace(/[^a-z0-9]/gi, '_')}.apkg`;
357 document.body.appendChild(a);
358 a.click();
359 document.body.removeChild(a);
360 URL.revokeObjectURL(url);
361
362 console.log('Successfully exported to Anki format');
363 return true;
364 } catch (error) {
365 console.error('Error exporting to Anki:', error);
366 throw error;
367 }
368 }
369
370 // Create flashcard UI
371 function createFlashcardUI() {
372 const container = document.createElement('div');
373 container.id = 'flashcard-container';
374 container.style.cssText = `
375 position: fixed;
376 top: 50%;
377 left: 50%;
378 transform: translate(-50%, -50%);
379 background: white;
380 border: 2px solid #0645ad;
381 border-radius: 12px;
382 padding: 30px;
383 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
384 z-index: 10000;
385 max-width: 600px;
386 width: 90%;
387 max-height: 80vh;
388 overflow-y: auto;
389 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
390 `;
391
392 container.innerHTML = `
393 <div style="text-align: center;">
394 <h2 style="margin: 0 0 20px 0; color: #202122; font-size: 24px;">Flashcard Generator</h2>
395 <div id="flashcard-status" style="margin-bottom: 20px; color: #54595d; font-size: 16px;">
396 Click "Generate Flashcards" to create study materials from this article.
397 </div>
398 <div id="flashcard-content" style="display: none;">
399 <div id="flashcard-display" style="
400 background: #f8f9fa;
401 border: 2px solid #a2a9b1;
402 border-radius: 8px;
403 padding: 30px;
404 min-height: 200px;
405 margin-bottom: 20px;
406 cursor: pointer;
407 transition: all 0.3s ease;
408 ">
409 <div id="flashcard-question" style="font-size: 18px; font-weight: 600; color: #202122; margin-bottom: 15px;"></div>
410 <div id="flashcard-answer" style="font-size: 16px; color: #54595d; display: none;"></div>
411 <div id="flashcard-hint" style="font-size: 14px; color: #72777d; margin-top: 10px; font-style: italic;">Click to reveal answer</div>
412 </div>
413 <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
414 <button id="prev-flashcard" style="
415 background: #eaecf0;
416 border: 1px solid #a2a9b1;
417 padding: 10px 20px;
418 border-radius: 6px;
419 cursor: pointer;
420 font-size: 14px;
421 font-weight: 600;
422 color: #202122;
423 ">← Previous</button>
424 <span id="flashcard-counter" style="align-self: center; color: #54595d; font-weight: 600;"></span>
425 <button id="next-flashcard" style="
426 background: #eaecf0;
427 border: 1px solid #a2a9b1;
428 padding: 10px 20px;
429 border-radius: 6px;
430 cursor: pointer;
431 font-size: 14px;
432 font-weight: 600;
433 color: #202122;
434 ">Next →</button>
435 </div>
436 </div>
437 <div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
438 <button id="generate-flashcards-btn" style="
439 background: #0645ad;
440 color: white;
441 border: none;
442 padding: 12px 24px;
443 border-radius: 6px;
444 cursor: pointer;
445 font-size: 16px;
446 font-weight: 600;
447 ">Generate Flashcards</button>
448 <button id="export-anki-btn" style="
449 background: #6c757d;
450 color: white;
451 border: none;
452 padding: 12px 24px;
453 border-radius: 6px;
454 cursor: pointer;
455 font-size: 16px;
456 font-weight: 600;
457 display: none;
458 ">📥 Export to Anki</button>
459 <button id="start-quiz-btn" style="
460 background: #00af89;
461 color: white;
462 border: none;
463 padding: 12px 24px;
464 border-radius: 6px;
465 cursor: pointer;
466 font-size: 16px;
467 font-weight: 600;
468 display: none;
469 ">Start Quiz</button>
470 <button id="close-flashcard-btn" style="
471 background: #eaecf0;
472 color: #202122;
473 border: 1px solid #a2a9b1;
474 padding: 12px 24px;
475 border-radius: 6px;
476 cursor: pointer;
477 font-size: 16px;
478 font-weight: 600;
479 ">Close</button>
480 </div>
481 </div>
482 `;
483
484 document.body.appendChild(container);
485 return container;
486 }
487
488 // Create quiz UI
489 function createQuizUI(flashcards) {
490 const container = document.createElement('div');
491 container.id = 'quiz-container';
492 container.style.cssText = `
493 position: fixed;
494 top: 50%;
495 left: 50%;
496 transform: translate(-50%, -50%);
497 background: white;
498 border: 2px solid #00af89;
499 border-radius: 12px;
500 padding: 30px;
501 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
502 z-index: 10001;
503 max-width: 600px;
504 width: 90%;
505 max-height: 80vh;
506 overflow-y: auto;
507 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
508 `;
509
510 container.innerHTML = `
511 <div style="text-align: center;">
512 <h2 style="margin: 0 0 20px 0; color: #202122; font-size: 24px;">Quiz Time!</h2>
513 <div id="quiz-progress" style="margin-bottom: 20px; color: #54595d; font-weight: 600;"></div>
514 <div id="quiz-question-container" style="
515 background: #f8f9fa;
516 border: 2px solid #a2a9b1;
517 border-radius: 8px;
518 padding: 30px;
519 margin-bottom: 20px;
520 ">
521 <div id="quiz-question" style="font-size: 18px; font-weight: 600; color: #202122; margin-bottom: 20px;"></div>
522 <textarea id="quiz-answer-input" style="
523 width: 100%;
524 min-height: 100px;
525 padding: 12px;
526 border: 2px solid #a2a9b1;
527 border-radius: 6px;
528 font-size: 16px;
529 font-family: inherit;
530 resize: vertical;
531 box-sizing: border-box;
532 " placeholder="Type your answer here..."></textarea>
533 </div>
534 <div id="quiz-feedback" style="
535 margin-bottom: 20px;
536 padding: 15px;
537 border-radius: 6px;
538 display: none;
539 "></div>
540 <div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
541 <button id="submit-answer-btn" style="
542 background: #00af89;
543 color: white;
544 border: none;
545 padding: 12px 24px;
546 border-radius: 6px;
547 cursor: pointer;
548 font-size: 16px;
549 font-weight: 600;
550 ">Submit Answer</button>
551 <button id="next-question-btn" style="
552 background: #0645ad;
553 color: white;
554 border: none;
555 padding: 12px 24px;
556 border-radius: 6px;
557 cursor: pointer;
558 font-size: 16px;
559 font-weight: 600;
560 display: none;
561 ">Next Question</button>
562 <button id="close-quiz-btn" style="
563 background: #eaecf0;
564 color: #202122;
565 border: 1px solid #a2a9b1;
566 padding: 12px 24px;
567 border-radius: 6px;
568 cursor: pointer;
569 font-size: 16px;
570 font-weight: 600;
571 ">Close Quiz</button>
572 </div>
573 </div>
574 `;
575
576 document.body.appendChild(container);
577 return container;
578 }
579
580 // Generate flashcards using AI
581 async function generateFlashcards(articleContent, articleTitle) {
582 console.log('Generating flashcards for:', articleTitle);
583
584 const prompt = `Create 8-10 educational flashcards from this Wikipedia article about "${articleTitle}".
585
586Article content:
587${articleContent}
588
589Generate flashcards that test key concepts, definitions, important facts, and relationships. Make questions clear and answers concise but informative.`;
590
591 try {
592 const flashcards = await RM.aiCall(prompt, {
593 type: 'json_schema',
594 json_schema: {
595 name: 'flashcard_generation',
596 schema: {
597 type: 'object',
598 properties: {
599 flashcards: {
600 type: 'array',
601 items: {
602 type: 'object',
603 properties: {
604 question: { type: 'string' },
605 answer: { type: 'string' }
606 },
607 required: ['question', 'answer']
608 }
609 }
610 },
611 required: ['flashcards']
612 }
613 }
614 });
615
616 console.log('Generated flashcards:', flashcards);
617 return flashcards.flashcards;
618 } catch (error) {
619 console.error('Error generating flashcards:', error);
620 throw error;
621 }
622 }
623
624 // Flashcard manager
625 class FlashcardManager {
626 constructor(flashcards) {
627 this.flashcards = flashcards;
628 this.currentIndex = 0;
629 this.revealed = false;
630 }
631
632 getCurrentCard() {
633 return this.flashcards[this.currentIndex];
634 }
635
636 next() {
637 if (this.currentIndex < this.flashcards.length - 1) {
638 this.currentIndex++;
639 this.revealed = false;
640 return true;
641 }
642 return false;
643 }
644
645 previous() {
646 if (this.currentIndex > 0) {
647 this.currentIndex--;
648 this.revealed = false;
649 return true;
650 }
651 return false;
652 }
653
654 reveal() {
655 this.revealed = true;
656 }
657
658 isRevealed() {
659 return this.revealed;
660 }
661
662 getProgress() {
663 return `${this.currentIndex + 1} / ${this.flashcards.length}`;
664 }
665 }
666
667 // Quiz manager
668 class QuizManager {
669 constructor(flashcards) {
670 this.flashcards = [...flashcards].sort(() => Math.random() - 0.5); // Shuffle
671 this.currentIndex = 0;
672 this.score = 0;
673 this.answered = false;
674 }
675
676 getCurrentQuestion() {
677 return this.flashcards[this.currentIndex];
678 }
679
680 async checkAnswer(userAnswer) {
681 const correctAnswer = this.flashcards[this.currentIndex].answer;
682
683 const prompt = `Compare the user's answer to the correct answer and determine if it's correct.
684
685Question: ${this.flashcards[this.currentIndex].question}
686Correct Answer: ${correctAnswer}
687User's Answer: ${userAnswer}
688
689Be lenient - if the user's answer captures the main idea or key points, consider it correct even if wording differs.`;
690
691 try {
692 const evaluation = await RM.aiCall(prompt, {
693 type: 'json_schema',
694 json_schema: {
695 name: 'answer_evaluation',
696 schema: {
697 type: 'object',
698 properties: {
699 isCorrect: { type: 'boolean' },
700 feedback: { type: 'string' },
701 correctAnswer: { type: 'string' }
702 },
703 required: ['isCorrect', 'feedback', 'correctAnswer']
704 }
705 }
706 });
707
708 if (evaluation.isCorrect) {
709 this.score++;
710 }
711
712 this.answered = true;
713 return evaluation;
714 } catch (error) {
715 console.error('Error evaluating answer:', error);
716 throw error;
717 }
718 }
719
720 next() {
721 if (this.currentIndex < this.flashcards.length - 1) {
722 this.currentIndex++;
723 this.answered = false;
724 return true;
725 }
726 return false;
727 }
728
729 getProgress() {
730 return `Question ${this.currentIndex + 1} / ${this.flashcards.length}`;
731 }
732
733 getScore() {
734 return `Score: ${this.score} / ${this.currentIndex + 1}`;
735 }
736
737 getFinalScore() {
738 return `Final Score: ${this.score} / ${this.flashcards.length}`;
739 }
740
741 isComplete() {
742 return this.currentIndex === this.flashcards.length - 1 && this.answered;
743 }
744 }
745
746 // Main initialization
747 async function init() {
748 console.log('Wikipedia Flashcard Generator initialized');
749
750 // Add floating button to trigger flashcard generation
751 const floatingBtn = document.createElement('button');
752 floatingBtn.id = 'flashcard-trigger-btn';
753 floatingBtn.innerHTML = '📚 Flashcards';
754 floatingBtn.style.cssText = `
755 position: fixed;
756 bottom: 20px;
757 right: 20px;
758 background: #0645ad;
759 color: white;
760 border: none;
761 padding: 14px 20px;
762 border-radius: 25px;
763 cursor: pointer;
764 font-size: 16px;
765 font-weight: 600;
766 box-shadow: 0 4px 12px rgba(0,0,0,0.2);
767 z-index: 9999;
768 transition: all 0.3s ease;
769 `;
770
771 floatingBtn.addEventListener('mouseenter', () => {
772 floatingBtn.style.transform = 'scale(1.05)';
773 floatingBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.3)';
774 });
775
776 floatingBtn.addEventListener('mouseleave', () => {
777 floatingBtn.style.transform = 'scale(1)';
778 floatingBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
779 });
780
781 floatingBtn.addEventListener('click', () => {
782 const existingContainer = document.getElementById('flashcard-container');
783 if (existingContainer) {
784 existingContainer.remove();
785 }
786 showFlashcardUI();
787 });
788
789 document.body.appendChild(floatingBtn);
790 }
791
792 // Show flashcard UI
793 function showFlashcardUI() {
794 const container = createFlashcardUI();
795 let flashcardManager = null;
796 let currentFlashcards = null;
797
798 const generateBtn = container.querySelector('#generate-flashcards-btn');
799 const closeBtn = container.querySelector('#close-flashcard-btn');
800 const statusDiv = container.querySelector('#flashcard-status');
801 const contentDiv = container.querySelector('#flashcard-content');
802 const displayDiv = container.querySelector('#flashcard-display');
803 const questionDiv = container.querySelector('#flashcard-question');
804 const answerDiv = container.querySelector('#flashcard-answer');
805 const hintDiv = container.querySelector('#flashcard-hint');
806 const counterSpan = container.querySelector('#flashcard-counter');
807 const prevBtn = container.querySelector('#prev-flashcard');
808 const nextBtn = container.querySelector('#next-flashcard');
809 const startQuizBtn = container.querySelector('#start-quiz-btn');
810 const exportAnkiBtn = container.querySelector('#export-anki-btn');
811
812 generateBtn.addEventListener('click', async () => {
813 generateBtn.disabled = true;
814 generateBtn.textContent = 'Generating...';
815 statusDiv.textContent = 'AI is analyzing the article and creating flashcards...';
816
817 try {
818 const articleContent = extractArticleContent();
819 const articleTitle = getArticleTitle();
820
821 if (!articleContent) {
822 throw new Error('Could not extract article content');
823 }
824
825 const flashcards = await generateFlashcards(articleContent, articleTitle);
826 currentFlashcards = flashcards;
827 flashcardManager = new FlashcardManager(flashcards);
828
829 // Save flashcards for this article
830 const pageUrl = window.location.href;
831 await GM.setValue('flashcards_' + btoa(pageUrl).slice(0, 50), JSON.stringify(flashcards));
832
833 statusDiv.style.display = 'none';
834 contentDiv.style.display = 'block';
835 startQuizBtn.style.display = 'inline-block';
836 exportAnkiBtn.style.display = 'inline-block';
837 generateBtn.style.display = 'none';
838
839 updateFlashcardDisplay();
840 } catch (error) {
841 console.error('Error generating flashcards:', error);
842 statusDiv.textContent = 'Error generating flashcards. Please try again.';
843 statusDiv.style.color = '#d33';
844 generateBtn.disabled = false;
845 generateBtn.textContent = 'Generate Flashcards';
846 }
847 });
848
849 function updateFlashcardDisplay() {
850 if (!flashcardManager) return;
851
852 const card = flashcardManager.getCurrentCard();
853 questionDiv.textContent = card.question;
854 answerDiv.textContent = card.answer;
855 counterSpan.textContent = flashcardManager.getProgress();
856
857 if (flashcardManager.isRevealed()) {
858 answerDiv.style.display = 'block';
859 hintDiv.style.display = 'none';
860 displayDiv.style.background = '#e8f4f8';
861 } else {
862 answerDiv.style.display = 'none';
863 hintDiv.style.display = 'block';
864 displayDiv.style.background = '#f8f9fa';
865 }
866
867 prevBtn.disabled = flashcardManager.currentIndex === 0;
868 nextBtn.disabled = flashcardManager.currentIndex === flashcardManager.flashcards.length - 1;
869 }
870
871 displayDiv.addEventListener('click', () => {
872 if (flashcardManager && !flashcardManager.isRevealed()) {
873 flashcardManager.reveal();
874 updateFlashcardDisplay();
875 }
876 });
877
878 prevBtn.addEventListener('click', () => {
879 if (flashcardManager && flashcardManager.previous()) {
880 updateFlashcardDisplay();
881 }
882 });
883
884 nextBtn.addEventListener('click', () => {
885 if (flashcardManager && flashcardManager.next()) {
886 updateFlashcardDisplay();
887 }
888 });
889
890 startQuizBtn.addEventListener('click', () => {
891 if (currentFlashcards) {
892 container.remove();
893 showQuizUI(currentFlashcards);
894 }
895 });
896
897 exportAnkiBtn.addEventListener('click', async () => {
898 if (currentFlashcards) {
899 exportAnkiBtn.disabled = true;
900 exportAnkiBtn.textContent = 'Exporting...';
901
902 try {
903 const articleTitle = getArticleTitle();
904 await exportToAnki(currentFlashcards, articleTitle);
905 exportAnkiBtn.textContent = '✓ Exported!';
906 setTimeout(() => {
907 exportAnkiBtn.textContent = '📥 Export to Anki';
908 exportAnkiBtn.disabled = false;
909 }, 2000);
910 } catch (error) {
911 console.error('Error exporting to Anki:', error);
912 alert('Error exporting to Anki. Please try again.');
913 exportAnkiBtn.textContent = '📥 Export to Anki';
914 exportAnkiBtn.disabled = false;
915 }
916 }
917 });
918
919 closeBtn.addEventListener('click', () => {
920 container.remove();
921 });
922 }
923
924 // Show quiz UI
925 function showQuizUI(flashcards) {
926 const container = createQuizUI(flashcards);
927 const quizManager = new QuizManager(flashcards);
928
929 const progressDiv = container.querySelector('#quiz-progress');
930 const questionDiv = container.querySelector('#quiz-question');
931 const answerInput = container.querySelector('#quiz-answer-input');
932 const feedbackDiv = container.querySelector('#quiz-feedback');
933 const submitBtn = container.querySelector('#submit-answer-btn');
934 const nextBtn = container.querySelector('#next-question-btn');
935 const closeBtn = container.querySelector('#close-quiz-btn');
936
937 function updateQuizDisplay() {
938 const question = quizManager.getCurrentQuestion();
939 questionDiv.textContent = question.question;
940 progressDiv.textContent = `${quizManager.getProgress()} | ${quizManager.getScore()}`;
941 answerInput.value = '';
942 answerInput.disabled = false;
943 feedbackDiv.style.display = 'none';
944 submitBtn.style.display = 'inline-block';
945 nextBtn.style.display = 'none';
946 }
947
948 submitBtn.addEventListener('click', async () => {
949 const userAnswer = answerInput.value.trim();
950
951 if (!userAnswer) {
952 alert('Please enter an answer');
953 return;
954 }
955
956 submitBtn.disabled = true;
957 submitBtn.textContent = 'Checking...';
958 answerInput.disabled = true;
959
960 try {
961 const evaluation = await quizManager.checkAnswer(userAnswer);
962
963 feedbackDiv.style.display = 'block';
964 if (evaluation.isCorrect) {
965 feedbackDiv.style.background = '#d4edda';
966 feedbackDiv.style.border = '2px solid #28a745';
967 feedbackDiv.style.color = '#155724';
968 feedbackDiv.innerHTML = `
969 <div style="font-weight: 600; margin-bottom: 8px;">✓ Correct!</div>
970 <div>${evaluation.feedback}</div>
971 `;
972 } else {
973 feedbackDiv.style.background = '#f8d7da';
974 feedbackDiv.style.border = '2px solid #dc3545';
975 feedbackDiv.style.color = '#721c24';
976 feedbackDiv.innerHTML = `
977 <div style="font-weight: 600; margin-bottom: 8px;">✗ Not quite right</div>
978 <div style="margin-bottom: 8px;">${evaluation.feedback}</div>
979 <div style="font-weight: 600;">Correct answer:</div>
980 <div>${evaluation.correctAnswer}</div>
981 `;
982 }
983
984 progressDiv.textContent = `${quizManager.getProgress()} | ${quizManager.getScore()}`;
985 submitBtn.style.display = 'none';
986
987 if (quizManager.isComplete()) {
988 nextBtn.textContent = 'View Results';
989 }
990 nextBtn.style.display = 'inline-block';
991
992 } catch (error) {
993 console.error('Error checking answer:', error);
994 alert('Error checking answer. Please try again.');
995 submitBtn.disabled = false;
996 submitBtn.textContent = 'Submit Answer';
997 answerInput.disabled = false;
998 }
999 });
1000
1001 nextBtn.addEventListener('click', () => {
1002 if (quizManager.isComplete()) {
1003 // Show final results
1004 feedbackDiv.style.display = 'block';
1005 feedbackDiv.style.background = '#d1ecf1';
1006 feedbackDiv.style.border = '2px solid #0c5460';
1007 feedbackDiv.style.color = '#0c5460';
1008 feedbackDiv.innerHTML = `
1009 <div style="font-size: 20px; font-weight: 600; margin-bottom: 10px;">Quiz Complete! 🎉</div>
1010 <div style="font-size: 18px;">${quizManager.getFinalScore()}</div>
1011 <div style="margin-top: 10px;">Great job studying!</div>
1012 `;
1013 container.querySelector('#quiz-question-container').style.display = 'none';
1014 nextBtn.style.display = 'none';
1015 submitBtn.style.display = 'none';
1016 } else {
1017 if (quizManager.next()) {
1018 submitBtn.disabled = false;
1019 submitBtn.textContent = 'Submit Answer';
1020 updateQuizDisplay();
1021 }
1022 }
1023 });
1024
1025 closeBtn.addEventListener('click', () => {
1026 container.remove();
1027 });
1028
1029 updateQuizDisplay();
1030 }
1031
1032 // Wait for page to load
1033 if (document.readyState === 'loading') {
1034 document.addEventListener('DOMContentLoaded', init);
1035 } else {
1036 init();
1037 }
1038})();