syurein
commited on
Commit
·
73d1bcc
1
Parent(s):
4fe3e32
First
Browse files- 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 |
-
//
|
|
|
|
|
100 |
}
|
101 |
}
|
102 |
}
|
@@ -189,8 +194,7 @@ async function handleGenerateSubmit() {
|
|
189 |
throw new Error(response.ok ? 'サーバーからの応答形式が不正です。' : `サーバーエラー (${response.status})`);
|
190 |
}
|
191 |
|
192 |
-
//
|
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 |
-
//
|
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 |
-
//
|
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 |
-
|
|
|
|
|
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 |
-
//
|
323 |
-
if (item.type === 'question' && item.text && item.answer) {
|
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 || 'この項目を表示できません。'}`;
|
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 |
-
|
|
|
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 ---
|