Spaces:
Running
Running
| class AppFeed extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.posts = []; | |
| } | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| // Initial Skeleton | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| } | |
| .post-card { | |
| background-color: #242526; | |
| border-radius: 5px; | |
| margin-bottom: 15px; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.2); | |
| overflow: hidden; | |
| } | |
| .status-box { | |
| background-color: #242526; | |
| border-radius: 5px; | |
| padding: 10px; | |
| margin-bottom: 15px; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.2); | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .status-input { | |
| flex: 1; | |
| background-color: #3a3b3c; | |
| border: 1px solid #3e4042; | |
| border-radius: 5px; | |
| padding: 8px; | |
| color: #e4e6eb; | |
| font-size: 14px; | |
| } | |
| .post-header { | |
| padding: 10px; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .avatar { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 2px; | |
| margin-right: 10px; | |
| } | |
| .info h4 { | |
| margin: 0; | |
| font-size: 14px; | |
| color: #e4e6eb; | |
| } | |
| .info span { | |
| font-size: 12px; | |
| color: #b0b3b8; | |
| } | |
| .post-content { | |
| padding: 0 10px 10px; | |
| font-size: 14px; | |
| line-height: 1.4; | |
| color: #e4e6eb; | |
| } | |
| .post-image { | |
| width: 100%; | |
| display: block; | |
| border-top: 1px solid #3e4042; | |
| border-bottom: 1px solid #3e4042; | |
| } | |
| .post-actions { | |
| padding: 5px 10px; | |
| background-color: #323232; | |
| display: flex; | |
| border-top: 1px solid #3e4042; | |
| } | |
| .action-btn { | |
| background: none; | |
| border: none; | |
| color: #b0b3b8; | |
| font-size: 12px; | |
| font-weight: bold; | |
| padding: 5px 10px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .action-btn:hover { | |
| background-color: #3a3b3c; | |
| border-radius: 3px; | |
| } | |
| .action-btn i { | |
| width: 14px; | |
| height: 14px; | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 20px; | |
| color: #b0b3b8; | |
| } | |
| </style> | |
| <div class="status-box"> | |
| <img src="http://static.photos/people/40x40/1" class="avatar" alt="Me"> | |
| <input type="text" class="status-input" placeholder="What's on your mind?"> | |
| </div> | |
| <div id="feed-container"> | |
| <div class="loading">Updating News Feed...</div> | |
| </div> | |
| `; | |
| this.fetchPosts(); | |
| } | |
| async fetchPosts() { | |
| try { | |
| // Fetch dummy text data | |
| const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5'); | |
| const data = await response.json(); | |
| // Fetch dummy users for avatars | |
| const usersResponse = await fetch('https://jsonplaceholder.typicode.com/users?_limit=5'); | |
| const users = await usersResponse.json(); | |
| this.posts = data.map((post, index) => ({ | |
| id: post.id, | |
| title: post.title, | |
| body: post.body, | |
| user: users[index] || { name: 'Facebook User', id: 1 }, | |
| likes: Math.floor(Math.random() * 50) + 1, | |
| imageSeed: Math.floor(Math.random() * 1000) | |
| })); | |
| this.renderFeed(); | |
| } catch (error) { | |
| console.error("Error fetching posts:", error); | |
| this.shadowRoot.getElementById('feed-container').innerHTML = '<div class="loading">Error loading feed.</div>'; | |
| } | |
| } | |
| renderFeed() { | |
| const container = this.shadowRoot.getElementById('feed-container'); | |
| container.innerHTML = ''; | |
| this.posts.forEach(post => { | |
| const card = document.createElement('div'); | |
| card.className = 'post-card'; | |
| card.innerHTML = ` | |
| <div class="post-header"> | |
| <img src="http://static.photos/people/40x40/${post.user.id}" class="avatar" alt="${post.user.name}"> | |
| <div class="info"> | |
| <a href="#">${post.user.name}</a> | |
| <br> | |
| <span>2 hours ago via iPhone</span> | |
| </div> | |
| </div> | |
| <div class="post-content"> | |
| <p>${post.body}</p> | |
| </div> | |
| ${Math.random() > 0.5 ? `<img src="http://static.photos/technology/640x360/${post.imageSeed}" class="post-image" alt="Post Image">` : ''} | |
| <div class="post-actions"> | |
| <button class="action-btn like-btn" data-id="${post.id}"> | |
| <i data-feather="thumbs-up"></i> Like | |
| </button> | |
| <button class="action-btn"> | |
| <i data-feather="message-circle"></i> Comment | |
| </button> | |
| <button class="action-btn"> | |
| <i data-feather="share"></i> Share | |
| </button> | |
| </div> | |
| `; | |
| container.appendChild(card); | |
| }); | |
| // Re-initialize icons for the new content | |
| if (window.feather) { | |
| feather.replace(); | |
| } | |
| // Add event listeners for like buttons | |
| const likeBtns = this.shadowRoot.querySelectorAll('.like-btn'); | |
| likeBtns.forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| const icon = btn.querySelector('i'); | |
| // Simple toggle visual | |
| if (btn.style.color === 'rgb(66, 103, 178)') { | |
| btn.style.color = '#b0b3b8'; | |
| icon.style.fill = 'none'; | |
| } else { | |
| btn.style.color = '#4267B2'; // FB Blue | |
| icon.style.fill = '#4267B2'; | |
| // Dispatch event | |
| this.dispatchEvent(new CustomEvent('post-liked', { | |
| detail: { id: btn.dataset.id }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } | |
| }); | |
| }); | |
| } | |
| } | |
| customElements.define('app-feed', AppFeed); |