FixedImagePostingIssue

This commit is contained in:
2026-01-29 08:52:22 +11:00
parent 6262023aa9
commit 4b64a9e13b

213
script.js
View File

@@ -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 savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
const id = card.getAttribute('data-post-id');
if (id && isPostDeleted(id)) { // Use a loop to handle async properly
card.remove(); const postsToRender = [...savedUserPosts].reverse();
} else if (id && state[id]) {
// Restore Likes/Comments for (const post of postsToRender) {
const data = state[id]; if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
const likeIcon = card.querySelector('.heart-icon'); // Try to get image from DB, fallback to post.imageSrc (legacy support)
const likesText = card.querySelector('.likes'); let imgSrc = post.imageSrc;
if (likeIcon && likesText) { if (!imgSrc || imgSrc.length < 100) { // If it's a placeholder (null)
if (data.liked) { const dbImg = await getImageFromDB(post.id);
likeIcon.classList.add('liked'); if (dbImg) imgSrc = dbImg;
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 (imgSrc) {
if (commentsContainer) { const postWithImg = { ...post, imageSrc: imgSrc };
commentsContainer.innerHTML = ''; renderPost(postWithImg, true);
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.
}
// 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);
}
});
// 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
}); });
} }