PostV1
This commit is contained in:
29
index.html
29
index.html
@@ -226,6 +226,35 @@
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Create Post View -->
|
||||
<div id="create-post-view" class="hidden">
|
||||
<header class="feed-header">
|
||||
<button class="text-btn" id="cancel-post-btn">Cancel</button>
|
||||
<span class="header-title">New Post</span>
|
||||
<button class="text-btn accent" id="share-post-btn">Share</button>
|
||||
</header>
|
||||
|
||||
<div class="post-creation-area">
|
||||
<div class="image-upload-wrapper" id="upload-trigger">
|
||||
<input type="file" id="post-image-input" accept="image/*" hidden>
|
||||
<div class="upload-placeholder">
|
||||
<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>
|
||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||
<polyline points="21 15 16 10 5 21"></polyline>
|
||||
</svg>
|
||||
<span>Tap to Select Photo</span>
|
||||
</div>
|
||||
<img id="image-preview" class="hidden" src="" alt="Preview">
|
||||
</div>
|
||||
|
||||
<div class="caption-wrapper">
|
||||
<img src="https://i.pravatar.cc/150?img=12" alt="User" class="avatar-sm">
|
||||
<textarea id="post-caption-input" placeholder="Write a caption..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
189
script.js
189
script.js
@@ -327,4 +327,193 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
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');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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>
|
||||
</div>
|
||||
<div class="post-image">
|
||||
<img src="${post.imageSrc}" alt="Post">
|
||||
</div>
|
||||
<div class="post-actions">
|
||||
<div class="action-left">
|
||||
<button class="icon-btn"><svg viewBox="0 0 24 24" width="24" height="24" stroke="white" fill="none" class="heart-icon"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg></button>
|
||||
<button class="icon-btn"><svg viewBox="0 0 24 24" width="24" height="24" stroke="white" fill="none"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg></button>
|
||||
</div>
|
||||
<button class="icon-btn"><svg viewBox="0 0 24 24" width="24" height="24" stroke="white" fill="none"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg></button>
|
||||
</div>
|
||||
<div class="post-content">
|
||||
<span class="likes">0 likes</span>
|
||||
<p><span class="username">${post.username}</span> ${post.caption}</p>
|
||||
</div>
|
||||
<div class="comment-section">
|
||||
<div class="added-comments"></div>
|
||||
<div class="comment-input-wrapper">
|
||||
<input type="text" class="comment-input" placeholder="Add a comment...">
|
||||
<button class="post-btn">Post</button>
|
||||
</div>
|
||||
</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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -647,4 +647,89 @@ p {
|
||||
.nav-btn.active svg {
|
||||
stroke: var(--text-main);
|
||||
stroke-width: 2.5px;
|
||||
}
|
||||
|
||||
/* --- CREATE POST VIEW --- */
|
||||
#create-post-view {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
min-height: 100vh;
|
||||
background: var(--bg-dark);
|
||||
position: relative;
|
||||
z-index: 200;
|
||||
/* Above everything */
|
||||
}
|
||||
|
||||
.text-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-main);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-btn.accent {
|
||||
color: #3B82F6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.post-creation-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-upload-wrapper {
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
background: #111;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
#image-preview {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.caption-wrapper {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
#post-caption-input {
|
||||
flex: 1;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-main);
|
||||
font-family: inherit;
|
||||
font-size: 15px;
|
||||
resize: none;
|
||||
height: 100px;
|
||||
outline: none;
|
||||
padding-top: 8px;
|
||||
/* Align with avatar */
|
||||
}
|
||||
Reference in New Issue
Block a user