syurein commited on
Commit
73d1bcc
·
1 Parent(s): 4fe3e32
Files changed (1) hide show
  1. static/script.js +41 -31
static/script.js CHANGED
@@ -51,7 +51,7 @@ function closeMenu() {
51
 
52
 
53
  /**
54
- * ローディングスピナーを表示/非表示します。
55
  * @param {boolean} show - trueで表示、falseで非表示
56
  * @param {string} buttonId - 操作対象のボタンID (input.html用)
57
  */
@@ -76,27 +76,32 @@ function toggleLoading(show, buttonId = 'generate-button') {
76
  // learning.html 用の汎用ローディング表示
77
  const loadingIndicator = document.getElementById('mode-indicator');
78
  const cardElement = document.getElementById('learning-card');
79
- const paginationElement = document.querySelector('.pagination');
80
  const optionsArea = document.getElementById('options-area');
81
  const tapToShowElement = document.getElementById('tap-to-show');
82
 
83
  const currentPathname = window.location.pathname;
84
  if (currentPathname.endsWith('/learning') || currentPathname.endsWith('/learning.html')) {
85
  if (show) {
 
86
  if (loadingIndicator) {
87
  loadingIndicator.textContent = '読み込み中...';
88
  loadingIndicator.classList.add('loading');
89
  }
90
  if (cardElement) cardElement.style.opacity = '0.5';
91
- if (paginationElement) paginationElement.style.display = 'none';
92
  if (optionsArea) optionsArea.style.display = 'none';
93
  if (tapToShowElement) tapToShowElement.style.display = 'none';
94
  } else {
 
95
  if (loadingIndicator) {
96
  loadingIndicator.classList.remove('loading');
 
97
  }
98
  if (cardElement) cardElement.style.opacity = '1';
99
- // 他の要素の表示は displayCurrentItem に任せる
 
 
100
  }
101
  }
102
  }
@@ -189,8 +194,7 @@ async function handleGenerateSubmit() {
189
  throw new Error(response.ok ? 'サーバーからの応答形式が不正です。' : `サーバーエラー (${response.status})`);
190
  }
191
 
192
- // ★★★ サーバーが返すJSON構造に合わせてチェック ★★★
193
- // { success: true, data: { id: '...' } } を期待
194
  if (response.ok && result && typeof result === 'object' && result.success && result.data && result.data.id) {
195
  console.log("Generation successful, navigating to learning page with ID:", result.data.id);
196
  goToLearning(result.data.id);
@@ -228,7 +232,7 @@ async function initializeLearningScreen() {
228
  return;
229
  }
230
  console.log('Content ID:', contentId);
231
- toggleLoading(true);
232
 
233
  try {
234
  const response = await fetch(`/api/learning/${contentId}`);
@@ -241,7 +245,7 @@ async function initializeLearningScreen() {
241
  throw new Error(errorMessage);
242
  }
243
 
244
- // ★★★ サーバーからの応答がオブジェクト {success: ..., data: {...}} であると想定 ★★★
245
  const result = await response.json();
246
  console.log('Fetched data object:', result);
247
 
@@ -251,7 +255,7 @@ async function initializeLearningScreen() {
251
  throw new Error('サーバーから受け取ったデータの形式が正しくありません。');
252
  }
253
 
254
- // ★★★ learningData を構築 (title と items を data から取得) ★★★
255
  learningData = {
256
  title: result.data.title || `学習セット (${contentId})`, // data.title があればそれを使う
257
  items: result.data.items // data.items を使う
@@ -277,9 +281,11 @@ async function initializeLearningScreen() {
277
  } catch (error) {
278
  console.error('Error initializing learning screen:', error);
279
  const message = (error instanceof SyntaxError) ? `サーバー応答の解析エラー: ${error.message}` : `読み込みエラー: ${error.message}`;
280
- displayLearningError(message);
281
  } finally {
282
- toggleLoading(false);
 
 
283
  }
284
  }
285
 
@@ -299,12 +305,12 @@ function displayCurrentItem() {
299
 
300
  if (!cardElement || !cardTextElement || !answerTextElement || !tapToShowElement || !optionsArea || !modeIndicator) {
301
  console.error("One or more required learning elements are missing.");
302
- displayLearningError("画面表示に必要な要素が見つかりません。");
303
  return;
304
  }
305
  if (!learningData || !learningData.items || currentItemIndex < 0 || currentItemIndex >= learningData.items.length) {
306
  console.error('Invalid learning data or index:', learningData, currentItemIndex);
307
- displayLearningError('表示する学習データが見つかりません。');
308
  return;
309
  }
310
 
@@ -319,15 +325,15 @@ function displayCurrentItem() {
319
  optionsArea.style.display = 'none';
320
  modeIndicator.classList.remove('loading');
321
 
322
- // ★★★ サーバーレスポンスのキーに合わせて item.text を使用 (変更なし) ★★★
323
- if (item.type === 'question' && item.text && item.answer) { // questionではなくtextキーで問題文を取得
324
  currentMode = 'quiz';
325
  modeIndicator.textContent = 'クイズモード';
326
  cardTextElement.textContent = item.text; // 問題文
327
  answerTextElement.textContent = `答え: ${item.answer}`;
328
 
329
  if (item.options && Array.isArray(item.options) && item.options.length > 0) {
330
- optionsArea.style.display = 'block';
331
  item.options.forEach(option => {
332
  const button = document.createElement('button');
333
  button.classList.add('option-button');
@@ -335,10 +341,10 @@ function displayCurrentItem() {
335
  button.onclick = () => handleOptionClick(option);
336
  optionsArea.appendChild(button);
337
  });
338
- tapToShowElement.style.display = 'block';
339
  } else {
340
  console.warn(`Quiz item ${currentItemIndex} has no options.`);
341
- tapToShowElement.style.display = 'block'; // 選択肢なくても解答表示は可能
342
  }
343
  cardElement.onclick = () => revealAnswer();
344
  tapToShowElement.onclick = () => revealAnswer();
@@ -348,20 +354,20 @@ function displayCurrentItem() {
348
  modeIndicator.textContent = '要約モード';
349
  cardTextElement.innerHTML = item.text.replace(/\n/g, '<br>');
350
  cardElement.onclick = null;
351
- tapToShowElement.style.display = 'none';
352
- optionsArea.style.display = 'none';
353
 
354
  } else {
355
  console.warn('Unknown or invalid item type/data:', item);
356
  currentMode = 'unknown';
357
  modeIndicator.textContent = 'データエラー';
358
- cardTextElement.textContent = `[不正なデータ形式] ${item.text || 'この項目を表示できません。'}`; // item.textを表示試行
359
  cardElement.onclick = null;
360
- tapToShowElement.style.display = 'none';
361
- optionsArea.style.display = 'none';
362
  }
363
 
364
- updatePagination();
365
  }
366
 
367
  /**
@@ -402,8 +408,8 @@ function revealAnswer(selectedOption = null) {
402
 
403
  if (answerTextElement && answerTextElement.style.display === 'block') return; // 表示済み
404
 
405
- if (answerTextElement) answerTextElement.style.display = 'block';
406
- if (tapToShowElement) tapToShowElement.style.display = 'none';
407
  if (cardElement) cardElement.onclick = null;
408
 
409
  if (optionsArea) {
@@ -427,7 +433,7 @@ function revealAnswer(selectedOption = null) {
427
  function goToNext() {
428
  if (learningData && learningData.items && currentItemIndex < learningData.items.length - 1) {
429
  currentItemIndex++;
430
- displayCurrentItem();
431
  } else {
432
  console.log("Already at the last item or no data.");
433
  if (learningData && learningData.items && currentItemIndex === learningData.items.length - 1) {
@@ -442,7 +448,7 @@ function goToNext() {
442
  function goToPrev() {
443
  if (learningData && learningData.items && currentItemIndex > 0) {
444
  currentItemIndex--;
445
- displayCurrentItem();
446
  } else {
447
  console.log("Already at the first item or no data.");
448
  }
@@ -460,6 +466,8 @@ function updatePagination() {
460
  console.warn("Pagination elements not found.");
461
  return;
462
  }
 
 
463
  if (learningData && learningData.items && learningData.items.length > 0) {
464
  const totalItems = learningData.items.length;
465
  pageInfo.textContent = `${currentItemIndex + 1} / ${totalItems}`;
@@ -490,13 +498,15 @@ function displayLearningError(message) {
490
  cardElement.innerHTML = `<p class="main-text error-text">${message}</p>`; // CSSでスタイル調整用クラス追加
491
  cardElement.onclick = null;
492
  }
 
493
  if (paginationElement) paginationElement.style.display = 'none';
494
  if (optionsArea) {
495
  optionsArea.innerHTML = '';
496
  optionsArea.style.display = 'none';
497
  }
498
  if (tapToShow) tapToShow.style.display = 'none';
499
- toggleLoading(false); // エラー時はローディング解除
 
500
  }
501
 
502
  /**
@@ -578,7 +588,7 @@ document.addEventListener('DOMContentLoaded', () => {
578
  if (pathname.endsWith('/learning') || pathname.endsWith('/learning.html')) {
579
  correctEffect = document.getElementById('correct-effect');
580
  if (!correctEffect) console.warn("Correct effect element not found.");
581
- initializeLearningScreen();
582
  }
583
 
584
  // inputページ固有の初期化
@@ -635,5 +645,5 @@ document.addEventListener('DOMContentLoaded', () => {
635
 
636
 
637
  // --- デバッグ用グローバル公開 ---
638
- // 本番環境では削除推奨
639
 
 
 
51
 
52
 
53
  /**
54
+ * ローディングスピナーを表示/非表示します。 learning.html の要素表示/非表示も制御します。
55
  * @param {boolean} show - trueで表示、falseで非表示
56
  * @param {string} buttonId - 操作対象のボタンID (input.html用)
57
  */
 
76
  // learning.html 用の汎用ローディング表示
77
  const loadingIndicator = document.getElementById('mode-indicator');
78
  const cardElement = document.getElementById('learning-card');
79
+ const paginationElement = document.querySelector('.pagination'); // pagination要素を取得
80
  const optionsArea = document.getElementById('options-area');
81
  const tapToShowElement = document.getElementById('tap-to-show');
82
 
83
  const currentPathname = window.location.pathname;
84
  if (currentPathname.endsWith('/learning') || currentPathname.endsWith('/learning.html')) {
85
  if (show) {
86
+ // ローディング開始時の処理
87
  if (loadingIndicator) {
88
  loadingIndicator.textContent = '読み込み中...';
89
  loadingIndicator.classList.add('loading');
90
  }
91
  if (cardElement) cardElement.style.opacity = '0.5';
92
+ if (paginationElement) paginationElement.style.display = 'none'; // ★ 非表示にする
93
  if (optionsArea) optionsArea.style.display = 'none';
94
  if (tapToShowElement) tapToShowElement.style.display = 'none';
95
  } else {
96
+ // ローディング終了時の処理
97
  if (loadingIndicator) {
98
  loadingIndicator.classList.remove('loading');
99
+ // モード表示は displayCurrentItem で更新される
100
  }
101
  if (cardElement) cardElement.style.opacity = '1';
102
+ // ★★★ ローディング終了時にページネーションを再表示 ★★★
103
+ if (paginationElement) paginationElement.style.display = 'flex'; // CSSでのdisplay値 (通常flex) に戻す
104
+ // optionsArea と tapToShowElement の表示は displayCurrentItem で適切に制御される
105
  }
106
  }
107
  }
 
194
  throw new Error(response.ok ? 'サーバーからの応答形式が不正です。' : `サーバーエラー (${response.status})`);
195
  }
196
 
197
+ // サーバーが返すJSON構造 { success: true, data: { id: '...' } } を期待
 
198
  if (response.ok && result && typeof result === 'object' && result.success && result.data && result.data.id) {
199
  console.log("Generation successful, navigating to learning page with ID:", result.data.id);
200
  goToLearning(result.data.id);
 
232
  return;
233
  }
234
  console.log('Content ID:', contentId);
235
+ toggleLoading(true); // ★ ローディング開始 (ここでpaginationが消える)
236
 
237
  try {
238
  const response = await fetch(`/api/learning/${contentId}`);
 
245
  throw new Error(errorMessage);
246
  }
247
 
248
+ // サーバーからの応答がオブジェクト {success: ..., data: {...}} であると想定
249
  const result = await response.json();
250
  console.log('Fetched data object:', result);
251
 
 
255
  throw new Error('サーバーから受け取ったデータの形式が正しくありません。');
256
  }
257
 
258
+ // learningData を構築 (title と items を data から取得)
259
  learningData = {
260
  title: result.data.title || `学習セット (${contentId})`, // data.title があればそれを使う
261
  items: result.data.items // data.items を使う
 
281
  } catch (error) {
282
  console.error('Error initializing learning screen:', error);
283
  const message = (error instanceof SyntaxError) ? `サーバー応答の解析エラー: ${error.message}` : `読み込みエラー: ${error.message}`;
284
+ displayLearningError(message); // エラー表示関数内で toggleLoading(false) が呼ばれる
285
  } finally {
286
+ // ★ 成功時も失敗時もここでローディングを終了させる
287
+ // displayLearningError内で呼ばれる場合もあるが、成功時はここで確実に呼ぶ
288
+ toggleLoading(false); // ★ ローディング終了 (ここでpaginationが再表示される)
289
  }
290
  }
291
 
 
305
 
306
  if (!cardElement || !cardTextElement || !answerTextElement || !tapToShowElement || !optionsArea || !modeIndicator) {
307
  console.error("One or more required learning elements are missing.");
308
+ displayLearningError("画面表示に必要な要素が見つかりません。"); // エラー表示内でtoggleLoading(false)される
309
  return;
310
  }
311
  if (!learningData || !learningData.items || currentItemIndex < 0 || currentItemIndex >= learningData.items.length) {
312
  console.error('Invalid learning data or index:', learningData, currentItemIndex);
313
+ displayLearningError('表示する学習データが見つかりません。'); // エラー表示内でtoggleLoading(false)される
314
  return;
315
  }
316
 
 
325
  optionsArea.style.display = 'none';
326
  modeIndicator.classList.remove('loading');
327
 
328
+ // サーバーレスポンスのキーに合わせて item.text を使用
329
+ if (item.type === 'question' && item.text && item.answer) {
330
  currentMode = 'quiz';
331
  modeIndicator.textContent = 'クイズモード';
332
  cardTextElement.textContent = item.text; // 問題文
333
  answerTextElement.textContent = `答え: ${item.answer}`;
334
 
335
  if (item.options && Array.isArray(item.options) && item.options.length > 0) {
336
+ optionsArea.style.display = 'block'; // ★ 表示
337
  item.options.forEach(option => {
338
  const button = document.createElement('button');
339
  button.classList.add('option-button');
 
341
  button.onclick = () => handleOptionClick(option);
342
  optionsArea.appendChild(button);
343
  });
344
+ tapToShowElement.style.display = 'block'; // ★ 表示
345
  } else {
346
  console.warn(`Quiz item ${currentItemIndex} has no options.`);
347
+ tapToShowElement.style.display = 'block'; // 選択肢なくても解答表示は可能
348
  }
349
  cardElement.onclick = () => revealAnswer();
350
  tapToShowElement.onclick = () => revealAnswer();
 
354
  modeIndicator.textContent = '要約モード';
355
  cardTextElement.innerHTML = item.text.replace(/\n/g, '<br>');
356
  cardElement.onclick = null;
357
+ tapToShowElement.style.display = 'none'; // ★ 非表示
358
+ optionsArea.style.display = 'none'; // ★ 非表示
359
 
360
  } else {
361
  console.warn('Unknown or invalid item type/data:', item);
362
  currentMode = 'unknown';
363
  modeIndicator.textContent = 'データエラー';
364
+ cardTextElement.textContent = `[不正なデータ形式] ${item.text || 'この項目を表示できません。'}`;
365
  cardElement.onclick = null;
366
+ tapToShowElement.style.display = 'none'; // ★ 非表示
367
+ optionsArea.style.display = 'none'; // ★ 非表示
368
  }
369
 
370
+ updatePagination(); // ★ ページネーションの表示/非表示ではなく、内容とボタン状態を更新
371
  }
372
 
373
  /**
 
408
 
409
  if (answerTextElement && answerTextElement.style.display === 'block') return; // 表示済み
410
 
411
+ if (answerTextElement) answerTextElement.style.display = 'block'; // ★ 表示
412
+ if (tapToShowElement) tapToShowElement.style.display = 'none'; // ★ 非表示
413
  if (cardElement) cardElement.onclick = null;
414
 
415
  if (optionsArea) {
 
433
  function goToNext() {
434
  if (learningData && learningData.items && currentItemIndex < learningData.items.length - 1) {
435
  currentItemIndex++;
436
+ displayCurrentItem(); // displayCurrentItem内で要素の表示/非表示が制御される
437
  } else {
438
  console.log("Already at the last item or no data.");
439
  if (learningData && learningData.items && currentItemIndex === learningData.items.length - 1) {
 
448
  function goToPrev() {
449
  if (learningData && learningData.items && currentItemIndex > 0) {
450
  currentItemIndex--;
451
+ displayCurrentItem(); // displayCurrentItem内で要素の表示/非表示が制御される
452
  } else {
453
  console.log("Already at the first item or no data.");
454
  }
 
466
  console.warn("Pagination elements not found.");
467
  return;
468
  }
469
+ // ★ ページネーション要素自体の表示/非表示は toggleLoading で行う
470
+ // ここでは内容とボタンの状態のみ更新
471
  if (learningData && learningData.items && learningData.items.length > 0) {
472
  const totalItems = learningData.items.length;
473
  pageInfo.textContent = `${currentItemIndex + 1} / ${totalItems}`;
 
498
  cardElement.innerHTML = `<p class="main-text error-text">${message}</p>`; // CSSでスタイル調整用クラス追加
499
  cardElement.onclick = null;
500
  }
501
+ // エラー時はページネーション等も非表示にする
502
  if (paginationElement) paginationElement.style.display = 'none';
503
  if (optionsArea) {
504
  optionsArea.innerHTML = '';
505
  optionsArea.style.display = 'none';
506
  }
507
  if (tapToShow) tapToShow.style.display = 'none';
508
+ // ★ エラー表示関数内でも toggleLoading(false) を呼ぶことを確認(重複しても問題ない)
509
+ toggleLoading(false);
510
  }
511
 
512
  /**
 
588
  if (pathname.endsWith('/learning') || pathname.endsWith('/learning.html')) {
589
  correctEffect = document.getElementById('correct-effect');
590
  if (!correctEffect) console.warn("Correct effect element not found.");
591
+ initializeLearningScreen(); // ★ initializeLearningScreen を呼ぶ
592
  }
593
 
594
  // inputページ固有の初期化
 
645
 
646
 
647
  // --- デバッグ用グローバル公開 ---
 
648
 
649
+ // --- END OF FILE script.js ---