User:Docmoates/Social: Difference between revisions

From XMethod Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Tag: Replaced
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
<html>
<div id="social-app">
<div id="sf-app">
<div style="text-align:center;padding:60px;color:#8e8e8e">
<style>
<div style="font-size:48px;margin-bottom:16px"></div>
#sf-app{max-width:680px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#f0f2f5;min-height:100vh;padding:20px}
<div>Loading Social Feed...</div>
#sf-app *{box-sizing:border-box}
.sf-card{background:#fff;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:16px}
.sf-avatar{width:44px;height:44px;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;font-size:16px;cursor:pointer;overflow:hidden;flex-shrink:0}
.sf-avatar img{width:100%;height:100%;object-fit:cover}
.sf-avatar-sm{width:32px;height:32px;font-size:12px}
.sf-btn{background:#1877f2;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-weight:600;cursor:pointer}
.sf-btn:disabled{opacity:.5}
.sf-btn-light{background:#e4e6eb;color:#050505}
.sf-modal{display:none;position:fixed;inset:0;background:rgba(255,255,255,.85);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:1000;align-items:center;justify-content:center}
.sf-modal.open{display:flex}
.sf-modal-box{background:#fff;border-radius:12px;padding:20px;width:90%;max-width:420px;box-shadow:0 8px 32px rgba(0,0,0,.15)}
.sf-modal-title{font-size:18px;font-weight:600;margin-bottom:16px}
.sf-tabs{display:flex;border-bottom:2px solid #e4e6eb;margin-bottom:16px}
.sf-tab{flex:1;padding:10px;text-align:center;cursor:pointer;font-weight:600;color:#65676b;border-bottom:2px solid transparent;margin-bottom:-2px}
.sf-tab.active{color:#1877f2;border-color:#1877f2}
.sf-tab-panel{display:none}
.sf-tab-panel.active{display:block}
.sf-upload-area{border:2px dashed #ccd0d5;border-radius:8px;padding:32px;text-align:center;cursor:pointer;transition:.2s}
.sf-upload-area:hover{border-color:#1877f2;background:#f0f7ff}
.sf-upload-area.drag{border-color:#1877f2;background:#e7f3ff}
.sf-input{width:100%;padding:10px 12px;border:1px solid #ccd0d5;border-radius:8px;font-size:14px;margin-bottom:12px}
.sf-preview{max-width:100%;max-height:120px;border-radius:8px;margin-top:12px}
.sf-progress{height:6px;background:#e4e6eb;border-radius:3px;margin-top:12px;overflow:hidden}
.sf-progress-bar{height:100%;background:#1877f2;width:0;transition:width .3s}
.sf-composer{padding:16px}
.sf-composer-top{display:flex;gap:12px}
.sf-composer-input{flex:1;border:none;background:#f0f2f5;border-radius:20px;padding:12px 16px;font-size:15px;resize:none;min-height:44px;outline:none}
.sf-composer-actions{display:flex;justify-content:space-between;align-items:center;margin-top:12px;padding-top:12px;border-top:1px solid #e4e6eb}
.sf-composer-btns{display:flex;gap:4px}
.sf-icon-btn{width:36px;height:36px;border:none;background:#f0f2f5;border-radius:8px;font-size:18px;cursor:pointer}
.sf-icon-btn:hover{background:#e4e6eb}
.sf-stories{display:flex;gap:12px;padding:16px;overflow-x:auto}
.sf-story{display:flex;flex-direction:column;align-items:center;gap:4px}
.sf-story-ring{width:60px;height:60px;border-radius:50%;padding:2px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)}
.sf-story-ring.add{background:#e4e6eb}
.sf-story-inner{width:100%;height:100%;border-radius:50%;border:2px solid #fff;overflow:hidden;display:flex;align-items:center;justify-content:center;background:#f0f2f5;cursor:pointer}
.sf-story-inner img{width:100%;height:100%;object-fit:cover}
.sf-story-name{font-size:11px;color:#65676b;max-width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sf-post-header{display:flex;gap:10px;padding:12px 16px}
.sf-post-info{flex:1}
.sf-post-name{font-weight:600;font-size:14px}
.sf-post-meta{font-size:12px;color:#65676b}
.sf-post-content{padding:0 16px 12px;font-size:15px;line-height:1.4;white-space:pre-wrap}
.sf-post-media img,.sf-post-media video,.sf-post-media iframe{width:100%;max-height:500px;object-fit:cover}
.sf-post-location{padding:4px 16px 8px;font-size:12px;color:#65676b}
.sf-post-stats{display:flex;justify-content:space-between;padding:8px 16px;font-size:13px;color:#65676b;border-bottom:1px solid #e4e6eb}
.sf-post-actions{display:flex;padding:4px 8px}
.sf-post-action{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:8px;border:none;background:none;font-size:14px;font-weight:600;color:#65676b;cursor:pointer;border-radius:4px}
.sf-post-action:hover{background:#f0f2f5}
.sf-post-action.liked{color:#1877f2}
.sf-comments{padding:8px 16px 12px;background:#f7f8fa}
.sf-comment{display:flex;gap:8px;margin-bottom:8px}
.sf-comment-bubble{background:#fff;border-radius:16px;padding:8px 12px}
.sf-comment-author{font-weight:600;font-size:12px}
.sf-comment-text{font-size:13px}
.sf-comment-form{display:flex;gap:8px;margin-top:8px}
.sf-comment-input{flex:1;border:none;background:#fff;border-radius:18px;padding:8px 12px;font-size:13px;outline:none}
.sf-empty{text-align:center;padding:48px 20px;color:#65676b}
.sf-empty-icon{font-size:48px;margin-bottom:12px}
.sf-viewer{display:none;position:fixed;inset:0;background:#000;z-index:2000;align-items:center;justify-content:center}
.sf-viewer.open{display:flex}
.sf-viewer img{max-width:100%;max-height:100%;object-fit:contain}
.sf-viewer-close{position:absolute;top:16px;right:16px;width:40px;height:40px;border-radius:50%;border:none;background:rgba(255,255,255,.2);color:#fff;font-size:20px;cursor:pointer}
.sf-viewer-progress{position:absolute;top:8px;left:8px;right:8px;height:3px;background:rgba(255,255,255,.3);border-radius:2px}
.sf-viewer-progress span{display:block;height:100%;background:#fff;border-radius:2px;animation:progress 5s linear}
@keyframes progress{from{width:0}to{width:100%}}
.sf-emoji-picker{position:absolute;bottom:44px;left:0;background:#fff;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,.15);padding:12px;width:280px;z-index:100;display:none}
.sf-emoji-picker.open{display:block}
.sf-emoji-cats{display:flex;gap:2px;margin-bottom:8px;border-bottom:1px solid #e4e6eb;padding-bottom:8px}
.sf-emoji-cat{border:none;background:none;font-size:16px;padding:4px 8px;cursor:pointer;border-radius:4px;opacity:.5}
.sf-emoji-cat:hover,.sf-emoji-cat.active{opacity:1;background:#f0f2f5}
.sf-emoji-list{display:grid;grid-template-columns:repeat(8,1fr);gap:2px;max-height:180px;overflow-y:auto}
.sf-emoji-list button{border:none;background:none;font-size:20px;padding:4px;cursor:pointer;border-radius:4px}
.sf-emoji-list button:hover{background:#f0f2f5}
.sf-pending{display:none;padding:8px 16px;margin-bottom:8px;background:#fff;border-radius:8px}
.sf-pending.show{display:flex;align-items:center;gap:8px}
.sf-pending img,.sf-pending video{max-height:80px;border-radius:6px}
.sf-del{margin-left:auto;border:none;background:none;color:#65676b;cursor:pointer}
</style>
 
<div class="sf-card sf-stories" id="stories"></div>
 
<div class="sf-card sf-composer">
<div class="sf-composer-top">
<div class="sf-avatar" id="my-avatar"></div>
<textarea class="sf-composer-input" id="post-text" placeholder="What's on your mind?"></textarea>
</div>
</div>
<div class="sf-pending" id="pending-media"></div>
<div class="sf-pending" id="pending-location"></div>
<div class="sf-composer-actions">
<div class="sf-composer-btns" style="position:relative">
<button class="sf-icon-btn" id="btn-photo" title="Photo">🖼️</button>
<button class="sf-icon-btn" id="btn-emoji" title="Emoji">😊</button>
<button class="sf-icon-btn" id="btn-video" title="Video">🎬</button>
<button class="sf-icon-btn" id="btn-location" title="Location">📍</button>
<div class="sf-emoji-picker" id="emoji-picker"></div>
</div>
</div>
<button class="sf-btn" id="btn-post">Post</button>
</div>
</div>
<div id="feed"></div>
<div class="sf-modal" id="modal-profile">
<div class="sf-modal-box">
<div class="sf-modal-title">Profile Photo</div>
<div class="sf-tabs"><div class="sf-tab active" data-t="p-upload">Upload</div><div class="sf-tab" data-t="p-url">URL</div></div>
<div class="sf-tab-panel active" id="p-upload">
<div class="sf-upload-area" id="profile-drop"><div style="font-size:32px;margin-bottom:8px">📸</div><div style="color:#65676b">Click or drag image</div><input type="file" accept="image/*" style="display:none"></div>
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
<img class="sf-preview" style="display:none">
</div>
<div class="sf-tab-panel" id="p-url">
<input class="sf-input" placeholder="Paste image URL..." id="profile-url">
<img class="sf-preview" style="display:none">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-profile')">Cancel</button>
<button class="sf-btn" id="profile-save">Save</button>
</div>
</div>
</div>
<div class="sf-modal" id="modal-photo">
<div class="sf-modal-box">
<div class="sf-modal-title">Add Photo</div>
<div class="sf-tabs"><div class="sf-tab active" data-t="i-upload">Upload</div><div class="sf-tab" data-t="i-url">URL</div></div>
<div class="sf-tab-panel active" id="i-upload">
<div class="sf-upload-area" id="photo-drop"><div style="font-size:32px;margin-bottom:8px">📤</div><div style="color:#65676b">Click or drag image</div><input type="file" accept="image/*" style="display:none"></div>
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
<img class="sf-preview" style="display:none">
</div>
<div class="sf-tab-panel" id="i-url">
<input class="sf-input" placeholder="Paste image URL..." id="photo-url">
<img class="sf-preview" style="display:none">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-photo')">Cancel</button>
<button class="sf-btn" id="photo-add">Add</button>
</div>
</div>
</div>
<div class="sf-modal" id="modal-video">
<div class="sf-modal-box">
<div class="sf-modal-title">Add Video</div>
<div class="sf-tabs"><div class="sf-tab active" data-t="v-upload">Upload</div><div class="sf-tab" data-t="v-url">URL</div></div>
<div class="sf-tab-panel active" id="v-upload">
<div class="sf-upload-area" id="video-drop"><div style="font-size:32px;margin-bottom:8px">🎬</div><div style="color:#65676b">Click or drag video</div><input type="file" accept="video/*" style="display:none"></div>
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
<video class="sf-preview" controls style="display:none"></video>
</div>
<div class="sf-tab-panel" id="v-url">
<input class="sf-input" placeholder="YouTube, Vimeo, or direct URL..." id="video-url">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-video')">Cancel</button>
<button class="sf-btn" id="video-add">Add</button>
</div>
</div>
</div>
<div class="sf-modal" id="modal-story">
<div class="sf-modal-box">
<div class="sf-modal-title">Add Story</div>
<div class="sf-tabs"><div class="sf-tab active" data-t="s-upload">Upload</div><div class="sf-tab" data-t="s-url">URL</div></div>
<div class="sf-tab-panel active" id="s-upload">
<div class="sf-upload-area" id="story-drop"><div style="font-size:32px;margin-bottom:8px">📷</div><div style="color:#65676b">Click or drag image</div><input type="file" accept="image/*" style="display:none"></div>
<div class="sf-progress" style="display:none"><div class="sf-progress-bar"></div></div>
<img class="sf-preview" style="display:none">
</div>
<div class="sf-tab-panel" id="s-url">
<input class="sf-input" placeholder="Paste image URL..." id="story-url">
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-story')">Cancel</button>
<button class="sf-btn" id="story-add">Add Story</button>
</div>
</div>
</div>
<div class="sf-modal" id="modal-location">
<div class="sf-modal-box">
<div class="sf-modal-title">Add Location</div>
<button class="sf-btn" id="detect-loc" style="width:100%;margin-bottom:12px">📍 Use Current Location</button>
<div style="text-align:center;color:#65676b;margin-bottom:12px">— or —</div>
<input class="sf-input" placeholder="Enter location..." id="loc-input">
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="sf-btn sf-btn-light" onclick="closeModal('modal-location')">Cancel</button>
<button class="sf-btn" id="loc-add">Add</button>
</div>
</div>
</div>
<div class="sf-viewer" id="story-viewer">
<div class="sf-viewer-progress"><span></span></div>
<button class="sf-viewer-close">✕</button>
<img src="">
</div>
<script>
(function(){
var $=window.jQuery,mw=window.mw;
if(!$||!mw){console.error('jQuery or mw not found');return;}
$.when(mw.loader.using(['mediawiki.api','mediawiki.util']),$.ready).then(function(){
var api=new mw.Api();
var apiUrl=mw.util.wikiScript('api');
var user=mw.config.get('wgUserName')||'Guest';
var photo=localStorage.getItem('sfphoto_'+user)||'';
var pending={img:'',video:'',loc:''};
var uploads={profile:null,photo:null,video:null,story:null};
var emojis={
'😀':['😀','😃','😄','😁','😅','😂','🤣','😊','😇','🙂','😉','😍','🥰','😘','😋','😛','😜','🤪','😎','🤩','🥳','😏','😒','😞','😔','😟','😕','🙁','😣','😖','😫','😩','🥺','😢','😭','😤','😠','😡','🤬','😈','👿','💀','💩','🤡','👻','👽','🤖','😺','😸','😹','😻','😼','😽','🙀','😿','😾'],
'👋':['👋','🤚','🖐','✋','🖖','👌','🤌','🤏','✌','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇','☝','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','👀','👁','👅','👄','💋','🧠','🫀','🫁','🦷','🦴'],
'🐶':['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🐯','🦁','🐮','🐷','🐸','🐵','🐔','🐧','🐦','🐤','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄','🐝','🐛','🦋','🐌','🐞','🐜','🦟','🐢','🐍','🦎','🦂','🦀','🦑','🐙','🦐','🐠','🐟','🐬','🐳','🦈','🐊','🐅','🐆','🦓','🦍','🐘','🦛','🦏','🐪','🦒','🦘','🐃','🐂','🐄','🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐈','🐓','🦃','🦚','🦜','🦢','🦩','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿','🦔'],
'🍎':['🍎','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🥑','🥦','🥬','🥒','🌶','🫑','🌽','🥕','🧄','🧅','🥔','🍠','🥐','🥯','🍞','🥖','🥨','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🌭','🍔','🍟','🍕','🥪','🥙','🧆','🌮','🌯','🥗','🥘','🥫','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🍤','🍙','🍚','🍘','🍥','🥠','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪','🌰','🥜','🍯','🥛','🍼','☕','🍵','🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊'],
'⚽':['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛼','🛷','⛸','🥌','🎿','⛷','🏂','🪂','🏋','🤼','🤸','⛹','🤺','🤾','🏌','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅','🎖','🏵','🎗','🎫','🎟','🎪','🎭','🎨','🎬','🎤','🎧','🎼','🎹','🥁','🪘','🎷','🎺','🪗','🎸','🪕','🎻','🎲','♟','🎯','🎳','🎮','🎰','🧩'],
'❤️':['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝','💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓','🆔','⚛️','🉑','☢️','☣️','📴','📳','🈶','🈚','🈸','🈺','🈷️','✴️','🆚','💮','🉐','㊙️','㊗️','🈴','🈵','🈹','🈲','🅰️','🅱️','🆎','🆑','🅾️','🆘','❌','⭕','🛑','⛔','📛','🚫','💯','💢','♨️','🚷','🚯','🚳','🚱','🔞','📵','🚭','❗','❕','❓','❔','‼️','⁉️','🔅','🔆','〽️','⚠️','🚸','🔱','⚜️','🔰','♻️','✅','🈯','💹','❇️','✳️','❎','🌐','💠']
};
function initials(n){return(n||'?').split(/[\s_]+/).map(function(w){return w[0]||'';}).join('').substring(0,2).toUpperCase()||'?';}
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML;}
function timeAgo(d){var s=Math.floor((Date.now()-new Date(d))/1e3);if(s<60)return'now';if(s<3600)return Math.floor(s/60)+'m';if(s<86400)return Math.floor(s/3600)+'h';return Math.floor(s/86400)+'d';}
function renderAvatar(el,name,pic){
el=$(el);
if(pic)el.html('<img src="'+pic+'">');
else el.text(initials(name));
}
function openModal(id){$('#'+id).addClass('open');}
window.closeModal=function(id){$('#'+id).removeClass('open');}
function setupTabs(container){
$(container).on('click','.sf-tab',function(){
var t=$(this),panel=$('#'+t.data('t'));
t.siblings().removeClass('active');t.addClass('active');
panel.siblings('.sf-tab-panel').removeClass('active');panel.addClass('active');
});
}
function setupUpload(dropId,type,onFile){
var drop=$('#'+dropId),input=drop.find('input'),progress=drop.siblings('.sf-progress'),bar=progress.find('.sf-progress-bar'),preview=drop.siblings('.sf-preview');
drop.on('click',function(){input.click();});
drop.on('dragover',function(e){e.preventDefault();drop.addClass('drag');});
drop.on('dragleave drop',function(){drop.removeClass('drag');});
drop.on('drop',function(e){e.preventDefault();if(e.originalEvent.dataTransfer.files[0])handleFile(e.originalEvent.dataTransfer.files[0]);});
input.on('change',function(){if(this.files[0])handleFile(this.files[0]);});
function handleFile(f){
uploads[type]=f;
if(f.type.startsWith('image/')){
var r=new FileReader();
r.onload=function(e){preview.attr('src',e.target.result).show();};
r.readAsDataURL(f);
}else if(f.type.startsWith('video/')){
preview.attr('src',URL.createObjectURL(f)).show();
}
if(onFile)onFile(f);
}
return{
reset:function(){uploads[type]=null;preview.hide();progress.hide();bar.css('width',0);input.val('');},
progress:function(p){progress.show();bar.css('width',p+'%');},
done:function(){bar.css('width','100%');}
};
}
function uploadToWiki(file,prefix,onProgress){
return api.getToken('csrf').then(function(token){
var fname=prefix+'_'+user+'_'+Date.now()+'.'+(file.name.split('.').pop()||'jpg');
var fd=new FormData();
fd.append('action','upload');fd.append('filename',fname);fd.append('file',file);
fd.append('token',token);fd.append('format','json');fd.append('ignorewarnings','1');
return $.ajax({
url:apiUrl,type:'POST',data:fd,processData:false,contentType:false,
xhr:function(){
var x=$.ajaxSettings.xhr();
if(x.upload)x.upload.onprogress=function(e){if(e.lengthComputable&&onProgress)onProgress(Math.round(e.loaded/e.total*100));};
return x;
}
});
}).then(function(r){
if(r.upload&&r.upload.imageinfo)return r.upload.imageinfo.url;
throw new Error(r.error?r.error.info:'Upload failed');
});
}
// Init avatar
renderAvatar('#my-avatar',user,photo);
$('#my-avatar').on('click',function(){
uploads.profile=null;
$('#p-upload .sf-preview').hide();$('#p-url .sf-preview').hide();
$('#profile-url').val(photo);
if(photo)$('#p-url .sf-preview').attr('src',photo).show();
openModal('modal-profile');
});
// Profile modal
setupTabs('#modal-profile');
var profileUp=setupUpload('profile-drop','profile');
$('#profile-url').on('input',function(){
var u=$(this).val().trim();
$('#p-url .sf-preview').attr('src',u).toggle(!!u);
});
$('#profile-save').on('click',function(){
var activeTab=$('#modal-profile .sf-tab.active').data('t');
if(activeTab==='p-url'){
photo=$('#profile-url').val().trim();
localStorage.setItem('sfphoto_'+user,photo);
renderAvatar('#my-avatar',user,photo);
closeModal('modal-profile');
}else if(uploads.profile){
uploadToWiki(uploads.profile,'Profile',profileUp.progress).then(function(url){
profileUp.done();
photo=url;localStorage.setItem('sfphoto_'+user,photo);
renderAvatar('#my-avatar',user,photo);
setTimeout(function(){closeModal('modal-profile');profileUp.reset();},500);
}).catch(function(e){alert('Upload error: '+e);});
}else{alert('Select an image');}
});
// Photo modal
setupTabs('#modal-photo');
var photoUp=setupUpload('photo-drop','photo');
$('#photo-url').on('input',function(){$('#i-url .sf-preview').attr('src',$(this).val()).toggle(!!$(this).val());});
$('#photo-add').on('click',function(){
var activeTab=$('#modal-photo .sf-tab.active').data('t');
if(activeTab==='i-url'){
var u=$('#photo-url').val().trim();
if(u){setPendingMedia('img',u);closeModal('modal-photo');}
else alert('Enter URL');
}else if(uploads.photo){
uploadToWiki(uploads.photo,'SocialPhoto',photoUp.progress).then(function(url){
photoUp.done();setPendingMedia('img',url);
setTimeout(function(){closeModal('modal-photo');photoUp.reset();},500);
}).catch(function(e){alert('Upload error: '+e);});
}else alert('Select an image');
});
// Video modal
setupTabs('#modal-video');
var videoUp=setupUpload('video-drop','video');
$('#video-add').on('click',function(){
var activeTab=$('#modal-video .sf-tab.active').data('t');
if(activeTab==='v-url'){
var u=$('#video-url').val().trim();
if(u){setPendingMedia('video',u);closeModal('modal-video');}
else alert('Enter URL');
}else if(uploads.video){
uploadToWiki(uploads.video,'SocialVideo',videoUp.progress).then(function(url){
videoUp.done();setPendingMedia('video',url);
setTimeout(function(){closeModal('modal-video');videoUp.reset();},500);
}).catch(function(e){alert('Upload error: '+e);});
}else alert('Select a video');
});
// Story modal
setupTabs('#modal-story');
var storyUp=setupUpload('story-drop','story');
$('#story-url').on('input',function(){$('#s-url .sf-preview').attr('src',$(this).val()).toggle(!!$(this).val());});
$('#story-add').on('click',function(){
var activeTab=$('#modal-story .sf-tab.active').data('t');
function postStory(url){
api.postWithToken('csrf',{action:'socialfeed',sfaction:'createstory',image_url:url}).then(function(){
closeModal('modal-story');storyUp.reset();$('#story-url').val('');loadStories();
}).catch(function(e){alert('Error: '+e);});
}
if(activeTab==='s-url'){
var u=$('#story-url').val().trim();
if(u)postStory(u);else alert('Enter URL');
}else if(uploads.story){
uploadToWiki(uploads.story,'Story',storyUp.progress).then(function(url){
storyUp.done();setTimeout(function(){postStory(url);},500);
}).catch(function(e){alert('Upload error: '+e);});
}else alert('Select an image');
});
// Location modal
$('#detect-loc').on('click',function(){
var btn=$(this);btn.text('Detecting...');
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(function(p){
$.getJSON('https://nominatim.openstreetmap.org/reverse?format=json&lat='+p.coords.latitude+'&lon='+p.coords.longitude,function(d){
$('#loc-input').val((d.display_name||'').split(',').slice(0,3).join(',')||p.coords.latitude+','+p.coords.longitude);
btn.text('📍 Use Current Location');
}).fail(function(){$('#loc-input').val(p.coords.latitude+','+p.coords.longitude);btn.text('📍 Use Current Location');});
},function(){alert('Location unavailable');btn.text('📍 Use Current Location');});
}else alert('Geolocation not supported');
});
$('#loc-add').on('click',function(){
var loc=$('#loc-input').val().trim();
if(loc){pending.loc=loc;$('#pending-location').addClass('show').html('📍 '+esc(loc)+'<button class="sf-del">✕</button>');closeModal('modal-location');}
else alert('Enter location');
});
$('#pending-location').on('click','.sf-del',function(){pending.loc='';$('#pending-location').removeClass('show').empty();});
// Buttons
$('#btn-photo').on('click',function(){$('#photo-url').val('');$('#i-url .sf-preview').hide();photoUp.reset();openModal('modal-photo');});
$('#btn-video').on('click',function(){$('#video-url').val('');videoUp.reset();openModal('modal-video');});
$('#btn-location').on('click',function(){$('#loc-input').val('');openModal('modal-location');});
// Emoji picker
var picker=$('#emoji-picker'),cats=Object.keys(emojis);
picker.html('<div class="sf-emoji-cats">'+cats.map(function(c,i){return'<button class="sf-emoji-cat'+(i===0?' active':'')+'" data-c="'+c+'">'+c+'</button>';}).join('')+'</div><div class="sf-emoji-list"></div>');
function showEmojis(cat){picker.find('.sf-emoji-list').html(emojis[cat].map(function(e){return'<button>'+e+'</button>';}).join(''));}
showEmojis(cats[0]);
picker.on('click','.sf-emoji-cat',function(){picker.find('.sf-emoji-cat').removeClass('active');$(this).addClass('active');showEmojis($(this).data('c'));});
picker.on('click','.sf-emoji-list button',function(){var ta=$('#post-text')[0],e=$(this).text(),s=ta.selectionStart,v=ta.value;ta.value=v.slice(0,s)+e+v.slice(ta.selectionEnd);ta.selectionStart=ta.selectionEnd=s+e.length;ta.focus();});
$('#btn-emoji').on('click',function(e){e.stopPropagation();picker.toggleClass('open');});
$(document).on('click',function(e){if(!$(e.target).closest('#emoji-picker,#btn-emoji').length)picker.removeClass('open');});
// Pending media
function setPendingMedia(type,url){
pending.img=pending.video='';
pending[type==='img'?'img':'video']=url;
var html=type==='img'?'<img src="'+url+'">':'<video src="'+url+'" style="max-height:80px"></video>';
$('#pending-media').addClass('show').html(html+'<button class="sf-del">✕</button>');
}
$('#pending-media').on('click','.sf-del',function(){pending.img=pending.video='';$('#pending-media').removeClass('show').empty();});
// Post
$('#btn-post').on('click',function(){
var txt=$('#post-text').val().trim();
if(!txt&&!pending.img&&!pending.video){alert('Write something or add media');return;}
var btn=$(this);btn.prop('disabled',true).text('Posting...');
var params={action:'socialfeed',sfaction:'createpost',content:txt||'(media)'};
if(pending.img)params.image_url=pending.img;
if(pending.video)params.video_url=pending.video;
if(pending.loc)params.location=pending.loc;
api.postWithToken('csrf',params).then(function(){
$('#post-text').val('');pending={img:'',video:'',loc:''};
$('#pending-media,#pending-location').removeClass('show').empty();
btn.prop('disabled',false).text('Post');
loadPosts();
}).catch(function(e){alert('Error: '+e);btn.prop('disabled',false).text('Post');});
});
// Load posts
function loadPosts(){
api.get({action:'socialfeed',sfaction:'getposts',limit:20}).then(function(r){
var posts=(r.socialfeed&&r.socialfeed.posts)||[];
if(!posts.length){$('#feed').html('<div class="sf-card sf-empty"><div class="sf-empty-icon">📝</div><div>No posts yet</div></div>');return;}
var html=posts.map(function(p){
var pic=localStorage.getItem('sfphoto_'+p.username)||'';
var av=pic?'<img src="'+pic+'">':initials(p.username);
var total=0;for(var k in p.reaction_counts)total+=p.reaction_counts[k];
var media='';
if(p.image_url)media='<div class="sf-post-media"><img src="'+p.image_url+'"></div>';
if(p.video_url){
if(p.video_url.match(/youtube|youtu\.be/)){
var id=(p.video_url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1];
if(id)media='<div class="sf-post-media"><iframe src="https://www.youtube.com/embed/'+id+'" frameborder="0" allowfullscreen style="height:315px"></iframe></div>';
}else media='<div class="sf-post-media"><video src="'+p.video_url+'" controls></video></div>';
}
var loc=p.location?'<div class="sf-post-location">📍 '+esc(p.location)+'</div>':'';
var del=p.username===user?'<button class="sf-del" data-del="'+p.id+'" style="font-size:16px">🗑️</button>':'';
return'<div class="sf-card"><div class="sf-post-header"><div class="sf-avatar sf-avatar-sm">'+av+'</div><div class="sf-post-info"><div class="sf-post-name">'+esc(p.username)+'</div><div class="sf-post-meta">'+timeAgo(p.created)+' · 🌐</div></div>'+del+'</div><div class="sf-post-content">'+esc(p.content)+'</div>'+loc+media+'<div class="sf-post-stats"><div>'+(total?'👍 '+total:'')+'</div><div>'+p.comments+' comments</div></div><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><div class="sf-comments" id="cmt-'+p.id+'" style="display:none"></div></div>';
}).join('');
$('#feed').html(html);
}).catch(function(){$('#feed').html('<div class="sf-card sf-empty"><div class="sf-empty-icon">😕</div><div>Error loading posts</div></div>');});
}
$('#feed').on('click','[data-del]',function(){
if(confirm('Delete this post?'))api.postWithToken('csrf',{action:'socialfeed',sfaction:'deletepost',post_id:$(this).data('del')}).then(loadPosts);
});
$('#feed').on('click','[data-like]',function(){
api.postWithToken('csrf',{action:'socialfeed',sfaction:'react',post_id:$(this).data('like'),reaction_type:'like'}).then(loadPosts);
});
$('#feed').on('click','[data-cmt]',function(){
var id=$(this).data('cmt'),box=$('#cmt-'+id);
if(box.is(':visible')){box.hide();}else{box.show();loadComments(id);}
});
function loadComments(id){
api.get({action:'socialfeed',sfaction:'getcomments',post_id:id}).then(function(r){
var cmts=(r.socialfeed&&r.socialfeed.comments)||[];
var myPic=photo?'<img src="'+photo+'">':initials(user);
var html=cmts.map(function(c){
var pic=localStorage.getItem('sfphoto_'+c.username)||'';
var av=pic?'<img src="'+pic+'">':initials(c.username);
return'<div class="sf-comment"><div class="sf-avatar sf-avatar-sm">'+av+'</div><div class="sf-comment-bubble"><div class="sf-comment-author">'+esc(c.username)+'</div><div class="sf-comment-text">'+esc(c.content)+'</div></div></div>';
}).join('');
html+='<div class="sf-comment-form"><div class="sf-avatar sf-avatar-sm">'+myPic+'</div><input class="sf-comment-input" data-pid="'+id+'" placeholder="Write a comment..."></div>';
$('#cmt-'+id).html(html);
});
}
$('#feed').on('keypress','.sf-comment-input',function(e){
if(e.which===13){
var input=$(this),txt=input.val().trim(),pid=input.data('pid');
if(txt){
input.val('');
api.postWithToken('csrf',{action:'socialfeed',sfaction:'comment',post_id:pid,content:txt}).then(function(){loadComments(pid);loadPosts();});
}
}
});
// Stories
function loadStories(){
api.get({action:'socialfeed',sfaction:'getstories'}).then(function(r){
var stories=(r.socialfeed&&r.socialfeed.stories)||[];
var html='<div class="sf-story"><div class="sf-story-ring add"><div class="sf-story-inner" id="add-story">+</div></div><div class="sf-story-name">Add</div></div>';
html+=stories.map(function(s){
var pic=localStorage.getItem('sfphoto_'+s.username)||'';
var av=pic?'<img src="'+pic+'">':initials(s.username);
return'<div class="sf-story"><div class="sf-story-ring"><div class="sf-story-inner" data-simg="'+s.image_url+'">'+av+'</div></div><div class="sf-story-name">'+esc(s.username)+'</div></div>';
}).join('');
$('#stories').html(html);
}).catch(function(){});
}
$('#stories').on('click','#add-story',function(){
$('#story-url').val('');$('#s-url .sf-preview').hide();storyUp.reset();openModal('modal-story');
});
$('#stories').on('click','[data-simg]',function(){
var viewer=$('#story-viewer'),img=$(this).data('simg');
viewer.find('img').attr('src',img);
viewer.addClass('open');
viewer.find('.sf-viewer-progress span').css('animation','none');
setTimeout(function(){viewer.find('.sf-viewer-progress span').css('animation','progress 5s linear');},10);
setTimeout(function(){viewer.removeClass('open');},5000);
});
$('#story-viewer').on('click',function(e){if($(e.target).is('.sf-viewer,.sf-viewer-close'))$(this).removeClass('open');});
// Init
loadPosts();
loadStories();
});
})();
</script>
</div>
</html>

Latest revision as of 11:37, 3 February 2026

Loading Social Feed...