MultipleImagesPost
This commit is contained in:
@@ -237,16 +237,17 @@
|
|||||||
|
|
||||||
<div class="post-creation-area">
|
<div class="post-creation-area">
|
||||||
<div class="image-upload-wrapper" id="upload-trigger">
|
<div class="image-upload-wrapper" id="upload-trigger">
|
||||||
<input type="file" id="post-image-input" accept="image/*" hidden>
|
<input type="file" id="post-image-input" accept="image/*,video/*" multiple hidden>
|
||||||
<div class="upload-placeholder">
|
<div class="upload-placeholder">
|
||||||
<svg viewBox="0 0 24 24" width="48" height="48" stroke="white" fill="none" stroke-width="1">
|
<svg viewBox="0 0 24 24" width="48" height="48" stroke="white" fill="none" stroke-width="1">
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||||
<polyline points="21 15 16 10 5 21"></polyline>
|
<polyline points="21 15 16 10 5 21"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Tap to Select Photo</span>
|
<span>Tap to Select Photos or Videos</span>
|
||||||
</div>
|
</div>
|
||||||
<img id="image-preview" class="hidden" src="" alt="Preview">
|
<!-- Preview Container for Carousel -->
|
||||||
|
<div id="media-preview-container" class="media-carousel hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="caption-wrapper">
|
<div class="caption-wrapper">
|
||||||
|
|||||||
324
script.js
324
script.js
@@ -1,5 +1,5 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// --- INDEXED DB HELPERS (For >5MB Storage) ---
|
// --- INDEXED DB HELPERS (Multi-Media Support) ---
|
||||||
const DB_NAME = 'SocialAppDB';
|
const DB_NAME = 'SocialAppDB';
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 1;
|
||||||
|
|
||||||
@@ -17,13 +17,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveImageToDB(id, dataUrl) {
|
// Save Array of Blobs/Strings
|
||||||
|
async function saveMediaToDB(id, mediaArray) {
|
||||||
try {
|
try {
|
||||||
const db = await openDB();
|
const db = await openDB();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction('images', 'readwrite');
|
const tx = db.transaction('images', 'readwrite');
|
||||||
const store = tx.objectStore('images');
|
const store = tx.objectStore('images');
|
||||||
store.put({ id, data: dataUrl });
|
store.put({ id, media: mediaArray });
|
||||||
tx.oncomplete = () => resolve();
|
tx.oncomplete = () => resolve();
|
||||||
tx.onerror = () => reject(tx.error);
|
tx.onerror = () => reject(tx.error);
|
||||||
});
|
});
|
||||||
@@ -33,14 +34,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getImageFromDB(id) {
|
// Get Media Array
|
||||||
|
async function getMediaFromDB(id) {
|
||||||
try {
|
try {
|
||||||
const db = await openDB();
|
const db = await openDB();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction('images', 'readonly');
|
const tx = db.transaction('images', 'readonly');
|
||||||
const store = tx.objectStore('images');
|
const store = tx.objectStore('images');
|
||||||
const request = store.get(id);
|
const request = store.get(id);
|
||||||
request.onsuccess = () => resolve(request.result ? request.result.data : null);
|
request.onsuccess = () => resolve(request.result ? request.result.media : null);
|
||||||
request.onerror = () => reject(request.error);
|
request.onerror = () => reject(request.error);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -60,7 +62,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
tx.onerror = () => reject(tx.error);
|
tx.onerror = () => reject(tx.error);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('IndexedDB Delete Error:', e);
|
console.log('IndexedDB Delete Error or ID not found:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const sharePostBtn = document.getElementById('share-post-btn');
|
const sharePostBtn = document.getElementById('share-post-btn');
|
||||||
const uploadTrigger = document.getElementById('upload-trigger');
|
const uploadTrigger = document.getElementById('upload-trigger');
|
||||||
const imageInput = document.getElementById('post-image-input');
|
const imageInput = document.getElementById('post-image-input');
|
||||||
const imagePreview = document.getElementById('image-preview');
|
const mediaPreviewContainer = document.getElementById('media-preview-container');
|
||||||
const captionInput = document.getElementById('post-caption-input');
|
const captionInput = document.getElementById('post-caption-input');
|
||||||
const navPostBtn = document.querySelector('.nav-btn:nth-child(2)');
|
const navPostBtn = document.querySelector('.nav-btn:nth-child(2)');
|
||||||
const navHomeBtn = document.querySelector('.nav-btn:first-child');
|
const navHomeBtn = document.querySelector('.nav-btn:first-child');
|
||||||
@@ -102,7 +104,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
function updateUI() {
|
function updateUI() {
|
||||||
if (!title || !subtitle || !btnText || !confirmPasswordGroup) return;
|
if (!title || !subtitle || !btnText || !confirmPasswordGroup) return;
|
||||||
|
|
||||||
if (isLoginMode) {
|
if (isLoginMode) {
|
||||||
title.innerText = 'Welcome Back';
|
title.innerText = 'Welcome Back';
|
||||||
subtitle.innerText = 'Enter your details below';
|
subtitle.innerText = 'Enter your details below';
|
||||||
@@ -120,7 +121,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
confirmPasswordGroup.classList.remove('hidden');
|
confirmPasswordGroup.classList.remove('hidden');
|
||||||
confirmPasswordInput.required = true;
|
confirmPasswordInput.required = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newLink = document.querySelector('.signup-link a');
|
const newLink = document.querySelector('.signup-link a');
|
||||||
if (newLink) {
|
if (newLink) {
|
||||||
newLink.addEventListener('click', (e) => {
|
newLink.addEventListener('click', (e) => {
|
||||||
@@ -129,7 +129,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
updateUI();
|
updateUI();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginForm) loginForm.reset();
|
if (loginForm) loginForm.reset();
|
||||||
resetButton();
|
resetButton();
|
||||||
}
|
}
|
||||||
@@ -148,19 +147,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
submitBtn.style.background = 'linear-gradient(135deg, #10B981 0%, #059669 100%)';
|
submitBtn.style.background = 'linear-gradient(135deg, #10B981 0%, #059669 100%)';
|
||||||
} else {
|
} else {
|
||||||
submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)';
|
submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)';
|
||||||
submitBtn.animate([
|
|
||||||
{ transform: 'translateX(0)' },
|
|
||||||
{ transform: 'translateX(-5px)' },
|
|
||||||
{ transform: 'translateX(5px)' },
|
|
||||||
{ transform: 'translateX(0)' }
|
|
||||||
], { duration: 300 });
|
|
||||||
}
|
}
|
||||||
|
setTimeout(() => { if (!message.includes('Success')) resetButton(); }, 2000);
|
||||||
setTimeout(() => {
|
|
||||||
if (!message.includes('Success')) {
|
|
||||||
resetButton();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateToFeed() {
|
function navigateToFeed() {
|
||||||
@@ -169,16 +157,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loginContainer.classList.add('hidden');
|
loginContainer.classList.add('hidden');
|
||||||
loginContainer.style.display = 'none';
|
loginContainer.style.display = 'none';
|
||||||
|
|
||||||
feedView.classList.remove('hidden');
|
feedView.classList.remove('hidden');
|
||||||
void feedView.offsetWidth; // Reflow
|
void feedView.offsetWidth;
|
||||||
feedView.classList.add('fade-in');
|
feedView.classList.add('fade-in');
|
||||||
|
initFeed();
|
||||||
initFeed(); // Load data (Async)
|
setTimeout(() => feedView.classList.remove('fade-in'), 600);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
feedView.classList.remove('fade-in');
|
|
||||||
}, 600);
|
|
||||||
}, 600);
|
}, 600);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,60 +196,45 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
existingUsers.push(newUser);
|
existingUsers.push(newUser);
|
||||||
localStorage.setItem('socialAppUsers', JSON.stringify(existingUsers));
|
localStorage.setItem('socialAppUsers', JSON.stringify(existingUsers));
|
||||||
showFeedback(true, 'Account Created!');
|
showFeedback(true, 'Account Created!');
|
||||||
setTimeout(() => {
|
setTimeout(() => { isLoginMode = true; updateUI(); setTimeout(navigateToFeed, 1000); }, 1500);
|
||||||
isLoginMode = true;
|
|
||||||
updateUI();
|
|
||||||
setTimeout(navigateToFeed, 1000);
|
|
||||||
}, 1500);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- DATA HELPERS ---
|
// --- DATA HELPERS ---
|
||||||
function getPostState() {
|
function getPostState() { return JSON.parse(localStorage.getItem('socialAppPostState') || '{}'); }
|
||||||
return JSON.parse(localStorage.getItem('socialAppPostState') || '{}');
|
function savePostState(state) { localStorage.setItem('socialAppPostState', JSON.stringify(state)); }
|
||||||
}
|
function isPostDeleted(id) { const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]'); return deletedPosts.includes(id); }
|
||||||
function savePostState(state) {
|
|
||||||
localStorage.setItem('socialAppPostState', JSON.stringify(state));
|
|
||||||
}
|
|
||||||
function isPostDeleted(id) {
|
|
||||||
const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]');
|
|
||||||
return deletedPosts.includes(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ASYNC LOAD FEED ---
|
// --- ASYNC LOAD FEED ---
|
||||||
async function initFeed() {
|
async function initFeed() {
|
||||||
// 1. Render User Posts (Fetch images from DB)
|
|
||||||
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||||
|
|
||||||
// Use a loop to handle async properly
|
|
||||||
const postsToRender = [...savedUserPosts].reverse();
|
const postsToRender = [...savedUserPosts].reverse();
|
||||||
|
|
||||||
for (const post of postsToRender) {
|
for (const post of postsToRender) {
|
||||||
if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
|
if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||||
// Try to get image from DB, fallback to post.imageSrc (legacy support)
|
let mediaItems = post.media;
|
||||||
let imgSrc = post.imageSrc;
|
// Backwards Compatibility
|
||||||
if (!imgSrc || imgSrc.length < 100) { // If it's a placeholder (null)
|
if (!mediaItems && post.imageSrc) {
|
||||||
const dbImg = await getImageFromDB(post.id);
|
mediaItems = [{ type: 'image', src: post.imageSrc }];
|
||||||
if (dbImg) imgSrc = dbImg;
|
}
|
||||||
|
if (!mediaItems || (mediaItems.length > 0 && mediaItems[0].src && mediaItems[0].src.length < 100)) {
|
||||||
|
const dbMedia = await getMediaFromDB(post.id);
|
||||||
|
if (dbMedia) mediaItems = dbMedia;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imgSrc) {
|
if (mediaItems && mediaItems.length > 0) {
|
||||||
const postWithImg = { ...post, imageSrc: imgSrc };
|
const postWithMedia = { ...post, media: mediaItems };
|
||||||
renderPost(postWithImg, true);
|
renderPost(postWithMedia, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Restore Interaction State (Likes/Comments)
|
// Restore State
|
||||||
// Give a small delay to ensure rendering catches up or run immediately
|
|
||||||
const state = getPostState();
|
const state = getPostState();
|
||||||
document.querySelectorAll('.post-card').forEach(card => {
|
document.querySelectorAll('.post-card').forEach(card => {
|
||||||
const id = card.getAttribute('data-post-id');
|
const id = card.getAttribute('data-post-id');
|
||||||
if (id && isPostDeleted(id)) {
|
if (id && isPostDeleted(id)) { card.remove(); return; }
|
||||||
card.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (id && state[id]) {
|
if (id && state[id]) {
|
||||||
const data = state[id];
|
const data = state[id];
|
||||||
const likeIcon = card.querySelector('.heart-icon');
|
const likeIcon = card.querySelector('.heart-icon');
|
||||||
@@ -297,7 +265,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// --- RENDER POST ---
|
// --- RENDER POST ---
|
||||||
function renderPost(post, prepend = false) {
|
function renderPost(post, prepend = false) {
|
||||||
// Avoid duplicates
|
|
||||||
if (document.querySelector(`[data-post-id="${post.id}"]`)) return;
|
if (document.querySelector(`[data-post-id="${post.id}"]`)) return;
|
||||||
|
|
||||||
const feedContainer = document.querySelector('.feed-container');
|
const feedContainer = document.querySelector('.feed-container');
|
||||||
@@ -318,6 +285,31 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
</div>
|
</div>
|
||||||
` : `<button class="icon-btn-sm">...</button>`;
|
` : `<button class="icon-btn-sm">...</button>`;
|
||||||
|
|
||||||
|
let slidesHTML = '';
|
||||||
|
let dotsHTML = '';
|
||||||
|
const media = post.media || (post.imageSrc ? [{ type: 'image', src: post.imageSrc }] : []);
|
||||||
|
|
||||||
|
if (media.length > 0) {
|
||||||
|
media.forEach((item, index) => {
|
||||||
|
if (item.type === 'video') {
|
||||||
|
slidesHTML += `
|
||||||
|
<div class="media-slide">
|
||||||
|
<video src="${item.src}" controls playsinline loop></video>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
slidesHTML += `
|
||||||
|
<div class="media-slide">
|
||||||
|
<img src="${item.src}" alt="Post Media ${index + 1}">
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (media.length > 1) {
|
||||||
|
dotsHTML += `<div class="dot ${index === 0 ? 'active' : ''}"></div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const paginationHTML = media.length > 1 ? `<div class="carousel-dots">${dotsHTML}</div>` : '';
|
||||||
|
|
||||||
article.innerHTML = `
|
article.innerHTML = `
|
||||||
<div class="post-header">
|
<div class="post-header">
|
||||||
<div class="post-user">
|
<div class="post-user">
|
||||||
@@ -326,8 +318,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
</div>
|
</div>
|
||||||
${optionsHTML}
|
${optionsHTML}
|
||||||
</div>
|
</div>
|
||||||
<div class="post-image">
|
<div class="post-image-wrapper post-media-wrapper">
|
||||||
<img src="${post.imageSrc}" alt="Post">
|
<div class="media-carousel" onscroll="updateDots(this)">
|
||||||
|
${slidesHTML}
|
||||||
|
</div>
|
||||||
|
${paginationHTML}
|
||||||
</div>
|
</div>
|
||||||
<div class="post-actions">
|
<div class="post-actions">
|
||||||
<div class="action-left">
|
<div class="action-left">
|
||||||
@@ -350,57 +345,82 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
if (prepend) {
|
if (prepend) {
|
||||||
// Check again for safety inside async flows
|
|
||||||
if (!feedContainer.querySelector(`[data-post-id="${post.id}"]`)) {
|
if (!feedContainer.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||||
feedContainer.insertBefore(article, feedContainer.firstChild);
|
feedContainer.insertBefore(article, feedContainer.firstChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else feedContainer.appendChild(article);
|
else feedContainer.appendChild(article);
|
||||||
|
|
||||||
|
// Attach Drag-to-Scroll for Desktop
|
||||||
|
if (media.length > 1) {
|
||||||
|
const carousel = article.querySelector('.media-carousel');
|
||||||
|
if (carousel) enableDragScroll(carousel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DRAG TO SCROLL HELPER ---
|
||||||
|
function enableDragScroll(container) {
|
||||||
|
let isDown = false;
|
||||||
|
let startX;
|
||||||
|
let scrollLeft;
|
||||||
|
|
||||||
|
container.addEventListener('mousedown', (e) => {
|
||||||
|
isDown = true;
|
||||||
|
container.style.cursor = 'grabbing';
|
||||||
|
startX = e.pageX - container.offsetLeft;
|
||||||
|
scrollLeft = container.scrollLeft;
|
||||||
|
});
|
||||||
|
container.addEventListener('mouseleave', () => { isDown = false; container.style.cursor = 'grab'; });
|
||||||
|
container.addEventListener('mouseup', () => { isDown = false; container.style.cursor = 'grab'; });
|
||||||
|
container.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDown) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const x = e.pageX - container.offsetLeft;
|
||||||
|
const walk = (x - startX) * 2; // Scroll-fast
|
||||||
|
container.scrollLeft = scrollLeft - walk;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global helper for dots
|
||||||
|
window.updateDots = function (carousel) {
|
||||||
|
const index = Math.round(carousel.scrollLeft / carousel.offsetWidth);
|
||||||
|
const dots = carousel.parentElement.querySelectorAll('.dot');
|
||||||
|
dots.forEach((dot, i) => {
|
||||||
|
if (i === index) dot.classList.add('active');
|
||||||
|
else dot.classList.remove('active');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- FEED LISTENERS ---
|
// --- FEED LISTENERS ---
|
||||||
if (feedView) {
|
if (feedView) {
|
||||||
initFeed(); // Initial Load logic
|
initFeed();
|
||||||
|
|
||||||
feedView.addEventListener('click', (e) => {
|
feedView.addEventListener('click', (e) => {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
|
|
||||||
// 1. OPTIONS MENU
|
|
||||||
const trigger = target.closest('.options-trigger');
|
const trigger = target.closest('.options-trigger');
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
const header = trigger.closest('.post-header');
|
const header = trigger.closest('.post-header');
|
||||||
const menu = header.querySelector('.options-menu');
|
const menu = header.querySelector('.options-menu');
|
||||||
if (menu) {
|
if (menu) {
|
||||||
document.querySelectorAll('.options-menu.active').forEach(m => {
|
document.querySelectorAll('.options-menu.active').forEach(m => m.classList.remove('active'));
|
||||||
if (m !== menu) m.classList.remove('active');
|
|
||||||
});
|
|
||||||
menu.classList.toggle('active');
|
menu.classList.toggle('active');
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. DELETE ACTION
|
|
||||||
const deleteBtn = target.closest('.delete');
|
const deleteBtn = target.closest('.delete');
|
||||||
if (deleteBtn) {
|
if (deleteBtn) {
|
||||||
const card = deleteBtn.closest('.post-card');
|
const card = deleteBtn.closest('.post-card');
|
||||||
const id = card.getAttribute('data-post-id');
|
const id = card.getAttribute('data-post-id');
|
||||||
|
if (id && id.startsWith('post_')) {
|
||||||
if (id && id.startsWith('post_')) { // Double check ownership
|
|
||||||
if (confirm('Delete this post?')) {
|
if (confirm('Delete this post?')) {
|
||||||
card.style.transition = 'opacity 0.3s, transform 0.3s';
|
card.style.transition = 'opacity 0.3s, transform 0.3s';
|
||||||
card.style.opacity = '0';
|
card.style.opacity = '0';
|
||||||
card.style.transform = 'scale(0.9)';
|
card.style.transform = 'scale(0.9)';
|
||||||
setTimeout(() => card.remove(), 300);
|
setTimeout(() => card.remove(), 300);
|
||||||
|
|
||||||
let userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
let userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||||
userPosts = userPosts.filter(p => p.id !== id);
|
userPosts = userPosts.filter(p => p.id !== id);
|
||||||
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
||||||
|
|
||||||
// Delete from DB too
|
|
||||||
deleteImageFromDB(id);
|
deleteImageFromDB(id);
|
||||||
|
|
||||||
// Also ban ID to be safe
|
|
||||||
const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]');
|
const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]');
|
||||||
if (!deletedPosts.includes(id)) {
|
if (!deletedPosts.includes(id)) {
|
||||||
deletedPosts.push(id);
|
deletedPosts.push(id);
|
||||||
@@ -410,19 +430,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. LIKE ACTION
|
|
||||||
const likeBtn = target.closest('.heart-icon')?.closest('.icon-btn');
|
const likeBtn = target.closest('.heart-icon')?.closest('.icon-btn');
|
||||||
if (likeBtn) {
|
if (likeBtn) {
|
||||||
const icon = likeBtn.querySelector('.heart-icon');
|
const icon = likeBtn.querySelector('.heart-icon');
|
||||||
const postCard = likeBtn.closest('.post-card');
|
const postCard = likeBtn.closest('.post-card');
|
||||||
const likesText = postCard.querySelector('.likes');
|
const likesText = postCard.querySelector('.likes');
|
||||||
const postId = postCard.getAttribute('data-post-id');
|
const postId = postCard.getAttribute('data-post-id');
|
||||||
|
|
||||||
icon.classList.toggle('liked');
|
icon.classList.toggle('liked');
|
||||||
let count = parseInt(likesText.innerText);
|
let count = parseInt(likesText.innerText);
|
||||||
let isLiked = false;
|
let isLiked = false;
|
||||||
|
|
||||||
if (icon.classList.contains('liked')) {
|
if (icon.classList.contains('liked')) {
|
||||||
icon.style.fill = '#EF4444';
|
icon.style.fill = '#EF4444';
|
||||||
icon.style.stroke = '#EF4444';
|
icon.style.stroke = '#EF4444';
|
||||||
@@ -435,7 +451,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
isLiked = false;
|
isLiked = false;
|
||||||
}
|
}
|
||||||
likesText.innerText = count + ' likes';
|
likesText.innerText = count + ' likes';
|
||||||
|
|
||||||
if (postId) {
|
if (postId) {
|
||||||
const state = getPostState();
|
const state = getPostState();
|
||||||
if (!state[postId]) state[postId] = { comments: [] };
|
if (!state[postId]) state[postId] = { comments: [] };
|
||||||
@@ -446,13 +461,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. COMMENT TOGGLE (Updated logic)
|
|
||||||
// Identify comment button by checking if it's the 2nd one or has specific path
|
|
||||||
const commentBtn = target.closest('.icon-btn');
|
const commentBtn = target.closest('.icon-btn');
|
||||||
if (commentBtn) {
|
if (commentBtn) {
|
||||||
const svgs = commentBtn.innerHTML;
|
const svgs = commentBtn.innerHTML;
|
||||||
if (svgs.includes('M21 11.5')) { // Comment icon path substring
|
if (svgs.includes('M21 11.5')) {
|
||||||
const postCard = commentBtn.closest('.post-card');
|
const postCard = commentBtn.closest('.post-card');
|
||||||
const commentSection = postCard.querySelector('.comment-section');
|
const commentSection = postCard.querySelector('.comment-section');
|
||||||
commentSection.classList.toggle('active');
|
commentSection.classList.toggle('active');
|
||||||
@@ -463,15 +475,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. POST COMMENT
|
|
||||||
if (target.classList.contains('post-btn')) {
|
if (target.classList.contains('post-btn')) {
|
||||||
const wrapper = target.closest('.comment-input-wrapper');
|
const wrapper = target.closest('.comment-input-wrapper');
|
||||||
const input = wrapper.querySelector('.comment-input');
|
const input = wrapper.querySelector('.comment-input');
|
||||||
const text = input.value.trim();
|
const text = input.value.trim();
|
||||||
const postCard = wrapper.closest('.post-card');
|
const postCard = wrapper.closest('.post-card');
|
||||||
const postId = postCard.getAttribute('data-post-id');
|
const postId = postCard.getAttribute('data-post-id');
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
addComment(wrapper.closest('.comment-section'), text);
|
addComment(wrapper.closest('.comment-section'), text);
|
||||||
if (postId) {
|
if (postId) {
|
||||||
@@ -485,15 +494,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enter key for comments
|
|
||||||
feedView.addEventListener('keydown', (e) => {
|
feedView.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter' && e.target.classList.contains('comment-input')) {
|
if (e.key === 'Enter' && e.target.classList.contains('comment-input')) {
|
||||||
const text = e.target.value.trim();
|
const text = e.target.value.trim();
|
||||||
const wrapper = e.target.closest('.comment-input-wrapper');
|
const wrapper = e.target.closest('.comment-input-wrapper');
|
||||||
const postCard = wrapper.closest('.post-card');
|
const postCard = wrapper.closest('.post-card');
|
||||||
const postId = postCard.getAttribute('data-post-id');
|
const postId = postCard.getAttribute('data-post-id');
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
addComment(e.target.closest('.comment-section'), text);
|
addComment(e.target.closest('.comment-section'), text);
|
||||||
if (postId) {
|
if (postId) {
|
||||||
@@ -507,8 +513,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close menus
|
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
if (!e.target.closest('.options-menu') && !e.target.closest('.options-trigger')) {
|
if (!e.target.closest('.options-menu') && !e.target.closest('.options-trigger')) {
|
||||||
document.querySelectorAll('.options-menu.active').forEach(m => m.classList.remove('active'));
|
document.querySelectorAll('.options-menu.active').forEach(m => m.classList.remove('active'));
|
||||||
@@ -525,69 +529,92 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- CREATE POST LOGIC ---
|
// --- CREATE POST LOGIC ---
|
||||||
// Navigation
|
|
||||||
if (navPostBtn) {
|
if (navPostBtn) {
|
||||||
navPostBtn.addEventListener('click', () => {
|
navPostBtn.addEventListener('click', () => { if (feedView && createPostView) { feedView.classList.add('hidden'); createPostView.classList.remove('hidden'); createPostView.classList.add('fade-in'); } });
|
||||||
if (feedView && createPostView) {
|
|
||||||
feedView.classList.add('hidden');
|
|
||||||
createPostView.classList.remove('hidden');
|
|
||||||
createPostView.classList.add('fade-in');
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel
|
|
||||||
if (cancelPostBtn) {
|
if (cancelPostBtn) {
|
||||||
cancelPostBtn.addEventListener('click', () => {
|
cancelPostBtn.addEventListener('click', () => { resetPostForm(); createPostView.classList.add('hidden'); feedView.classList.remove('hidden'); });
|
||||||
resetPostForm();
|
|
||||||
createPostView.classList.add('hidden');
|
|
||||||
feedView.classList.remove('hidden');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// Home Nav
|
|
||||||
if (navHomeBtn) {
|
if (navHomeBtn) {
|
||||||
navHomeBtn.addEventListener('click', () => {
|
navHomeBtn.addEventListener('click', () => { if (createPostView && !createPostView.classList.contains('hidden')) { resetPostForm(); createPostView.classList.add('hidden'); feedView.classList.remove('hidden'); } });
|
||||||
if (createPostView && !createPostView.classList.contains('hidden')) {
|
|
||||||
resetPostForm();
|
|
||||||
createPostView.classList.add('hidden');
|
|
||||||
feedView.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload
|
// Staging Area for Selected Files
|
||||||
|
let stagedMedia = [];
|
||||||
|
|
||||||
|
// Upload Handler (Multiple Files)
|
||||||
if (uploadTrigger && imageInput) {
|
if (uploadTrigger && imageInput) {
|
||||||
uploadTrigger.addEventListener('click', () => imageInput.click());
|
uploadTrigger.addEventListener('click', () => imageInput.click());
|
||||||
imageInput.addEventListener('change', (e) => {
|
imageInput.addEventListener('change', async (e) => {
|
||||||
const file = e.target.files[0];
|
const files = Array.from(e.target.files);
|
||||||
if (file) {
|
if (files.length > 0) {
|
||||||
const reader = new FileReader();
|
stagedMedia = [];
|
||||||
reader.onload = (e) => {
|
mediaPreviewContainer.innerHTML = '';
|
||||||
imagePreview.src = e.target.result;
|
mediaPreviewContainer.classList.remove('hidden');
|
||||||
imagePreview.classList.remove('hidden');
|
|
||||||
document.querySelector('.upload-placeholder').classList.add('hidden');
|
document.querySelector('.upload-placeholder').classList.add('hidden');
|
||||||
};
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file.type.startsWith('image/') && !file.type.startsWith('video/')) {
|
||||||
|
alert('Skipping invalid file: ' + file.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVideo = file.type.startsWith('video/');
|
||||||
|
const dataUrl = await new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => resolve(e.target.result);
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
stagedMedia.push({ type: isVideo ? 'video' : 'image', src: dataUrl });
|
||||||
|
|
||||||
|
const slide = document.createElement('div');
|
||||||
|
slide.className = 'media-slide';
|
||||||
|
if (isVideo) {
|
||||||
|
slide.innerHTML = `<video src="${dataUrl}" controls style="width:100%; height:100%; object-fit:contain;"></video>`;
|
||||||
|
} else {
|
||||||
|
slide.innerHTML = `<img src="${dataUrl}" style="width:100%; height:100%; object-fit:contain;">`;
|
||||||
|
}
|
||||||
|
mediaPreviewContainer.appendChild(slide);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Dots for Preview
|
||||||
|
const wrapper = document.querySelector('#create-post-view .image-upload-wrapper');
|
||||||
|
const oldDots = wrapper.querySelector('.carousel-dots');
|
||||||
|
if (oldDots) oldDots.remove();
|
||||||
|
|
||||||
|
if (stagedMedia.length > 1) {
|
||||||
|
const dotsContainer = document.createElement('div');
|
||||||
|
dotsContainer.className = 'carousel-dots';
|
||||||
|
stagedMedia.forEach((_, i) => {
|
||||||
|
const dot = document.createElement('div');
|
||||||
|
dot.className = `dot ${i === 0 ? 'active' : ''}`;
|
||||||
|
dotsContainer.appendChild(dot);
|
||||||
|
});
|
||||||
|
wrapper.appendChild(dotsContainer);
|
||||||
|
|
||||||
|
// Attach Listeners
|
||||||
|
mediaPreviewContainer.onscroll = () => window.updateDots(mediaPreviewContainer);
|
||||||
|
enableDragScroll(mediaPreviewContainer); // Desktop Swipe Support
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share (Logic Updated for IndexedDB)
|
// Share Handler
|
||||||
if (sharePostBtn) {
|
if (sharePostBtn) {
|
||||||
sharePostBtn.addEventListener('click', async () => {
|
sharePostBtn.addEventListener('click', async () => {
|
||||||
if (!imagePreview.src || imagePreview.classList.contains('hidden')) {
|
if (stagedMedia.length === 0) {
|
||||||
alert('Please select an image first.');
|
alert('Please select at least one photo or video.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const caption = captionInput.value.trim();
|
const caption = captionInput.value.trim();
|
||||||
const imageSrc = imagePreview.src;
|
|
||||||
const id = 'post_' + Date.now();
|
const id = 'post_' + Date.now();
|
||||||
|
|
||||||
// 1. Render Optimistically (Immediate Feedback)
|
|
||||||
const displayPost = {
|
const displayPost = {
|
||||||
id,
|
id,
|
||||||
imageSrc,
|
media: stagedMedia,
|
||||||
caption,
|
caption,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
username: 'you',
|
username: 'you',
|
||||||
@@ -595,11 +622,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
};
|
};
|
||||||
renderPost(displayPost, true);
|
renderPost(displayPost, true);
|
||||||
|
|
||||||
// 2. Save Metadata to LocalStorage (Small footprint)
|
|
||||||
// IMPORTANT: We do NOT save the imageSrc string here to save space
|
|
||||||
const storagePost = {
|
const storagePost = {
|
||||||
id,
|
id,
|
||||||
imageSrc: null, // Placeholder, will look up in DB
|
media: null,
|
||||||
caption,
|
caption,
|
||||||
timestamp: displayPost.timestamp,
|
timestamp: displayPost.timestamp,
|
||||||
username: displayPost.username,
|
username: displayPost.username,
|
||||||
@@ -610,13 +635,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
userPosts.unshift(storagePost);
|
userPosts.unshift(storagePost);
|
||||||
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
||||||
|
|
||||||
// 3. Save Image Data to IndexedDB (Large Capacity)
|
|
||||||
// Async operation
|
|
||||||
try {
|
try {
|
||||||
await saveImageToDB(id, imageSrc);
|
await saveMediaToDB(id, stagedMedia);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to save to DB:', e);
|
console.error('Failed to save media array:', e);
|
||||||
alert('Uploaded, but image data count not be saved permanently due to storage error.');
|
alert('Uploaded, but media could not be saved permanently due to storage error.');
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPostForm();
|
resetPostForm();
|
||||||
@@ -628,10 +651,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
function resetPostForm() {
|
function resetPostForm() {
|
||||||
if (imageInput) imageInput.value = '';
|
if (imageInput) imageInput.value = '';
|
||||||
if (captionInput) captionInput.value = '';
|
if (captionInput) captionInput.value = '';
|
||||||
if (imagePreview) {
|
stagedMedia = [];
|
||||||
imagePreview.src = '';
|
if (mediaPreviewContainer) {
|
||||||
imagePreview.classList.add('hidden');
|
mediaPreviewContainer.innerHTML = '';
|
||||||
|
mediaPreviewContainer.classList.add('hidden');
|
||||||
|
mediaPreviewContainer.onscroll = null;
|
||||||
}
|
}
|
||||||
|
const dots = document.querySelector('#create-post-view .carousel-dots');
|
||||||
|
if (dots) dots.remove();
|
||||||
|
|
||||||
const ph = document.querySelector('.upload-placeholder');
|
const ph = document.querySelector('.upload-placeholder');
|
||||||
if (ph) ph.classList.remove('hidden');
|
if (ph) ph.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -784,3 +784,89 @@ p {
|
|||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
/* Align with avatar */
|
/* Align with avatar */
|
||||||
}
|
}
|
||||||
|
/* --- CAROUSEL & VIDEO STYLES --- */
|
||||||
|
|
||||||
|
/* Carousel Container */
|
||||||
|
.media-carousel {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE/Edge */
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-carousel::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Individual Slide */
|
||||||
|
.media-slide {
|
||||||
|
min-width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
scroll-snap-align: center;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-slide img,
|
||||||
|
.media-slide video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover; /* or contain depending on pref */
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pagination Dots */
|
||||||
|
.carousel-dots {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.active {
|
||||||
|
background: #fff;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post Media Wrapper (replaces .post-image) */
|
||||||
|
.post-media-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 4/5;
|
||||||
|
background: #111;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update create post view to handle carousel preview */
|
||||||
|
#create-post-view .image-upload-wrapper {
|
||||||
|
/* Keep aspect ratio but allow overflow for carousel */
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#media-preview-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user