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