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', () => {
// --- 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 ---
const loginForm = document.querySelector('.login-form');
const loginContainer = document.querySelector('.login-container');
@@ -109,7 +174,7 @@ document.addEventListener('DOMContentLoaded', () => {
void feedView.offsetWidth; // Reflow
feedView.classList.add('fade-in');
loadPostState(); // Load data
initFeed(); // Load data (Async)
setTimeout(() => {
feedView.classList.remove('fade-in');
@@ -169,76 +234,32 @@ document.addEventListener('DOMContentLoaded', () => {
return deletedPosts.includes(id);
}
function loadPostState() {
const state = getPostState();
// 1. Remove deleted static posts
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';
// --- ASYNC LOAD FEED ---
async function initFeed() {
// 1. Render User Posts (Fetch images from DB)
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
// 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}"]`)) {
// Try to get image from DB, fallback to post.imageSrc (legacy support)
let imgSrc = post.imageSrc;
if (!imgSrc || imgSrc.length < 100) { // If it's a placeholder (null)
const dbImg = await getImageFromDB(post.id);
if (dbImg) imgSrc = dbImg;
}
// 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);
});
}
if (imgSrc) {
const postWithImg = { ...post, imageSrc: imgSrc };
renderPost(postWithImg, true);
}
}
});
// 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();
document.querySelectorAll('.post-card').forEach(card => {
const id = card.getAttribute('data-post-id');
@@ -276,6 +297,9 @@ document.addEventListener('DOMContentLoaded', () => {
// --- RENDER POST ---
function renderPost(post, prepend = false) {
// Avoid duplicates
if (document.querySelector(`[data-post-id="${post.id}"]`)) return;
const feedContainer = document.querySelector('.feed-container');
const article = document.createElement('article');
article.className = 'post-card';
@@ -325,7 +349,12 @@ document.addEventListener('DOMContentLoaded', () => {
</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);
}
@@ -368,6 +397,9 @@ document.addEventListener('DOMContentLoaded', () => {
userPosts = userPosts.filter(p => p.id !== id);
localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts));
// Delete from DB too
deleteImageFromDB(id);
// Also ban ID to be safe
const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]');
if (!deletedPosts.includes(id)) {
@@ -415,18 +447,18 @@ document.addEventListener('DOMContentLoaded', () => {
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');
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:
if (commentBtn) {
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 commentSection = postCard.querySelector('.comment-section');
commentSection.classList.toggle('active');
if (commentSection.classList.contains('active')) {
commentSection.querySelector('input').focus();
const input = commentSection.querySelector('input');
if (input) input.focus();
}
return;
}
@@ -540,9 +572,9 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// Share
// Share (Logic Updated for IndexedDB)
if (sharePostBtn) {
sharePostBtn.addEventListener('click', () => {
sharePostBtn.addEventListener('click', async () => {
if (!imagePreview.src || imagePreview.classList.contains('hidden')) {
alert('Please select an image first.');
return;
@@ -552,7 +584,8 @@ document.addEventListener('DOMContentLoaded', () => {
const imageSrc = imagePreview.src;
const id = 'post_' + Date.now();
const newPost = {
// 1. Render Optimistically (Immediate Feedback)
const displayPost = {
id,
imageSrc,
caption,
@@ -560,17 +593,35 @@ document.addEventListener('DOMContentLoaded', () => {
username: 'you',
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') || '[]');
userPosts.unshift(newPost);
userPosts.unshift(storagePost);
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();
createPostView.classList.add('hidden');
feedView.classList.remove('hidden');
// Note: Don't need full reload, but let's ensure state is clean
});
}