|
|
| (One intermediate revision by the same user not shown) |
| Line 1: |
Line 1: |
| <div id="social-app"></div> | | <div id="social-app"> |
| <style>
| | <div style="text-align:center;padding:60px;color:#8e8e8e"> |
| #social-app{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#fafafa;min-height:100vh;padding-bottom:40px}
| | <div style="font-size:48px;margin-bottom:16px">⏳</div> |
| #social-app *{box-sizing:border-box;margin:0;padding:0}
| | <div>Loading Social Feed...</div> |
| .sa-profile-header{background:#fff;border-bottom:1px solid #dbdbdb}
| | </div> |
| .sa-cover{height:200px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);position:relative}
| | </div> |
| .sa-cover img{width:100%;height:100%;object-fit:cover}
| |
| .sa-cover-edit{position:absolute;bottom:12px;right:12px;background:rgba(0,0,0,.6);color:#fff;border:none;padding:8px 16px;border-radius:8px;cursor:pointer;font-size:13px}
| |
| .sa-profile-info{max-width:935px;margin:0 auto;padding:0 20px;display:flex;gap:30px;margin-top:-50px;position:relative;z-index:10}
| |
| .sa-profile-pic{width:150px;height:150px;border-radius:50%;border:4px solid #fff;background:#fff;overflow:hidden;cursor:pointer;flex-shrink:0;box-shadow:0 2px 10px rgba(0,0,0,.1)}
| |
| .sa-profile-pic img{width:100%;height:100%;object-fit:cover}
| |
| .sa-profile-pic-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:48px;font-weight:600}
| |
| .sa-profile-details{flex:1;padding-top:60px}
| |
| .sa-profile-top{display:flex;align-items:center;gap:20px;margin-bottom:16px;flex-wrap:wrap}
| |
| .sa-username{font-size:28px;font-weight:300}
| |
| .sa-edit-btn{background:#0095f6;color:#fff;border:none;padding:8px 24px;border-radius:8px;font-weight:600;cursor:pointer;font-size:14px}
| |
| .sa-edit-btn:hover{background:#1877f2}
| |
| .sa-stats{display:flex;gap:32px;margin-bottom:16px}
| |
| .sa-stat{text-align:center}
| |
| .sa-stat-num{font-weight:600;font-size:18px}
| |
| .sa-stat-label{color:#8e8e8e;font-size:14px}
| |
| .sa-bio{max-width:400px;line-height:1.5}
| |
| .sa-bio-name{font-weight:600}
| |
| .sa-main{max-width:935px;margin:0 auto;padding:20px}
| |
| .sa-stories{display:flex;gap:16px;padding:16px 0;overflow-x:auto;margin-bottom:20px}
| |
| .sa-story{display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer}
| |
| .sa-story-ring{width:66px;height:66px;border-radius:50%;padding:3px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)}
| |
| .sa-story-ring.add{background:#dbdbdb}
| |
| .sa-story-ring.seen{background:#dbdbdb}
| |
| .sa-story-inner{width:100%;height:100%;border-radius:50%;border:2px solid #fff;overflow:hidden;background:#fafafa;display:flex;align-items:center;justify-content:center}
| |
| .sa-story-inner img{width:100%;height:100%;object-fit:cover}
| |
| .sa-story-add{font-size:24px;color:#0095f6}
| |
| .sa-story-name{font-size:12px;color:#262626;max-width:66px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
| |
| .sa-composer{background:#fff;border:1px solid #dbdbdb;border-radius:12px;margin-bottom:20px}
| |
| .sa-composer-top{display:flex;gap:12px;padding:16px}
| |
| .sa-composer-avatar{width:44px;height:44px;border-radius:50%;overflow:hidden;flex-shrink:0}
| |
| .sa-composer-avatar img{width:100%;height:100%;object-fit:cover}
| |
| .sa-composer-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;font-weight:600}
| |
| .sa-composer-input{flex:1;border:none;resize:none;font-size:16px;line-height:1.5;min-height:60px;outline:none;font-family:inherit}
| |
| .sa-composer-input::placeholder{color:#8e8e8e}
| |
| .sa-composer-preview{padding:0 16px 16px;display:none}
| |
| .sa-composer-preview.show{display:block}
| |
| .sa-composer-preview-inner{position:relative;display:inline-block}
| |
| .sa-composer-preview img,.sa-composer-preview video{max-width:100%;max-height:300px;border-radius:8px}
| |
| .sa-composer-preview-remove{position:absolute;top:8px;right:8px;width:28px;height:28px;border-radius:50%;background:rgba(0,0,0,.7);color:#fff;border:none;cursor:pointer;font-size:16px}
| |
| .sa-composer-location{padding:0 16px 12px;font-size:14px;color:#8e8e8e;display:none}
| |
| .sa-composer-location.show{display:flex;align-items:center;gap:8px}
| |
| .sa-composer-location button{background:none;border:none;cursor:pointer;font-size:14px}
| |
| .sa-composer-actions{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-top:1px solid #efefef}
| |
| .sa-composer-buttons{display:flex;gap:4px}
| |
| .sa-icon-btn{width:40px;height:40px;border:none;background:none;cursor:pointer;font-size:20px;border-radius:50%;transition:background .2s}
| |
| .sa-icon-btn:hover{background:#f0f0f0}
| |
| .sa-post-btn{background:#0095f6;color:#fff;border:none;padding:10px 24px;border-radius:8px;font-weight:600;cursor:pointer;font-size:14px}
| |
| .sa-post-btn:disabled{opacity:.3;cursor:default}
| |
| .sa-post-btn:not(:disabled):hover{background:#1877f2}
| |
| .sa-feed{display:flex;flex-direction:column;gap:20px}
| |
| .sa-post{background:#fff;border:1px solid #dbdbdb;border-radius:12px;overflow:hidden}
| |
| .sa-post-header{display:flex;align-items:center;gap:12px;padding:14px 16px}
| |
| .sa-post-avatar{width:42px;height:42px;border-radius:50%;overflow:hidden}
| |
| .sa-post-avatar img{width:100%;height:100%;object-fit:cover}
| |
| .sa-post-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:600}
| |
| .sa-post-user{flex:1}
| |
| .sa-post-username{font-weight:600;font-size:14px;color:#262626}
| |
| .sa-post-meta{font-size:12px;color:#8e8e8e}
| |
| .sa-post-menu{background:none;border:none;cursor:pointer;font-size:20px;color:#262626}
| |
| .sa-post-content{padding:0 16px 12px;font-size:15px;line-height:1.5;white-space:pre-wrap}
| |
| .sa-post-location{padding:0 16px 8px;font-size:13px;color:#8e8e8e}
| |
| .sa-post-media{background:#000}
| |
| .sa-post-media img{width:100%;max-height:600px;object-fit:contain}
| |
| .sa-post-media video{width:100%;max-height:600px}
| |
| .sa-post-media iframe{width:100%;height:400px;border:none}
| |
| .sa-post-actions{display:flex;gap:16px;padding:12px 16px}
| |
| .sa-post-action{background:none;border:none;cursor:pointer;font-size:24px;padding:0;transition:transform .2s}
| |
| .sa-post-action:hover{transform:scale(1.1)}
| |
| .sa-post-action.liked{color:#ed4956}
| |
| .sa-post-likes{padding:0 16px 8px;font-weight:600;font-size:14px}
| |
| .sa-post-caption{padding:0 16px 8px;font-size:14px;line-height:1.4}
| |
| .sa-post-caption strong{font-weight:600}
| |
| .sa-post-comments-count{padding:0 16px 8px;font-size:14px;color:#8e8e8e;cursor:pointer}
| |
| .sa-post-time{padding:0 16px 12px;font-size:10px;color:#8e8e8e;text-transform:uppercase}
| |
| .sa-post-comment-form{display:flex;gap:12px;padding:12px 16px;border-top:1px solid #efefef}
| |
| .sa-post-comment-input{flex:1;border:none;font-size:14px;outline:none}
| |
| .sa-post-comment-input::placeholder{color:#8e8e8e}
| |
| .sa-post-comment-submit{background:none;border:none;color:#0095f6;font-weight:600;cursor:pointer;font-size:14px}
| |
| .sa-post-comment-submit:disabled{opacity:.3;cursor:default}
| |
| .sa-comments-section{display:none;padding:0 16px 12px;max-height:300px;overflow-y:auto}
| |
| .sa-comments-section.show{display:block}
| |
| .sa-comment{display:flex;gap:12px;margin-bottom:12px}
| |
| .sa-comment-avatar{width:32px;height:32px;border-radius:50%;overflow:hidden;flex-shrink:0}
| |
| .sa-comment-avatar img{width:100%;height:100%;object-fit:cover}
| |
| .sa-comment-avatar-placeholder{width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:600}
| |
| .sa-comment-body{flex:1}
| |
| .sa-comment-text{font-size:14px;line-height:1.4}
| |
| .sa-comment-text strong{font-weight:600}
| |
| .sa-comment-time{font-size:12px;color:#8e8e8e;margin-top:4px}
| |
| .sa-empty{text-align:center;padding:60px 20px;background:#fff;border:1px solid #dbdbdb;border-radius:12px}
| |
| .sa-empty-icon{font-size:64px;margin-bottom:16px}
| |
| .sa-empty-title{font-size:24px;font-weight:300;margin-bottom:8px}
| |
| .sa-empty-text{color:#8e8e8e}
| |
| .sa-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.65);z-index:1000;align-items:center;justify-content:center;padding:20px}
| |
| .sa-modal.open{display:flex}
| |
| .sa-modal-content{background:#fff;border-radius:12px;width:100%;max-width:500px;max-height:90vh;overflow:auto;animation:modalIn .2s}
| |
| @keyframes modalIn{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}
| |
| .sa-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid #dbdbdb}
| |
| .sa-modal-title{font-weight:600;font-size:16px}
| |
| .sa-modal-close{background:none;border:none;font-size:24px;cursor:pointer;padding:0;line-height:1}
| |
| .sa-modal-body{padding:20px}
| |
| .sa-tabs{display:flex;border-bottom:1px solid #dbdbdb}
| |
| .sa-tab{flex:1;padding:16px;text-align:center;cursor:pointer;font-weight:600;color:#8e8e8e;border-bottom:1px solid transparent;margin-bottom:-1px;transition:all .2s}
| |
| .sa-tab:hover{color:#262626}
| |
| .sa-tab.active{color:#262626;border-bottom-color:#262626}
| |
| .sa-tab-panel{display:none;padding:20px}
| |
| .sa-tab-panel.active{display:block}
| |
| .sa-upload-zone{border:2px dashed #dbdbdb;border-radius:8px;padding:40px;text-align:center;cursor:pointer;transition:all .2s}
| |
| .sa-upload-zone:hover{border-color:#0095f6;background:#f0f7ff}
| |
| .sa-upload-zone.dragover{border-color:#0095f6;background:#e3f2fd}
| |
| .sa-upload-zone input{display:none}
| |
| .sa-upload-icon{font-size:48px;margin-bottom:12px}
| |
| .sa-upload-text{color:#8e8e8e}
| |
| .sa-upload-text span{color:#0095f6;font-weight:600}
| |
| .sa-input{width:100%;padding:14px;border:1px solid #dbdbdb;border-radius:8px;font-size:14px;margin-bottom:12px;outline:none;transition:border-color .2s}
| |
| .sa-input:focus{border-color:#262626}
| |
| .sa-textarea{width:100%;padding:14px;border:1px solid #dbdbdb;border-radius:8px;font-size:14px;margin-bottom:12px;outline:none;resize:vertical;min-height:80px;font-family:inherit}
| |
| .sa-textarea:focus{border-color:#262626}
| |
| .sa-preview-img{width:100%;max-height:200px;object-fit:contain;border-radius:8px;margin-top:12px;display:none}
| |
| .sa-preview-img.show{display:block}
| |
| .sa-progress{height:4px;background:#dbdbdb;border-radius:2px;margin-top:12px;overflow:hidden;display:none}
| |
| .sa-progress.show{display:block}
| |
| .sa-progress-bar{height:100%;background:linear-gradient(90deg,#0095f6,#00d4ff);width:0;transition:width .3s}
| |
| .sa-btn-row{display:flex;gap:12px;margin-top:20px}
| |
| .sa-btn{flex:1;padding:14px;border:none;border-radius:8px;font-weight:600;font-size:14px;cursor:pointer;transition:all .2s}
| |
| .sa-btn-primary{background:#0095f6;color:#fff}
| |
| .sa-btn-primary:hover{background:#1877f2}
| |
| .sa-btn-secondary{background:#efefef;color:#262626}
| |
| .sa-btn-secondary:hover{background:#dbdbdb}
| |
| .sa-emoji-picker{position:absolute;bottom:50px;left:0;background:#fff;border-radius:12px;box-shadow:0 0 20px rgba(0,0,0,.15);width:320px;z-index:100;display:none}
| |
| .sa-emoji-picker.open{display:block}
| |
| .sa-emoji-header{display:flex;gap:4px;padding:12px;border-bottom:1px solid #efefef;overflow-x:auto}
| |
| .sa-emoji-cat{background:none;border:none;font-size:20px;padding:8px;cursor:pointer;border-radius:8px;opacity:.5;transition:all .2s}
| |
| .sa-emoji-cat:hover,.sa-emoji-cat.active{opacity:1;background:#efefef}
| |
| .sa-emoji-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:4px;padding:12px;max-height:200px;overflow-y:auto}
| |
| .sa-emoji-btn{background:none;border:none;font-size:24px;padding:4px;cursor:pointer;border-radius:4px;transition:background .2s}
| |
| .sa-emoji-btn:hover{background:#efefef}
| |
| .sa-story-viewer{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:#1a1a1a;z-index:2000;align-items:center;justify-content:center}
| |
| .sa-story-viewer.open{display:flex}
| |
| .sa-story-viewer-content{position:relative;max-width:400px;width:100%}
| |
| .sa-story-viewer-progress{position:absolute;top:16px;left:16px;right:16px;height:2px;background:rgba(255,255,255,.3);border-radius:1px}
| |
| .sa-story-viewer-progress-bar{height:100%;background:#fff;border-radius:1px;width:0;animation:storyProgress 5s linear forwards}
| |
| @keyframes storyProgress{from{width:0}to{width:100%}}
| |
| .sa-story-viewer-header{position:absolute;top:24px;left:16px;right:16px;display:flex;align-items:center;gap:12px;color:#fff}
| |
| .sa-story-viewer-avatar{width:32px;height:32px;border-radius:50%;overflow:hidden;border:2px solid #fff}
| |
| .sa-story-viewer-avatar img{width:100%;height:100%;object-fit:cover}
| |
| .sa-story-viewer-name{font-weight:600;font-size:14px}
| |
| .sa-story-viewer-close{position:absolute;top:16px;right:16px;background:none;border:none;color:#fff;font-size:28px;cursor:pointer;z-index:10}
| |
| .sa-story-viewer img{max-width:100%;max-height:80vh;border-radius:8px}
| |
| @media(max-width:768px){
| |
| .sa-profile-info{flex-direction:column;align-items:center;text-align:center;margin-top:-75px}
| |
| .sa-profile-pic{width:120px;height:120px}
| |
| .sa-profile-details{padding-top:16px}
| |
| .sa-stats{justify-content:center}
| |
| .sa-bio{margin:0 auto}
| |
| .sa-cover{height:150px}
| |
| }
| |
| </style>
| |
| <script>
| |
| (function(){
| |
| 'use strict';
| |
| | |
| function waitForMw(callback,maxWait){
| |
| var start=Date.now();
| |
| maxWait=maxWait||10000;
| |
| (function check(){
| |
| if(window.jQuery&&window.mw&&mw.loader&&mw.config){
| |
| callback(window.jQuery,window.mw);
| |
| }else if(Date.now()-start<maxWait){
| |
| setTimeout(check,100);
| |
| }else{
| |
| console.error('MediaWiki not loaded');
| |
| document.getElementById('social-app').innerHTML='<div style="padding:40px;text-align:center">Error: MediaWiki not loaded. Please refresh.</div>';
| |
| }
| |
| })();
| |
| }
| |
| | |
| waitForMw(function($,mw){
| |
| mw.loader.using(['mediawiki.api','mediawiki.util']).then(function(){
| |
| initApp($,mw);
| |
| }).catch(function(e){
| |
| console.error('Module load error:',e);
| |
| document.getElementById('social-app').innerHTML='<div style="padding:40px;text-align:center">Error loading modules. Please refresh.</div>';
| |
| });
| |
| });
| |
| | |
| function initApp($,mw){
| |
| var api=new mw.Api();
| |
| var apiUrl=mw.util.wikiScript('api');
| |
| var currentUser=mw.config.get('wgUserName')||'Guest';
| |
| var isLoggedIn=currentUser!=='Guest'&¤tUser!==null;
| |
| | |
| var userData={
| |
| photo:localStorage.getItem('sf_photo_'+currentUser)||'',
| |
| cover:localStorage.getItem('sf_cover_'+currentUser)||'',
| |
| bio:localStorage.getItem('sf_bio_'+currentUser)||'',
| |
| displayName:localStorage.getItem('sf_name_'+currentUser)||currentUser
| |
| };
| |
| | |
| var pendingPost={image:'',video:'',location:''};
| |
| var uploadFiles={};
| |
| | |
| var emojis={
| |
| '😀':['😀','😃','😄','😁','😆','😅','🤣','😂','🙂','😉','😊','😇','🥰','😍','🤩','😘','😗','😚','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','🤥','😌','😔','😪','🤤','😴','😷','🤒','🤕','🤢','🤮','🤧','🥵','🥶','🥴','😵','🤯','🤠','🥳','🥸','😎','🤓','🧐','😕','😟','🙁','☹️','😮','😯','😲','😳','🥺','😦','😧','😨','😰','😥','😢','😭','😱','😖','😣','😞','😓','😩','😫','🥱','😤','😡','😠','🤬','😈','👿','💀','☠️','💩','🤡','👹','👺','👻','👽','👾','🤖'],
| |
| '👋':['👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💅','🤳','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','🧠','🫀','🫁','🦷','🦴','👀','👁️','👅','👄','💋','🩸'],
| |
| '❤️':['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝','💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓'],
| |
| '🐶':['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐻❄️','🐨','🐯','🦁','🐮','🐷','🐽','🐸','🐵','🙈','🙉','🙊','🐒','🐔','🐧','🐦','🐤','🐣','🐥','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄','🐝','🪱','🐛','🦋','🐌','🐞','🐜','🪰','🪲','🪳','🦟','🦗','🕷️','🦂','🐢','🐍','🦎','🦖','🦕','🐙','🦑','🦐','🦞','🦀','🐡','🐠','🐟','🐬','🐳','🐋','🦈','🐊','🐅','🐆','🦓','🦍','🦧','🦣','🐘','🦛','🦏','🐪','🐫','🦒','🦘','🦬','🐃','🐂','🐄','🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐕🦺','🐈','🐈⬛','🪶','🐓','🦃','🦤','🦚','🦜','🦢','🦩','🕊️','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿️','🦔'],
| |
| '🍎':['🍎','🍐','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🍆','🥑','🥦','🥬','🥒','🌶️','🫑','🌽','🥕','🫒','🧄','🧅','🥔','🍠','🥐','🥯','🍞','🥖','🥨','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🦴','🌭','🍔','🍟','🍕','🫓','🥪','🥙','🧆','🌮','🌯','🫔','🥗','🥘','🫕','🥫','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🦪','🍤','🍙','🍚','🍘','🍥','🥠','🥮','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪','🌰','🥜','🍯','🥛','🍼','🫖','☕','🍵','🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊','🥄','🍴','🍽️','🥣','🥡','🥢','🧂'],
| |
| '⚽':['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛼','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️','🤼','🤸','⛹️','🤺','🤾','🏌️','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅','🎖️','🏵️','🎗️','🎫','🎟️','🎪','🤹','🎭','🩰','🎨','🎬','🎤','🎧','🎼','🎹','🥁','🪘','🎷','🎺','🪗','🎸','🪕','🎻','🪈','🎲','♟️','🎯','🎳','🎮','🎰','🧩']
| |
| };
| |
| | |
| function getInitials(name){
| |
| return(name||'?').split(/[\s_]+/).map(function(w){return(w[0]||'').toUpperCase();}).join('').substring(0,2)||'?';
| |
| }
| |
| | |
| function escapeHtml(text){
| |
| var div=document.createElement('div');
| |
| div.textContent=text;
| |
| return div.innerHTML;
| |
| }
| |
| | |
| function timeAgo(dateStr){
| |
| var seconds=Math.floor((Date.now()-new Date(dateStr))/1000);
| |
| if(seconds<60)return'Just now';
| |
| var minutes=Math.floor(seconds/60);
| |
| if(minutes<60)return minutes+'m ago';
| |
| var hours=Math.floor(minutes/60);
| |
| if(hours<24)return hours+'h ago';
| |
| var days=Math.floor(hours/24);
| |
| if(days<7)return days+'d ago';
| |
| var weeks=Math.floor(days/7);
| |
| return weeks+'w ago';
| |
| }
| |
| | |
| function renderAvatar(pic,name,size){
| |
| if(pic)return'<img src="'+escapeHtml(pic)+'" alt="">';
| |
| var cls=size==='small'?'sa-comment-avatar-placeholder':size==='post'?'sa-post-avatar-placeholder':size==='composer'?'sa-composer-avatar-placeholder':'sa-profile-pic-placeholder';
| |
| return'<div class="'+cls+'">'+getInitials(name)+'</div>';
| |
| }
| |
| | |
| function openModal(id){$('#'+id).addClass('open');}
| |
| function closeModal(id){$('#'+id).removeClass('open');}
| |
| | |
| function uploadFile(file,prefix,onProgress){
| |
| return api.getToken('csrf').then(function(token){
| |
| var ext=file.name.split('.').pop()||'jpg';
| |
| var filename=prefix+'_'+currentUser+'_'+Date.now()+'.'+ext;
| |
| var formData=new FormData();
| |
| formData.append('action','upload');
| |
| formData.append('filename',filename);
| |
| formData.append('file',file);
| |
| formData.append('token',token);
| |
| formData.append('format','json');
| |
| formData.append('ignorewarnings','1');
| |
| return $.ajax({
| |
| url:apiUrl,
| |
| type:'POST',
| |
| data:formData,
| |
| processData:false,
| |
| contentType:false,
| |
| xhr:function(){
| |
| var xhr=$.ajaxSettings.xhr();
| |
| if(xhr.upload&&onProgress){
| |
| xhr.upload.addEventListener('progress',function(e){
| |
| if(e.lengthComputable)onProgress(Math.round(e.loaded/e.total*100));
| |
| });
| |
| }
| |
| return xhr;
| |
| }
| |
| });
| |
| }).then(function(response){
| |
| if(response.upload&&response.upload.imageinfo){
| |
| return response.upload.imageinfo.url;
| |
| }
| |
| throw new Error(response.error?response.error.info:'Upload failed');
| |
| });
| |
| }
| |
| | |
| function buildApp(){
| |
| var html='\
| |
| <div class="sa-profile-header">\
| |
| <div class="sa-cover" id="cover-area">'+(userData.cover?'<img src="'+escapeHtml(userData.cover)+'" alt="">':'')+'<button class="sa-cover-edit" id="edit-cover-btn">📷 Edit Cover</button></div>\
| |
| <div class="sa-profile-info">\
| |
| <div class="sa-profile-pic" id="profile-pic">'+renderAvatar(userData.photo,currentUser,'profile')+'</div>\
| |
| <div class="sa-profile-details">\
| |
| <div class="sa-profile-top">\
| |
| <h1 class="sa-username" id="display-name">'+escapeHtml(userData.displayName)+'</h1>\
| |
| <button class="sa-edit-btn" id="edit-profile-btn">Edit Profile</button>\
| |
| </div>\
| |
| <div class="sa-stats">\
| |
| <div class="sa-stat"><div class="sa-stat-num" id="post-count">0</div><div class="sa-stat-label">posts</div></div>\
| |
| <div class="sa-stat"><div class="sa-stat-num">0</div><div class="sa-stat-label">followers</div></div>\
| |
| <div class="sa-stat"><div class="sa-stat-num">0</div><div class="sa-stat-label">following</div></div>\
| |
| </div>\
| |
| <div class="sa-bio" id="bio-display">'+(userData.bio?'<div class="sa-bio-name">'+escapeHtml(userData.displayName)+'</div>'+escapeHtml(userData.bio).replace(/\n/g,'<br>'):'Click Edit Profile to add a bio')+'</div>\
| |
| </div>\
| |
| </div>\
| |
| </div>\
| |
| <div class="sa-main">\
| |
| <div class="sa-stories" id="stories-container"></div>\
| |
| <div class="sa-composer">\
| |
| <div class="sa-composer-top">\
| |
| <div class="sa-composer-avatar" id="composer-avatar">'+renderAvatar(userData.photo,currentUser,'composer')+'</div>\
| |
| <textarea class="sa-composer-input" id="post-input" placeholder="What\'s on your mind, '+escapeHtml(currentUser.split(/[\s_]/)[0])+'?"></textarea>\
| |
| </div>\
| |
| <div class="sa-composer-preview" id="post-preview"><div class="sa-composer-preview-inner"><button class="sa-composer-preview-remove" id="remove-media">×</button></div></div>\
| |
| <div class="sa-composer-location" id="post-location">📍 <span></span><button id="remove-location">×</button></div>\
| |
| <div class="sa-composer-actions">\
| |
| <div class="sa-composer-buttons" style="position:relative">\
| |
| <button class="sa-icon-btn" id="btn-add-photo" title="Photo">🖼️</button>\
| |
| <button class="sa-icon-btn" id="btn-add-video" title="Video">🎬</button>\
| |
| <button class="sa-icon-btn" id="btn-add-location" title="Location">📍</button>\
| |
| <button class="sa-icon-btn" id="btn-add-emoji" title="Emoji">😊</button>\
| |
| <div class="sa-emoji-picker" id="emoji-picker"></div>\
| |
| </div>\
| |
| <button class="sa-post-btn" id="btn-submit-post" disabled>Post</button>\
| |
| </div>\
| |
| </div>\
| |
| <div class="sa-feed" id="feed-container"></div>\
| |
| </div>';
| |
| | |
| // Modals
| |
| html+='\
| |
| <div class="sa-modal" id="modal-edit-profile">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Edit Profile</span><button class="sa-modal-close" data-close="modal-edit-profile">×</button></div>\
| |
| <div class="sa-modal-body">\
| |
| <label style="font-weight:600;display:block;margin-bottom:8px">Display Name</label>\
| |
| <input class="sa-input" id="input-display-name" value="'+escapeHtml(userData.displayName)+'">\
| |
| <label style="font-weight:600;display:block;margin-bottom:8px">Bio</label>\
| |
| <textarea class="sa-textarea" id="input-bio" placeholder="Tell people about yourself...">'+escapeHtml(userData.bio)+'</textarea>\
| |
| <div class="sa-btn-row">\
| |
| <button class="sa-btn sa-btn-secondary" data-close="modal-edit-profile">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="save-profile">Save</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-modal" id="modal-profile-pic">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Profile Picture</span><button class="sa-modal-close" data-close="modal-profile-pic">×</button></div>\
| |
| <div class="sa-tabs"><div class="sa-tab active" data-panel="panel-pic-upload">Upload</div><div class="sa-tab" data-panel="panel-pic-url">URL</div></div>\
| |
| <div class="sa-tab-panel active" id="panel-pic-upload">\
| |
| <div class="sa-upload-zone" id="zone-profile-pic"><input type="file" accept="image/*"><div class="sa-upload-icon">📷</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
| |
| <div class="sa-progress" id="progress-profile-pic"><div class="sa-progress-bar"></div></div>\
| |
| <img class="sa-preview-img" id="preview-profile-pic">\
| |
| </div>\
| |
| <div class="sa-tab-panel" id="panel-pic-url">\
| |
| <input class="sa-input" id="input-pic-url" placeholder="Paste image URL...">\
| |
| <img class="sa-preview-img" id="preview-pic-url">\
| |
| </div>\
| |
| <div class="sa-btn-row" style="padding:0 20px 20px">\
| |
| <button class="sa-btn sa-btn-secondary" data-close="modal-profile-pic">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="save-profile-pic">Save</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-modal" id="modal-cover">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Cover Photo</span><button class="sa-modal-close" data-close="modal-cover">×</button></div>\
| |
| <div class="sa-tabs"><div class="sa-tab active" data-panel="panel-cover-upload">Upload</div><div class="sa-tab" data-panel="panel-cover-url">URL</div></div>\
| |
| <div class="sa-tab-panel active" id="panel-cover-upload">\
| |
| <div class="sa-upload-zone" id="zone-cover"><input type="file" accept="image/*"><div class="sa-upload-icon">🖼️</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
| |
| <div class="sa-progress" id="progress-cover"><div class="sa-progress-bar"></div></div>\
| |
| <img class="sa-preview-img" id="preview-cover">\
| |
| </div>\
| |
| <div class="sa-tab-panel" id="panel-cover-url">\
| |
| <input class="sa-input" id="input-cover-url" placeholder="Paste image URL...">\
| |
| <img class="sa-preview-img" id="preview-cover-url">\
| |
| </div>\
| |
| <div class="sa-btn-row" style="padding:0 20px 20px">\
| |
| <button class="sa-btn sa-btn-secondary" data-close="modal-cover">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="save-cover">Save</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-modal" id="modal-photo">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Add Photo</span><button class="sa-modal-close" data-close="modal-photo">×</button></div>\
| |
| <div class="sa-tabs"><div class="sa-tab active" data-panel="panel-photo-upload">Upload</div><div class="sa-tab" data-panel="panel-photo-url">URL</div></div>\
| |
| <div class="sa-tab-panel active" id="panel-photo-upload">\
| |
| <div class="sa-upload-zone" id="zone-photo"><input type="file" accept="image/*"><div class="sa-upload-icon">📤</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
| |
| <div class="sa-progress" id="progress-photo"><div class="sa-progress-bar"></div></div>\
| |
| <img class="sa-preview-img" id="preview-photo">\
| |
| </div>\
| |
| <div class="sa-tab-panel" id="panel-photo-url">\
| |
| <input class="sa-input" id="input-photo-url" placeholder="Paste image URL...">\
| |
| <img class="sa-preview-img" id="preview-photo-url">\
| |
| </div>\
| |
| <div class="sa-btn-row" style="padding:0 20px 20px">\
| |
| <button class="sa-btn sa-btn-secondary" data-close="modal-photo">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="add-photo">Add Photo</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-modal" id="modal-video">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Add Video</span><button class="sa-modal-close" data-close="modal-video">×</button></div>\
| |
| <div class="sa-tabs"><div class="sa-tab active" data-panel="panel-video-upload">Upload</div><div class="sa-tab" data-panel="panel-video-url">URL</div></div>\
| |
| <div class="sa-tab-panel active" id="panel-video-upload">\
| |
| <div class="sa-upload-zone" id="zone-video"><input type="file" accept="video/*"><div class="sa-upload-icon">🎬</div><div class="sa-upload-text">Drag video here or <span>browse</span></div></div>\
| |
| <div class="sa-progress" id="progress-video"><div class="sa-progress-bar"></div></div>\
| |
| <video class="sa-preview-img" id="preview-video" controls style="display:none"></video>\
| |
| </div>\
| |
| <div class="sa-tab-panel" id="panel-video-url">\
| |
| <input class="sa-input" id="input-video-url" placeholder="YouTube, Vimeo, or direct video URL...">\
| |
| <p style="color:#8e8e8e;font-size:12px">Supports YouTube, Vimeo, and direct video links</p>\
| |
| </div>\
| |
| <div class="sa-btn-row" style="padding:0 20px 20px">\
| |
| <button class="sa-btn sa-btn-secondary" data-close="modal-video">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="add-video">Add Video</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-modal" id="modal-location">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Add Location</span><button class="sa-modal-close" data-close="modal-location">×</button></div>\
| |
| <div class="sa-modal-body">\
| |
| <button class="sa-btn sa-btn-primary" id="detect-location" style="width:100%;margin-bottom:16px">📍 Use Current Location</button>\
| |
| <div style="text-align:center;color:#8e8e8e;margin-bottom:16px">— or —</div>\
| |
| <input class="sa-input" id="input-location" placeholder="Enter location manually...">\
| |
| <div class="sa-btn-row">\ | |
| <button class="sa-btn sa-btn-secondary" data-close="modal-location">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="add-location">Add</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-modal" id="modal-story">\
| |
| <div class="sa-modal-content">\
| |
| <div class="sa-modal-header"><span class="sa-modal-title">Add to Story</span><button class="sa-modal-close" data-close="modal-story">×</button></div>\
| |
| <div class="sa-tabs"><div class="sa-tab active" data-panel="panel-story-upload">Upload</div><div class="sa-tab" data-panel="panel-story-url">URL</div></div>\
| |
| <div class="sa-tab-panel active" id="panel-story-upload">\
| |
| <div class="sa-upload-zone" id="zone-story"><input type="file" accept="image/*"><div class="sa-upload-icon">📷</div><div class="sa-upload-text">Drag photo here or <span>browse</span></div></div>\
| |
| <div class="sa-progress" id="progress-story"><div class="sa-progress-bar"></div></div>\
| |
| <img class="sa-preview-img" id="preview-story">\
| |
| </div>\
| |
| <div class="sa-tab-panel" id="panel-story-url">\
| |
| <input class="sa-input" id="input-story-url" placeholder="Paste image URL...">\
| |
| <img class="sa-preview-img" id="preview-story-url">\
| |
| </div>\
| |
| <div class="sa-btn-row" style="padding:0 20px 20px">\
| |
| <button class="sa-btn sa-btn-secondary" data-close="modal-story">Cancel</button>\
| |
| <button class="sa-btn sa-btn-primary" id="add-story">Share to Story</button>\
| |
| </div>\
| |
| </div>\
| |
| </div>';
| |
| | |
| html+='\
| |
| <div class="sa-story-viewer" id="story-viewer">\
| |
| <div class="sa-story-viewer-content">\
| |
| <div class="sa-story-viewer-progress"><div class="sa-story-viewer-progress-bar"></div></div>\
| |
| <button class="sa-story-viewer-close" id="close-story-viewer">×</button>\
| |
| <div class="sa-story-viewer-header">\
| |
| <div class="sa-story-viewer-avatar" id="story-viewer-avatar"></div>\
| |
| <div class="sa-story-viewer-name" id="story-viewer-name"></div>\
| |
| </div>\
| |
| <img id="story-viewer-image" src="">\
| |
| </div>\
| |
| </div>';
| |
| | |
| $('#social-app').html(html);
| |
| }
| |
| | |
| function setupEventListeners(){
| |
| var $app=$('#social-app');
| |
| | |
| // Modal close buttons
| |
| $app.on('click','[data-close]',function(){
| |
| closeModal($(this).data('close'));
| |
| });
| |
| | |
| // Modal backdrop click
| |
| $app.on('click','.sa-modal',function(e){
| |
| if($(e.target).hasClass('sa-modal'))closeModal(this.id);
| |
| });
| |
| | |
| // Tabs
| |
| $app.on('click','.sa-tab',function(){
| |
| var $tab=$(this),$panel=$('#'+$tab.data('panel'));
| |
| $tab.addClass('active').siblings().removeClass('active');
| |
| $panel.addClass('active').siblings('.sa-tab-panel').removeClass('active');
| |
| });
| |
| | |
| // Upload zones
| |
| function setupUploadZone(zoneId,previewId,progressId,fileKey){
| |
| var $zone=$('#'+zoneId),$input=$zone.find('input'),$preview=$('#'+previewId),$progress=$('#'+progressId);
| |
| $zone.on('click',function(){$input.click();});
| |
| $zone.on('dragover',function(e){e.preventDefault();$(this).addClass('dragover');});
| |
| $zone.on('dragleave drop',function(){$(this).removeClass('dragover');});
| |
| $zone.on('drop',function(e){
| |
| e.preventDefault();
| |
| var files=e.originalEvent.dataTransfer.files;
| |
| if(files.length)handleFile(files[0]);
| |
| });
| |
| $input.on('change',function(){if(this.files.length)handleFile(this.files[0]);});
| |
| function handleFile(file){
| |
| uploadFiles[fileKey]=file;
| |
| if(file.type.startsWith('image/')){
| |
| var reader=new FileReader();
| |
| reader.onload=function(e){$preview.attr('src',e.target.result).addClass('show');};
| |
| reader.readAsDataURL(file);
| |
| }else if(file.type.startsWith('video/')){
| |
| $preview.attr('src',URL.createObjectURL(file)).addClass('show').show();
| |
| }
| |
| }
| |
| return{
| |
| reset:function(){uploadFiles[fileKey]=null;$preview.removeClass('show').hide().attr('src','');$progress.removeClass('show').find('.sa-progress-bar').css('width',0);$input.val('');},
| |
| setProgress:function(pct){$progress.addClass('show').find('.sa-progress-bar').css('width',pct+'%');}
| |
| };
| |
| }
| |
| | |
| var uploadProfilePic=setupUploadZone('zone-profile-pic','preview-profile-pic','progress-profile-pic','profilePic');
| |
| var uploadCover=setupUploadZone('zone-cover','preview-cover','progress-cover','cover');
| |
| var uploadPhoto=setupUploadZone('zone-photo','preview-photo','progress-photo','photo');
| |
| var uploadVideo=setupUploadZone('zone-video','preview-video','progress-video','video');
| |
| var uploadStory=setupUploadZone('zone-story','preview-story','progress-story','story');
| |
| | |
| // URL preview inputs
| |
| $('#input-pic-url').on('input',function(){var v=$(this).val();$('#preview-pic-url').attr('src',v).toggleClass('show',!!v);});
| |
| $('#input-cover-url').on('input',function(){var v=$(this).val();$('#preview-cover-url').attr('src',v).toggleClass('show',!!v);});
| |
| $('#input-photo-url').on('input',function(){var v=$(this).val();$('#preview-photo-url').attr('src',v).toggleClass('show',!!v);});
| |
| $('#input-story-url').on('input',function(){var v=$(this).val();$('#preview-story-url').attr('src',v).toggleClass('show',!!v);});
| |
| | |
| // Edit profile button
| |
| $('#edit-profile-btn').on('click',function(){openModal('modal-edit-profile');});
| |
| $('#save-profile').on('click',function(){
| |
| userData.displayName=$('#input-display-name').val().trim()||currentUser;
| |
| userData.bio=$('#input-bio').val().trim();
| |
| localStorage.setItem('sf_name_'+currentUser,userData.displayName);
| |
| localStorage.setItem('sf_bio_'+currentUser,userData.bio);
| |
| $('#display-name').text(userData.displayName);
| |
| $('#bio-display').html(userData.bio?'<div class="sa-bio-name">'+escapeHtml(userData.displayName)+'</div>'+escapeHtml(userData.bio).replace(/\n/g,'<br>'):'Click Edit Profile to add a bio');
| |
| closeModal('modal-edit-profile');
| |
| });
| |
| | |
| // Profile picture
| |
| $('#profile-pic').on('click',function(){uploadProfilePic.reset();$('#input-pic-url').val('');$('#preview-pic-url').removeClass('show');openModal('modal-profile-pic');});
| |
| $('#save-profile-pic').on('click',function(){
| |
| var activePanel=$('#modal-profile-pic .sa-tab.active').data('panel');
| |
| if(activePanel==='panel-pic-url'){
| |
| var url=$('#input-pic-url').val().trim();
| |
| if(url){saveProfilePic(url);}else{alert('Please enter a URL');}
| |
| }else if(uploadFiles.profilePic){
| |
| uploadFile(uploadFiles.profilePic,'ProfilePic',uploadProfilePic.setProgress).then(function(url){
| |
| saveProfilePic(url);
| |
| }).catch(function(e){alert('Upload error: '+e.message);});
| |
| }else{alert('Please select an image');}
| |
| });
| |
| function saveProfilePic(url){
| |
| userData.photo=url;
| |
| localStorage.setItem('sf_photo_'+currentUser,url);
| |
| $('#profile-pic').html(renderAvatar(url,currentUser,'profile'));
| |
| $('#composer-avatar').html(renderAvatar(url,currentUser,'composer'));
| |
| closeModal('modal-profile-pic');
| |
| uploadProfilePic.reset();
| |
| }
| |
| | |
| // Cover photo
| |
| $('#edit-cover-btn').on('click',function(){uploadCover.reset();$('#input-cover-url').val('');$('#preview-cover-url').removeClass('show');openModal('modal-cover');});
| |
| $('#save-cover').on('click',function(){
| |
| var activePanel=$('#modal-cover .sa-tab.active').data('panel');
| |
| if(activePanel==='panel-cover-url'){
| |
| var url=$('#input-cover-url').val().trim();
| |
| if(url){saveCover(url);}else{alert('Please enter a URL');}
| |
| }else if(uploadFiles.cover){
| |
| uploadFile(uploadFiles.cover,'Cover',uploadCover.setProgress).then(function(url){
| |
| saveCover(url);
| |
| }).catch(function(e){alert('Upload error: '+e.message);});
| |
| }else{alert('Please select an image');}
| |
| });
| |
| function saveCover(url){
| |
| userData.cover=url;
| |
| localStorage.setItem('sf_cover_'+currentUser,url);
| |
| $('#cover-area').html('<img src="'+url+'" alt=""><button class="sa-cover-edit" id="edit-cover-btn">📷 Edit Cover</button>');
| |
| closeModal('modal-cover');
| |
| uploadCover.reset();
| |
| }
| |
| | |
| // Photo for post
| |
| $('#btn-add-photo').on('click',function(){uploadPhoto.reset();$('#input-photo-url').val('');$('#preview-photo-url').removeClass('show');openModal('modal-photo');});
| |
| $('#add-photo').on('click',function(){
| |
| var activePanel=$('#modal-photo .sa-tab.active').data('panel');
| |
| if(activePanel==='panel-photo-url'){
| |
| var url=$('#input-photo-url').val().trim();
| |
| if(url){setPendingMedia('image',url);closeModal('modal-photo');}else{alert('Please enter a URL');}
| |
| }else if(uploadFiles.photo){
| |
| uploadFile(uploadFiles.photo,'Post',uploadPhoto.setProgress).then(function(url){
| |
| setPendingMedia('image',url);
| |
| closeModal('modal-photo');
| |
| uploadPhoto.reset();
| |
| }).catch(function(e){alert('Upload error: '+e.message);});
| |
| }else{alert('Please select an image');}
| |
| });
| |
| | |
| // Video for post
| |
| $('#btn-add-video').on('click',function(){uploadVideo.reset();$('#input-video-url').val('');openModal('modal-video');});
| |
| $('#add-video').on('click',function(){
| |
| var activePanel=$('#modal-video .sa-tab.active').data('panel');
| |
| if(activePanel==='panel-video-url'){
| |
| var url=$('#input-video-url').val().trim();
| |
| if(url){setPendingMedia('video',url);closeModal('modal-video');}else{alert('Please enter a URL');}
| |
| }else if(uploadFiles.video){
| |
| uploadFile(uploadFiles.video,'Video',uploadVideo.setProgress).then(function(url){
| |
| setPendingMedia('video',url);
| |
| closeModal('modal-video');
| |
| uploadVideo.reset();
| |
| }).catch(function(e){alert('Upload error: '+e.message);});
| |
| }else{alert('Please select a video');}
| |
| });
| |
| | |
| function setPendingMedia(type,url){
| |
| pendingPost.image='';pendingPost.video='';
| |
| pendingPost[type]=url;
| |
| var $preview=$('#post-preview'),$inner=$preview.find('.sa-composer-preview-inner');
| |
| if(type==='image'){
| |
| $inner.find('img,video').remove();
| |
| $inner.append('<img src="'+url+'" alt="">');
| |
| }else{
| |
| $inner.find('img,video').remove();
| |
| if(url.match(/youtube|youtu\.be/)){
| |
| $inner.append('<img src="https://img.youtube.com/vi/'+(url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1]+'/hqdefault.jpg" alt="">');
| |
| }else{
| |
| $inner.append('<video src="'+url+'" style="max-height:200px"></video>');
| |
| }
| |
| }
| |
| $preview.addClass('show');
| |
| updatePostButton();
| |
| }
| |
| | |
| $('#remove-media').on('click',function(){
| |
| pendingPost.image='';pendingPost.video='';
| |
| $('#post-preview').removeClass('show').find('img,video').remove();
| |
| updatePostButton();
| |
| });
| |
| | |
| // Location
| |
| $('#btn-add-location').on('click',function(){$('#input-location').val('');openModal('modal-location');});
| |
| $('#detect-location').on('click',function(){
| |
| var $btn=$(this);
| |
| $btn.text('Detecting...').prop('disabled',true);
| |
| if(navigator.geolocation){
| |
| navigator.geolocation.getCurrentPosition(function(pos){
| |
| $.getJSON('https://nominatim.openstreetmap.org/reverse?format=json&lat='+pos.coords.latitude+'&lon='+pos.coords.longitude).done(function(data){
| |
| var loc=(data.display_name||'').split(',').slice(0,3).join(',')||pos.coords.latitude+','+pos.coords.longitude;
| |
| $('#input-location').val(loc);
| |
| $btn.text('📍 Use Current Location').prop('disabled',false);
| |
| }).fail(function(){
| |
| $('#input-location').val(pos.coords.latitude+','+pos.coords.longitude);
| |
| $btn.text('📍 Use Current Location').prop('disabled',false);
| |
| });
| |
| },function(){
| |
| alert('Could not get location');
| |
| $btn.text('📍 Use Current Location').prop('disabled',false);
| |
| });
| |
| }else{
| |
| alert('Geolocation not supported');
| |
| $btn.text('📍 Use Current Location').prop('disabled',false);
| |
| }
| |
| });
| |
| $('#add-location').on('click',function(){
| |
| var loc=$('#input-location').val().trim();
| |
| if(loc){
| |
| pendingPost.location=loc;
| |
| $('#post-location').addClass('show').find('span').text(loc);
| |
| closeModal('modal-location');
| |
| }else{alert('Please enter a location');}
| |
| });
| |
| $('#remove-location').on('click',function(){
| |
| pendingPost.location='';
| |
| $('#post-location').removeClass('show');
| |
| });
| |
| | |
| // Emoji picker
| |
| var $picker=$('#emoji-picker');
| |
| var emojiCats=Object.keys(emojis);
| |
| $picker.html('<div class="sa-emoji-header">'+emojiCats.map(function(c,i){return'<button class="sa-emoji-cat'+(i===0?' active':'')+'" data-cat="'+c+'">'+c+'</button>';}).join('')+'</div><div class="sa-emoji-grid"></div>');
| |
| function showEmojis(cat){$picker.find('.sa-emoji-grid').html(emojis[cat].map(function(e){return'<button class="sa-emoji-btn">'+e+'</button>';}).join(''));}
| |
| showEmojis(emojiCats[0]);
| |
| $picker.on('click','.sa-emoji-cat',function(){$(this).addClass('active').siblings().removeClass('active');showEmojis($(this).data('cat'));});
| |
| $picker.on('click','.sa-emoji-btn',function(){
| |
| var emoji=$(this).text(),$input=$('#post-input')[0];
| |
| var start=$input.selectionStart,val=$input.value;
| |
| $input.value=val.slice(0,start)+emoji+val.slice($input.selectionEnd);
| |
| $input.selectionStart=$input.selectionEnd=start+emoji.length;
| |
| $input.focus();
| |
| updatePostButton();
| |
| });
| |
| $('#btn-add-emoji').on('click',function(e){e.stopPropagation();$picker.toggleClass('open');});
| |
| $(document).on('click',function(e){if(!$(e.target).closest('#emoji-picker,#btn-add-emoji').length)$picker.removeClass('open');});
| |
| | |
| // Post input
| |
| $('#post-input').on('input',updatePostButton);
| |
| function updatePostButton(){
| |
| var hasContent=$('#post-input').val().trim()||pendingPost.image||pendingPost.video;
| |
| $('#btn-submit-post').prop('disabled',!hasContent);
| |
| }
| |
| | |
| // Submit post
| |
| $('#btn-submit-post').on('click',function(){
| |
| var text=$('#post-input').val().trim();
| |
| if(!text&&!pendingPost.image&&!pendingPost.video){alert('Please add some content');return;}
| |
| var $btn=$(this);
| |
| $btn.prop('disabled',true).text('Posting...');
| |
| var params={action:'socialfeed',sfaction:'createpost',content:text||'(media)'};
| |
| if(pendingPost.image)params.image_url=pendingPost.image;
| |
| if(pendingPost.video)params.video_url=pendingPost.video;
| |
| if(pendingPost.location)params.location=pendingPost.location;
| |
| api.postWithToken('csrf',params).then(function(){
| |
| $('#post-input').val('');
| |
| pendingPost={image:'',video:'',location:''};
| |
| $('#post-preview').removeClass('show').find('img,video').remove();
| |
| $('#post-location').removeClass('show');
| |
| $btn.prop('disabled',true).text('Post');
| |
| loadPosts();
| |
| }).catch(function(e){
| |
| alert('Error posting: '+(e.message||e));
| |
| $btn.prop('disabled',false).text('Post');
| |
| });
| |
| });
| |
| | |
| // Story
| |
| $app.on('click','#add-story-btn',function(){uploadStory.reset();$('#input-story-url').val('');$('#preview-story-url').removeClass('show');openModal('modal-story');});
| |
| $('#add-story').on('click',function(){
| |
| var activePanel=$('#modal-story .sa-tab.active').data('panel');
| |
| function postStory(url){
| |
| api.postWithToken('csrf',{action:'socialfeed',sfaction:'createstory',image_url:url}).then(function(){
| |
| closeModal('modal-story');
| |
| uploadStory.reset();
| |
| loadStories();
| |
| }).catch(function(e){alert('Error: '+e.message);});
| |
| }
| |
| if(activePanel==='panel-story-url'){
| |
| var url=$('#input-story-url').val().trim();
| |
| if(url){postStory(url);}else{alert('Please enter a URL');}
| |
| }else if(uploadFiles.story){
| |
| uploadFile(uploadFiles.story,'Story',uploadStory.setProgress).then(function(url){
| |
| postStory(url);
| |
| }).catch(function(e){alert('Upload error: '+e.message);});
| |
| }else{alert('Please select an image');}
| |
| });
| |
| | |
| // Story viewer
| |
| var storyTimeout;
| |
| $app.on('click','.sa-story[data-story-url]',function(){
| |
| var url=$(this).data('story-url'),name=$(this).data('story-user'),pic=localStorage.getItem('sf_photo_'+name)||'';
| |
| $('#story-viewer-image').attr('src',url);
| |
| $('#story-viewer-name').text(name);
| |
| $('#story-viewer-avatar').html(renderAvatar(pic,name,'small'));
| |
| $('#story-viewer').addClass('open');
| |
| $('.sa-story-viewer-progress-bar').css('animation','none');
| |
| setTimeout(function(){$('.sa-story-viewer-progress-bar').css('animation','storyProgress 5s linear forwards');},10);
| |
| clearTimeout(storyTimeout);
| |
| storyTimeout=setTimeout(function(){$('#story-viewer').removeClass('open');},5000);
| |
| });
| |
| $('#close-story-viewer,#story-viewer').on('click',function(e){
| |
| if(e.target.id==='close-story-viewer'||e.target.id==='story-viewer'){
| |
| $('#story-viewer').removeClass('open');
| |
| clearTimeout(storyTimeout);
| |
| }
| |
| });
| |
| | |
| // Feed interactions
| |
| $app.on('click','.sa-post-action[data-like]',function(){
| |
| var postId=$(this).data('like');
| |
| api.postWithToken('csrf',{action:'socialfeed',sfaction:'react',post_id:postId,reaction_type:'like'}).then(loadPosts);
| |
| });
| |
| | |
| $app.on('click','.sa-post-menu[data-delete]',function(){
| |
| if(confirm('Delete this post?')){
| |
| api.postWithToken('csrf',{action:'socialfeed',sfaction:'deletepost',post_id:$(this).data('delete')}).then(loadPosts);
| |
| }
| |
| });
| |
| | |
| $app.on('click','.sa-post-comments-count',function(){
| |
| var postId=$(this).data('post');
| |
| var $section=$('#comments-'+postId);
| |
| if($section.hasClass('show')){
| |
| $section.removeClass('show');
| |
| }else{
| |
| $section.addClass('show');
| |
| loadComments(postId);
| |
| }
| |
| });
| |
| | |
| $app.on('keypress','.sa-post-comment-input',function(e){
| |
| if(e.which===13){
| |
| var $input=$(this),text=$input.val().trim(),postId=$input.data('post');
| |
| if(text){
| |
| $input.val('').prop('disabled',true);
| |
| api.postWithToken('csrf',{action:'socialfeed',sfaction:'comment',post_id:postId,content:text}).then(function(){
| |
| $input.prop('disabled',false);
| |
| loadComments(postId);
| |
| loadPosts();
| |
| }).catch(function(){$input.prop('disabled',false);});
| |
| }
| |
| }
| |
| });
| |
| }
| |
| | |
| function loadPosts(){
| |
| api.get({action:'socialfeed',sfaction:'getposts',limit:20}).then(function(response){
| |
| var posts=(response.socialfeed&&response.socialfeed.posts)||[];
| |
| $('#post-count').text(posts.length);
| |
| if(!posts.length){
| |
| $('#feed-container').html('<div class="sa-empty"><div class="sa-empty-icon">📷</div><div class="sa-empty-title">Share Photos</div><div class="sa-empty-text">When you share photos, they will appear on your profile.</div></div>');
| |
| return;
| |
| }
| |
| var html=posts.map(function(post){
| |
| var userPic=localStorage.getItem('sf_photo_'+post.username)||'';
| |
| var totalReactions=0;
| |
| for(var k in post.reaction_counts)totalReactions+=post.reaction_counts[k];
| |
| var media='';
| |
| if(post.image_url){
| |
| media='<div class="sa-post-media"><img src="'+escapeHtml(post.image_url)+'" alt=""></div>';
| |
| }
| |
| if(post.video_url){
| |
| if(post.video_url.match(/youtube|youtu\.be/)){
| |
| var videoId=(post.video_url.match(/(?:v=|youtu\.be\/)([^&]+)/)||[])[1];
| |
| if(videoId)media='<div class="sa-post-media"><iframe src="https://www.youtube.com/embed/'+videoId+'" allowfullscreen></iframe></div>';
| |
| }else if(post.video_url.match(/vimeo/)){
| |
| var vimeoId=(post.video_url.match(/vimeo\.com\/(\d+)/)||[])[1];
| |
| if(vimeoId)media='<div class="sa-post-media"><iframe src="https://player.vimeo.com/video/'+vimeoId+'" allowfullscreen></iframe></div>';
| |
| }else{
| |
| media='<div class="sa-post-media"><video src="'+escapeHtml(post.video_url)+'" controls></video></div>';
| |
| }
| |
| }
| |
| var location=post.location?'<div class="sa-post-location">📍 '+escapeHtml(post.location)+'</div>':'';
| |
| var deleteBtn=post.username===currentUser?'<button class="sa-post-menu" data-delete="'+post.id+'">🗑️</button>':'';
| |
| var likedClass=post.user_reaction==='like'?' liked':'';
| |
| return'\
| |
| <div class="sa-post">\
| |
| <div class="sa-post-header">\
| |
| <div class="sa-post-avatar">'+renderAvatar(userPic,post.username,'post')+'</div>\
| |
| <div class="sa-post-user">\
| |
| <div class="sa-post-username">'+escapeHtml(post.username)+'</div>\
| |
| <div class="sa-post-meta">'+timeAgo(post.created)+'</div>\
| |
| </div>\
| |
| '+deleteBtn+'\
| |
| </div>\
| |
| '+(post.content&&post.content!=='(media)'?'<div class="sa-post-content">'+escapeHtml(post.content)+'</div>':'')+'\
| |
| '+location+media+'\
| |
| <div class="sa-post-actions">\
| |
| <button class="sa-post-action'+likedClass+'" data-like="'+post.id+'">'+(post.user_reaction==='like'?'❤️':'🤍')+'</button>\
| |
| <button class="sa-post-action">💬</button>\
| |
| <button class="sa-post-action">📤</button>\
| |
| <button class="sa-post-action" style="margin-left:auto">🔖</button>\
| |
| </div>\
| |
| '+(totalReactions?'<div class="sa-post-likes">'+totalReactions+' like'+(totalReactions>1?'s':'')+'</div>':'')+'\
| |
| '+(post.content&&post.content!=='(media)'?'':'')+(post.comments?'<div class="sa-post-comments-count" data-post="'+post.id+'">View all '+post.comments+' comment'+(post.comments>1?'s':'')+'</div>':'')+'\
| |
| <div class="sa-comments-section" id="comments-'+post.id+'"></div>\
| |
| <div class="sa-post-time">'+timeAgo(post.created)+'</div>\
| |
| <div class="sa-post-comment-form">\
| |
| <input class="sa-post-comment-input" data-post="'+post.id+'" placeholder="Add a comment...">\
| |
| <button class="sa-post-comment-submit">Post</button>\
| |
| </div>\
| |
| </div>';
| |
| }).join('');
| |
| $('#feed-container').html(html);
| |
| }).catch(function(e){
| |
| console.error('Error loading posts:',e);
| |
| $('#feed-container').html('<div class="sa-empty"><div class="sa-empty-icon">😕</div><div class="sa-empty-title">Error Loading</div><div class="sa-empty-text">Could not load posts. Please refresh.</div></div>');
| |
| });
| |
| }
| |
| | |
| function loadComments(postId){
| |
| api.get({action:'socialfeed',sfaction:'getcomments',post_id:postId}).then(function(response){
| |
| var comments=(response.socialfeed&&response.socialfeed.comments)||[];
| |
| var html=comments.map(function(comment){
| |
| var userPic=localStorage.getItem('sf_photo_'+comment.username)||'';
| |
| return'\
| |
| <div class="sa-comment">\
| |
| <div class="sa-comment-avatar">'+renderAvatar(userPic,comment.username,'small')+'</div>\
| |
| <div class="sa-comment-body">\
| |
| <div class="sa-comment-text"><strong>'+escapeHtml(comment.username)+'</strong> '+escapeHtml(comment.content)+'</div>\
| |
| <div class="sa-comment-time">'+timeAgo(comment.created)+'</div>\
| |
| </div>\
| |
| </div>'; | |
| }).join('');
| |
| $('#comments-'+postId).html(html||'<div style="color:#8e8e8e;font-size:14px">No comments yet</div>');
| |
| });
| |
| }
| |
| | |
| function loadStories(){
| |
| api.get({action:'socialfeed',sfaction:'getstories'}).then(function(response){
| |
| var stories=(response.socialfeed&&response.socialfeed.stories)||[];
| |
| var html='<div class="sa-story"><div class="sa-story-ring add"><div class="sa-story-inner" id="add-story-btn">+</div></div><div class="sa-story-name">Your story</div></div>';
| |
| html+=stories.map(function(story){
| |
| var userPic=localStorage.getItem('sf_photo_'+story.username)||'';
| |
| return'\
| |
| <div class="sa-story" data-story-url="'+escapeHtml(story.image_url)+'" data-story-user="'+escapeHtml(story.username)+'">\
| |
| <div class="sa-story-ring"><div class="sa-story-inner">'+renderAvatar(userPic,story.username,'small')+'</div></div>\
| |
| <div class="sa-story-name">'+escapeHtml(story.username)+'</div>\
| |
| </div>';
| |
| }).join('');
| |
| $('#stories-container').html(html);
| |
| }).catch(function(){});
| |
| }
| |
| | |
| // Initialize
| |
| buildApp();
| |
| setupEventListeners();
| |
| loadPosts();
| |
| loadStories();
| |
| }
| |
| })();
| |
| </script>
| |