Файл: assets/app.js
Строк: 2474
<?php
let currentUploadType = 'post';
function openModal(type = 'post') {
currentUploadType = type;
const modal = document.getElementById('create-modal');
const container = document.getElementById('create-modal-container');
const headerTitle = document.getElementById('create-modal-title');
// Reset to Step 1
document.getElementById('create-step-select').style.display = 'flex';
document.getElementById('create-step-preview').style.display = 'none';
container.style.maxWidth = '600px';
container.style.width = '600px';
if (type === 'story') headerTitle.textContent = 'Создать историю';
else if (type === 'reel') headerTitle.textContent = 'Создать Reels';
else headerTitle.textContent = 'Создать публикацию';
// Set accepted types
const input = document.getElementById('file-input');
if (type === 'post') input.accept = 'image/*,video/*';
else if (type === 'story') input.accept = 'image/*,video/*';
else if (type === 'reel') input.accept = 'video/*';
modal.classList.add('active');
modal.style.display = 'flex';
}
function closeModal() {
const modal = document.getElementById('create-modal');
const container = document.getElementById('create-modal-container');
modal.classList.remove('active');
setTimeout(() => { modal.style.display = 'none'; }, 300);
// Reset form
document.getElementById('file-input').value = '';
document.getElementById('caption-input').value = '';
document.getElementById('image-preview').src = '';
document.getElementById('video-preview').src = '';
// Reset steps
document.getElementById('create-step-select').style.display = 'flex';
document.getElementById('create-step-preview').style.display = 'none';
container.style.maxWidth = '600px';
container.style.width = '600px';
}
// Profile Tabs Logic
// Profile Tabs Logic
function switchProfileTab(tabName, element) {
if (!element) return;
// Visual update
document.querySelectorAll('.profile-tab').forEach(el => {
el.classList.remove('active');
el.style.borderTop = 'none';
el.style.color = 'var(--text-secondary)';
const svg = el.querySelector('svg');
if(svg) svg.style.color = 'inherit';
});
element.classList.add('active');
element.style.borderTop = '1px solid white';
element.style.color = 'white';
const grid = document.querySelector('.profile-grid');
if (!grid) return;
// Show Loading
grid.innerHTML = '<div style="grid-column:1/-1; text-align:center; padding:40px; color:var(--text-secondary);">Завантаження...</div>';
const formData = new FormData();
formData.append('action', 'get_profile_posts');
if (typeof profileUserId !== 'undefined') {
formData.append('user_id', profileUserId);
} else {
// Fallback or read from URL if profileUserId is not defined (e.g. visiting own profile via menu?)
// Currently profile.php defines profileUserId.
}
formData.append('tab', tabName);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
grid.innerHTML = '';
if (data.success && data.posts.length > 0) {
grid.style.display = 'grid';
data.posts.forEach(post => {
const isVideo = post.media_type === 'video';
const el = document.createElement('div');
el.className = 'grid-post';
el.style.cssText = "aspect-ratio: 1; overflow: hidden; cursor: pointer; position: relative;";
el.onclick = () => openPostModal(post.id);
if (isVideo) {
el.innerHTML = `<video src="${post.image_url}" style="width: 100%; height: 100%; object-fit: cover;"></video>
<div style="position:absolute; top:10px; right:10px; color:white;">
<svg fill="currentColor" height="18" viewBox="0 0 24 24" width="18"><path d="M5.888 22.5a3.46 3.46 0 0 1-1.721-.46l-.003-.002a3.451 3.451 0 0 1-1.72-2.982V4.943a3.445 3.445 0 0 1 5.163-2.987l12.226 7.059a3.444 3.444 0 0 1-.001 5.967l-12.22 7.056a3.462 3.462 0 0 1-1.724.462Z"></path></svg>
</div>`;
} else {
el.innerHTML = `<img src="${post.image_url}" style="width: 100%; height: 100%; object-fit: cover;">`;
}
grid.appendChild(el);
});
} else {
// Emtpy State
grid.style.display = 'flex';
grid.style.justifyContent = 'center';
grid.style.padding = '60px 20px';
let icon = '📷';
let title = 'Постов пока нет';
let desc = 'Сделайте свой первый пост';
if (tabName === 'reels') {
icon = '🎬';
title = 'Видео пока нет';
desc = 'Поделитесь своими видео';
} else if (tabName === 'saved') {
icon = '🔖';
title = 'Сохраненные посты';
desc = 'Сохраняйте фотографии и видео, чтобы просмотреть их позже.';
}
grid.innerHTML = `
<div class="profile-placeholder" style="text-align:center; color: var(--text-secondary);">
<div style="font-size: 40px; margin-bottom:15px; border: 2px solid var(--text-secondary); border-radius: 50%; width: 70px; height: 70px; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px auto;">${icon}</div>
<div style="font-size: 20px; font-weight: 700; color: white; margin-bottom: 8px;">${title}</div>
<div style="font-size: 14px;">${desc}</div>
</div>
`;
}
})
.catch(err => {
grid.innerHTML = '<div style="grid-column:1/-1; text-align:center; padding:20px; color:red;">Ошибка загрузки</div>';
console.error(err);
});
}
// File Preview & Step Switch
document.getElementById('file-input').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const imgPreview = document.getElementById('image-preview');
const vidPreview = document.getElementById('video-preview');
const container = document.getElementById('create-modal-container');
const reader = new FileReader();
reader.onload = function(e) {
if (file.type.startsWith('video/')) {
vidPreview.src = e.target.result;
vidPreview.style.display = 'block';
imgPreview.style.display = 'none';
} else {
imgPreview.src = e.target.result;
imgPreview.style.display = 'block';
vidPreview.style.display = 'none';
}
// Switch steps
document.getElementById('create-step-select').style.display = 'none';
document.getElementById('create-step-preview').style.display = 'flex';
container.style.maxWidth = '900px';
container.style.width = 'calc(100% - 40px)';
document.getElementById('create-modal-title').textContent = 'Создать новый пост';
}
reader.readAsDataURL(file);
}
});
// Upload Post
function uploadPost() {
const fileInput = document.getElementById('file-input');
const captionInput = document.getElementById('caption-input');
if (fileInput.files.length === 0) {
showToast('Пожалуйста, выберите файл', 'error');
return;
}
const formData = new FormData();
formData.append('action', 'upload');
formData.append('image', fileInput.files[0]);
formData.append('caption', captionInput.value);
formData.append('type', (typeof currentUploadType !== 'undefined') ? currentUploadType : 'post');
const btn = document.querySelector('.create-sidebar .btn-primary');
const originalText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Публикация...';
fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) throw new Error('Ошибка сервера');
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (err) {
console.error('API Error:', text);
throw new Error('Неверный ответ сервера (JSON Error)');
}
});
})
.then(data => {
if (data.success) {
showToast('Опубликовано!', 'success');
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
showToast(data.error || 'Ошибка', 'error');
btn.disabled = false;
btn.textContent = originalText;
}
})
.catch(err => {
console.error(err);
showToast(err.message, 'error');
btn.disabled = false;
btn.textContent = originalText;
});
}
// Like Post
function toggleLike(postId) {
const formData = new FormData();
formData.append('action', 'like');
formData.append('post_id', postId);
fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update Feed Cards
const cards = document.querySelectorAll(`.post-card[data-id="${postId}"], .reel-item[data-id="${postId}"]`);
cards.forEach(card => {
const btn = card.querySelector('.like-btn, .reel-action-btn');
const countSpan = card.querySelector('.like-count, .reel-action-btn span');
if (data.liked) {
btn.classList.add('liked');
const svg = btn.querySelector('svg');
if(svg) svg.setAttribute('stroke-width', '0');
} else {
btn.classList.remove('liked');
const svg = btn.querySelector('svg');
if(svg) svg.setAttribute('stroke-width', '2');
}
if(countSpan) countSpan.textContent = data.count;
});
// Update Detail Modal if open
if (window.currentPostId == postId) {
const modalLikeBtn = document.getElementById('detail-like-btn');
const modalLikeCount = document.getElementById('detail-likes-count');
if (modalLikeBtn) {
if (data.liked) {
modalLikeBtn.classList.add('liked');
modalLikeBtn.innerHTML = `<svg fill="#ed4956" height="24" viewBox="0 0 48 48" width="24"><path d="M34.6 3.1c-4.5 0-7.9 1.8-10.6 5.6-2.7-3.7-6.1-5.5-10.6-5.5C6 3.1 0 9.6 0 17.6c0 7.3 5.4 12 10.6 16.5.6.5 1.3 1.1 1.9 1.7l11.5 10.6c.2.2.6.3.9.3.3 0 .6-.1.9-.3l11.5-10.6c.6-.6 1.3-1.2 1.9-1.7 5.2-4.5 10.6-9.2 10.6-16.5 0-8-6-14.5-13.4-14.5z"></path></svg>`;
} else {
modalLikeBtn.classList.remove('liked');
modalLikeBtn.innerHTML = `<svg fill="none" height="24" viewBox="0 0 24 24" width="24" stroke="currentColor" stroke-width="2"><path d="M16.792 3.904A4.989 4.989 0 0 1 21.5 9.122c0 3.072-2.652 4.959-5.197 7.222-2.512 2.243-3.865 3.469-4.303 3.752-.477-.309-2.143-1.823-4.303-3.752C5.141 14.072 2.5 12.167 2.5 9.122a4.989 4.989 0 0 1 4.708-5.218 4.21 4.21 0 0 1 3.675 1.941c.84 1.175.98 1.763 1.12 1.763s.278-.588 1.11-1.766a4.17 4.17 0 0 1 3.679-1.938m0-2a6.04 6.04 0 0 0-4.797 2.127 6.052 6.052 0 0 0-4.787-2.127A6.985 6.985 0 0 0 .5 9.122c0 3.61 2.55 5.827 5.015 7.97.283.246.569.494.853.747l1.027.918a44.998 44.998 0 0 0 3.518 3.018 2 2 0 0 0 2.174 0 45.263 45.263 0 0 0 3.626-3.115l.922-.824c.293-.26.59-.519.885-.774 2.334-2.025 4.98-4.32 4.98-7.94a6.985 6.985 0 0 0-6.708-7.218Z"></path></svg>`;
}
}
if (modalLikeCount) modalLikeCount.textContent = data.count;
}
}
});
}
// Update Profile
function updateProfile(e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
formData.append('action', 'update_profile');
fetch('api.php', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if(data.success) {
showToast('Профиль обновлен');
setTimeout(() => window.location.href='?view=profile', 500);
} else {
showToast('Ошибка');
}
});
}
function previewSettingsAvatar(input) {
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('settings-avatar-preview').src = e.target.result;
}
reader.readAsDataURL(input.files[0]);
}
}
// Post Options (Instagram style)
function openPostOptions(postId, postUserId) {
const existing = document.getElementById('options-modal');
if (existing) existing.remove();
const isMyPost = postUserId == window.currentUserId;
const modal = document.createElement('div');
modal.id = 'options-modal';
modal.className = 'modal-overlay';
modal.style.display = 'flex';
modal.style.zIndex = '2000';
let buttonsHtml = '';
if (isMyPost) {
buttonsHtml = `
<button class="options-btn destructive" onclick="deletePost(${postId})">Удалить</button>
<button class="options-btn" onclick="editPostUI(${postId}, '')">Редактировать</button>
<button class="options-btn">Скрыть количество лайков</button>
<button class="options-btn">Отключить комментарии</button>
`;
} else {
buttonsHtml = `
<button class="options-btn destructive" onclick="showToast('Жалоба отправлена')">Жалоба</button>
<button class="options-btn destructive" onclick="toggleFollow(${postUserId}, true)">Отписаться</button>
<button class="options-btn" onclick="showToast('Добавлено в избранное')">Добавить в избранное</button>
`;
}
buttonsHtml += `
<button class="options-btn" onclick="window.location='index.php?view=post&id=${postId}'">Перейти к публикации</button>
<button class="options-btn" onclick="showToast('Функция обмена появится в ближайшее время')">Поделиться в...</button>
<button class="options-btn" onclick="copyToClipboard('${window.location.origin}/index.php?view=post&id=${postId}')">Скопируйте ссылку</button>
<button class="options-btn" onclick="showToast('Скопировано')">Вставить</button>
<button class="options-btn">Об этом аккаунте</button>
<button class="options-btn" onclick="document.getElementById('options-modal').remove()">Отмена</button>
`;
modal.innerHTML = `
<div class="options-modal-content">
${buttonsHtml}
</div>
`;
document.body.appendChild(modal);
modal.addEventListener('click', function(e) {
if (e.target === modal) modal.remove();
});
}
function toggleFollow(userId, fromMenu = false) {
const formData = new FormData();
formData.append('action', 'follow');
formData.append('user_id', userId);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.success) {
showToast(data.following ? 'Вы подписались' : 'Вы отписались');
if(fromMenu) {
const modal = document.getElementById('options-modal');
if(modal) modal.remove();
}
// Optionally update UI on profile page if needed
setTimeout(() => window.location.reload(), 500);
}
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showToast('Ссылка скопирована');
const modal = document.getElementById('options-modal');
if(modal) modal.remove();
});
}
function editPostUI(id, currentCaption) {
document.getElementById('options-modal').remove();
const newCaption = prompt('Изменить подпись:', currentCaption);
if(newCaption !== null && newCaption !== currentCaption) {
const formData = new FormData();
formData.append('action', 'edit_post');
formData.append('post_id', id);
formData.append('caption', newCaption);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.success) {
showToast('Изменения сохранены.');
setTimeout(() => window.location.reload(), 500);
}
});
}
}
function deletePost(id) {
if(confirm('Вы уверены, что хотите удалить этот пост?')) {
const formData = new FormData();
formData.append('action', 'delete_post');
formData.append('post_id', id);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.success) {
showToast('Сообщение было удалено');
setTimeout(() => window.location.reload(), 500);
}
});
}
}
// Story Viewer Logic
let currentStoryIndex = 0;
let storyTimer = null;
let storyDuration = 5000; // 5 seconds default
function openStoryViewer(index) {
if (!window.storiesData || !window.storiesData[index]) return;
currentStoryIndex = index;
// Create UI if not exists
let overlay = document.getElementById('story-viewer-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'story-viewer-overlay';
overlay.className = 'story-viewer-overlay';
overlay.innerHTML = `
<div class="story-container-frame">
<div class="story-progress-container">
<div class="story-progress-bar"><div class="progress-fill"></div></div>
</div>
<div class="story-header-info">
<img id="story-user-avatar" src="">
<span id="story-user-name"></span>
<span id="story-time"></span>
<button class="close-story-btn" onclick="closeStoryViewer()">✕</button>
</div>
<div class="story-nav-area left" onclick="prevStory()"></div>
<div class="story-nav-area right" onclick="nextStory()"></div>
<div id="story-media-container"></div>
</div>
`;
document.body.appendChild(overlay);
}
overlay.classList.add('active');
showStory(currentStoryIndex);
}
function showStory(index) {
if (index < 0 || index >= window.storiesData.length) {
closeStoryViewer();
return;
}
currentStoryIndex = index;
const story = window.storiesData[index];
const overlay = document.getElementById('story-viewer-overlay');
// Update Header
document.getElementById('story-user-avatar').src = story.avatar || 'assets/default_avatar.svg';
document.getElementById('story-user-name').textContent = story.username;
// Media
const container = document.getElementById('story-media-container');
container.innerHTML = '';
const ext = story.media_url.split('.').pop().toLowerCase();
let mediaEl;
if (['mp4', 'mov', 'webm'].includes(ext)) {
mediaEl = document.createElement('video');
mediaEl.src = story.media_url;
mediaEl.autoplay = true;
mediaEl.muted = false; // Stories usually have sound on
mediaEl.playsInline = true;
// Auto advance when video ends
mediaEl.onended = () => nextStory();
storyDuration = 0; // Video controls duration
} else {
mediaEl = document.createElement('img');
mediaEl.src = story.media_url;
storyDuration = 5000;
}
mediaEl.className = 'story-media-content';
container.appendChild(mediaEl);
// Progress Bar Reset & Start
const fill = overlay.querySelector('.progress-fill');
fill.style.transition = 'none';
fill.style.width = '0%';
// Force reflow
void fill.offsetWidth;
if (storyDuration > 0) {
fill.style.transition = `width ${storyDuration}ms linear`;
fill.style.width = '100%';
if (storyTimer) clearTimeout(storyTimer);
storyTimer = setTimeout(nextStory, storyDuration);
} else {
// For video, we can animate progress bar based on timeupdate if we wanted,
// to keep it simple, we just rely on 'onended' for navigation and don't animate bar perfectly or fill it fully.
// Let's just fill it to 100% over a rough estimate or simply rely on video ending.
fill.style.width = '100%'; // Static full or handle timeupdate
}
}
function nextStory() {
if (currentStoryIndex < window.storiesData.length - 1) {
showStory(currentStoryIndex + 1);
} else {
closeStoryViewer();
}
}
function prevStory() {
if (currentStoryIndex > 0) {
showStory(currentStoryIndex - 1);
}
}
function closeStoryViewer() {
const overlay = document.getElementById('story-viewer-overlay');
if (overlay) overlay.classList.remove('active');
const container = document.getElementById('story-media-container');
if (container) container.innerHTML = ''; // Stop video
if (storyTimer) clearTimeout(storyTimer);
}
function showToast(message, type = 'success') {
let container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
container.style.cssText = "position:fixed;bottom:70px;left:50%;transform:translateX(-50%);z-index:999;";
document.body.appendChild(container);
}
const toast = document.createElement('div');
toast.style.background = type === 'success' ? '#4CAF50' : '#f44336';
toast.style.color = 'white';
toast.style.padding = '12px 24px';
toast.style.borderRadius = '24px';
toast.style.marginBottom = '10px';
toast.style.boxShadow = '0 3px 10px rgba(0,0,0,0.2)';
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
toast.textContent = message;
container.appendChild(toast);
// Animate in
setTimeout(() => toast.style.opacity = '1', 10);
// Remove
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Toggle "More" Menu
function toggleMoreMenu(e) {
e.stopPropagation();
const menu = document.getElementById('more-menu-popover');
if (menu) {
// Toggle display logic
if (menu.style.display === 'block') {
menu.style.display = 'none';
} else {
menu.style.display = 'block';
}
}
}
// Close more menu when clicking outside
document.addEventListener('click', function(e) {
if (e.target.id === 'create-modal') {
closeModal();
}
// Close More Menu if clicked outside
const menu = document.getElementById('more-menu-popover');
const moreBtn = document.querySelector('.nav-row[onclick="toggleMoreMenu(event)"]');
if (menu && menu.style.display === 'block') {
if (!menu.contains(e.target) && (!moreBtn || !moreBtn.contains(e.target))) {
menu.style.display = 'none';
}
}
});
// Toggle Notifications Panel
function toggleNotifications() {
const panel = document.getElementById('notifications-panel');
const searchPanel = document.getElementById('search-panel');
const notifBtn = document.querySelector('.nav-row[onclick="toggleNotifications()"]');
const searchBtn = document.querySelector('.nav-row[onclick="toggleSearch()"]');
// Close search if open
if (searchPanel) {
searchPanel.classList.remove('active');
if (searchBtn) searchBtn.classList.remove('panel-active');
}
const panelActive = panel.classList.contains('active');
if (panelActive) {
panel.classList.remove('active');
if (notifBtn) notifBtn.classList.remove('panel-active');
document.body.classList.remove('panel-open');
} else {
panel.classList.add('active');
if (notifBtn) notifBtn.classList.add('panel-active');
document.body.classList.add('panel-open');
}
}
// Toggle Search Panel
function toggleSearch() {
const panel = document.getElementById('search-panel');
const notifPanel = document.getElementById('notifications-panel');
const notifBtn = document.querySelector('.nav-row[onclick="toggleNotifications()"]');
const searchBtn = document.querySelector('.nav-row[onclick="toggleSearch()"]');
// Close notifications if open
if (notifPanel) {
notifPanel.classList.remove('active');
if (notifBtn) notifBtn.classList.remove('panel-active');
}
const panelActive = panel.classList.contains('active');
if (panelActive) {
panel.classList.remove('active');
if (searchBtn) searchBtn.classList.remove('panel-active');
document.body.classList.remove('panel-open');
} else {
panel.classList.add('active');
if (searchBtn) searchBtn.classList.add('panel-active');
document.body.classList.add('panel-open');
// Auto focus search input
setTimeout(() => {
const input = document.getElementById('sidebar-search-input');
if (input) input.focus();
}, 300);
}
}
function handleSidebarSearch(query) {
const clearBtn = document.getElementById('search-clear-btn');
const resultsList = document.getElementById('search-results-list');
const recentSection = document.getElementById('search-recent-section');
if (query.length > 0) {
clearBtn.style.display = 'flex';
// Mock search results
resultsList.innerHTML = `<div style="padding:16px; color:var(--text-secondary); text-align:center;">Поиск "${query}"...</div>`;
// In a real app, perform fetch('api.php?action=search&q='+query) here
} else {
clearBtn.style.display = 'none';
resultsList.innerHTML = `<div style="padding: 20px; text-align: center; color: var(--text-secondary); font-size: 14px;">Нет недавних поисков.</div>`;
}
}
function clearSidebarSearch() {
const input = document.getElementById('sidebar-search-input');
input.value = '';
handleSidebarSearch('');
input.focus();
}
// Close panels when clicking outside
document.addEventListener('click', function(e) {
const notifPanel = document.getElementById('notifications-panel');
const searchPanel = document.getElementById('search-panel');
const notifBtn = document.querySelector('.nav-row[onclick="toggleNotifications()"]');
const searchBtn = document.querySelector('.nav-row[onclick="toggleSearch()"]');
// Close notifications panel
if (notifPanel && notifPanel.classList.contains('active')) {
if (!notifPanel.contains(e.target) && (!notifBtn || !notifBtn.contains(e.target))) {
notifPanel.classList.remove('active');
document.body.classList.remove('panel-open');
}
}
// Close search panel
if (searchPanel && searchPanel.classList.contains('active')) {
if (!searchPanel.contains(e.target) && (!searchBtn || !searchBtn.contains(e.target))) {
searchPanel.classList.remove('active');
document.body.classList.remove('panel-open');
}
}
});
// Post Detail Modal (Comments & Zoom)
function openPostModal(postId) {
// 1. Create Modal UI if not exists
let modal = document.getElementById('post-detail-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'post-detail-modal';
modal.className = 'modal-overlay';
modal.style.zIndex = '1100';
modal.innerHTML = `
<button class="post-detail-close" onclick="closePostModal()">✕</button>
<div class="post-detail-card" onclick="event.stopPropagation()">
<div class="post-detail-media" id="detail-media-container"></div>
<div class="post-detail-sidebar">
<!-- HEADER -->
<div class="detail-header">
<img id="detail-avatar" class="user-avatar" src="">
<div class="detail-header-info">
<span id="detail-username" class="username"></span>
<span class="detail-location">USERNAME</span>
</div>
<button class="detail-options-btn" id="detail-options-trigger">
<svg aria-label="Більше" fill="currentColor" height="24" role="img" viewBox="0 0 24 24" width="24"><circle cx="12" cy="12" r="1.5"></circle><circle cx="6" cy="12" r="1.5"></circle><circle cx="18" cy="12" r="1.5"></circle></svg>
</button>
</div>
<!-- SCROLL AREA: Caption + Comments -->
<div class="detail-scroll-area" id="detail-scroll-area">
<div id="detail-caption-container"></div>
<div id="detail-comments-list"></div>
</div>
<!-- FOOTER -->
<div class="detail-footer">
<div class="detail-actions-row">
<div class="left-actions">
<span id="detail-like-btn">
<svg aria-label="Мне это нравится" class="like-icon-svg" fill="none" height="24" viewBox="0 0 24 24" width="24" stroke="currentColor" stroke-width="2"><path d="M16.792 3.904A4.989 4.989 0 0 1 21.5 9.122c0 3.072-2.652 4.959-5.197 7.222-2.512 2.243-3.865 3.469-4.303 3.752-.477-.309-2.143-1.823-4.303-3.752C5.141 14.072 2.5 12.167 2.5 9.122a4.989 4.989 0 0 1 4.708-5.218 4.21 4.21 0 0 1 3.675 1.941c.84 1.175.98 1.763 1.12 1.763s.278-.588 1.11-1.766a4.17 4.17 0 0 1 3.679-1.938m0-2a6.04 6.04 0 0 0-4.797 2.127 6.052 6.052 0 0 0-4.787-2.127A6.985 6.985 0 0 0 .5 9.122c0 3.61 2.55 5.827 5.015 7.97.283.246.569.494.853.747l1.027.918a44.998 44.998 0 0 0 3.518 3.018 2 2 0 0 0 2.174 0 45.263 45.263 0 0 0 3.626-3.115l.922-.824c.293-.26.59-.519.885-.774 2.334-2.025 4.98-4.32 4.98-7.94a6.985 6.985 0 0 0-6.708-7.218Z"></path></svg>
</span>
<svg aria-label="Комментарий" fill="currentColor" height="24" viewBox="0 0 24 24" width="24"><path d="M20.656 17.008a9.993 9.993 0 1 0-3.59 3.615L22 22Z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="2"></path></svg>
<svg aria-label="Поделиться" fill="currentColor" height="24" viewBox="0 0 24 24" width="24"><line fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="2" x1="22" x2="9.218" y1="3" y2="10.083"></line><polygon fill="none" points="11.698 20.334 22 3.001 2 3.001 9.218 10.084 11.698 20.334" stroke="currentColor" stroke-linejoin="round" stroke-width="2"></polygon></svg>
</div>
<div class="right-actions">
<button id="detail-save-btn" class="action-btn" style="background:none; border:none; color:white; padding:0; cursor:pointer;">
<svg aria-label="Cохранить" fill="none" height="24" viewBox="0 0 24 24" width="24"><polygon fill="none" points="20 21 12 13.44 4 21 4 3 20 3 20 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polygon></svg>
</button>
</div>
</div>
<div class="detail-likes">Нравится: <span id="detail-likes-count">0</span></div>
<div class="detail-time" id="detail-time-ago">1 ЧАС НАЗАД</div>
<div class="detail-input-area" style="position:relative;">
<div id="emoji-picker" class="emoji-picker-panel">
<div class="emoji-section-title">Самый популярный</div>
<div class="emoji-grid">
<span class="emoji-item" onclick="insertEmoji('😂')">😂</span>
<span class="emoji-item" onclick="insertEmoji('😲')">😲</span>
<span class="emoji-item" onclick="insertEmoji('😍')">😍</span>
<span class="emoji-item" onclick="insertEmoji('😢')">😢</span>
<span class="emoji-item" onclick="insertEmoji('👏')">👏</span>
<span class="emoji-item" onclick="insertEmoji('🔥')">🔥</span>
<span class="emoji-item" onclick="insertEmoji('🎉')">🎉</span>
<span class="emoji-item" onclick="insertEmoji('💯')">💯</span>
<span class="emoji-item" onclick="insertEmoji('❤️')">❤️</span>
<span class="emoji-item" onclick="insertEmoji('🤣')">🤣</span>
<span class="emoji-item" onclick="insertEmoji('🥰')">🥰</span>
<span class="emoji-item" onclick="insertEmoji('😘')">😘</span>
<span class="emoji-item" onclick="insertEmoji('😭')">😭</span>
<span class="emoji-item" onclick="insertEmoji('😊')">😊</span>
</div>
<div class="emoji-section-title" style="margin-top:15px;">Занятие</div>
<div class="emoji-grid">
<span class="emoji-item" onclick="insertEmoji('🧘')">🧘</span>
<span class="emoji-item" onclick="insertEmoji('🧗')">🧗</span>
<span class="emoji-item" onclick="insertEmoji('🚵')">🚵</span>
<span class="emoji-item" onclick="insertEmoji('🚴')">🚴</span>
<span class="emoji-item" onclick="insertEmoji('🏇')">🏇</span>
<span class="emoji-item" onclick="insertEmoji('🏂')">🏂</span>
<span class="emoji-item" onclick="insertEmoji('🏄')">🏄</span>
<span class="emoji-item" onclick="insertEmoji('🚣')">🚣</span>
<span class="emoji-item" onclick="insertEmoji('🏊')">🏊</span>
<span class="emoji-item" onclick="insertEmoji('🤽')">🤽</span>
<span class="emoji-item" onclick="insertEmoji('⛹️')">⛹️</span>
<span class="emoji-item" onclick="insertEmoji('🏋️')">🏋️</span>
</div>
</div>
<svg aria-label="Смайл" fill="currentColor" height="24" viewBox="0 0 24 24" width="24" style="cursor:pointer;" onclick="toggleEmojiPicker()"><path d="M15.83 10.997a1.001 1.001 0 1 0 1.002 1.003 1.001 1.001 0 0 0-1.002-1.003ZM12 2a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2Zm0 18a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8ZM8.17 10.997a1.001 1.001 0 1 0 1.002 1.003 1.001 1.001 0 0 0-1.002-1.003Zm-1.838 5.71a.998.998 0 0 0 1.332 1.503 5.968 5.968 0 0 1 8.672 0 .999.999 0 0 0 1.332-1.5 7.965 7.965 0 0 0-11.335-.003Z"></path></svg>
<input type="text" id="detail-comment-input" placeholder="Добавить комментарий...">
<button id="detail-post-btn">Опубликовать</button>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
modal.addEventListener('click', (e) => {
if (e.target === modal) closePostModal();
});
}
currentPostId = postId;
// Update the dynamic parts for this postId
const likeBtn = document.getElementById('detail-like-btn');
const postBtn = document.getElementById('detail-post-btn');
if (likeBtn) likeBtn.onclick = () => toggleLike(postId);
if (postBtn) postBtn.onclick = () => postComment(postId);
// 2. Fetch Data
modal.classList.add('active');
modal.style.display = 'flex';
document.getElementById('detail-media-container').innerHTML = ''; // Clear prev media
document.getElementById('detail-caption-container').innerHTML = '';
document.getElementById('detail-comments-list').innerHTML = '';
const postData = new FormData();
postData.append('action', 'get_post_details');
postData.append('post_id', postId);
// Fetch Post Details
fetch('api.php', { method: 'POST', body: postData })
.then(res => res.json())
.then(data => {
if (data.success) {
const p = data.post;
const mediaContainer = document.getElementById('detail-media-container');
// Media
if (p.media_type === 'video') {
mediaContainer.innerHTML = `<video src="${p.image_url}" controls autoplay muted loop playsinline></video>`;
} else {
mediaContainer.innerHTML = `<img src="${p.image_url}">`;
}
// Header Info
document.getElementById('detail-avatar').src = p.avatar || 'assets/default_avatar.svg';
document.getElementById('detail-username').textContent = p.username;
document.getElementById('detail-likes-count').textContent = p.like_count;
document.getElementById('detail-options-trigger').onclick = () => openPostOptions(postId, p.user_id);
// Setup Caption as "First Comment"
const captionHtml = `
<div class="comment-row">
<img src="${p.avatar || 'assets/default_avatar.svg'}" class="comment-avatar">
<div class="comment-row-content">
<div class="comment-text-block">
<span class="comment-username">${p.username}</span>
<span class="comment-text">${p.caption}</span>
</div>
<div class="comment-meta">
<span>15 т.</span>
</div>
</div>
</div>
`;
document.getElementById('detail-caption-container').innerHTML = captionHtml;
// Time (Rough approximation or from DB if added)
document.getElementById('detail-time-ago').textContent = '1 ЧАС НАЗАД'; // Mock for now or calculate in PHP
// Save Button State
const saveBtn = document.getElementById('detail-save-btn');
if (saveBtn) {
saveBtn.onclick = () => savePost(postId);
const svg = saveBtn.querySelector('svg');
if (p.is_saved > 0) {
svg.innerHTML = '<path d="M20 21V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v16l8-4.572L20 21Z"></path>';
svg.setAttribute('fill', 'currentColor');
} else {
svg.innerHTML = '<polygon fill="none" points="20 21 12 13.44 4 21 4 3 20 3 20 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polygon>';
svg.setAttribute('fill', 'none');
}
}
}
});
// Fetch Comments
const commentData = new FormData();
commentData.append('action', 'get_comments');
commentData.append('post_id', postId);
fetch('api.php', { method: 'POST', body: commentData })
.then(res => res.json())
.then(data => {
if (data.success) {
const list = document.getElementById('detail-comments-list');
list.innerHTML = '';
data.comments.forEach(c => {
list.innerHTML += `
<div class="comment-row">
<img src="${c.avatar || 'assets/default_avatar.svg'}" class="comment-avatar">
<div class="comment-row-content">
<div class="comment-text-block">
<span class="comment-username">${c.username}</span>
<span class="comment-text">${c.comment_text}</span>
</div>
<div class="comment-meta">
<span>${c.time_ago}</span>
<span style="font-weight:600; color:var(--text-secondary);">Ответить</span>
</div>
</div>
<svg class="comment-like-icon" fill="none" height="12" viewBox="0 0 24 24" width="12" stroke="currentColor" stroke-width="2"><path d="M16.792 3.904A4.989 4.989 0 0 1 21.5 9.122c0 3.072-2.652 4.959-5.197 7.222-2.512 2.243-3.865 3.469-4.303 3.752-.477-.309-2.143-1.823-4.303-3.752C5.141 14.072 2.5 12.167 2.5 9.122a4.989 4.989 0 0 1 4.708-5.218 4.21 4.21 0 0 1 3.675 1.941c.84 1.175.98 1.763 1.12 1.763s.278-.588 1.11-1.766a4.17 4.17 0 0 1 3.679-1.938m0-2a6.04 6.04 0 0 0-4.797 2.127 6.052 6.052 0 0 0-4.787-2.127A6.985 6.985 0 0 0 .5 9.122c0 3.61 2.55 5.827 5.015 7.97.283.246.569.494.853.747l1.027.918a44.998 44.998 0 0 0 3.518 3.018 2 2 0 0 0 2.174 0 45.263 45.263 0 0 0 3.626-3.115l.922-.824c.293-.26.59-.519.885-.774 2.334-2.025 4.98-4.32 4.98-7.94a6.985 6.985 0 0 0-6.708-7.218Z"></path></svg>
</div>
`;
});
}
});
}
function closePostModal() {
const modal = document.getElementById('post-detail-modal');
if (modal) {
modal.classList.remove('active');
modal.style.display = 'none';
document.getElementById('detail-media-container').innerHTML = ''; // Stop video
}
}
function postComment(postId) {
const input = document.getElementById('detail-comment-input');
const text = input.value;
if (!text.trim()) return;
const formData = new FormData();
formData.append('action', 'add_comment');
formData.append('post_id', postId);
formData.append('text', text);
const btn = document.getElementById('detail-post-btn');
btn.disabled = true;
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
btn.disabled = false;
if (data.success) {
input.value = '';
const c = data.comment;
const list = document.getElementById('detail-comments-list');
// Remove 'no comments' if exists
// if (list.textContent.includes('Пока нет комментариев')) list.innerHTML = '';
const html = `
<div class="comment-row">
<img src="${c.avatar || 'assets/default_avatar.svg'}" class="comment-avatar">
<div class="comment-row-content">
<div class="comment-text-block">
<span class="comment-username">${c.username}</span>
<span class="comment-text">${c.comment_text}</span>
</div>
<div class="comment-meta">
<span>Just now</span>
<span style="font-weight:600; cursor:pointer;">Ответить</span>
</div>
</div>
<svg class="comment-like-icon" fill="none" height="12" viewBox="0 0 24 24" width="12" stroke="currentColor" stroke-width="2"><path d="M16.792 3.904A4.989 4.989 0 0 1 21.5 9.122c0 3.072-2.652 4.959-5.197 7.222-2.512 2.243-3.865 3.469-4.303 3.752-.477-.309-2.143-1.823-4.303-3.752C5.141 14.072 2.5 12.167 2.5 9.122a4.989 4.989 0 0 1 4.708-5.218 4.21 4.21 0 0 1 3.675 1.941c.84 1.175.98 1.763 1.12 1.763s.278-.588 1.11-1.766a4.17 4.17 0 0 1 3.679-1.938m0-2a6.04 6.04 0 0 0-4.797 2.127 6.052 6.052 0 0 0-4.787-2.127A6.985 6.985 0 0 0 .5 9.122c0 3.61 2.55 5.827 5.015 7.97.283.246.569.494.853.747l1.027.918a44.998 44.998 0 0 0 3.518 3.018 2 2 0 0 0 2.174 0 45.263 45.263 0 0 0 3.626-3.115l.922-.824c.293-.26.59-.519.885-.774 2.334-2.025 4.98-4.32 4.98-7.94a6.985 6.985 0 0 0-6.708-7.218Z"></path></svg>
</div>
`;
list.insertAdjacentHTML('beforeend', html);
// Auto scroll to bottom
const scrollArea = document.getElementById('detail-scroll-area');
if(scrollArea) scrollArea.scrollTop = scrollArea.scrollHeight;
} else {
showToast(data.error || 'Ajax Error');
}
});
}
function postCardComment(postId) {
console.log('postCardComment called with postId:', postId);
const card = document.querySelector(`.post-card[data-id="${postId}"]`);
console.log('Found card:', card);
if (!card) {
console.error('Card not found for postId:', postId);
return;
}
const input = card.querySelector('.card-comment-input');
const btn = card.querySelector('.card-post-btn');
const text = input.value.trim();
console.log('Comment text:', text);
if (!text) {
console.warn('Empty comment text');
return;
}
btn.disabled = true;
const formData = new FormData();
formData.append('action', 'add_comment');
formData.append('post_id', postId);
formData.append('text', text);
console.log('Sending comment to API...');
fetch('api.php', { method: 'POST', body: formData })
.then(res => {
console.log('API response status:', res.status);
return res.json();
})
.then(data => {
console.log('API response data:', data);
btn.disabled = false;
if (data.success) {
input.value = '';
showToast('Комментарий добавлен!');
console.log('Comment added successfully');
// Add comment to DOM
const comment = data.comment;
let previewSection = card.querySelector('.post-comments-preview');
// Create preview section if it doesn't exist
if (!previewSection) {
previewSection = document.createElement('div');
previewSection.className = 'post-comments-preview';
previewSection.style.padding = '4px 4px 8px';
// Insert before the input area
const inputArea = card.querySelector('.post-comment-input-area');
inputArea.parentNode.insertBefore(previewSection, inputArea);
}
// Create new comment element
const commentDiv = document.createElement('div');
commentDiv.className = 'comment-preview';
commentDiv.style.marginBottom = '8px';
commentDiv.style.display = 'flex';
commentDiv.style.justifyContent = 'space-between';
commentDiv.style.alignItems = 'start';
commentDiv.setAttribute('data-comment-id', comment.id);
commentDiv.innerHTML = `
<div style="flex: 1;">
<span class="username" style="font-weight: 600; margin-right: 6px;">${comment.username}</span>
<span class="comment-text" style="font-size: 14px;">${comment.comment_text}</span>
<div class="comment-actions" style="margin-top: 4px; display: flex; gap: 12px;">
<button class="comment-action-btn" onclick="replyToComment('${comment.username}', ${postId})" style="background: none; border: none; color: var(--text-secondary); font-size: 12px; cursor: pointer; padding: 0;">
Ответить
</button>
<button class="comment-action-btn" onclick="deleteComment(${comment.id}, ${postId})" style="background: none; border: none; color: var(--text-secondary); font-size: 12px; cursor: pointer; padding: 0;">
Удалить
</button>
</div>
</div>
<button class="comment-like-btn" onclick="likeComment(${comment.id})" style="background: none; border: none; cursor: pointer; padding: 4px; margin-left: 8px;">
<svg fill="none" height="12" viewBox="0 0 24 24" width="12" stroke="currentColor" stroke-width="2">
<path d="M16.792 3.904A4.989 4.989 0 0 1 21.5 9.122c0 3.072-2.652 4.959-5.197 7.222-2.512 2.243-3.865 3.469-4.303 3.752-.477-.309-2.143-1.823-4.303-3.752C5.141 14.072 2.5 12.167 2.5 9.122a4.989 4.989 0 0 1 4.708-5.218 4.21 4.21 0 0 1 3.675 1.941c.84 1.175.98 1.763 1.12 1.763s.278-.588 1.11-1.766a4.17 4.17 0 0 1 3.679-1.938m0-2a6.04 6.04 0 0 0-4.797 2.127 6.052 6.052 0 0 0-4.787-2.127A6.985 6.985 0 0 0 .5 9.122c0 3.61 2.55 5.827 5.015 7.97.283.246.569.494.853.747l1.027.918a44.998 44.998 0 0 0 3.518 3.018 2 2 0 0 0 2.174 0 45.263 45.263 0 0 0 3.626-3.115l.922-.824c.293-.26.59-.519.885-.774 2.334-2.025 4.98-4.32 4.98-7.94a6.985 6.985 0 0 0-6.708-7.218Z"></path>
</svg>
</button>
`;
// Add to top of preview section
previewSection.insertBefore(commentDiv, previewSection.firstChild);
// Limit to 3 comments shown
const commentPreviews = previewSection.querySelectorAll('.comment-preview');
if (commentPreviews.length > 3) {
commentPreviews[commentPreviews.length - 1].remove();
}
} else {
showToast(data.error || 'Ошибка');
console.error('Comment error:', data.error);
}
})
.catch(error => {
console.error('Fetch error:', error);
btn.disabled = false;
showToast('Ошибка подключения');
});
}
// Reply to comment - adds @username to input
function replyToComment(username, postId) {
const card = document.querySelector(`.post-card[data-id="${postId}"]`);
if (!card) return;
const input = card.querySelector('.card-comment-input');
if (input) {
input.value = `@${username} `;
input.focus();
}
}
// Delete comment
function deleteComment(commentId, postId) {
if (!confirm('Удалить этот комментарий?')) return;
const formData = new FormData();
formData.append('action', 'delete_comment');
formData.append('comment_id', commentId);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
// Remove comment from DOM
const commentEl = document.querySelector(`.comment-preview[data-comment-id="${commentId}"]`);
if (commentEl) {
commentEl.style.opacity = '0';
commentEl.style.transition = 'opacity 0.3s';
setTimeout(() => commentEl.remove(), 300);
}
showToast('Комментарий удален');
} else {
showToast(data.error || 'Ошибка удаления');
}
})
.catch(error => {
console.error('Delete error:', error);
showToast('Ошибка подключения');
});
}
// Like comment (placeholder for future implementation)
function likeComment(commentId) {
console.log('Like comment:', commentId);
// TODO: Implement comment likes
showToast('Функция в разработке');
}
// Reels Interaction Logic
function scrollReels(direction) {
const container = document.getElementById('reels-container');
if (!container) return;
const scrollAmount = window.innerHeight - 80;
if (direction === 'up') {
container.scrollBy({ top: -scrollAmount, behavior: 'smooth' });
} else {
container.scrollBy({ top: scrollAmount, behavior: 'smooth' });
}
}
function toggleReelMute(btn) {
const video = btn.closest('.reel-item').querySelector('video');
const icon = btn.querySelector('.mute-icon');
if (video.muted) {
video.muted = false;
icon.innerHTML = '<path d="M16.636 7.028a1.5 1.5 0 1 0-2.395 1.807 3.5 3.5 0 0 1 0 4.33 1.5 1.5 0 1 0 2.395 1.807 6.5 6.5 0 0 0 0-7.944Z"></path><path d="M12.157 2.189a1.5 1.5 0 0 0-1.941.039L5.47 6.5H2.5A1.5 1.5 0 0 0 1 8v8a1.5 1.5 0 0 0 1.5 1.5h2.97l4.746 4.272a1.5 1.5 0 0 0 1.941.039c.562-.437.843-1.127.843-1.811V4c0-.684-.281-1.374-.843-1.811Z"></path>';
} else {
video.muted = true;
icon.innerHTML = '<path d="m20.25 10.84-.53-.53a.249.249 0 0 0-.35 0l-.53.53a.249.249 0 0 0 0 .35l.53.53a.249.249 0 0 0 .35 0l.53-.53a.249.249 0 0 0 0-.35Z"></path><path d="M12.157 2.189a1.5 1.5 0 0 0-1.941.039L5.47 6.5H2.5A1.5 1.5 0 0 0 1 8v8a1.5 1.5 0 0 0 1.5 1.5h2.97l4.746 4.272a1.5 1.5 0 0 0 1.941.039c.562-.437.843-1.127.843-1.811V4c0-.684-.281-1.374-.843-1.811Z"></path>';
}
}
// Bottom Nav Active State
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const view = urlParams.get('view') || 'home';
document.querySelectorAll('.bottom-nav .nav-item').forEach(item => {
if(item.href && item.href.includes(`view=${view}`)) {
item.classList.add('active');
}
});
});
function toggleEmojiPicker() {
const picker = document.getElementById('emoji-picker');
if(picker) picker.classList.toggle('active');
}
function insertEmoji(emoji) {
const input = document.getElementById('detail-comment-input');
if(input) {
input.value += emoji;
input.focus();
toggleEmojiPicker();
}
}
function showToast(message) {
const container = document.getElementById('toast-container');
if (!container) return;
const toast = document.createElement('div');
toast.className = 'toast-message';
toast.style.background = '#363636';
toast.style.color = 'white';
toast.style.padding = '12px 24px';
toast.style.borderRadius = '8px';
toast.style.marginBottom = '10px';
toast.style.boxShadow = '0 4px 12px rgba(0,0,0,0.5)';
toast.style.fontSize = '14px';
toast.style.animation = 'fadeInOut 3s forwards';
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
// Add animation to head
const style = document.createElement('style');
style.innerHTML = `
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(20px); }
10% { opacity: 1; transform: translateY(0); }
90% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-20px); }
}
`;
document.head.appendChild(style);
function toggleMoreMenu(event) {
if(event) event.stopPropagation();
const menu = document.getElementById('more-menu-popover');
if(menu) menu.classList.toggle('active');
}
// Global click listener for closing popovers
document.addEventListener('click', (e) => {
const moreMenu = document.getElementById('more-menu-popover');
if (moreMenu && !moreMenu.contains(e.target) && !e.target.closest('[onclick="toggleMoreMenu(event)"]')) {
moreMenu.classList.remove('active');
}
});
// MESSENGER (DIRECT) LOGIC
let currentConversationId = null;
function loadChat(convId) {
currentConversationId = convId;
const placeholder = document.getElementById('chat-placeholder');
if (placeholder) placeholder.style.display = 'none';
const activeChat = document.getElementById('active-chat');
if (activeChat) {
activeChat.style.display = 'flex';
activeChat.innerHTML = '<div style="flex:1; display:flex; align-items:center; justify-content:center; color:var(--text-secondary);">Пошук повідомлень...</div>';
}
const formData = new FormData();
formData.append('action', 'get_messages');
formData.append('conversation_id', convId);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
renderActiveChat(data.messages);
}
});
}
function renderActiveChat(messages) {
const activeChat = document.getElementById('active-chat');
if (!activeChat) return;
activeChat.innerHTML = `
<div class="chat-header" style="height:75px; border-bottom:1px solid var(--border-color); display:flex; align-items:center; padding:0 20px; justify-content:space-between;">
<div style="display:flex; align-items:center; gap:12px;">
<img src="assets/default_avatar.svg" style="width:44px; height:44px; border-radius:50%;">
<span style="font-weight:700;">Чат</span>
</div>
<div style="display:flex; gap:15px; color:white;">
<svg aria-label="Звонок" fill="currentColor" height="24" viewBox="0 0 24 24" width="24"><path d="M12.001 2.002a10 10 0 1 0 10 10 10.011 10.011 0 0 0-10-10Zm0 18a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8Z"></path></svg>
<svg aria-label="Видеозвонок" fill="currentColor" height="24" viewBox="0 0 24 24" width="24"><path d="M22.5 7h-5.093a3.446 3.446 0 0 1-3.445 3.445v9.11a3.446 3.446 0 0 1 3.445 3.445h5.093a3.446 3.446 0 0 1 3.445-3.445v-9.11A3.446 3.446 0 0 1 22.5 7Z"></path></svg>
<svg aria-label="Сообщение" fill="currentColor" height="24" viewBox="0 0 24 24" width="24"><circle cx="12" cy="12" r="1.5"></circle><circle cx="12" cy="6" r="1.5"></circle><circle cx="12" cy="18" r="1.5"></circle></svg>
</div>
</div>
<div class="chat-messages-area" id="chat-messages-area" style="flex:1; overflow-y:auto; padding:20px; display:flex; flex-direction:column; gap:8px;">
${messages.length === 0 ? '<div style="margin:auto; text-align:center; color:var(--text-secondary);">Сообщений пока нет. Начни разговор!</div>' : ''}
${messages.map(m => `
<div class="msg-row ${m.sender_id == window.currentUserId ? 'mine' : 'theirs'}" style="display:flex; ${m.sender_id == window.currentUserId ? 'justify-content:flex-end' : 'justify-content:flex-start'};">
<div class="msg-bubble" style="max-width:70%; padding:8px 16px; border-radius:22px; font-size:14px; background:${m.sender_id == window.currentUserId ? '#3797f0' : '#262626'}; color:white;">
${m.message}
</div>
</div>
`).join('')}
</div>
<div class="chat-input-area" style="padding:20px;">
<div style="border:1px solid #363636; border-radius:22px; display:flex; align-items:center; padding:10px 15px; background:var(--bg-body);">
<svg aria-label="Смайл" fill="currentColor" height="24" viewBox="0 0 24 24" width="24" style="margin-right:12px;"><path d="M15.83 10.997a1.001 1.001 0 1 0 1.002 1.003 1.001 1.001 0 0 0-1.002-1.003ZM12 2a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2Zm0 18a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8ZM8.17 10.997a1.001 1.001 0 1 0 1.002 1.003 1.001 1.001 0 0 0-1.002-1.003Zm-1.838 5.71a.998.998 0 0 0 1.332 1.503 5.968 5.968 0 0 1 8.672 0 .999.999 0 0 0 1.332-1.5 7.965 7.965 0 0 0-11.335-.003Z"></path></svg>
<input type="text" id="chat-msg-input" placeholder="Напишите сообщение..." style="background:none; border:none; color:white; flex:1; outline:none; font-size:14px;" onkeydown="if(event.key==='Enter') sendMessage()">
<button onclick="sendMessage()" style="background:none; border:none; color:#0095f6; font-weight:700; cursor:pointer; margin-left:10px;">Отправить</button>
</div>
</div>
`;
const area = document.getElementById('chat-messages-area');
if (area) area.scrollTop = area.scrollHeight;
}
function sendMessage() {
const input = document.getElementById('chat-msg-input');
if (!input) return;
const text = input.value.trim();
if (!text || !currentConversationId) return;
const formData = new FormData();
formData.append('action', 'send_message');
formData.append('conversation_id', currentConversationId);
formData.append('message', text);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
input.value = '';
loadChat(currentConversationId);
}
});
}
// Toggle theme (dark/light mode)
function toggleTheme() {
showToast('Функция смены темы находится в разработке.');
// TODO: Implement theme switching
// localStorage.setItem('theme', theme === 'dark' ? 'light' : 'dark');
}
// Report a problem
function reportProblem() {
const problem = prompt('Опишите проблему:');
if (problem && problem.trim()) {
const formData = new FormData();
formData.append('action', 'report_problem');
formData.append('description', problem);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
showToast('Спасибо за сообщение!');
} else {
showToast('Ошибка отправки');
}
});
}
}
// Switch account
function switchAccount() {
if (confirm('Выйти из текущего аккаунта?')) {
window.location.href = '?view=logout';
}
}
// Save/Unsave post
function savePost(postId) {
const formData = new FormData();
formData.append('action', 'save_post');
formData.append('post_id', postId);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
// Update save button icon
const saveBtn = document.querySelector(`[onclick="savePost(${postId})"]`);
if (saveBtn) {
const svg = saveBtn.querySelector('svg');
if (data.saved) {
// Filled icon
svg.innerHTML = '<path d="M20 21V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v16l8-4.572L20 21Z"></path>';
svg.setAttribute('fill', 'currentColor');
showToast('Сохранено');
} else {
// Outline icon
svg.innerHTML = '<polygon fill="none" points="20 21 12 13.44 4 21 4 3 20 3 20 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polygon>';
svg.setAttribute('fill', 'none');
showToast('Удалено из сохраненных');
}
}
} else {
showToast(data.error || 'Ошибка');
}
})
.catch(error => {
console.error('Save error:', error);
showToast('Ошибка подключения');
});
}
// MESSENGER: NOTES & NEW MSG
// Auto-init for Messenger
document.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('messenger-notes-row')) {
loadNotes();
}
});
// Load Notes
function loadNotes() {
const container = document.getElementById('messenger-notes-row');
if (!container) return;
fetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'get_notes'}) })
.then(res => res.json())
.then(data => {
if (data.success) {
container.innerHTML = ''; // Clear container
// Separate my note from others
const myId = window.currentUserId;
const myNote = data.notes.find(n => n.user_id == myId);
const otherNotes = data.notes.filter(n => n.user_id != myId);
// 1. Render MY NOTE item
const myItem = document.createElement('div');
myItem.className = 'note-item';
myItem.onclick = createNote;
const myAvatar = window.currentUserAvatar || 'assets/default_avatar.svg';
if (myNote) {
// I have a note
myItem.innerHTML = `
<div class="note-avatar-wrapper">
<img src="${myAvatar}" class="note-avatar">
<div class="note-bubble">${myNote.note_text}</div>
<div class="note-sub">Ваша заметка</div>
</div>
`;
} else {
// No note -> Plus icon
myItem.innerHTML = `
<div class="note-avatar-wrapper">
<img src="${myAvatar}" class="note-avatar">
<div class="note-bubble plus">+</div>
<div class="note-sub">Ваша заметка</div>
</div>
`;
}
container.appendChild(myItem);
// 2. Render others
otherNotes.forEach(note => {
const item = document.createElement('div');
item.className = 'note-item';
item.innerHTML = `
<div class="note-avatar-wrapper">
<img src="${note.avatar || 'assets/default_avatar.svg'}" class="note-avatar">
<div class="note-bubble">${note.note_text}</div>
<div class="note-sub">${note.username}</div>
</div>
`;
container.appendChild(item);
});
}
});
}
function createNote() {
const text = prompt("Поделитесь своим мнением (максимум 60 символов):");
if (text !== null) {
if (text.length > 60) {
alert('Слишком длинный текст!');
return;
}
const formData = new FormData();
formData.append('action', 'create_note');
formData.append('text', text);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
loadNotes();
showToast('Заметка обновлена');
} else {
showToast(data.error || 'Ошибка');
}
});
}
}
// New Message Modal
function openNewMsgModal() {
const modal = document.getElementById('new-msg-modal');
if (modal) {
modal.classList.add('active');
const input = document.getElementById('new-msg-search');
if(input) input.focus();
}
}
function closeNewMsgModal() {
const modal = document.getElementById('new-msg-modal');
if (modal) modal.classList.remove('active');
}
let searchMsgTimeout = null;
function searchUsersForMsg(query) {
if (searchMsgTimeout) clearTimeout(searchMsgTimeout);
const results = document.getElementById('new-msg-results');
const btn = document.getElementById('start-chat-btn');
if (query.trim().length === 0) {
results.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-secondary);">Поиск пользователей...</div>';
btn.disabled = true;
return;
}
searchMsgTimeout = setTimeout(() => {
const formData = new FormData();
formData.append('action', 'search_users');
formData.append('q', query);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
results.innerHTML = '';
if (data.success && data.users.length > 0) {
data.users.forEach(u => {
// Don't show self
if (window.currentUserId && u.id == window.currentUserId) return;
const el = document.createElement('div');
el.className = 'msg-search-item';
el.style.padding = '10px 16px';
el.style.display = 'flex';
el.style.alignItems = 'center';
el.style.cursor = 'pointer';
el.style.borderRadius = '8px';
el.style.margin = '4px 8px';
// Hover effect via JS or CSS? CSS is better but inline for brevity
el.onmouseover = () => el.style.background = 'rgba(255,255,255,0.1)';
el.onmouseout = () => el.style.background = 'transparent';
el.onclick = () => selectUserForChat(u.id, el);
el.innerHTML = `
<img src="${u.avatar || 'assets/default_avatar.svg'}" style="width:40px;height:40px;border-radius:50%;margin-right:12px;">
<div>
<div style="font-weight:600;">${u.username}</div>
<div style="font-size:12px;color:var(--text-secondary);">${u.full_name}</div>
</div>
<div class="check-circle" style="margin-left:auto;width:24px;height:24px;border:1px solid #363636;border-radius:50%;display:flex;align-items:center;justify-content:center;"></div>
`;
results.appendChild(el);
});
} else {
results.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-secondary);">Никто не найден.</div>';
}
});
}, 300);
}
let selectedChatUser = null;
function selectUserForChat(userId, el) {
// Single select for now
document.querySelectorAll('.msg-search-item .check-circle').forEach(c => {
c.style.background = 'transparent';
c.style.border = '1px solid #363636';
c.innerHTML = '';
});
selectedChatUser = userId;
const circle = el.querySelector('.check-circle');
circle.style.background = '#0095f6';
circle.style.border = 'none';
circle.innerHTML = '<svg fill="white" height="16" viewBox="0 0 24 24" width="16"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></svg>';
document.getElementById('start-chat-btn').disabled = false;
}
function createChatFromModal() {
if (!selectedChatUser) return;
const formData = new FormData();
formData.append('action', 'start_conversation');
formData.append('user_id', selectedChatUser);
fetch('api.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.success) {
window.location.href = `?view=direct&conv=${data.conversation_id}`;
}
});
}
?>