DeletePost
This commit is contained in:
716
script.js
716
script.js
@@ -1,4 +1,5 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// --- QUERY SELECTORS ---
|
||||
const loginForm = document.querySelector('.login-form');
|
||||
const loginContainer = document.querySelector('.login-container');
|
||||
const feedView = document.getElementById('feed-view');
|
||||
@@ -7,15 +8,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const confirmPasswordInput = document.getElementById('confirm-password');
|
||||
const confirmPasswordGroup = document.getElementById('confirm-password-group');
|
||||
const submitBtn = document.querySelector('.btn-primary');
|
||||
const btnText = submitBtn.querySelector('span');
|
||||
const helperText = document.querySelector('.signup-link');
|
||||
const btnText = submitBtn ? submitBtn.querySelector('span') : null;
|
||||
const toggleLink = document.querySelector('.signup-link a');
|
||||
const title = document.querySelector('.brand-section h1');
|
||||
const subtitle = document.querySelector('.brand-section p');
|
||||
|
||||
// Post Creation Elements
|
||||
const createPostView = document.getElementById('create-post-view');
|
||||
const cancelPostBtn = document.getElementById('cancel-post-btn');
|
||||
const sharePostBtn = document.getElementById('share-post-btn');
|
||||
const uploadTrigger = document.getElementById('upload-trigger');
|
||||
const imageInput = document.getElementById('post-image-input');
|
||||
const imagePreview = document.getElementById('image-preview');
|
||||
const captionInput = document.getElementById('post-caption-input');
|
||||
const navPostBtn = document.querySelector('.nav-btn:nth-child(2)');
|
||||
const navHomeBtn = document.querySelector('.nav-btn:first-child');
|
||||
|
||||
let isLoginMode = true;
|
||||
|
||||
// Toggle between Login and Signup
|
||||
// --- AUTH LOGIC ---
|
||||
if (toggleLink) {
|
||||
toggleLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
@@ -25,47 +36,53 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
if (!title || !subtitle || !btnText || !confirmPasswordGroup) return;
|
||||
|
||||
if (isLoginMode) {
|
||||
title.innerText = 'Welcome Back';
|
||||
subtitle.innerText = 'Enter your details below';
|
||||
btnText.innerText = 'Sign In';
|
||||
helperText.innerHTML = 'Don\'t have an account? <a href="#">Create One</a>';
|
||||
const helper = document.querySelector('.signup-link');
|
||||
if (helper) helper.innerHTML = 'Don\'t have an account? <a href="#">Create One</a>';
|
||||
confirmPasswordGroup.classList.add('hidden');
|
||||
confirmPasswordInput.required = false;
|
||||
} else {
|
||||
title.innerText = 'Create Account';
|
||||
subtitle.innerText = 'Start your journey with us';
|
||||
btnText.innerText = 'Sign Up';
|
||||
helperText.innerHTML = 'Already have an account? <a href="#">Sign In</a>';
|
||||
const helper = document.querySelector('.signup-link');
|
||||
if (helper) helper.innerHTML = 'Already have an account? <a href="#">Sign In</a>';
|
||||
confirmPasswordGroup.classList.remove('hidden');
|
||||
confirmPasswordInput.required = true;
|
||||
}
|
||||
|
||||
// Re-attach listener to the new link element
|
||||
const newLink = document.querySelector('.signup-link a');
|
||||
newLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
isLoginMode = !isLoginMode;
|
||||
updateUI();
|
||||
});
|
||||
if (newLink) {
|
||||
newLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
isLoginMode = !isLoginMode;
|
||||
updateUI();
|
||||
});
|
||||
}
|
||||
|
||||
// Reset form and styles
|
||||
loginForm.reset();
|
||||
if (loginForm) loginForm.reset();
|
||||
resetButton();
|
||||
}
|
||||
|
||||
function resetButton() {
|
||||
if (!submitBtn || !btnText) return;
|
||||
submitBtn.style.background = '';
|
||||
submitBtn.style.transform = '';
|
||||
btnText.innerText = isLoginMode ? 'Sign In' : 'Sign Up';
|
||||
}
|
||||
|
||||
function showFeedback(isSuccess, message) {
|
||||
if (!btnText || !submitBtn) return;
|
||||
btnText.innerText = message;
|
||||
if (isSuccess) {
|
||||
submitBtn.style.background = 'linear-gradient(135deg, #10B981 0%, #059669 100%)'; // Success Green
|
||||
submitBtn.style.background = 'linear-gradient(135deg, #10B981 0%, #059669 100%)';
|
||||
} else {
|
||||
submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)'; // Error Red
|
||||
submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)';
|
||||
submitBtn.animate([
|
||||
{ transform: 'translateX(0)' },
|
||||
{ transform: 'translateX(-5px)' },
|
||||
@@ -83,77 +100,54 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
function navigateToFeed() {
|
||||
if (loginContainer && feedView) {
|
||||
// Fade out login
|
||||
loginContainer.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
loginContainer.classList.add('hidden');
|
||||
loginContainer.style.display = 'none'; // Ensure it's gone
|
||||
loginContainer.style.display = 'none';
|
||||
|
||||
// Show Feed
|
||||
feedView.classList.remove('hidden');
|
||||
// Trigger reflow
|
||||
void feedView.offsetWidth;
|
||||
void feedView.offsetWidth; // Reflow
|
||||
feedView.classList.add('fade-in');
|
||||
|
||||
// Load persisted state for posts
|
||||
loadPostState();
|
||||
loadPostState(); // Load data
|
||||
|
||||
// Remove fade-in class after animation to fix fixed positioning context
|
||||
setTimeout(() => {
|
||||
feedView.classList.remove('fade-in');
|
||||
}, 600);
|
||||
}, 600); // Wait for transition
|
||||
}, 600);
|
||||
}
|
||||
}
|
||||
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = emailInput.value.trim();
|
||||
const password = passwordInput.value.trim();
|
||||
const confirmPassword = confirmPasswordInput.value.trim();
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Get existing users
|
||||
const existingUsers = JSON.parse(localStorage.getItem('socialAppUsers') || '[]');
|
||||
|
||||
if (isLoginMode) {
|
||||
// --- LOGIN LOGIC ---
|
||||
const user = existingUsers.find(u => u.email === email && u.password === password);
|
||||
|
||||
if (user) {
|
||||
console.log('Login Successful:', user);
|
||||
showFeedback(true, 'Success!');
|
||||
setTimeout(navigateToFeed, 1000);
|
||||
} else {
|
||||
console.warn('Login Failed: Invalid credentials');
|
||||
showFeedback(false, 'Incorrect details');
|
||||
}
|
||||
|
||||
} else {
|
||||
// --- SIGNUP LOGIC ---
|
||||
// 1. Check if user already exists
|
||||
if (existingUsers.some(u => u.email === email)) {
|
||||
showFeedback(false, 'User exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Validate Password Match
|
||||
if (password !== confirmPassword) {
|
||||
showFeedback(false, 'Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
const newUser = { email, password, joinedAt: timestamp };
|
||||
existingUsers.push(newUser);
|
||||
localStorage.setItem('socialAppUsers', JSON.stringify(existingUsers));
|
||||
|
||||
console.log('New User Registered:', newUser);
|
||||
showFeedback(true, 'Account Created!');
|
||||
|
||||
// Switch to login mode after success
|
||||
setTimeout(() => {
|
||||
isLoginMode = true;
|
||||
updateUI();
|
||||
@@ -163,290 +157,150 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
}
|
||||
|
||||
// --- PERSISTENCE LOGIC ---
|
||||
// --- DATA HELPERS ---
|
||||
function getPostState() {
|
||||
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 loadPostState() {
|
||||
const state = getPostState();
|
||||
const posts = document.querySelectorAll('.post-card');
|
||||
|
||||
posts.forEach(card => {
|
||||
// 1. Remove deleted static posts
|
||||
document.querySelectorAll('.post-card').forEach(card => {
|
||||
const id = card.getAttribute('data-post-id');
|
||||
if (id && state[id]) {
|
||||
if (id && isPostDeleted(id)) {
|
||||
card.remove();
|
||||
} else if (id && state[id]) {
|
||||
// Restore Likes/Comments
|
||||
const data = state[id];
|
||||
|
||||
// Restore Likes
|
||||
const likeIcon = card.querySelector('.heart-icon');
|
||||
const likesText = card.querySelector('.likes');
|
||||
|
||||
if (data.liked) {
|
||||
likeIcon.classList.add('liked');
|
||||
likeIcon.style.fill = '#EF4444';
|
||||
likeIcon.style.stroke = '#EF4444';
|
||||
} else {
|
||||
likeIcon.classList.remove('liked');
|
||||
likeIcon.style.fill = 'none';
|
||||
likeIcon.style.stroke = 'white';
|
||||
if (likeIcon && likesText) {
|
||||
if (data.liked) {
|
||||
likeIcon.classList.add('liked');
|
||||
likeIcon.style.fill = '#EF4444';
|
||||
likeIcon.style.stroke = '#EF4444';
|
||||
} else {
|
||||
likeIcon.classList.remove('liked');
|
||||
likeIcon.style.fill = 'none';
|
||||
likeIcon.style.stroke = 'white';
|
||||
}
|
||||
likesText.innerText = data.likesCount + ' likes';
|
||||
}
|
||||
likesText.innerText = data.likesCount + ' likes';
|
||||
|
||||
// Restore Comments
|
||||
const commentsContainer = card.querySelector('.added-comments');
|
||||
commentsContainer.innerHTML = ''; // Clear existing to prevent dupes on re-run
|
||||
if (data.comments && data.comments.length > 0) {
|
||||
data.comments.forEach(text => {
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.classList.add('comment-item');
|
||||
commentEl.innerHTML = `<span class="comment-user">you</span> ${text}`;
|
||||
commentsContainer.appendChild(commentEl);
|
||||
});
|
||||
if (commentsContainer) {
|
||||
commentsContainer.innerHTML = '';
|
||||
if (data.comments && data.comments.length > 0) {
|
||||
data.comments.forEach(text => {
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.classList.add('comment-item');
|
||||
commentEl.innerHTML = `<span class="comment-user">you</span> ${text}`;
|
||||
commentsContainer.appendChild(commentEl);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Render User Posts (if not already present)
|
||||
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||
// We iterate correctly to put newest on top
|
||||
[...savedUserPosts].reverse().forEach(post => {
|
||||
if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||
renderPost(post, true);
|
||||
// Re-apply state if needed? create function handles it mostly,
|
||||
// but checking `state` for these dynamic posts is also good practice if we tracked likes on them.
|
||||
// For now, let's assume they load fresh or we'd need to run the state logic on them too.
|
||||
// Actually, logic is: render then apply state.
|
||||
}
|
||||
});
|
||||
|
||||
// Re-run state application for newly added dynamic posts
|
||||
// Optimization: splitting this logic explicitly would be better, but re-querying all is fine for small apps.
|
||||
// Let's just do a second pass for dynamic posts to support likes on them?
|
||||
// Existing loop `document.querySelectorAll` covers them if they are in DOM.
|
||||
// So this order (Filter Static -> Render Dynamic -> (Ideally re-check state on all)) is slightly off.
|
||||
// Better: Render Dynamic First, THEN apply state to ALL.
|
||||
}
|
||||
|
||||
// --- FEED INTERACTIONS ---
|
||||
if (feedView) {
|
||||
feedView.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
// Improve Load Flow
|
||||
function initFeed() {
|
||||
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||
[...savedUserPosts].reverse().forEach(post => {
|
||||
if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||
renderPost(post, true);
|
||||
}
|
||||
});
|
||||
|
||||
// 1. LIKE ACTION
|
||||
const likeBtn = target.closest('.heart-icon')?.closest('.icon-btn');
|
||||
if (likeBtn) {
|
||||
const icon = likeBtn.querySelector('.heart-icon');
|
||||
const postCard = likeBtn.closest('.post-card');
|
||||
const likesText = postCard.querySelector('.likes');
|
||||
const postId = postCard.getAttribute('data-post-id');
|
||||
|
||||
// Toggle State
|
||||
icon.classList.toggle('liked');
|
||||
|
||||
// Update Count (Simple parsing)
|
||||
let count = parseInt(likesText.innerText);
|
||||
let isLiked = false;
|
||||
|
||||
if (icon.classList.contains('liked')) {
|
||||
icon.style.fill = '#EF4444';
|
||||
icon.style.stroke = '#EF4444';
|
||||
count++;
|
||||
isLiked = true;
|
||||
} else {
|
||||
icon.style.fill = 'none';
|
||||
icon.style.stroke = 'white';
|
||||
count--;
|
||||
isLiked = false;
|
||||
}
|
||||
likesText.innerText = count + ' likes';
|
||||
|
||||
// Save State
|
||||
if (postId) {
|
||||
const state = getPostState();
|
||||
if (!state[postId]) state[postId] = { comments: [] };
|
||||
state[postId].liked = isLiked;
|
||||
state[postId].likesCount = count;
|
||||
// Preserve existing comments if we didn't just load them
|
||||
if (!state[postId].comments) state[postId].comments = [];
|
||||
savePostState(state);
|
||||
}
|
||||
const state = getPostState();
|
||||
document.querySelectorAll('.post-card').forEach(card => {
|
||||
const id = card.getAttribute('data-post-id');
|
||||
if (id && isPostDeleted(id)) {
|
||||
card.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. COMMENT TOGGLE ACTION
|
||||
const commentBtn = target.closest('.icon-btn');
|
||||
if (commentBtn && !commentBtn.querySelector('.heart-icon')) {
|
||||
// Check if it's the comment button (has specific path)
|
||||
const path = commentBtn.querySelector('path');
|
||||
if (path && (path.getAttribute('d').startsWith('M21 11.5') || path.closest('svg')?.innerHTML.includes('M21 11.5'))) {
|
||||
const postCard = commentBtn.closest('.post-card');
|
||||
const commentSection = postCard.querySelector('.comment-section');
|
||||
commentSection.classList.toggle('active');
|
||||
if (commentSection.classList.contains('active')) {
|
||||
commentSection.querySelector('input').focus();
|
||||
if (id && state[id]) {
|
||||
const data = state[id];
|
||||
const likeIcon = card.querySelector('.heart-icon');
|
||||
const likesText = card.querySelector('.likes');
|
||||
if (likeIcon && likesText) {
|
||||
if (data.liked) {
|
||||
likeIcon.classList.add('liked');
|
||||
likeIcon.style.fill = '#EF4444';
|
||||
likeIcon.style.stroke = '#EF4444';
|
||||
}
|
||||
return;
|
||||
likesText.innerText = data.likesCount + ' likes';
|
||||
}
|
||||
}
|
||||
|
||||
// 3. POST COMMENT ACTION
|
||||
if (target.classList.contains('post-btn')) {
|
||||
const wrapper = target.closest('.comment-input-wrapper');
|
||||
const input = wrapper.querySelector('.comment-input');
|
||||
const text = input.value.trim();
|
||||
const postCard = wrapper.closest('.post-card');
|
||||
const postId = postCard.getAttribute('data-post-id');
|
||||
|
||||
if (text) {
|
||||
addComment(wrapper.closest('.comment-section'), text);
|
||||
|
||||
if (postId) {
|
||||
const state = getPostState();
|
||||
if (!state[postId]) state[postId] = { liked: false, likesCount: parseInt(postCard.querySelector('.likes').innerText) || 0, comments: [] };
|
||||
if (!state[postId].comments) state[postId].comments = [];
|
||||
state[postId].comments.push(text);
|
||||
savePostState(state);
|
||||
const commentsContainer = card.querySelector('.added-comments');
|
||||
if (commentsContainer) {
|
||||
commentsContainer.innerHTML = '';
|
||||
if (data.comments) {
|
||||
data.comments.forEach(text => {
|
||||
const el = document.createElement('div');
|
||||
el.classList.add('comment-item');
|
||||
el.innerHTML = `<span class="comment-user">you</span> ${text}`;
|
||||
commentsContainer.appendChild(el);
|
||||
});
|
||||
}
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Enter key for comments
|
||||
feedView.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && e.target.classList.contains('comment-input')) {
|
||||
const text = e.target.value.trim();
|
||||
const wrapper = e.target.closest('.comment-input-wrapper');
|
||||
const postCard = wrapper.closest('.post-card');
|
||||
const postId = postCard.getAttribute('data-post-id');
|
||||
|
||||
if (text) {
|
||||
addComment(e.target.closest('.comment-section'), text);
|
||||
|
||||
if (postId) {
|
||||
const state = getPostState();
|
||||
if (!state[postId]) state[postId] = { liked: false, likesCount: parseInt(postCard.querySelector('.likes').innerText) || 0, comments: [] };
|
||||
if (!state[postId].comments) state[postId].comments = [];
|
||||
state[postId].comments.push(text);
|
||||
savePostState(state);
|
||||
}
|
||||
e.target.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addComment(section, text) {
|
||||
const container = section.querySelector('.added-comments');
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.classList.add('comment-item');
|
||||
commentEl.innerHTML = `<span class="comment-user">you</span> ${text}`;
|
||||
container.appendChild(commentEl);
|
||||
}
|
||||
|
||||
// --- CREATE POST LOGIC ---
|
||||
const createPostView = document.getElementById('create-post-view');
|
||||
const cancelPostBtn = document.getElementById('cancel-post-btn');
|
||||
const sharePostBtn = document.getElementById('share-post-btn');
|
||||
const uploadTrigger = document.getElementById('upload-trigger');
|
||||
const imageInput = document.getElementById('post-image-input');
|
||||
const imagePreview = document.getElementById('image-preview');
|
||||
const captionInput = document.getElementById('post-caption-input');
|
||||
const navPostBtn = document.querySelector('.nav-btn:nth-child(2)'); // Center Post Button
|
||||
const navHomeBtn = document.querySelector('.nav-btn:first-child');
|
||||
|
||||
// 1. Navigation
|
||||
if (navPostBtn) {
|
||||
navPostBtn.addEventListener('click', () => {
|
||||
feedView.classList.add('hidden');
|
||||
createPostView.classList.remove('hidden');
|
||||
createPostView.classList.add('fade-in');
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelPostBtn) {
|
||||
cancelPostBtn.addEventListener('click', () => {
|
||||
resetPostForm();
|
||||
createPostView.classList.add('hidden');
|
||||
feedView.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (navHomeBtn && feedView.classList.contains('hidden') && !createPostView.classList.contains('hidden')) {
|
||||
// Logic handled by cancel mostly, but good to have explicit nav
|
||||
navHomeBtn.addEventListener('click', () => {
|
||||
if (!createPostView.classList.contains('hidden')) {
|
||||
resetPostForm();
|
||||
createPostView.classList.add('hidden');
|
||||
feedView.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Image Selection
|
||||
if (uploadTrigger) {
|
||||
uploadTrigger.addEventListener('click', () => imageInput.click());
|
||||
}
|
||||
|
||||
if (imageInput) {
|
||||
imageInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
imagePreview.src = e.target.result;
|
||||
imagePreview.classList.remove('hidden');
|
||||
document.querySelector('.upload-placeholder').classList.add('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Share Post
|
||||
if (sharePostBtn) {
|
||||
sharePostBtn.addEventListener('click', () => {
|
||||
if (!imagePreview.src || imagePreview.classList.contains('hidden')) {
|
||||
alert('Please select an image first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const caption = captionInput.value.trim();
|
||||
const imageSrc = imagePreview.src;
|
||||
const id = 'post_' + Date.now();
|
||||
|
||||
const newPost = {
|
||||
id,
|
||||
imageSrc,
|
||||
caption,
|
||||
timestamp: new Date().toISOString(),
|
||||
username: 'you', // Mock user
|
||||
likes: 0
|
||||
};
|
||||
|
||||
// Save to LocalStorage
|
||||
const userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||
userPosts.unshift(newPost); // Add to top
|
||||
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
||||
|
||||
// Render to Feed
|
||||
renderPost(newPost, true); // true = prepend
|
||||
|
||||
// Reset and Go Back
|
||||
resetPostForm();
|
||||
createPostView.classList.add('hidden');
|
||||
feedView.classList.remove('hidden');
|
||||
|
||||
// Re-bind listeners isn't needed strictly due to delegation, but state refresh might be
|
||||
loadPostState();
|
||||
});
|
||||
}
|
||||
|
||||
function resetPostForm() {
|
||||
imageInput.value = '';
|
||||
captionInput.value = '';
|
||||
imagePreview.src = '';
|
||||
imagePreview.classList.add('hidden');
|
||||
document.querySelector('.upload-placeholder').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// --- RENDER POST ---
|
||||
function renderPost(post, prepend = false) {
|
||||
const feedContainer = document.querySelector('.feed-container');
|
||||
const article = document.createElement('article');
|
||||
article.className = 'post-card';
|
||||
article.setAttribute('data-post-id', post.id);
|
||||
|
||||
const isUserPost = post.id && post.id.startsWith('post_');
|
||||
const optionsHTML = isUserPost ? `
|
||||
<button class="icon-btn-sm options-trigger">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="options-menu">
|
||||
<button class="menu-btn delete">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
|
||||
Delete Post
|
||||
</button>
|
||||
</div>
|
||||
` : `<button class="icon-btn-sm">...</button>`;
|
||||
|
||||
article.innerHTML = `
|
||||
<div class="post-header">
|
||||
<div class="post-user">
|
||||
<img src="https://i.pravatar.cc/150?img=12" alt="User" class="avatar-sm">
|
||||
<span class="username">${post.username}</span>
|
||||
</div>
|
||||
<button class="icon-btn-sm">...</button>
|
||||
${optionsHTML}
|
||||
</div>
|
||||
<div class="post-image">
|
||||
<img src="${post.imageSrc}" alt="Post">
|
||||
@@ -471,49 +325,263 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// This is a bit tricky with existing static posts.
|
||||
// Ideally we keep static posts static and just inject dynamic ones.
|
||||
if (prepend) {
|
||||
feedContainer.insertBefore(article, feedContainer.firstChild);
|
||||
// Note: index.html has static articles directly. `firstChild` might be text node or first article.
|
||||
// A safer insert would be after the first spacer if I had one, or simply `prepend`.
|
||||
// Let's use `prepend` on the container, but strict ordering might be weird with existing static HTML.
|
||||
// Actually, `prepend` puts it before the first child, which IS the first post card. Perfect.
|
||||
} else {
|
||||
feedContainer.appendChild(article);
|
||||
}
|
||||
if (prepend) feedContainer.insertBefore(article, feedContainer.firstChild);
|
||||
else feedContainer.appendChild(article);
|
||||
}
|
||||
|
||||
// Load User Posts on Init
|
||||
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||
savedUserPosts.forEach(post => {
|
||||
// Check if already in DOM to avoid dupes (simple check)
|
||||
if (!document.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||
renderPost(post, true); // Keep order inverted?
|
||||
// If we loop saved posts [newest, ..., oldest], we should Append?
|
||||
// Actually saved as [newest, oldest].
|
||||
// If we prepend one by one:
|
||||
// 1. Newest -> Prepend -> Top.
|
||||
// 2. Oldest -> Prepend -> Top (Wrong).
|
||||
// So we should iterate in reverse if we prepend, OR just append at top once properly sorted.
|
||||
// Let's keep it simple: Iterate in reverse order [oldest ... newest] and prepend each, so newest ends up on top.
|
||||
}
|
||||
});
|
||||
// Fix order: Reverse iteration for prepend logic
|
||||
// savedUserPosts.reverse().forEach(...) -> Wait, `unshift` adds to start. So [0] is newest.
|
||||
// Loop:
|
||||
// Post 0 (Newest) -> Prepend (become first).
|
||||
// Post 1 (Older) -> Prepend (become first).
|
||||
// Result: Post 1 is above Post 0. WRONG.
|
||||
// Correct: Iterate from last to first (reverse) and prepend.
|
||||
// Post Last (Oldest) -> Prepend.
|
||||
// ...
|
||||
// Post 0 (Newest) -> Prepend (Top). CORRECT.
|
||||
// So:
|
||||
[...savedUserPosts].reverse().forEach(post => {
|
||||
if (!document.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||
renderPost(post, true);
|
||||
}
|
||||
});
|
||||
// --- FEED LISTENERS ---
|
||||
if (feedView) {
|
||||
initFeed(); // Initial Load logic
|
||||
|
||||
feedView.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
|
||||
// 1. OPTIONS MENU
|
||||
const trigger = target.closest('.options-trigger');
|
||||
if (trigger) {
|
||||
const header = trigger.closest('.post-header');
|
||||
const menu = header.querySelector('.options-menu');
|
||||
if (menu) {
|
||||
document.querySelectorAll('.options-menu.active').forEach(m => {
|
||||
if (m !== menu) m.classList.remove('active');
|
||||
});
|
||||
menu.classList.toggle('active');
|
||||
e.stopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. DELETE ACTION
|
||||
const deleteBtn = target.closest('.delete');
|
||||
if (deleteBtn) {
|
||||
const card = deleteBtn.closest('.post-card');
|
||||
const id = card.getAttribute('data-post-id');
|
||||
|
||||
if (id && id.startsWith('post_')) { // Double check ownership
|
||||
if (confirm('Delete this post?')) {
|
||||
card.style.transition = 'opacity 0.3s, transform 0.3s';
|
||||
card.style.opacity = '0';
|
||||
card.style.transform = 'scale(0.9)';
|
||||
setTimeout(() => card.remove(), 300);
|
||||
|
||||
let userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||
userPosts = userPosts.filter(p => p.id !== id);
|
||||
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
||||
|
||||
// Also ban ID to be safe
|
||||
const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]');
|
||||
if (!deletedPosts.includes(id)) {
|
||||
deletedPosts.push(id);
|
||||
localStorage.setItem('socialAppDeletedPosts', JSON.stringify(deletedPosts));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. LIKE ACTION
|
||||
const likeBtn = target.closest('.heart-icon')?.closest('.icon-btn');
|
||||
if (likeBtn) {
|
||||
const icon = likeBtn.querySelector('.heart-icon');
|
||||
const postCard = likeBtn.closest('.post-card');
|
||||
const likesText = postCard.querySelector('.likes');
|
||||
const postId = postCard.getAttribute('data-post-id');
|
||||
|
||||
icon.classList.toggle('liked');
|
||||
let count = parseInt(likesText.innerText);
|
||||
let isLiked = false;
|
||||
|
||||
if (icon.classList.contains('liked')) {
|
||||
icon.style.fill = '#EF4444';
|
||||
icon.style.stroke = '#EF4444';
|
||||
count++;
|
||||
isLiked = true;
|
||||
} else {
|
||||
icon.style.fill = 'none';
|
||||
icon.style.stroke = 'white';
|
||||
count--;
|
||||
isLiked = false;
|
||||
}
|
||||
likesText.innerText = count + ' likes';
|
||||
|
||||
if (postId) {
|
||||
const state = getPostState();
|
||||
if (!state[postId]) state[postId] = { comments: [] };
|
||||
state[postId].liked = isLiked;
|
||||
state[postId].likesCount = count;
|
||||
if (!state[postId].comments) state[postId].comments = [];
|
||||
savePostState(state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. COMMENT TOGGLE
|
||||
const commentBtn = target.closest('.icon-btn');
|
||||
if (commentBtn && !commentBtn.querySelector('.heart-icon') && !commentBtn.closest('.post-actions').querySelector('.action-left').contains(commentBtn) === false) {
|
||||
// The comment button is the second one in action-left.
|
||||
// Easier check:
|
||||
const svgs = commentBtn.innerHTML;
|
||||
if (svgs.includes('M21 11.5')) {
|
||||
const postCard = commentBtn.closest('.post-card');
|
||||
const commentSection = postCard.querySelector('.comment-section');
|
||||
commentSection.classList.toggle('active');
|
||||
if (commentSection.classList.contains('active')) {
|
||||
commentSection.querySelector('input').focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. POST COMMENT
|
||||
if (target.classList.contains('post-btn')) {
|
||||
const wrapper = target.closest('.comment-input-wrapper');
|
||||
const input = wrapper.querySelector('.comment-input');
|
||||
const text = input.value.trim();
|
||||
const postCard = wrapper.closest('.post-card');
|
||||
const postId = postCard.getAttribute('data-post-id');
|
||||
|
||||
if (text) {
|
||||
addComment(wrapper.closest('.comment-section'), text);
|
||||
if (postId) {
|
||||
const state = getPostState();
|
||||
if (!state[postId]) state[postId] = { liked: false, likesCount: parseInt(postCard.querySelector('.likes').innerText) || 0, comments: [] };
|
||||
if (!state[postId].comments) state[postId].comments = [];
|
||||
state[postId].comments.push(text);
|
||||
savePostState(state);
|
||||
}
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Enter key for comments
|
||||
feedView.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && e.target.classList.contains('comment-input')) {
|
||||
const text = e.target.value.trim();
|
||||
const wrapper = e.target.closest('.comment-input-wrapper');
|
||||
const postCard = wrapper.closest('.post-card');
|
||||
const postId = postCard.getAttribute('data-post-id');
|
||||
|
||||
if (text) {
|
||||
addComment(e.target.closest('.comment-section'), text);
|
||||
if (postId) {
|
||||
const state = getPostState();
|
||||
if (!state[postId]) state[postId] = { liked: false, likesCount: parseInt(postCard.querySelector('.likes').innerText) || 0, comments: [] };
|
||||
if (!state[postId].comments) state[postId].comments = [];
|
||||
state[postId].comments.push(text);
|
||||
savePostState(state);
|
||||
}
|
||||
e.target.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close menus
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.options-menu') && !e.target.closest('.options-trigger')) {
|
||||
document.querySelectorAll('.options-menu.active').forEach(m => m.classList.remove('active'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addComment(section, text) {
|
||||
const container = section.querySelector('.added-comments');
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.classList.add('comment-item');
|
||||
commentEl.innerHTML = `<span class="comment-user">you</span> ${text}`;
|
||||
container.appendChild(commentEl);
|
||||
}
|
||||
|
||||
// --- CREATE POST LOGIC ---
|
||||
// Navigation
|
||||
if (navPostBtn) {
|
||||
navPostBtn.addEventListener('click', () => {
|
||||
if (feedView && createPostView) {
|
||||
feedView.classList.add('hidden');
|
||||
createPostView.classList.remove('hidden');
|
||||
createPostView.classList.add('fade-in');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel
|
||||
if (cancelPostBtn) {
|
||||
cancelPostBtn.addEventListener('click', () => {
|
||||
resetPostForm();
|
||||
createPostView.classList.add('hidden');
|
||||
feedView.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
// Home Nav
|
||||
if (navHomeBtn) {
|
||||
navHomeBtn.addEventListener('click', () => {
|
||||
if (createPostView && !createPostView.classList.contains('hidden')) {
|
||||
resetPostForm();
|
||||
createPostView.classList.add('hidden');
|
||||
feedView.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Upload
|
||||
if (uploadTrigger && imageInput) {
|
||||
uploadTrigger.addEventListener('click', () => imageInput.click());
|
||||
imageInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
imagePreview.src = e.target.result;
|
||||
imagePreview.classList.remove('hidden');
|
||||
document.querySelector('.upload-placeholder').classList.add('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Share
|
||||
if (sharePostBtn) {
|
||||
sharePostBtn.addEventListener('click', () => {
|
||||
if (!imagePreview.src || imagePreview.classList.contains('hidden')) {
|
||||
alert('Please select an image first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const caption = captionInput.value.trim();
|
||||
const imageSrc = imagePreview.src;
|
||||
const id = 'post_' + Date.now();
|
||||
|
||||
const newPost = {
|
||||
id,
|
||||
imageSrc,
|
||||
caption,
|
||||
timestamp: new Date().toISOString(),
|
||||
username: 'you',
|
||||
likes: 0
|
||||
};
|
||||
|
||||
const userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||
userPosts.unshift(newPost);
|
||||
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
||||
|
||||
renderPost(newPost, true);
|
||||
resetPostForm();
|
||||
createPostView.classList.add('hidden');
|
||||
feedView.classList.remove('hidden');
|
||||
|
||||
// Note: Don't need full reload, but let's ensure state is clean
|
||||
});
|
||||
}
|
||||
|
||||
function resetPostForm() {
|
||||
if (imageInput) imageInput.value = '';
|
||||
if (captionInput) captionInput.value = '';
|
||||
if (imagePreview) {
|
||||
imagePreview.src = '';
|
||||
imagePreview.classList.add('hidden');
|
||||
}
|
||||
const ph = document.querySelector('.upload-placeholder');
|
||||
if (ph) ph.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -600,6 +600,57 @@ p {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Options Menu */
|
||||
.post-header {
|
||||
position: relative;
|
||||
/* Context for menu */
|
||||
}
|
||||
|
||||
.options-menu {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 15px;
|
||||
background: rgba(20, 20, 22, 0.95);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 5px;
|
||||
z-index: 10;
|
||||
min-width: 120px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.options-menu.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
text-align: left;
|
||||
color: var(--text-main);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.menu-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.menu-btn.delete {
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
/* Bottom Navigation */
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user