User:Docmoates/Social: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 2: | Line 2: | ||
<div id="social-feed-app"> | <div id="social-feed-app"> | ||
<style> | <style> | ||
#social-feed-app * { box-sizing: border-box; } | #social-feed-app * { box-sizing: border-box; } | ||
#social-feed-app { | #social-feed-app { | ||
max-width: 680px; | max-width: 680px; | ||
margin: 0 auto; | margin: 0 auto; | ||
font-family: -apple-system, BlinkMacSystemFont, | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; | ||
background: #f0f2f5; | background: #f0f2f5; | ||
min-height: 100vh; | min-height: 100vh; | ||
padding: 20px; | padding: 20px; | ||
} | } | ||
.sf-composer { | .sf-composer { | ||
background: white; | background: white; | ||
| Line 21: | Line 18: | ||
overflow: hidden; | overflow: hidden; | ||
} | } | ||
.sf-composer-main { | .sf-composer-main { | ||
display: flex; | display: flex; | ||
| Line 65: | Line 23: | ||
gap: 12px; | gap: 12px; | ||
} | } | ||
.sf-avatar { | .sf-avatar { | ||
width: 48px; | width: 48px; | ||
| Line 71: | Line 28: | ||
border-radius: 50%; | border-radius: 50%; | ||
object-fit: cover; | object-fit: cover; | ||
background: #e4e6eb; | |||
} | } | ||
.sf-avatar-small { width: 36px; height: 36px; } | |||
.sf-composer-input { | .sf-composer-input { | ||
flex: 1; | flex: 1; | ||
| Line 90: | Line 42: | ||
outline: none; | outline: none; | ||
color: #050505; | color: #050505; | ||
font-family: inherit; | |||
} | } | ||
.sf-composer-input::placeholder { color: #65676b; } | .sf-composer-input::placeholder { color: #65676b; } | ||
.sf-composer-actions { | .sf-composer-actions { | ||
display: flex; | display: flex; | ||
| Line 101: | Line 51: | ||
padding: 12px 20px; | padding: 12px 20px; | ||
border-top: 1px solid #e4e6eb; | border-top: 1px solid #e4e6eb; | ||
gap: 12px; | |||
} | } | ||
.sf-action-buttons { display: flex; gap: 8px; } | |||
.sf-action-buttons { | |||
} | |||
.sf-action-btn { | .sf-action-btn { | ||
width: | width: 40px; | ||
height: | height: 40px; | ||
border-radius: | border-radius: 8px; | ||
border: none; | border: none; | ||
background: | background: #f0f2f5; | ||
cursor: pointer; | cursor: pointer; | ||
font-size: | font-size: 18px; | ||
} | } | ||
.sf-action-btn:hover { background: #e4e6eb; } | |||
.sf-action-btn:hover { background: # | |||
} | |||
.sf-post-btn { | .sf-post-btn { | ||
background: linear-gradient(135deg, # | background: linear-gradient(135deg, #1877f2 0%, #0d65d9 100%); | ||
color: white; | color: white; | ||
border: none; | border: none; | ||
border-radius: 8px; | border-radius: 8px; | ||
padding: | padding: 10px 24px; | ||
font-size: 15px; | font-size: 15px; | ||
font-weight: 600; | font-weight: 600; | ||
cursor: pointer; | cursor: pointer; | ||
} | } | ||
.sf-post-btn:disabled { opacity: 0.5; } | |||
.sf-post-btn: | |||
.sf-post { | .sf-post { | ||
background: white; | background: white; | ||
| Line 167: | Line 80: | ||
box-shadow: 0 1px 3px rgba(0,0,0,0.1); | box-shadow: 0 1px 3px rgba(0,0,0,0.1); | ||
margin-bottom: 16px; | margin-bottom: 16px; | ||
} | } | ||
.sf-post-header { | .sf-post-header { | ||
display: flex; | display: flex; | ||
| Line 176: | Line 87: | ||
gap: 12px; | gap: 12px; | ||
} | } | ||
.sf-post-user-info { flex: 1; } | |||
.sf-post-user-info { | .sf-post-username { font-weight: 600; color: #050505; font-size: 15px; } | ||
.sf-post-meta { font-size: 13px; color: #65676b; } | |||
} | .sf-post-menu { background: none; border: none; font-size: 18px; color: #65676b; cursor: pointer; padding: 8px; border-radius: 50%; } | ||
.sf-post-username { | |||
} | |||
.sf-post-meta { | |||
} | |||
.sf-post-menu { | |||
} | |||
.sf-post-menu:hover { background: #f0f2f5; } | .sf-post-menu:hover { background: #f0f2f5; } | ||
.sf-post-content { padding: 0 16px 16px; font-size: 15px; line-height: 1.5; color: #050505; white-space: pre-wrap; } | |||
.sf-post-content { | .sf-post-image { width: 100%; max-height: 500px; object-fit: cover; } | ||
.sf-post-stats { display: flex; justify-content: space-between; padding: 12px 16px; font-size: 14px; color: #65676b; border-bottom: 1px solid #e4e6eb; } | |||
.sf-post-stats-left { display: flex; align-items: center; gap: 6px; } | |||
.sf-reaction-icons { display: flex; } | |||
.sf-reaction-icon { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; margin-left: -4px; } | |||
} | |||
.sf-post-image { | |||
} | |||
.sf-post-stats { | |||
} | |||
.sf-post-stats-left { | |||
} | |||
.sf-reaction-icons { | |||
} | |||
.sf-reaction-icon { | |||
} | |||
.sf-reaction-icon:first-child { margin-left: 0; } | .sf-reaction-icon:first-child { margin-left: 0; } | ||
.sf-reaction-icon.like { background: #1877f2; color: white; } | .sf-reaction-icon.like { background: #1877f2; color: white; } | ||
.sf-reaction-icon.love { background: #e91e63; color: white; } | .sf-reaction-icon.love { background: #e91e63; color: white; } | ||
.sf-post-actions { display: flex; padding: 4px 16px; } | |||
.sf-post-action { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px; background: none; border: none; font-size: 15px; font-weight: 600; color: #65676b; cursor: pointer; border-radius: 8px; } | |||
.sf-post-actions { | |||
} | |||
.sf-post-action { | |||
} | |||
.sf-post-action:hover { background: #f0f2f5; } | .sf-post-action:hover { background: #f0f2f5; } | ||
.sf-post-action.liked { color: #1877f2; } | .sf-post-action.liked { color: #1877f2; } | ||
.sf-comments { padding: 12px 16px; background: #f7f8fa; } | |||
.sf-comment { display: flex; gap: 8px; margin-bottom: 12px; } | |||
.sf-comment-bubble { background: white; border-radius: 18px; padding: 10px 14px; } | |||
.sf-comments { | .sf-comment-author { font-weight: 600; font-size: 13px; } | ||
.sf-comment-text { font-size: 14px; } | |||
.sf-add-comment { display: flex; gap: 8px; align-items: center; } | |||
} | .sf-comment-input { flex: 1; background: white; border: none; border-radius: 20px; padding: 10px 16px; font-size: 14px; outline: none; } | ||
.sf-empty { text-align: center; padding: 60px 20px; background: white; border-radius: 12px; color: #65676b; } | |||
.sf-comment { | .sf-empty-icon { font-size: 64px; margin-bottom: 16px; } | ||
.sf-empty-text { font-size: 18px; font-weight: 500; } | |||
.sf-empty-sub { font-size: 14px; margin-top: 8px; color: #8a8d91; } | |||
.sf-loading { text-align: center; padding: 40px; color: #65676b; } | |||
} | .sf-stories { display: flex; gap: 12px; padding: 16px; background: white; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; overflow-x: auto; } | ||
.sf-story { display: flex; flex-direction: column; align-items: center; gap: 8px; cursor: pointer; } | |||
.sf-comment-bubble { | .sf-story-ring { width: 68px; height: 68px; border-radius: 50%; padding: 3px; background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); } | ||
.sf-story-avatar { width: 100%; height: 100%; border-radius: 50%; border: 3px solid white; object-fit: cover; } | |||
.sf-story-name { font-size: 12px; color: #050505; } | |||
.sf-create-story { width: 68px; height: 68px; border-radius: 50%; background: #e4e6eb; display: flex; align-items: center; justify-content: center; font-size: 28px; color: #1877f2; cursor: pointer; border: none; } | |||
} | |||
.sf-comment-author { | |||
} | |||
.sf-comment-text { | |||
} | |||
.sf-comment- | |||
} | |||
.sf- | |||
.sf- | |||
} | |||
.sf- | |||
} | |||
.sf- | |||
.sf-stories { | |||
} | |||
.sf-story { | |||
} | |||
.sf-story-ring { | |||
.sf-story-avatar { | |||
} | |||
.sf-story-name { | |||
} | |||
.sf-create-story { | |||
} | |||
.sf-create-story:hover { background: #d8dadf; } | .sf-create-story:hover { background: #d8dadf; } | ||
</style> | </style> | ||
<div class="sf-stories" id="stories-container"> | |||
<div class="sf-stories" | |||
<div class="sf-story"> | <div class="sf-story"> | ||
< | <button class="sf-create-story">+</button> | ||
<span class="sf-story-name">Add Story</span> | |||
<span class="sf-story-name"> | |||
</div> | </div> | ||
</div> | </div> | ||
<div class="sf-composer"> | <div class="sf-composer"> | ||
<div class="sf-composer-main"> | <div class="sf-composer-main"> | ||
<img class="sf-avatar" src="https://docmoates.com/images/thumb/8/8b/Docmoates_Profile.jpeg/120px-Docmoates_Profile.jpeg" alt=" | <img class="sf-avatar" id="composer-avatar" src="https://docmoates.com/images/thumb/8/8b/Docmoates_Profile.jpeg/120px-Docmoates_Profile.jpeg" alt=""> | ||
<textarea class="sf-composer-input" placeholder="What | <textarea class="sf-composer-input" id="post-content" placeholder="What is on your mind?"></textarea> | ||
</div> | </div> | ||
<div class="sf-composer-actions"> | <div class="sf-composer-actions"> | ||
<div class="sf-action-buttons"> | <div class="sf-action-buttons"> | ||
<button class="sf-action-btn | <button class="sf-action-btn" title="Photo">🖼️</button> | ||
<button class="sf-action-btn" title="Emoji">😊</button> | |||
<button class="sf-action-btn | <button class="sf-action-btn" title="GIF">🎬</button> | ||
<button class="sf-action-btn" title="Location">📍</button> | |||
<button class="sf-action-btn | |||
<button class="sf-action-btn | |||
</div> | </div> | ||
<button class="sf-post-btn" id="post-btn">Post</button> | |||
</div> | </div> | ||
</div> | </div> | ||
<div | <div id="timeline-container"> | ||
<div class="sf- | <div class="sf-loading">Loading...</div> | ||
</div> | </div> | ||
<script> | |||
(function() { | |||
var currentUser = mw.config.get("wgUserName"); | |||
if (currentUser) { | |||
document.getElementById("post-content").placeholder = "What is on your mind, " + currentUser + "?"; | |||
} | |||
document.getElementById("post-btn").addEventListener("click", createPost); | |||
loadPosts(); | |||
loadStories(); | |||
function loadPosts() { | |||
new mw.Api().get({ | |||
action: "socialfeed", | |||
sfaction: "getposts", | |||
limit: 20 | |||
}).done(function(data) { | |||
renderPosts(data.socialfeed.posts || []); | |||
}).fail(function() { | |||
document.getElementById("timeline-container").innerHTML = "<div class=\"sf-empty\"><div class=\"sf-empty-icon\">😕</div><div class=\"sf-empty-text\">Could not load posts</div></div>"; | |||
}); | |||
} | |||
function loadStories() { | |||
new mw.Api().get({ | |||
action: "socialfeed", | |||
sfaction: "getstories" | |||
}).done(function(data) { | |||
renderStories(data.socialfeed.stories || []); | |||
}); | |||
} | |||
function renderStories(stories) { | |||
var container = document.getElementById("stories-container"); | |||
var html = "<div class=\"sf-story\"><button class=\"sf-create-story\">+</button><span class=\"sf-story-name\">Add Story</span></div>"; | |||
stories.forEach(function(s) { | |||
html += "<div class=\"sf-story\"><div class=\"sf-story-ring\"><img class=\"sf-story-avatar\" src=\"" + s.image_url + "\"></div><span class=\"sf-story-name\">" + s.username + "</span></div>"; | |||
}); | |||
container.innerHTML = html; | |||
} | |||
function renderPosts(posts) { | |||
var container = document.getElementById("timeline-container"); | |||
if (posts.length === 0) { | |||
container.innerHTML = "<div class=\"sf-empty\"><div class=\"sf-empty-icon\">📝</div><div class=\"sf-empty-text\">No posts yet</div><div class=\"sf-empty-sub\">Be the first to share something!</div></div>"; | |||
return; | |||
} | |||
var html = ""; | |||
posts.forEach(function(post) { | |||
var timeAgo = getTimeAgo(new Date(post.created)); | |||
var totalReactions = 0; | |||
for (var k in post.reaction_counts) totalReactions += post.reaction_counts[k]; | |||
html += "<div class=\"sf-post\" data-id=\"" + post.id + "\">"; | |||
html += "<div class=\"sf-post-header\"><img class=\"sf-avatar\" src=\"https://docmoates.com/images/thumb/8/8b/Docmoates_Profile.jpeg/120px-Docmoates_Profile.jpeg\"><div class=\"sf-post-user-info\"><div class=\"sf-post-username\">" + post.username + "</div><div class=\"sf-post-meta\">" + timeAgo + " · 🌐</div></div>"; | |||
if (post.username === currentUser) html += "<button class=\"sf-post-menu\" onclick=\"deletePost(" + post.id + ")\">🗑️</button>"; | |||
html += "</div>"; | |||
html += "<div class=\"sf-post-content\">" + escapeHtml(post.content) + "</div>"; | |||
if (post.image_url) html += "<img class=\"sf-post-image\" src=\"" + post.image_url + "\">"; | |||
html += "<div class=\"sf-post-stats\"><div class=\"sf-post-stats-left\">"; | |||
if (totalReactions > 0) { | |||
html += "<div class=\"sf-reaction-icons\">"; | |||
if (post.reaction_counts.like) html += "<span class=\"sf-reaction-icon like\">👍</span>"; | |||
if (post.reaction_counts.love) html += "<span class=\"sf-reaction-icon love\">❤️</span>"; | |||
html += "</div><span>" + totalReactions + "</span>"; | |||
} | |||
html += "</div><span>" + post.comments + " comments</span></div>"; | |||
html += "<div class=\"sf-post-actions\">"; | |||
html += "<button class=\"sf-post-action " + (post.user_reaction === "like" ? "liked" : "") + "\" onclick=\"toggleReaction(" + post.id + ")\">👍 Like</button>"; | |||
html += "<button class=\"sf-post-action\" onclick=\"toggleComments(" + post.id + ")\">💬 Comment</button>"; | |||
html += "<button class=\"sf-post-action\">↗️ Share</button></div>"; | |||
html += "<div class=\"sf-comments\" id=\"comments-" + post.id + "\" style=\"display:none\"></div></div>"; | |||
}); | |||
container.innerHTML = html; | |||
} | |||
window.createPost = function() { | |||
var content = document.getElementById("post-content").value.trim(); | |||
if (!content) return; | |||
document.getElementById("post-btn").disabled = true; | |||
new mw.Api().postWithToken("csrf", { | |||
action: "socialfeed", | |||
sfaction: "createpost", | |||
content: content | |||
}).done(function() { | |||
document.getElementById("post-content").value = ""; | |||
document.getElementById("post-btn").disabled = false; | |||
loadPosts(); | |||
}).fail(function(c, d) { | |||
alert("Error: " + (d.error ? d.error.info : c)); | |||
document.getElementById("post-btn").disabled = false; | |||
}); | |||
}; | |||
window.deletePost = function(id) { | |||
if (!confirm("Delete this post?")) return; | |||
new mw.Api().postWithToken("csrf", { action: "socialfeed", sfaction: "deletepost", post_id: id }).done(loadPosts); | |||
}; | |||
window.toggleReaction = function(id) { | |||
new mw.Api().postWithToken("csrf", { action: "socialfeed", sfaction: "react", post_id: id, reaction_type: "like" }).done(loadPosts); | |||
}; | |||
window.toggleComments = function(id) { | |||
var c = document.getElementById("comments-" + id); | |||
if (c.style.display === "none") { c.style.display = "block"; loadComments(id); } | |||
else { c.style.display = "none"; } | |||
}; | |||
function loadComments(id) { | |||
var c = document.getElementById("comments-" + id); | |||
new mw.Api().get({ action: "socialfeed", sfaction: "getcomments", post_id: id }).done(function(data) { | |||
var comments = data.socialfeed.comments || []; | |||
var html = ""; | |||
comments.forEach(function(cm) { | |||
html += "<div class=\"sf-comment\"><img class=\"sf-avatar sf-avatar-small\" src=\"https://i.pravatar.cc/150\"><div class=\"sf-comment-bubble\"><div class=\"sf-comment-author\">" + cm.username + "</div><div class=\"sf-comment-text\">" + escapeHtml(cm.content) + "</div></div></div>"; | |||
}); | |||
html += "<div class=\"sf-add-comment\"><img class=\"sf-avatar sf-avatar-small\" src=\"https://docmoates.com/images/thumb/8/8b/Docmoates_Profile.jpeg/120px-Docmoates_Profile.jpeg\"><input class=\"sf-comment-input\" placeholder=\"Write a comment...\" onkeypress=\"if(event.key===Enter)addComment(" + id + ",this)\"></div>"; | |||
c.innerHTML = html; | |||
}); | |||
} | |||
window.addComment = function(id, input) { | |||
var content = input.value.trim(); | |||
if (!content) return; | |||
new mw.Api().postWithToken("csrf", { action: "socialfeed", sfaction: "comment", post_id: id, content: content }).done(function() { loadComments(id); loadPosts(); }); | |||
}; | |||
function getTimeAgo(date) { | |||
var s = Math.floor((new Date() - date) / 1000); | |||
if (s < 60) return "Just now"; | |||
var m = Math.floor(s / 60); if (m < 60) return m + "m"; | |||
var h = Math.floor(m / 60); if (h < 24) return h + "h"; | |||
var d = Math.floor(h / 24); if (d < 7) return d + "d"; | |||
return date.toLocaleDateString(); | |||
} | |||
function escapeHtml(t) { var d = document.createElement("div"); d.textContent = t; return d.innerHTML; } | |||
})(); | |||
</script> | |||
</div> | </div> | ||
</html> | </html> | ||
Revision as of 21:31, 2 February 2026