User:Docmoates/Social: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 3: | Line 3: | ||
<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; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; background: #f0f2f5; min-height: 100vh; padding: 20px; } | ||
.sf-composer { background: white; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; } | |||
.sf-composer-main { display: flex; padding: 16px 20px; gap: 12px; } | |||
.sf-avatar { width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 18px; cursor: pointer; position: relative; overflow: hidden; flex-shrink: 0; } | |||
.sf-avatar img { width: 100%; height: 100%; object-fit: cover; } | |||
} | |||
.sf-composer { | |||
} | |||
.sf-composer-main { | |||
} | |||
.sf-avatar { | |||
} | |||
.sf-avatar img { | |||
} | |||
.sf-avatar-small { width: 36px; height: 36px; font-size: 14px; } | .sf-avatar-small { width: 36px; height: 36px; font-size: 14px; } | ||
.sf-avatar-edit { | .sf-avatar-edit { position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,0.6); color: white; font-size: 10px; padding: 2px; text-align: center; opacity: 0; transition: 0.2s; } | ||
} | |||
.sf-avatar:hover .sf-avatar-edit { opacity: 1; } | .sf-avatar:hover .sf-avatar-edit { opacity: 1; } | ||
.sf-composer-input { | .sf-composer-input { flex: 1; background: #f0f2f5; border: none; border-radius: 20px; padding: 14px 20px; font-size: 17px; resize: none; min-height: 80px; outline: none; font-family: inherit; } | ||
.sf-composer-actions { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; border-top: 1px solid #e4e6eb; gap: 12px; } | |||
.sf-composer-actions { | |||
} | |||
.sf-action-buttons { display: flex; gap: 8px; } | .sf-action-buttons { display: flex; gap: 8px; } | ||
.sf-action-btn { | .sf-action-btn { width: 40px; height: 40px; border-radius: 8px; border: none; background: #f0f2f5; cursor: pointer; font-size: 18px; } | ||
} | |||
.sf-action-btn:hover { background: #e4e6eb; } | .sf-action-btn:hover { background: #e4e6eb; } | ||
.sf-post-btn { | .sf-post-btn { background: linear-gradient(135deg, #1877f2, #0d65d9); color: white; border: none; border-radius: 8px; padding: 10px 24px; font-size: 15px; font-weight: 600; cursor: pointer; } | ||
} | |||
.sf-post-btn:disabled { opacity: 0.5; } | .sf-post-btn:disabled { opacity: 0.5; } | ||
.sf-post { | .sf-post { background: white; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 16px; } | ||
.sf-post-header { display: flex; align-items: center; padding: 16px; gap: 12px; } | |||
} | |||
.sf-post-header { | |||
} | |||
.sf-post-user-info { flex: 1; } | .sf-post-user-info { flex: 1; } | ||
.sf-post-username { font-weight: 600 | .sf-post-username { font-weight: 600; font-size: 15px; } | ||
.sf-post-meta { font-size: 13px; color: #65676b; } | .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-menu { background: none; border: none; font-size: 18px; color: #65676b; cursor: pointer; padding: 8px; border-radius: 50%; } | ||
.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 | .sf-post-content { padding: 0 16px 16px; font-size: 15px; line-height: 1.5; white-space: pre-wrap; } | ||
.sf-post-image { width: 100%; max-height: 500px; object-fit: cover; } | .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 { 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-post-stats-left { display: flex; align-items: center; gap: 6px; } | ||
.sf-reaction-icon { width: 20px; height: 20px; border-radius: 50%; background: #1877f2; color: white; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; } | |||
.sf-reaction-icon { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px | |||
.sf-post-actions { display: flex; padding: 4px 16px; } | .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-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; } | ||
| Line 146: | Line 45: | ||
.sf-empty-text { font-size: 18px; font-weight: 500; } | .sf-empty-text { font-size: 18px; font-weight: 500; } | ||
.sf-empty-sub { font-size: 14px; margin-top: 8px; color: #8a8d91; } | .sf-empty-sub { font-size: 14px; margin-top: 8px; color: #8a8d91; } | ||
.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 | .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; } | ||
.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-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-modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 9999; align-items: center; justify-content: center; } | |||
.sf-modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: | |||
.sf-modal.active { display: flex; } | .sf-modal.active { display: flex; } | ||
.sf-modal-content { background: white; border-radius: 12px; padding: 24px; max-width: 400px; width: 90%; } | .sf-modal-content { background: white; border-radius: 12px; padding: 24px; max-width: 400px; width: 90%; } | ||
| Line 160: | Line 54: | ||
.sf-modal-buttons { display: flex; gap: 12px; justify-content: flex-end; } | .sf-modal-buttons { display: flex; gap: 12px; justify-content: flex-end; } | ||
.sf-modal-btn { padding: 10px 20px; border-radius: 8px; border: none; font-weight: 600; cursor: pointer; } | .sf-modal-btn { padding: 10px 20px; border-radius: 8px; border: none; font-weight: 600; cursor: pointer; } | ||
.sf-modal-btn-cancel { background: #e4e6eb | .sf-modal-btn-cancel { background: #e4e6eb; } | ||
.sf-modal-btn-save { background: #1877f2; color: white; } | .sf-modal-btn-save { background: #1877f2; color: white; } | ||
#image-preview { display: none; padding: 12px 20px; background: white; margin-bottom: 20px; border-radius: 12px; } | |||
#image-preview img { max-width: 100%; max-height: 200px; border-radius: 8px; } | |||
</style> | </style> | ||
<div class="sf-stories | <div class="sf-stories"><div class="sf-story"><button class="sf-create-story" id="add-story-btn">+</button><span style="font-size:12px;margin-top:8px;">Add Story</span></div></div> | ||
</div> | |||
<div class="sf-composer"> | <div class="sf-composer"> | ||
<div class="sf-composer-main"> | |||
<div class="sf-avatar" id="composer-avatar"><span id="avatar-text">?</span><div class="sf-avatar-edit">Edit</div></div> | |||
<textarea class="sf-composer-input" id="post-content" placeholder="What is on your mind?"></textarea> | |||
</div> | |||
<div class="sf-composer-actions"> | |||
<div class="sf-action-buttons"> | |||
<button class="sf-action-btn" id="add-image-btn">🖼️</button> | |||
<button class="sf-action-btn">😊</button> | |||
<button class="sf-action-btn">🎬</button> | |||
<button class="sf-action-btn">📍</button> | |||
</div> | |||
<button class="sf-post-btn" id="post-btn">Post</button> | |||
</div> | |||
</div> | </div> | ||
<div id="image-preview | <div id="image-preview"><img id="preview-img"><button id="remove-image-btn" style="margin-left:10px;padding:5px 10px;">Remove</button></div> | ||
</div> | |||
<div id="timeline-container"> | <div id="timeline-container"><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></div> | ||
</div> | |||
<div class="sf-modal" id="profile-modal"> | <div class="sf-modal" id="profile-modal"> | ||
<div class="sf-modal-content"> | |||
<div class="sf-modal-title">Set Profile Photo</div> | |||
<input type="text" class="sf-modal-input" id="profile-url-input" placeholder="Enter image URL..."> | |||
<p style="font-size:12px;color:#65676b;margin-bottom:12px;">Upload to wiki first, then paste URL</p> | |||
<div class="sf-modal-buttons"> | |||
<button class="sf-modal-btn sf-modal-btn-cancel" id="profile-cancel-btn">Cancel</button> | |||
<button class="sf-modal-btn sf-modal-btn-save" id="profile-save-btn">Save</button> | |||
</div> | |||
</div> | |||
</div> | </div> | ||
<script> | <script> | ||
(function() { | mw.loader.using(['mediawiki.api']).then(function(){ | ||
var user=mw.config.get('wgUserName')||'Guest'; | |||
var photo=localStorage.getItem('sf_photo_'+user)||''; | |||
var pendingImg=''; | |||
var api=new mw.Api(); | |||
function init(n){return n.split(/[\s_]+/).map(function(w){return w[0]||'';}).join('').substring(0,2).toUpperCase()||'?';} | |||
function esc(t){var d=document.createElement('div');d.textContent=t;return d.innerHTML;} | |||
function ago(d){var s=Math.floor((Date.now()-d)/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';return Math.floor(h/24)+'d';} | |||
function setAv(){ | |||
var el=document.getElementById('composer-avatar'); | |||
el.innerHTML=photo?'<img src="'+photo+'">':init(user); | |||
el.innerHTML+='<div class="sf-avatar-edit">Edit</div>'; | |||
} | |||
setAv(); | |||
document.getElementById('post-content').placeholder='What is on your mind, '+user+'?'; | |||
document.getElementById('composer-avatar').onclick=function(){ | |||
document.getElementById('profile-url-input').value=photo; | |||
document.getElementById('profile-modal').classList.add('active'); | |||
}; | |||
document.getElementById('profile-cancel-btn').onclick=function(){document.getElementById('profile-modal').classList.remove('active');}; | |||
document.getElementById('profile-save-btn').onclick=function(){ | |||
photo=document.getElementById('profile-url-input').value.trim(); | |||
localStorage.setItem('sf_photo_'+user,photo); | |||
setAv(); | |||
document.getElementById('profile-modal').classList.remove('active'); | |||
}; | |||
document.getElementById('add-image-btn').onclick=function(){ | |||
var u=prompt('Enter image URL:'); | |||
if(u){pendingImg=u;document.getElementById('preview-img').src=u;document.getElementById('image-preview').style.display='block';} | |||
}; | |||
document.getElementById('remove-image-btn').onclick=function(){pendingImg='';document.getElementById('image-preview').style.display='none';}; | |||
document.getElementById('post-btn').onclick=function(){ | |||
var c=document.getElementById('post-content').value.trim(); | |||
if(!c&&!pendingImg){alert('Write something or add image');return;} | |||
var btn=this;btn.disabled=true;btn.textContent='Posting...'; | |||
var p={action:'socialfeed',sfaction:'createpost',content:c||'(photo)'}; | |||
if(pendingImg)p.image_url=pendingImg; | |||
api.postWithToken('csrf',p).done(function(){ | |||
document.getElementById('post-content').value=''; | |||
pendingImg='';document.getElementById('image-preview').style.display='none'; | |||
btn.disabled=false;btn.textContent='Post'; | |||
load(); | |||
}).fail(function(code,data){alert('Error: '+(data&&data.error?data.error.info:code));btn.disabled=false;btn.textContent='Post';}); | |||
}; | |||
function load(){ | |||
api.get({action:'socialfeed',sfaction:'getposts',limit:20}).done(function(d){render(d.socialfeed.posts||[]);}).fail(function(){ | |||
document.getElementById('timeline-container').innerHTML='<div class="sf-empty"><div class="sf-empty-icon">😕</div><div class="sf-empty-text">Error loading</div></div>'; | |||
}); | |||
} | |||
function render(posts){ | |||
var c=document.getElementById('timeline-container'); | |||
if(!posts.length){c.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!</div></div>';return;} | |||
var h=''; | |||
posts.forEach(function(p){ | |||
var t=ago(new Date(p.created)); | |||
var total=0;for(var k in p.reaction_counts)total+=p.reaction_counts[k]; | |||
var pic=localStorage.getItem('sf_photo_'+p.username)||''; | |||
var av=pic?'<img src="'+pic+'">':init(p.username); | |||
h+='<div class="sf-post"><div class="sf-post-header"><div class="sf-avatar sf-avatar-small">'+av+'</div><div class="sf-post-user-info"><div class="sf-post-username">'+esc(p.username)+'</div><div class="sf-post-meta">'+t+' · 🌐</div></div>'; | |||
if(p.username===user)h+='<button class="sf-post-menu" data-del="'+p.id+'">🗑️</button>'; | |||
h+='</div><div class="sf-post-content">'+esc(p.content)+'</div>'; | |||
if(p.image_url)h+='<img class="sf-post-image" src="'+p.image_url+'">'; | |||
h+='<div class="sf-post-stats"><div class="sf-post-stats-left">'; | |||
if(total>0)h+='<span class="sf-reaction-icon">👍</span><span>'+total+'</span>'; | |||
h+='</div><span>'+p.comments+' comments</span></div>'; | |||
h+='<div class="sf-post-actions"><button class="sf-post-action'+(p.user_reaction==='like'?' liked':'')+'" data-like="'+p.id+'">👍 Like</button><button class="sf-post-action" data-cmt="'+p.id+'">💬 Comment</button><button class="sf-post-action">↗️ Share</button></div>'; | |||
h+='<div class="sf-comments" id="cmt-'+p.id+'" style="display:none"></div></div>'; | |||
}); | |||
c.innerHTML=h; | |||
c.querySelectorAll('[data-del]').forEach(function(b){b.onclick=function(){if(confirm('Delete?'))api.postWithToken('csrf',{action:'socialfeed',sfaction:'deletepost',post_id:b.dataset.del}).done(load);};}); | |||
c.querySelectorAll('[data-like]').forEach(function(b){b.onclick=function(){api.postWithToken('csrf',{action:'socialfeed',sfaction:'react',post_id:b.dataset.like,reaction_type:'like'}).done(load);};}); | |||
c.querySelectorAll('[data-cmt]').forEach(function(b){b.onclick=function(){var box=document.getElementById('cmt-'+b.dataset.cmt);if(box.style.display==='none'){box.style.display='block';loadCmt(b.dataset.cmt);}else{box.style.display='none';}};}); | |||
} | |||
function loadCmt(id){ | |||
var box=document.getElementById('cmt-'+id); | |||
api.get({action:'socialfeed',sfaction:'getcomments',post_id:id}).done(function(d){ | |||
var cmts=d.socialfeed.comments||[]; | |||
var h=''; | |||
cmts.forEach(function(cm){var pic=localStorage.getItem('sf_photo_'+cm.username)||'';var av=pic?'<img src="'+pic+'">':init(cm.username);h+='<div class="sf-comment"><div class="sf-avatar sf-avatar-small">'+av+'</div><div class="sf-comment-bubble"><div class="sf-comment-author">'+esc(cm.username)+'</div><div class="sf-comment-text">'+esc(cm.content)+'</div></div></div>';}); | |||
var myPic=photo?'<img src="'+photo+'">':init(user); | |||
h+='<div class="sf-add-comment"><div class="sf-avatar sf-avatar-small">'+myPic+'</div><input class="sf-comment-input" id="cinput-'+id+'" placeholder="Write a comment..."></div>'; | |||
box.innerHTML=h; | |||
document.getElementById('cinput-'+id).onkeypress=function(e){if(e.key==='Enter'&&this.value.trim()){var v=this.value.trim();this.value='';api.postWithToken('csrf',{action:'socialfeed',sfaction:'comment',post_id:id,content:v}).done(function(){loadCmt(id);load();});}}; | |||
}); | |||
} | |||
load(); | |||
}); | |||
} | |||
</script> | </script> | ||
</div> | </div> | ||
</html> | </html> | ||
Revision as of 21:42, 2 February 2026
Upload to wiki first, then paste URL