| <?php
|
| require_once 'config.php';
|
| session_start();
|
|
|
|
|
| if (!isset($_SESSION['user_id'])) {
|
| header('Location: login.php');
|
| exit;
|
| }
|
|
|
| $search_query = $_GET['q'] ?? '';
|
| $sort = $_GET['sort'] ?? 'default';
|
|
|
|
|
| $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);
|
| }
|
|
|
| $sql = "SELECT * FROM projects WHERE is_public = 1";
|
| $params = [];
|
|
|
| if (!empty($search_query)) {
|
| $sql .= " AND (project_name LIKE ? OR description LIKE ?)";
|
| $search_param = "%$search_query%";
|
| $params = array_merge($params, [$search_param, $search_param]);
|
| }
|
|
|
|
|
| switch ($sort) {
|
| case 'stars':
|
| $sql .= " ORDER BY stars DESC";
|
| break;
|
| case 'views':
|
| $sql .= " ORDER BY views DESC";
|
| break;
|
| case 'custom':
|
|
|
| $sql .= " ORDER BY (stars * 2 + views) DESC";
|
| break;
|
| default:
|
|
|
| if (!empty($search_query)) {
|
| $sql .= " ORDER BY
|
| (CASE WHEN project_name LIKE ? THEN 3
|
| WHEN description LIKE ? THEN 2
|
| ELSE 1 END) DESC";
|
| $exact_param = "$search_query%";
|
| $params = array_merge($params, [$exact_param, $exact_param]);
|
| } else {
|
| $sql .= " ORDER BY created_at DESC";
|
| }
|
| }
|
|
|
| $stmt = $conn->prepare($sql);
|
|
|
|
|
| if (!empty($params)) {
|
| $types = str_repeat('s', count($params));
|
| $stmt->bind_param($types, ...$params);
|
| }
|
|
|
| $stmt->execute();
|
| $result = $stmt->get_result();
|
| $projects = $result->fetch_all(MYSQLI_ASSOC);
|
|
|
| $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>プロジェクト検索 - Scratch School</title>
|
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
|
| <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;
|
| }
|
|
|
| h1 {
|
| color: var(--primary-color);
|
| margin-bottom: 20px;
|
| text-align: center;
|
| }
|
|
|
| .form-group {
|
| margin-bottom: 20px;
|
| }
|
|
|
| label {
|
| display: block;
|
| margin-bottom: 5px;
|
| font-weight: 500;
|
| }
|
|
|
| input[type="text"],
|
| textarea {
|
| width: 100%;
|
| padding: 10px;
|
| border: 1px solid
|
| border-radius: 4px;
|
| font-family: inherit;
|
| font-size: 16px;
|
| }
|
|
|
| textarea {
|
| min-height: 120px;
|
| resize: vertical;
|
| }
|
|
|
| .thumbnail-preview {
|
| width: 200px;
|
| height: 150px;
|
| background-color:
|
| border-radius: 4px;
|
| margin-bottom: 10px;
|
| overflow: hidden;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| }
|
|
|
| .thumbnail-preview img {
|
| max-width: 100%;
|
| max-height: 100%;
|
| object-fit: contain;
|
| }
|
|
|
| .btn {
|
| background-color: var(--primary-color);
|
| color: white;
|
| border: none;
|
| padding: 10px 20px;
|
| border-radius: 4px;
|
| cursor: pointer;
|
| font-size: 16px;
|
| font-weight: 500;
|
| transition: background-color 0.3s;
|
| }
|
|
|
| .btn:hover {
|
| background-color:
|
| }
|
|
|
| .btn-secondary {
|
| background-color: var(--secondary-color);
|
| }
|
|
|
| .btn-secondary:hover {
|
| background-color:
|
| }
|
|
|
| .error {
|
| color: var(--error-color);
|
| margin-bottom: 15px;
|
| }
|
|
|
| .success {
|
| color: var(--success-color);
|
| margin-bottom: 15px;
|
| }
|
|
|
| .checkbox-group {
|
| display: flex;
|
| align-items: center;
|
| margin-bottom: 20px;
|
| }
|
|
|
| .checkbox-group input {
|
| margin-right: 10px;
|
| }
|
| .search-container {
|
| display: flex;
|
| margin-bottom: 20px;
|
| }
|
|
|
| .search-input {
|
| flex-grow: 1;
|
| padding: 10px;
|
| border: 1px solid
|
| border-radius: 4px 0 0 4px;
|
| font-size: 16px;
|
| }
|
|
|
| .search-button {
|
| padding: 10px 20px;
|
| background-color: var(--primary-color);
|
| color: white;
|
| border: none;
|
| border-radius: 0 4px 4px 0;
|
| cursor: pointer;
|
| }
|
|
|
| .sort-options {
|
| display: flex;
|
| gap: 10px;
|
| margin-bottom: 20px;
|
| }
|
|
|
| .sort-option {
|
| padding: 5px 10px;
|
| background-color:
|
| border-radius: 4px;
|
| cursor: pointer;
|
| }
|
|
|
| .sort-option.active {
|
| background-color: var(--primary-color);
|
| color: white;
|
| }
|
|
|
| .projects-grid {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
| gap: 20px;
|
| }
|
|
|
| .project-card {
|
| background-color: var(--card-bg);
|
| border-radius: 8px;
|
| overflow: hidden;
|
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
| transition: transform 0.3s, box-shadow 0.3s;
|
| cursor: pointer;
|
| }
|
|
|
| .project-card:hover {
|
| transform: translateY(-5px);
|
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
| }
|
|
|
| .project-thumbnail {
|
| width: 100%;
|
| height: 150px;
|
| background-color:
|
| overflow: hidden;
|
| position: relative;
|
| }
|
|
|
| .project-thumbnail img {
|
| width: 100%;
|
| height: 100%;
|
| object-fit: cover;
|
| }
|
|
|
| .project-info {
|
| padding: 15px;
|
| }
|
|
|
| .project-title {
|
| font-weight: 500;
|
| margin-bottom: 5px;
|
| white-space: nowrap;
|
| overflow: hidden;
|
| text-overflow: ellipsis;
|
| }
|
|
|
| .project-author {
|
| color: var(--secondary-color);
|
| font-size: 14px;
|
| margin-bottom: 10px;
|
| }
|
|
|
| .project-stats {
|
| display: flex;
|
| justify-content: space-between;
|
| font-size: 14px;
|
| color: var(--secondary-color);
|
| }
|
|
|
| .loading-thumbnail {
|
| position: absolute;
|
| top: 50%;
|
| left: 50%;
|
| transform: translate(-50%, -50%);
|
| color:
|
| }
|
| </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">
|
| <h1>プロジェクトを探す</h1>
|
|
|
| <form method="GET" class="search-container">
|
| <input type="text" name="q" class="search-input" placeholder="プロジェクトを検索..." value="<?php echo htmlspecialchars($search_query); ?>">
|
| <button type="submit" class="search-button">検索</button>
|
| </form>
|
|
|
| <div class="sort-options">
|
| <a href="?q=<?php echo urlencode($search_query); ?>&sort=default" class="sort-option <?php echo $sort === 'default' ? 'active' : ''; ?>">関連順</a>
|
| <a href="?q=<?php echo urlencode($search_query); ?>&sort=stars" class="sort-option <?php echo $sort === 'stars' ? 'active' : ''; ?>">星の数</a>
|
| <a href="?q=<?php echo urlencode($search_query); ?>&sort=views" class="sort-option <?php echo $sort === 'views' ? 'active' : ''; ?>">閲覧数</a>
|
| <a href="?q=<?php echo urlencode($search_query); ?>&sort=custom" class="sort-option <?php echo $sort === 'custom' ? 'active' : ''; ?>">おすすめ</a>
|
| </div>
|
|
|
| <?php if (empty($projects)): ?>
|
| <p>プロジェクトが見つかりませんでした</p>
|
| <?php else: ?>
|
| <div class="projects-grid">
|
| <?php foreach ($projects as $project): ?>
|
| <a href="view.php?id=<?php echo $project['id']; ?>" class="project-card" target="_blank">
|
| <div class="project-thumbnail" data-drive-id="<?php echo htmlspecialchars($project['drive_id']); ?>">
|
| <div class="loading-thumbnail">...</div>
|
| <!-- 画像はJavaScriptで動的に挿入されます -->
|
| </div>
|
| <div class="project-info">
|
| <h3 class="project-title"><?php echo htmlspecialchars($project['project_name']); ?></h3>
|
| <p class="project-author">by <?php echo htmlspecialchars($project['author_name']); ?></p>
|
| <div class="project-stats">
|
| <span>★ <?php echo $project['stars']; ?></span>
|
| <span>👁️ <?php echo $project['views']; ?></span>
|
| </div>
|
| </div>
|
| </a>
|
| <?php endforeach; ?>
|
| </div>
|
| <?php endif; ?>
|
| </div>
|
|
|
| <script>
|
| document.addEventListener('DOMContentLoaded', function() {
|
| const accessToken = '<?php echo $_SESSION['access_token'] ?? ''; ?>';
|
| const thumbnailContainers = document.querySelectorAll('.project-thumbnail');
|
|
|
| thumbnailContainers.forEach(container => {
|
| const driveId = container.dataset.driveId;
|
| const loadingElement = container.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();
|
| container.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';
|
| }
|
| });
|
| });
|
| });
|
| </script>
|
| </body>
|
| </html> |