| <?php |
| $logFile = __DIR__ . '/logs.txt'; |
| if (!file_exists($logFile)) { |
| file_put_contents($logFile, ''); |
| chmod($logFile, 0666); |
| } |
|
|
| ini_set('log_errors', 1); |
| ini_set('error_log', $logFile); |
| ini_set('display_errors', 0); |
| error_reporting(E_ALL); |
|
|
| |
| require_once 'config.php'; |
| header('Content-Type: text/html; charset=UTF-8'); |
| mb_internal_encoding('UTF-8'); |
| session_start(); |
|
|
| |
| if (!isset($_SESSION['user_id'])) { |
| header('Location: login.php'); |
| exit; |
| } |
|
|
| $project_id = $_GET['id'] ?? 0; |
|
|
| |
| $conn = new mysqli($host, $username, $password, $dbname); |
| if ($conn->connect_error) { |
| die("Connection failed: " . $conn->connect_error); |
| } |
| if (!$conn->set_charset("utf8mb4")) { |
| throw new Exception("文字コード設定失敗: " . $conn->error); |
| } |
|
|
| |
| $stmt = $conn->prepare("SELECT p.*, u.display_name as author_display_name, u.icon_url as author_icon_url |
| FROM projects p |
| LEFT JOIN user_data u ON p.author_id = u.google_id |
| WHERE p.id = ?"); |
| $stmt->bind_param("i", $project_id); |
| $stmt->execute(); |
| $result = $stmt->get_result(); |
| $project = $result->fetch_assoc(); |
|
|
| if (!$project) { |
| die("プロジェクトが見つかりません"); |
| } |
|
|
| |
| $password_required = !empty($project['password']); |
| $password_verified = false; |
|
|
| if ($password_required) { |
| if (isset($_POST['project_password'])) { |
| if ($_POST['project_password'] === $project['password']) { |
| $_SESSION['verified_projects'][$project_id] = true; |
| $password_verified = true; |
| } else { |
| $password_error = "パスワードが間違っています"; |
| } |
| } elseif (isset($_SESSION['verified_projects'][$project_id])) { |
| $password_verified = true; |
| } |
| } else { |
| $password_verified = true; |
| } |
|
|
| if (!$password_verified) { |
| |
| ?> |
| <!DOCTYPE html> |
| <html lang="ja"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>パスワードが必要です - Scratch School</title> |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet"> |
| <style> |
| body { |
| font-family: 'Noto Sans JP', sans-serif; |
| background-color: |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| height: 100vh; |
| margin: 0; |
| } |
| .password-form { |
| background: white; |
| padding: 30px; |
| border-radius: 10px; |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
| width: 100%; |
| max-width: 400px; |
| text-align: center; |
| } |
| .password-form h1 { |
| margin-bottom: 20px; |
| font-size: 24px; |
| } |
| .password-form input { |
| width: 100%; |
| padding: 10px; |
| margin-bottom: 20px; |
| border: 1px solid |
| border-radius: 5px; |
| font-size: 16px; |
| } |
| .password-form button { |
| background-color: |
| color: white; |
| border: none; |
| padding: 10px 20px; |
| border-radius: 5px; |
| cursor: pointer; |
| font-size: 16px; |
| } |
| .error { |
| color: |
| margin-bottom: 20px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="password-form"> |
| <h1>このプロジェクトはパスワードで保護されています</h1> |
| <?php if (isset($password_error)): ?> |
| <div class="error"><?php echo htmlspecialchars($password_error); ?></div> |
| <?php endif; ?> |
| <form method="POST"> |
| <input type="password" name="project_password" placeholder="パスワードを入力" required> |
| <button type="submit">送信</button> |
| </form> |
| </div> |
| </body> |
| </html> |
| <?php |
| exit; |
| } |
| |
| // 閲覧数更新 |
| $conn->query("UPDATE projects SET views = views + 1 WHERE id = $project_id"); |
| |
| // ユーザーが星を付けたかどうか確認 |
| $has_starred = false; |
| $starred_users = json_decode($project['starred_users'] ?? '[]', true) ?? []; |
| if (isset($_SESSION['user_id'])) { |
| $has_starred = in_array($_SESSION['user_id'], $starred_users); |
| } |
|
|
| |
| if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['toggle_star'])) { |
| if ($has_starred) { |
| |
| $starred_users = array_diff($starred_users, [$_SESSION['user_id']]); |
| $starred_users_json = $conn->real_escape_string(json_encode(array_values($starred_users))); |
| $conn->query("UPDATE projects SET stars = stars - 1, starred_users = '$starred_users_json' WHERE id = $project_id"); |
| $project['stars']--; |
| $has_starred = false; |
| } else { |
| |
| $starred_users[] = $_SESSION['user_id']; |
| $starred_users_json = $conn->real_escape_string(json_encode(array_values($starred_users))); |
| $conn->query("UPDATE projects SET stars = stars + 1, starred_users = '$starred_users_json' WHERE id = $project_id"); |
| $project['stars']++; |
| $has_starred = true; |
| } |
| } |
|
|
| |
| $comment_error = null; |
| $comment_success = null; |
| $comments = []; |
|
|
| |
| $comment_data = json_decode($project['comment'], true) ?? ['can_comment' => false, 'history' => []]; |
| $can_comment = $comment_data['can_comment'] ?? false; |
| $comments = $comment_data['history'] ?? []; |
|
|
| if ($can_comment) { |
| |
| if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['comment_content'])) { |
| $content = trim($_POST['comment_content']); |
| |
| if (empty($content)) { |
| $comment_error = "コメント内容を入力してください"; |
| } elseif (strlen($content) > 1000) { |
| $comment_error = "コメントは1000文字以内で入力してください"; |
| } else { |
| |
| $stmt = $conn->prepare("SELECT display_name, icon_url FROM user_data WHERE google_id = ?"); |
| $stmt->bind_param("s", $_SESSION['user_id']); |
| $stmt->execute(); |
| $user_result = $stmt->get_result(); |
| $user_data = $user_result->fetch_assoc(); |
| |
| if ($user_data) { |
| $new_comment = [ |
| 'user_id' => $_SESSION['user_id'], |
| 'name' => $user_data['display_name'], |
| 'icon_url' => $user_data['icon_url'], |
| 'content' => $content, |
| 'timestamp' => time() |
| ]; |
| |
| array_unshift($comments, $new_comment); |
| $comment_data['history'] = $comments; |
| |
| |
| $json_data = json_encode($comment_data, JSON_UNESCAPED_UNICODE); |
| $stmt = $conn->prepare("UPDATE projects SET comment = ? WHERE id = ?"); |
| $stmt->bind_param("si", $json_data, $project_id); |
| if ($stmt->execute()) { |
| $comment_success = "コメントを投稿しました"; |
| } else { |
| $comment_error = "コメントの保存に失敗しました"; |
| } |
| } else { |
| $comment_error = "ユーザー情報が見つかりません"; |
| } |
| } |
| } |
| |
| |
| if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['comment_action'])) { |
| $index = $_POST['comment_index'] ?? null; |
| $is_owner = ($_SESSION['user_id'] === $project['author_id']); |
| |
| if ($index !== null && isset($comments[$index])) { |
| $comment = $comments[$index]; |
| $is_comment_owner = ($comment['user_id'] === $_SESSION['user_id']); |
| |
| if ($is_owner || $is_comment_owner) { |
| if ($_POST['comment_action'] === 'delete') { |
| |
| array_splice($comments, $index, 1); |
| $comment_data['history'] = $comments; |
| $json_data = json_encode($comment_data, JSON_UNESCAPED_UNICODE); |
| $stmt = $conn->prepare("UPDATE projects SET comment = ? WHERE id = ?"); |
| $stmt->bind_param("si", $json_data, $project_id); |
| $stmt->execute(); |
| } elseif ($_POST['comment_action'] === 'edit' && isset($_POST['edited_content'])) { |
| |
| $edited_content = trim($_POST['edited_content']); |
| if (!empty($edited_content) && strlen($edited_content) <= 1000) { |
| $comments[$index]['content'] = $edited_content; |
| $comments[$index]['edited'] = true; |
| $comment_data['history'] = $comments; |
| $json_data = json_encode($comment_data, JSON_UNESCAPED_UNICODE); |
| $stmt = $conn->prepare("UPDATE projects SET comment = ? WHERE id = ?"); |
| $stmt->bind_param("si", $json_data, $project_id); |
| $stmt->execute(); |
| } |
| } |
| } |
| } |
| } |
| } |
|
|
| |
| $current_user_stmt = $conn->prepare("SELECT display_name, icon_url FROM user_data WHERE google_id = ?"); |
| $current_user_stmt->bind_param("s", $_SESSION['user_id']); |
| $current_user_stmt->execute(); |
| $current_user_result = $current_user_stmt->get_result(); |
| $current_user = $current_user_result->fetch_assoc(); |
|
|
| |
| $comment_user_ids = array_column($comments, 'user_id'); |
| if (!empty($comment_user_ids)) { |
| $placeholders = implode(',', array_fill(0, count($comment_user_ids), '?')); |
| $types = str_repeat('s', count($comment_user_ids)); |
| $stmt = $conn->prepare("SELECT google_id, icon_url FROM user_data WHERE google_id IN ($placeholders)"); |
| $stmt->bind_param($types, ...$comment_user_ids); |
| $stmt->execute(); |
| $icon_result = $stmt->get_result(); |
| $user_icons = []; |
| while ($row = $icon_result->fetch_assoc()) { |
| $user_icons[$row['google_id']] = $row['icon_url']; |
| } |
| |
| |
| foreach ($comments as &$comment) { |
| if (isset($user_icons[$comment['user_id']])) { |
| $comment['icon_url'] = $user_icons[$comment['user_id']]; |
| } |
| } |
| } |
|
|
| $conn->close(); |
| ?> |
|
|
| <!DOCTYPE html> |
| <html lang="ja"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <!-- Google Tag Manager --> |
| <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': |
| new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], |
| j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= |
| 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); |
| })(window,document,'script','dataLayer','GTM-PJXRKX3F');</script> |
| <!-- End Google Tag Manager --> |
| <title><?php echo htmlspecialchars($project['project_name']); ?> - Scratch School</title> |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
| <style> |
| :root { |
| --primary-color: |
| --secondary-color: |
| --background-color: |
| --card-bg: |
| --text-color: |
| --error-color: |
| --success-color: |
| } |
| |
| * { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| } |
| |
| body { |
| font-family: 'Noto Sans JP', sans-serif; |
| background-color: var(--background-color); |
| color: var(--text-color); |
| line-height: 1.6; |
| padding: 20px; |
| } |
| |
| .container { |
| max-width: 98%; |
| margin: 0 auto; |
| background-color: var(--card-bg); |
| border-radius: 10px; |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
| padding: 30px; |
| } |
| |
| .project-header { |
| display: flex; |
| margin-bottom: 20px; |
| gap: 20px; |
| align-items: center; |
| } |
| |
| .project-thumbnail { |
| width: 200px; |
| height: 150px; |
| background-color: |
| border-radius: 8px; |
| overflow: hidden; |
| flex-shrink: 0; |
| position: relative; |
| } |
| |
| .project-thumbnail img { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| } |
| |
| .project-meta { |
| flex-grow: 1; |
| } |
| |
| .project-title-container { |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| margin-bottom: 10px; |
| flex-wrap: wrap; |
| } |
| |
| .project-title { |
| font-size: 24px; |
| margin: 0; |
| } |
| |
| .project-author { |
| color: var(--secondary-color); |
| margin-bottom: 10px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .author-icon { |
| width: 24px; |
| height: 24px; |
| border-radius: 50%; |
| object-fit: cover; |
| } |
| |
| .project-stats { |
| display: flex; |
| gap: 20px; |
| margin-bottom: 10px; |
| } |
| |
| .star-button { |
| background: none; |
| border: none; |
| cursor: pointer; |
| font-size: 24px; |
| color: <?php echo $has_starred ? '#ffcc00' : '#ccc'; ?>; |
| padding: 0; |
| } |
| |
| .project-content { |
| display: flex; |
| gap: 20px; |
| margin-bottom: 20px; |
| } |
| |
| .project-iframe-container { |
| flex: 2; |
| } |
| |
| .project-description-container { |
| flex: 1; |
| overflow-y: auto; |
| max-height: 600px; |
| padding: 10px; |
| border: 1px solid |
| border-radius: 8px; |
| } |
| |
| .project-description { |
| white-space: pre-wrap; |
| } |
| |
| .markdown-preview { |
| border: 1px solid |
| padding: 10px; |
| border-radius: 5px; |
| margin-bottom: 10px; |
| background-color: |
| } |
| |
| .project-iframe { |
| width: 100%; |
| height: 600px; |
| border: none; |
| border-radius: 8px; |
| background-color: |
| } |
| |
| .loading-thumbnail { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| color: |
| } |
| |
| .action-button { |
| background-color: var(--primary-color); |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| border-radius: 5px; |
| cursor: pointer; |
| text-decoration: none; |
| font-size: 14px; |
| } |
| |
| |
| .comments-section { |
| margin-top: 40px; |
| border-top: 1px solid |
| padding-top: 20px; |
| } |
| |
| .comments-title { |
| font-size: 20px; |
| margin-bottom: 20px; |
| } |
| |
| .comment-form { |
| margin-bottom: 30px; |
| } |
| |
| .comment-textarea { |
| width: 100%; |
| padding: 10px; |
| border: 1px solid |
| border-radius: 5px; |
| min-height: 100px; |
| margin-bottom: 10px; |
| font-family: inherit; |
| } |
| |
| .comment-submit { |
| background-color: var(--primary-color); |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| border-radius: 5px; |
| cursor: pointer; |
| } |
| |
| .comment-preview { |
| margin-top: 10px; |
| padding: 10px; |
| border: 1px dashed |
| border-radius: 5px; |
| background-color: |
| display: none; |
| } |
| |
| .comment-list { |
| display: flex; |
| flex-direction: column; |
| gap: 20px; |
| } |
| |
| .comment-item { |
| border: 1px solid |
| border-radius: 8px; |
| padding: 15px; |
| position: relative; |
| } |
| |
| .comment-header { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| margin-bottom: 10px; |
| } |
| |
| .comment-user-icon { |
| width: 32px; |
| height: 32px; |
| border-radius: 50%; |
| object-fit: cover; |
| } |
| |
| .comment-user-name { |
| font-weight: 500; |
| } |
| |
| .comment-time { |
| color: |
| font-size: 12px; |
| margin-left: auto; |
| } |
| |
| .comment-content { |
| margin-left: 42px; |
| } |
| |
| .comment-actions { |
| margin-top: 10px; |
| display: flex; |
| gap: 10px; |
| justify-content: flex-end; |
| } |
| |
| .comment-action-button { |
| background: none; |
| border: none; |
| color: |
| cursor: pointer; |
| font-size: 12px; |
| } |
| |
| .comment-action-button:hover { |
| color: var(--primary-color); |
| } |
| |
| .comment-edit-form { |
| display: none; |
| margin-top: 10px; |
| } |
| |
| .error-message { |
| color: var(--error-color); |
| margin-bottom: 10px; |
| } |
| |
| .success-message { |
| color: var(--success-color); |
| margin-bottom: 10px; |
| } |
| |
| .preview-toggle { |
| background: none; |
| border: none; |
| color: |
| cursor: pointer; |
| font-size: 13px; |
| margin-bottom: 10px; |
| } |
| </style> |
| </head> |
| <body> |
| <!-- Google Tag Manager (noscript) --> |
| <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PJXRKX3F" |
| height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> |
| <!-- End Google Tag Manager (noscript) --> |
| <div class="container"> |
| <div class="project-header"> |
| <div class="project-thumbnail" id="project-thumbnail" data-drive-id="<?php echo htmlspecialchars($project['drive_id']); ?>"> |
| <div class="loading-thumbnail">読み込み中...</div> |
| <!-- 画像はJavaScriptで動的に挿入されます --> |
| </div> |
| <div class="project-meta"> |
| <div class="project-title-container"> |
| <h1 class="project-title"><?php echo htmlspecialchars($project['project_name']); ?></h1> |
| <a href="https://soiz1-soiz1-s4s-editor.hf.space/editor.html?project_url=https://drive-proxy-s4s.vercel.app/%3Ffile_id%3D<?php echo urlencode($project['drive_id']); ?>" |
| class="action-button" target="_blank">エディターで開く</a> |
| </div> |
| <p class="project-author"> |
| <?php if ($project['author_icon_url']): ?> |
| <img src="<?php echo htmlspecialchars($project['author_icon_url']); ?>" class="author-icon" alt="作者のアイコン"> |
| <?php endif; ?> |
| by <?php echo htmlspecialchars($project['author_display_name']); ?> |
| </p> |
| <div class="project-stats"> |
| <form method="POST"> |
| <button type="submit" name="toggle_star" class="star-button">★ <?php echo $project['stars']; ?></button> |
| </form> |
| <span>👁️ <?php echo $project['views']; ?> 回閲覧</span> |
| </div> |
| </div> |
| </div> |
| |
| <div class="project-content"> |
| <div class="project-iframe-container"> |
| <iframe |
| src="https://soiz1-soiz1-s4s-editor.hf.space/fullscreen.html?project_url=https://drive-proxy-s4s.vercel.app/%3Ffile_id%3D<?php echo urlencode($project['drive_id']); ?>" |
| class="project-iframe" |
| allow="accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; clipboard-read *; clipboard-write *; display-capture *; document-domain *; encrypted-media *; fullscreen *; geolocation *; gyroscope *; magnetometer *; microphone *; midi *; navigation-override *; oversized-images *; payment *; picture-in-picture *; publickey-credentials-get *; screen-wake-lock *; speaker-selection *; storage-access *; sync-script *; sync-xhr *; usb *; web-share *; window-management *; xr-spatial-tracking *" |
| allowfullscreen> |
| </iframe> |
| </div> |
| |
| <div class="project-description-container"> |
| <div class="project-description" id="project-description"></div> |
| </div> |
| </div> |
| |
| <?php if ($can_comment): ?> |
| <div class="comments-section"> |
| <h2 class="comments-title">コメント</h2> |
| |
| <?php if ($comment_error): ?> |
| <div class="error-message"><?php echo htmlspecialchars($comment_error); ?></div> |
| <?php endif; ?> |
| |
| <?php if ($comment_success): ?> |
| <div class="success-message"><?php echo htmlspecialchars($comment_success); ?></div> |
| <?php endif; ?> |
| |
| <form method="POST" class="comment-form"> |
| <textarea name="comment_content" class="comment-textarea" placeholder="コメントを入力 (マークダウン対応)"></textarea> |
| <button type="button" class="preview-toggle" onclick="togglePreview(this)">プレビューを表示</button> |
| <div class="comment-preview"></div> |
| <button type="submit" class="comment-submit">コメントを投稿</button> |
| </form> |
| |
| <div class="comment-list"> |
| <?php foreach ($comments as $index => $comment): |
| $is_owner = ($_SESSION['user_id'] === $project['author_id']); |
| $is_comment_owner = ($comment['user_id'] === $_SESSION['user_id']); |
| $can_edit = $is_owner || $is_comment_owner; |
| |
| $user_icon = $comment['icon_url'] ?? ''; |
| ?> |
| <div class="comment-item" id="comment-<?php echo $index; ?>"> |
| <div class="comment-header"> |
| <?php if ($user_icon): ?> |
| <img src="<?php echo htmlspecialchars($user_icon); ?>" class="comment-user-icon" alt="ユーザーアイコン"> |
| <?php endif; ?> |
| <span class="comment-user-name"><?php echo htmlspecialchars($comment['name']); ?></span> |
| <span class="comment-time"> |
| <?php echo date('Y/m/d H:i', $comment['timestamp'] ?? time()); ?> |
| <?php if (!empty($comment['edited'])): ?> |
| (編集済み) |
| <?php endif; ?> |
| </span> |
| </div> |
| <div class="comment-content" id="comment-content-<?php echo $index; ?>" style="display:none;"> |
| <?php echo htmlspecialchars($comment['content'], ENT_QUOTES, 'UTF-8'); ?> |
| </div> |
| <div class="comment-render" id="comment-render-<?php echo $index; ?>"></div> |
| |
| |
| <?php if ($can_edit): ?> |
| <div class="comment-actions"> |
| <button type="button" class="comment-action-button" onclick="showEditForm(<?php echo $index; ?>)">編集</button> |
| <form method="POST" style="display:inline;"> |
| <input type="hidden" name="comment_index" value="<?php echo $index; ?>"> |
| <button type="submit" name="comment_action" value="delete" class="comment-action-button" onclick="return confirm('本当に削除しますか?');">削除</button> |
| </form> |
| </div> |
| |
| <form method="POST" class="comment-edit-form" id="edit-form-<?php echo $index; ?>"> |
| <input type="hidden" name="comment_index" value="<?php echo $index; ?>"> |
| <textarea name="edited_content" class="comment-textarea"><?php echo htmlspecialchars($comment['content']); ?></textarea> |
| <button type="button" class="preview-toggle" onclick="toggleEditPreview(<?php echo $index; ?>)">プレビューを表示</button> |
| <div class="comment-preview" id="edit-preview-<?php echo $index; ?>"></div> |
| <button type="submit" name="comment_action" value="edit" class="comment-submit">更新</button> |
| <button type="button" class="comment-action-button" onclick="hideEditForm(<?php echo $index; ?>)">キャンセル</button> |
| </form> |
| <?php endif; ?> |
| </div> |
| <?php endforeach; ?> |
| |
| <?php if (empty($comments)): ?> |
| <p>まだコメントはありません</p> |
| <?php endif; ?> |
| </div> |
| </div> |
| <?php endif; ?> |
| </div> |
| |
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const description = document.getElementById('project-description'); |
| if (description) { |
| const markdown = `<?php echo addslashes($project['description']); ?>`; |
| description.innerHTML = marked.parse(markdown); |
| } |
| |
| |
| document.querySelectorAll('.comment-content').forEach((element, i) => { |
| const raw = element.textContent; |
| const renderTarget = document.getElementById(`comment-render-${i}`); |
| renderTarget.innerHTML = marked.parse(raw); |
| }); |
| |
| |
| const accessToken = '<?php echo $_SESSION['access_token'] ?? ''; ?>'; |
| const thumbnailContainer = document.getElementById('project-thumbnail'); |
| if (thumbnailContainer) { |
| const driveId = thumbnailContainer.dataset.driveId; |
| const loadingElement = thumbnailContainer.querySelector('.loading-thumbnail'); |
| |
| if (!driveId) { |
| loadingElement.textContent = 'サムネイルなし'; |
| return; |
| } |
| |
| const thumbnailName = `Scratch-Thumbnail-${driveId}.png`; |
| |
| |
| fetch(`https://www.googleapis.com/drive/v3/files?q=name='${encodeURIComponent(thumbnailName)}'`, { |
| headers: { |
| 'Authorization': `Bearer ${accessToken}` |
| } |
| }) |
| .then(response => { |
| if (!response.ok) { |
| throw new Error('サムネイル検索に失敗しました'); |
| } |
| return response.json(); |
| }) |
| .then(data => { |
| if (data.files && data.files.length > 0) { |
| const thumbnailId = data.files[0].id; |
| const img = document.createElement('img'); |
| img.src = `https: |
| img.alt = 'プロジェクトサムネイル'; |
| img.onload = () => { |
| loadingElement.remove(); |
| thumbnailContainer.appendChild(img); |
| }; |
| img.onerror = () => { |
| loadingElement.textContent = 'サムネイル読み込みエラー'; |
| }; |
| } else { |
| loadingElement.textContent = 'サムネイルなし'; |
| } |
| }) |
| .catch(error => { |
| console.error('サムネイル取得エラー:', error); |
| loadingElement.textContent = '読み込みエラー'; |
| |
| if (error.message.includes('401')) { |
| |
| window.location.href = 'login.php'; |
| } |
| }); |
| } |
| }); |
| |
| |
| function togglePreview(button) { |
| const form = button.closest('.comment-form'); |
| const textarea = form.querySelector('.comment-textarea'); |
| const preview = form.querySelector('.comment-preview'); |
| |
| if (preview.style.display === 'block') { |
| preview.style.display = 'none'; |
| button.textContent = 'プレビューを表示'; |
| } else { |
| preview.innerHTML = marked.parse(textarea.value); |
| preview.style.display = 'block'; |
| button.textContent = 'プレビューを非表示'; |
| } |
| } |
| |
| |
| function showEditForm(index) { |
| document.getElementById(`edit-form-${index}`).style.display = 'block'; |
| document.getElementById(`comment-content-${index}`).style.display = 'none'; |
| } |
| |
| |
| function hideEditForm(index) { |
| document.getElementById(`edit-form-${index}`).style.display = 'none'; |
| document.getElementById(`comment-content-${index}`).style.display = 'block'; |
| } |
| |
| |
| function toggleEditPreview(index) { |
| const form = document.getElementById(`edit-form-${index}`); |
| const textarea = form.querySelector('.comment-textarea'); |
| const preview = document.getElementById(`edit-preview-${index}`); |
| const button = form.querySelector('.preview-toggle'); |
| |
| if (preview.style.display === 'block') { |
| preview.style.display = 'none'; |
| button.textContent = 'プレビューを表示'; |
| } else { |
| preview.innerHTML = marked.parse(textarea.value); |
| preview.style.display = 'block'; |
| button.textContent = 'プレビューを非表示'; |
| } |
| } |
| </script> |
| </body> |
| </html> |