FixedImagePostingIssue
This commit is contained in:
209
script.js
209
script.js
@@ -1,4 +1,69 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// --- INDEXED DB HELPERS (For >5MB Storage) ---
|
||||||
|
const DB_NAME = 'SocialAppDB';
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
|
||||||
|
function openDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onupgradeneeded = (e) => {
|
||||||
|
const db = e.target.result;
|
||||||
|
if (!db.objectStoreNames.contains('images')) {
|
||||||
|
db.createObjectStore('images', { keyPath: 'id' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveImageToDB(id, dataUrl) {
|
||||||
|
try {
|
||||||
|
const db = await openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction('images', 'readwrite');
|
||||||
|
const store = tx.objectStore('images');
|
||||||
|
store.put({ id, data: dataUrl });
|
||||||
|
tx.oncomplete = () => resolve();
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('IndexedDB Save Error:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getImageFromDB(id) {
|
||||||
|
try {
|
||||||
|
const db = await openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction('images', 'readonly');
|
||||||
|
const store = tx.objectStore('images');
|
||||||
|
const request = store.get(id);
|
||||||
|
request.onsuccess = () => resolve(request.result ? request.result.data : null);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('IndexedDB Read Error:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteImageFromDB(id) {
|
||||||
|
try {
|
||||||
|
const db = await openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction('images', 'readwrite');
|
||||||
|
const store = tx.objectStore('images');
|
||||||
|
store.delete(id);
|
||||||
|
tx.oncomplete = () => resolve();
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('IndexedDB Delete Error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- QUERY SELECTORS ---
|
// --- QUERY SELECTORS ---
|
||||||
const loginForm = document.querySelector('.login-form');
|
const loginForm = document.querySelector('.login-form');
|
||||||
const loginContainer = document.querySelector('.login-container');
|
const loginContainer = document.querySelector('.login-container');
|
||||||
@@ -109,7 +174,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
void feedView.offsetWidth; // Reflow
|
void feedView.offsetWidth; // Reflow
|
||||||
feedView.classList.add('fade-in');
|
feedView.classList.add('fade-in');
|
||||||
|
|
||||||
loadPostState(); // Load data
|
initFeed(); // Load data (Async)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
feedView.classList.remove('fade-in');
|
feedView.classList.remove('fade-in');
|
||||||
@@ -169,76 +234,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return deletedPosts.includes(id);
|
return deletedPosts.includes(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPostState() {
|
// --- ASYNC LOAD FEED ---
|
||||||
const state = getPostState();
|
async function initFeed() {
|
||||||
// 1. Remove deleted static posts
|
// 1. Render User Posts (Fetch images from DB)
|
||||||
document.querySelectorAll('.post-card').forEach(card => {
|
|
||||||
const id = card.getAttribute('data-post-id');
|
|
||||||
if (id && isPostDeleted(id)) {
|
|
||||||
card.remove();
|
|
||||||
} else if (id && state[id]) {
|
|
||||||
// Restore Likes/Comments
|
|
||||||
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';
|
|
||||||
} else {
|
|
||||||
likeIcon.classList.remove('liked');
|
|
||||||
likeIcon.style.fill = 'none';
|
|
||||||
likeIcon.style.stroke = 'white';
|
|
||||||
}
|
|
||||||
likesText.innerText = data.likesCount + ' likes';
|
|
||||||
}
|
|
||||||
// Restore Comments
|
|
||||||
const commentsContainer = card.querySelector('.added-comments');
|
|
||||||
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') || '[]');
|
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||||
// We iterate correctly to put newest on top
|
|
||||||
[...savedUserPosts].reverse().forEach(post => {
|
// Use a loop to handle async properly
|
||||||
|
const postsToRender = [...savedUserPosts].reverse();
|
||||||
|
|
||||||
|
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}"]`)) {
|
||||||
renderPost(post, true);
|
// Try to get image from DB, fallback to post.imageSrc (legacy support)
|
||||||
// Re-apply state if needed? create function handles it mostly,
|
let imgSrc = post.imageSrc;
|
||||||
// but checking `state` for these dynamic posts is also good practice if we tracked likes on them.
|
if (!imgSrc || imgSrc.length < 100) { // If it's a placeholder (null)
|
||||||
// For now, let's assume they load fresh or we'd need to run the state logic on them too.
|
const dbImg = await getImageFromDB(post.id);
|
||||||
// Actually, logic is: render then apply state.
|
if (dbImg) imgSrc = dbImg;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Improve Load Flow
|
if (imgSrc) {
|
||||||
function initFeed() {
|
const postWithImg = { ...post, imageSrc: imgSrc };
|
||||||
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
renderPost(postWithImg, true);
|
||||||
[...savedUserPosts].reverse().forEach(post => {
|
}
|
||||||
if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
|
}
|
||||||
renderPost(post, true);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
// 2. Restore Interaction State (Likes/Comments)
|
||||||
|
// 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');
|
||||||
@@ -276,6 +297,9 @@ 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;
|
||||||
|
|
||||||
const feedContainer = document.querySelector('.feed-container');
|
const feedContainer = document.querySelector('.feed-container');
|
||||||
const article = document.createElement('article');
|
const article = document.createElement('article');
|
||||||
article.className = 'post-card';
|
article.className = 'post-card';
|
||||||
@@ -325,7 +349,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (prepend) feedContainer.insertBefore(article, feedContainer.firstChild);
|
if (prepend) {
|
||||||
|
// Check again for safety inside async flows
|
||||||
|
if (!feedContainer.querySelector(`[data-post-id="${post.id}"]`)) {
|
||||||
|
feedContainer.insertBefore(article, feedContainer.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
else feedContainer.appendChild(article);
|
else feedContainer.appendChild(article);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,6 +397,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
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);
|
||||||
|
|
||||||
// Also ban ID to be safe
|
// 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)) {
|
||||||
@@ -415,18 +447,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. COMMENT TOGGLE
|
// 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 && !commentBtn.querySelector('.heart-icon') && !commentBtn.closest('.post-actions').querySelector('.action-left').contains(commentBtn) === false) {
|
if (commentBtn) {
|
||||||
// The comment button is the second one in action-left.
|
|
||||||
// Easier check:
|
|
||||||
const svgs = commentBtn.innerHTML;
|
const svgs = commentBtn.innerHTML;
|
||||||
if (svgs.includes('M21 11.5')) {
|
if (svgs.includes('M21 11.5')) { // Comment icon path substring
|
||||||
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');
|
||||||
if (commentSection.classList.contains('active')) {
|
if (commentSection.classList.contains('active')) {
|
||||||
commentSection.querySelector('input').focus();
|
const input = commentSection.querySelector('input');
|
||||||
|
if (input) input.focus();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -540,9 +572,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share
|
// Share (Logic Updated for IndexedDB)
|
||||||
if (sharePostBtn) {
|
if (sharePostBtn) {
|
||||||
sharePostBtn.addEventListener('click', () => {
|
sharePostBtn.addEventListener('click', async () => {
|
||||||
if (!imagePreview.src || imagePreview.classList.contains('hidden')) {
|
if (!imagePreview.src || imagePreview.classList.contains('hidden')) {
|
||||||
alert('Please select an image first.');
|
alert('Please select an image first.');
|
||||||
return;
|
return;
|
||||||
@@ -552,7 +584,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const imageSrc = imagePreview.src;
|
const imageSrc = imagePreview.src;
|
||||||
const id = 'post_' + Date.now();
|
const id = 'post_' + Date.now();
|
||||||
|
|
||||||
const newPost = {
|
// 1. Render Optimistically (Immediate Feedback)
|
||||||
|
const displayPost = {
|
||||||
id,
|
id,
|
||||||
imageSrc,
|
imageSrc,
|
||||||
caption,
|
caption,
|
||||||
@@ -560,17 +593,35 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
username: 'you',
|
username: 'you',
|
||||||
likes: 0
|
likes: 0
|
||||||
};
|
};
|
||||||
|
renderPost(displayPost, true);
|
||||||
|
|
||||||
|
// 2. Save Metadata to LocalStorage (Small footprint)
|
||||||
|
// IMPORTANT: We do NOT save the imageSrc string here to save space
|
||||||
|
const storagePost = {
|
||||||
|
id,
|
||||||
|
imageSrc: null, // Placeholder, will look up in DB
|
||||||
|
caption,
|
||||||
|
timestamp: displayPost.timestamp,
|
||||||
|
username: displayPost.username,
|
||||||
|
likes: 0
|
||||||
|
};
|
||||||
|
|
||||||
const userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
const userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
||||||
userPosts.unshift(newPost);
|
userPosts.unshift(storagePost);
|
||||||
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
|
||||||
|
|
||||||
renderPost(newPost, true);
|
// 3. Save Image Data to IndexedDB (Large Capacity)
|
||||||
|
// Async operation
|
||||||
|
try {
|
||||||
|
await saveImageToDB(id, imageSrc);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save to DB:', e);
|
||||||
|
alert('Uploaded, but image data count not be saved permanently due to storage error.');
|
||||||
|
}
|
||||||
|
|
||||||
resetPostForm();
|
resetPostForm();
|
||||||
createPostView.classList.add('hidden');
|
createPostView.classList.add('hidden');
|
||||||
feedView.classList.remove('hidden');
|
feedView.classList.remove('hidden');
|
||||||
|
|
||||||
// Note: Don't need full reload, but let's ensure state is clean
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user