PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.11
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.11
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.11, at blocks/flashcard/frontend.js

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