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