Files
Hell13Cat 2a103d246b 0.0.5s
2026-05-27 01:28:03 +03:00

192 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Post - LocalBooru</title>
<style>
:root {
--bg-main: #1e1e1e; --bg-header: #252525; --bg-panel: #2a2a2a; --bg-input: #333;
--text-main: #eee; --text-muted: #aaa; --border-color: #444; --link-color: #4da8da;
--color-g: #4caf50; --color-s: #ff9800; --color-q: #ffeb3b; --color-e: #f44336;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background-color: var(--bg-main); color: var(--text-main); font-family: sans-serif; font-size: 14px; }
header { display: flex; align-items: center; background-color: var(--bg-header); padding: 10px 20px; border-bottom: 1px solid var(--border-color); gap: 15px; }
.logo { font-weight: bold; font-size: 18px; color: white; text-decoration: none; }
.search-wrapper { flex-grow: 1; max-width: 400px; position: relative; }
.search-wrapper input { width: 100%; background-color: var(--bg-input); border: 1px solid var(--border-color); color: white; padding: 6px 10px; border-radius: 3px; outline: none; }
.autocomplete-list { position: absolute; top: 100%; left: 0; right: 0; background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 3px; margin-top: 4px; list-style: none; z-index: 1000; max-height: 250px; overflow-y: auto; box-shadow: 0 4px 10px rgba(0,0,0,0.5); display: none; }
.autocomplete-item { padding: 8px 10px; cursor: pointer; display: flex; justify-content: space-between; border-bottom: 1px solid #333; }
.autocomplete-item:last-child { border-bottom: none; }
.autocomplete-item:hover { background-color: var(--bg-input); }
.autocomplete-count { color: var(--text-muted); font-size: 12px; }
main { padding: 20px; max-width: 1600px; margin: 0 auto; display: grid; grid-template-columns: 300px 1fr; gap: 20px; align-items: start; }
.sidebar { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 4px; padding: 15px; display: flex; flex-direction: column; gap: 20px; }
.section-title { font-size: 16px; font-weight: bold; border-bottom: 1px solid var(--border-color); padding-bottom: 8px; margin-bottom: 10px; }
.stat-row { margin-bottom: 6px; word-break: break-all; line-height: 1.4;}
.stat-label { font-weight: bold; }
.tags-list { display: flex; flex-direction: column; gap: 6px; }
.tags-list a { color: var(--link-color); text-decoration: none; }
.tags-list a:hover { text-decoration: underline; }
.media-container { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 4px; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 400px; }
.media-container img, .media-container video { max-width: 100%; max-height: 85vh; object-fit: contain; border-radius: 2px; }
.loader { grid-column: 1/-1; text-align: center; padding: 50px; color: var(--text-muted); }
@media (max-width: 900px) { main { grid-template-columns: 1fr; } .media-container { order: -1; } header { flex-wrap: wrap; } .search-wrapper { min-width: 100%; order: 3; margin-top: 10px; } }
</style>
</head>
<body>
<header>
<a href="/" class="logo">LocalBooru</a>
<div class="search-wrapper">
<input type="text" id="searchInput" placeholder="Search tags...">
<ul id="autocompleteList" class="autocomplete-list"></ul>
</div>
</header>
<main id="mainContent">
<div class="loader">Загрузка поста...</div>
</main>
<script>
// Инициализация автодополнения
const searchInput = document.getElementById('searchInput');
const list = document.getElementById('autocompleteList');
let debounceTimer;
searchInput.addEventListener('input', () => {
clearTimeout(debounceTimer);
const words = searchInput.value.split(' ');
const currentWord = words[words.length - 1];
if (currentWord.length < 2) {
list.style.display = 'none';
return;
}
debounceTimer = setTimeout(async () => {
const req = { type: "autocomplete", q: currentWord };
try {
const response = await fetch('/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req)
});
if (!response.ok) throw new Error('Ошибка API автодополнения');
const data = await response.json();
if (!data || data.length === 0) {
list.style.display = 'none';
return;
}
list.innerHTML = data.map(item => `
<li class="autocomplete-item" data-tag="${item.tag}">
<span>${item.tag}</span><span class="autocomplete-count">${item.count}</span>
</li>
`).join('');
list.style.display = 'block';
list.querySelectorAll('.autocomplete-item').forEach(li => {
li.addEventListener('click', () => {
words[words.length - 1] = li.getAttribute('data-tag');
searchInput.value = words.join(' ') + ' ';
list.style.display = 'none';
searchInput.focus();
});
});
} catch (error) {
console.error("Автодополнение недоступно:", error);
}
}, 200);
});
// Скрытие списка автодополнения при клике вне него
document.addEventListener('click', (e) => {
if (e.target !== searchInput && e.target !== list) {
list.style.display = 'none';
}
});
// Переход на главную при поиске
searchInput.addEventListener('keypress', (e) => {
if(e.key === 'Enter') {
window.location.href = `index.html?query=${encodeURIComponent(searchInput.value.trim())}`;
}
});
// Загрузка данных поста
async function fetchPost() {
// Получаем ID из параметра ?id=... или из конца URL пути
let postId = new URLSearchParams(window.location.search).get('id') || window.location.pathname.split('/').pop();
if (!postId || postId.includes('.html')) {
document.getElementById('mainContent').innerHTML = `<div class="loader">ID поста не указан</div>`;
return;
}
const req = { type: "post", post_id: postId };
try {
const response = await fetch('/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req)
});
if (!response.ok) throw new Error('Ошибка загрузки данных с сервера');
const data = await response.json();
// Рендер медиа (предполагается, что оригиналы лежат в /img/)
let mediaHtml = '';
const mediaUrl = `/file/${data.local_filename}`;
if (data.is_video) {
mediaHtml = `<video controls autoplay loop src="${mediaUrl}"></video>`;
} else {
// И для изображений, и для GIF подойдет тег <img>
mediaHtml = `<img src="${mediaUrl}" alt="Post Media">`;
}
// Рендер тегов
const tagsHtml = data.tags.map(t => `<a href="/?query=${encodeURIComponent(t)}">${t}</a>`).join('');
// Рендер всей страницы
document.getElementById('mainContent').innerHTML = `
<aside class="sidebar">
<div class="statistics">
<div class="section-title">Statistics</div>
<div class="stat-row"><span class="stat-label">File:</span> ${data.local_filename}</div>
<div class="stat-row"><span class="stat-label">Date:</span> ${data.display_date[1]}</div>
<div class="stat-row"><span class="stat-label">Rating:</span> <span style="color:var(--color-${data.rating[0].toLowerCase()})">${data.rating}</span></div>
<div class="stat-row"><span class="stat-label">Score:</span> ${data.score}</div>
<div class="stat-row"><span class="stat-label">Resolution:</span> ${data.width}x${data.height}</div>
<div class="stat-row"><span class="stat-label">Source:</span> <a href="${data.source}" target="_blank" rel="noopener noreferrer" style="color: var(--link-color); text-decoration: none;">Link</a></div>
</div>
<div class="tags">
<div class="section-title">Tags</div>
<div class="tags-list">${tagsHtml}</div>
</div>
</aside>
<div class="media-container">
${mediaHtml}
</div>
`;
} catch (err) {
console.error(err);
document.getElementById('mainContent').innerHTML = `<div class="loader">Ошибка загрузки поста. Проверьте соединение с сервером.</div>`;
}
}
// Вызов функции при старте
fetchPost();
</script>
</body>
</html>