PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.10
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.10
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / flashcard / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.10, at blocks/flashcard/frontend.js

347 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 function typoCssVarsForEl(typo, prefix, el) {
5 if (!typo || typeof typo !== 'object') return;
6 if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', sans-serif");
7 if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight);
8 if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform);
9 if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style);
10 if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration);
11 ['size', 'lineHeight', 'letterSpacing', 'wordSpacing'].forEach(function (prop) {
12 var unit = typo[prop + 'Unit'] || 'px';
13 var d = typo[prop + 'Desktop'], t = typo[prop + 'Tablet'], m = typo[prop + 'Mobile'];
14 var css = { size: 'font-size', lineHeight: 'line-height', letterSpacing: 'letter-spacing', wordSpacing: 'word-spacing' }[prop];
15 if (d !== undefined && d !== '') el.style.setProperty(prefix + css + '-d', d + unit);
16 if (t !== undefined && t !== '') el.style.setProperty(prefix + css + '-t', t + unit);
17 if (m !== undefined && m !== '') el.style.setProperty(prefix + css + '-m', m + unit);
18 });
19 }
20
21 function shuffle(arr) {
22 var a = arr.slice();
23 for (var i = a.length - 1; i > 0; i--) {
24 var j = Math.floor(Math.random() * (i + 1));
25 var t = a[i]; a[i] = a[j]; a[j] = t;
26 }
27 return a;
28 }
29
30 function luminance(hex) {
31 var c = (hex || '#000').replace('#', '');
32 return 0.299 * parseInt(c.slice(0, 2), 16) + 0.587 * parseInt(c.slice(2, 4), 16) + 0.114 * parseInt(c.slice(4, 6), 16);
33 }
34
35 function initBlock(root) {
36 var opts;
37 try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
38
39 var a = opts;
40 var rawCards;
41 try { rawCards = JSON.parse(a.cards); } catch (e) { rawCards = []; }
42
43 var deck = rawCards.map(function (c) { return { id: c.id, front: c.front, back: c.back, known: false }; });
44 var currentIndex = 0;
45 var isFlipped = false;
46 var keyboardShortcutsEnabled = a.enableKeyboardShortcuts !== false;
47
48 root.innerHTML = '';
49
50 var wrap = document.createElement('div');
51 wrap.className = 'bkbg-fc-wrap';
52 wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;';
53 wrap.setAttribute('tabindex', '-1');
54 root.appendChild(wrap);
55 typoCssVarsForEl(a.typoTitle, '--bkbg-flash-tt-', wrap);
56 typoCssVarsForEl(a.typoSubtitle, '--bkbg-flash-ts-', wrap);
57 typoCssVarsForEl(a.typoCard, '--bkbg-flash-tc-', wrap);
58
59 if (a.showTitle) {
60 var h = document.createElement('div');
61 h.className = 'bkbg-fc-title';
62 h.style.color = a.titleColor;
63 h.textContent = a.title;
64 wrap.appendChild(h);
65 }
66
67 if (a.showSubtitle) {
68 var s = document.createElement('div');
69 s.className = 'bkbg-fc-subtitle';
70 s.style.color = a.subtitleColor;
71 s.textContent = a.subtitle;
72 wrap.appendChild(s);
73 }
74
75 var progressBarWrap = null;
76 var progressBar = null;
77 var progressLabel = null;
78
79 if (a.showProgress) {
80 var progressRow = document.createElement('div');
81 progressRow.className = 'bkbg-fc-progress-row';
82 var pLabel = document.createElement('span');
83 pLabel.style.color = a.labelColor;
84 pLabel.textContent = 'Progress';
85 progressLabel = document.createElement('span');
86 progressLabel.style.color = a.labelColor;
87 progressRow.appendChild(pLabel);
88 progressRow.appendChild(progressLabel);
89 wrap.appendChild(progressRow);
90 progressBarWrap = document.createElement('div');
91 progressBarWrap.className = 'bkbg-fc-progress-bar-wrap';
92 progressBar = document.createElement('div');
93 progressBar.className = 'bkbg-fc-progress-bar';
94 progressBar.style.background = a.knownColor;
95 progressBarWrap.appendChild(progressBar);
96 wrap.appendChild(progressBarWrap);
97 }
98
99 // Card scene
100 var scene = document.createElement('div');
101 scene.className = 'bkbg-fc-scene';
102 scene.style.height = a.cardHeight + 'px';
103 wrap.appendChild(scene);
104
105 var card = document.createElement('div');
106 card.className = 'bkbg-fc-card';
107 card.style.height = a.cardHeight + 'px';
108 card.setAttribute('role', 'button');
109 card.setAttribute('tabindex', '0');
110 card.setAttribute('aria-label', 'Flip flashcard');
111 scene.appendChild(card);
112
113 var front = document.createElement('div');
114 front.className = 'bkbg-fc-face bkbg-fc-front';
115 front.style.cssText = 'background:' + a.cardFrontBg + ';color:' + a.labelColor + ';';
116 card.appendChild(front);
117
118 var back = document.createElement('div');
119 back.className = 'bkbg-fc-face bkbg-fc-back';
120 back.style.cssText = 'background:' + a.cardBackBg + ';color:' + a.cardBackColor + ';';
121 card.appendChild(back);
122
123 var flipHint = document.createElement('div');
124 flipHint.className = 'bkbg-fc-flip-hint';
125 flipHint.style.color = a.labelColor;
126 flipHint.textContent = 'Click to flip';
127 front.appendChild(flipHint);
128
129 card.addEventListener('click', function () {
130 isFlipped = !isFlipped;
131 card.classList.toggle('bkbg-fc-flipped', isFlipped);
132 });
133
134 // Navigation
135 var nav = document.createElement('div');
136 nav.className = 'bkbg-fc-nav';
137 wrap.appendChild(nav);
138
139 var prevBtn = document.createElement('button');
140 prevBtn.className = 'bkbg-fc-nav-btn';
141 prevBtn.textContent = '←';
142 prevBtn.setAttribute('aria-label', 'Previous card');
143 nav.appendChild(prevBtn);
144
145 if (a.showKnownBtn) {
146 var knownBtn = document.createElement('button');
147 knownBtn.className = 'bkbg-fc-known-btn';
148 knownBtn.style.background = a.knownColor;
149 knownBtn.textContent = '✓ Know it';
150 knownBtn.addEventListener('click', function () {
151 deck[currentIndex].known = true;
152 render();
153 if (currentIndex < deck.length - 1) {
154 currentIndex++;
155 isFlipped = false;
156 card.classList.remove('bkbg-fc-flipped');
157 render();
158 } else {
159 showSummary();
160 }
161 });
162 nav.appendChild(knownBtn);
163 }
164
165 var nextBtn = document.createElement('button');
166 nextBtn.className = 'bkbg-fc-nav-btn';
167 nextBtn.textContent = '→';
168 nextBtn.setAttribute('aria-label', 'Next card');
169 nav.appendChild(nextBtn);
170
171 var counterRow = document.createElement('div');
172 counterRow.className = 'bkbg-fc-counter-row';
173 counterRow.style.color = a.labelColor;
174 wrap.appendChild(counterRow);
175
176 var counter = document.createElement('span');
177 counter.className = 'bkbg-fc-counter';
178 counterRow.appendChild(counter);
179
180 if (a.showShuffleBtn) {
181 var shuffleBtn = document.createElement('button');
182 shuffleBtn.className = 'bkbg-fc-action-btn';
183 shuffleBtn.style.cssText = 'border:1px solid ' + a.accentColor + ';color:' + a.accentColor + ';';
184 shuffleBtn.textContent = '⇌ Shuffle';
185 shuffleBtn.addEventListener('click', function () {
186 deck = shuffle(deck);
187 currentIndex = 0;
188 isFlipped = false;
189 card.classList.remove('bkbg-fc-flipped');
190 render();
191 });
192 counterRow.appendChild(shuffleBtn);
193 }
194
195 var resetBtn = document.createElement('button');
196 resetBtn.className = 'bkbg-fc-action-btn';
197 resetBtn.style.cssText = 'border:1px solid #e5e7eb;color:' + a.subtitleColor + ';';
198 resetBtn.textContent = '↺ Reset';
199 resetBtn.addEventListener('click', function () {
200 deck.forEach(function (c) { c.known = false; });
201 currentIndex = 0;
202 isFlipped = false;
203 card.classList.remove('bkbg-fc-flipped');
204 scene.style.display = '';
205 nav.style.display = '';
206 counterRow.style.display = '';
207 if (skHint) skHint.style.display = '';
208 if (summaryEl) summaryEl.remove(), summaryEl = null;
209 render();
210 });
211 counterRow.appendChild(resetBtn);
212
213 var skHint = null;
214 if (keyboardShortcutsEnabled && a.showKeyboardHint) {
215 skHint = document.createElement('div');
216 skHint.className = 'bkbg-fc-keyboard-hint';
217 skHint.style.color = a.subtitleColor;
218 skHint.textContent = '← → Arrow keys to navigate • Space to flip';
219 wrap.appendChild(skHint);
220 }
221
222 prevBtn.addEventListener('click', function () {
223 if (currentIndex > 0) {
224 currentIndex--;
225 isFlipped = false;
226 card.classList.remove('bkbg-fc-flipped');
227 render();
228 }
229 });
230
231 nextBtn.addEventListener('click', function () {
232 if (currentIndex < deck.length - 1) {
233 currentIndex++;
234 isFlipped = false;
235 card.classList.remove('bkbg-fc-flipped');
236 render();
237 }
238 });
239
240 var summaryEl = null;
241
242 function showSummary() {
243 scene.style.display = 'none';
244 nav.style.display = 'none';
245 counterRow.style.display = 'none';
246 if (skHint) skHint.style.display = 'none';
247
248 summaryEl = document.createElement('div');
249 summaryEl.className = 'bkbg-fc-summary';
250 summaryEl.style.color = a.labelColor;
251
252 var knownCount = deck.filter(function (c) { return c.known; }).length;
253 var icon = knownCount === deck.length ? '🎉' : '📚';
254 summaryEl.innerHTML = '<div class="bkbg-fc-summary-icon">' + icon + '</div>' +
255 '<h3 style="color:' + a.titleColor + '">Deck Complete!</h3>' +
256 '<p>' + knownCount + ' of ' + deck.length + ' cards marked as known</p>';
257
258 var rsBtn = document.createElement('button');
259 rsBtn.className = 'bkbg-fc-restart-btn';
260 rsBtn.style.background = a.accentColor;
261 rsBtn.textContent = '↺ Study Again';
262 rsBtn.addEventListener('click', function () {
263 deck.forEach(function (c) { c.known = false; });
264 currentIndex = 0;
265 isFlipped = false;
266 card.classList.remove('bkbg-fc-flipped');
267 scene.style.display = '';
268 nav.style.display = '';
269 counterRow.style.display = '';
270 if (skHint) skHint.style.display = '';
271 summaryEl.remove();
272 summaryEl = null;
273 render();
274 });
275 summaryEl.appendChild(rsBtn);
276 wrap.appendChild(summaryEl);
277 }
278
279 function render() {
280 var c = deck[currentIndex];
281 if (!c) return;
282
283 // known badge
284 front.querySelectorAll('.bkbg-fc-known-badge').forEach(function (el) { el.remove(); });
285 if (c.known) {
286 var badge = document.createElement('div');
287 badge.className = 'bkbg-fc-known-badge';
288 badge.style.cssText = 'background:' + a.knownColor + ';color:#fff;';
289 badge.textContent = '✓ Known';
290 front.prepend(badge);
291 }
292
293 // front text
294 var frontText = front.querySelector('.bkbg-fc-text');
295 if (!frontText) {
296 frontText = document.createElement('div');
297 frontText.className = 'bkbg-fc-text';
298 front.insertBefore(frontText, flipHint);
299 }
300 frontText.textContent = c.front;
301
302 // back text
303 back.textContent = c.back;
304
305 // counter
306 counter.textContent = (currentIndex + 1) + ' / ' + deck.length;
307
308 // nav buttons
309 prevBtn.disabled = currentIndex === 0;
310 nextBtn.disabled = currentIndex === deck.length - 1;
311
312 // progress
313 if (progressBar && progressLabel) {
314 var knownCount = deck.filter(function (d) { return d.known; }).length;
315 progressBar.style.width = (knownCount / deck.length * 100) + '%';
316 progressLabel.textContent = knownCount + ' / ' + deck.length + ' known';
317 }
318 }
319
320 function isTypingTarget(target) {
321 if (!target || target === document.body) return false;
322 if (target.isContentEditable) return true;
323 return !!target.closest('input, textarea, select, button, [contenteditable="true"], [role="textbox"]');
324 }
325
326 // Keyboard nav is scoped to this block so typing in other plugins is never intercepted.
327 wrap.addEventListener('keydown', function (e) {
328 if (!keyboardShortcutsEnabled || isTypingTarget(e.target)) return;
329 if (e.key === 'ArrowLeft') { e.preventDefault(); prevBtn.click(); }
330 if (e.key === 'ArrowRight') { e.preventDefault(); nextBtn.click(); }
331 if (e.key === ' ' || e.key === 'Spacebar') { e.preventDefault(); card.click(); }
332 });
333
334 render();
335 }
336
337 function init() {
338 document.querySelectorAll('.bkbg-fc-app').forEach(initBlock);
339 }
340
341 if (document.readyState === 'loading') {
342 document.addEventListener('DOMContentLoaded', init);
343 } else {
344 init();
345 }
346 })();
347