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 ---
|