User:Docmoates/Social: Difference between revisions

From XMethod Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Tag: Replaced
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
<html>
<div id="social-app">
<div id="social-feed-app">
<div style="text-align:center;padding:60px;color:#8e8e8e">
<style>
<div style="font-size:48px;margin-bottom:16px"></div>
#social-feed-app * { box-sizing: border-box; }
<div>Loading Social Feed...</div>
#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-avatar-small { width: 36px; height: 36px; font-size: 14px; }
.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-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-action-buttons { display: flex; gap: 8px; position: relative; }
.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-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 { 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-user-info { flex: 1; }
.sf-post-username { font-weight: 600; font-size: 15px; }
.sf-post-meta { font-size: 13px; color: #65676b; }
.sf-post-menu { background: none; border: none; font-size: 18px; color: #65676b; cursor: pointer; padding: 8px; border-radius: 50%; }
.sf-post-menu:hover { background: #f0f2f5; }
.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-video { width: 100%; max-height: 500px; }
.sf-post-location { padding: 8px 16px; font-size: 13px; color: #65676b; display: flex; align-items: center; gap: 4px; }
.sf-post-stats { display: flex; justify-content: space-between; padding: 12px 16px; font-size: 14px; color: #65676b; border-bottom: 1px solid #e4e6eb; }
.sf-post-stats-left { display: flex; align-items: center; gap: 6px; }
.sf-reaction-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-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:hover { background: #f0f2f5; }
.sf-post-action.liked { color: #1877f2; }
.sf-comments { padding: 12px 16px; background: #f7f8fa; }
.sf-comment { display: flex; gap: 8px; margin-bottom: 12px; }
.sf-comment-bubble { background: white; border-radius: 18px; padding: 10px 14px; }
.sf-comment-author { font-weight: 600; font-size: 13px; }
.sf-comment-text { font-size: 14px; }
.sf-add-comment { display: flex; gap: 8px; align-items: center; }
.sf-comment-input { flex: 1; background: white; border: none; border-radius: 20px; padding: 10px 16px; font-size: 14px; outline: none; }
.sf-empty { text-align: center; padding: 60px 20px; background: white; border-radius: 12px; color: #65676b; }
.sf-empty-icon { font-size: 64px; margin-bottom: 16px; }
.sf-empty-text { font-size: 18px; font-weight: 500; }
.sf-empty-sub { font-size: 14px; margin-top: 8px; color: #8a8d91; }
.sf-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-modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.8); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 9999; align-items: center; justify-content: center; }
.sf-modal.active { display: flex; }
.sf-modal-content { background: white; border-radius: 12px; padding: 24px; max-width: 450px; width: 90%; }
.sf-modal-title { font-size: 20px; font-weight: 600; margin-bottom: 16px; }
.sf-modal-input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; margin-bottom: 12px; }
.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-cancel { background: #e4e6eb; }
.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, #image-preview video { max-width: 100%; max-height: 200px; border-radius: 8px; }
#location-preview { display: none; padding: 8px 20px; background: white; margin-bottom: 10px; border-radius: 8px; font-size: 14px; color: #65676b; }
.sf-tabs { display: flex; border-bottom: 2px solid #e4e6eb; margin-bottom: 16px; }
.sf-tab { flex: 1; padding: 12px; text-align: center; cursor: pointer; font-weight: 600; color: #65676b; border-bottom: 2px solid transparent; margin-bottom: -2px; }
.sf-tab.active { color: #1877f2; border-bottom-color: #1877f2; }
.sf-tab-content { display: none; }
.sf-tab-content.active { display: block; }
.sf-upload-zone { border: 2px dashed #ccc; border-radius: 12px; padding: 40px 20px; text-align: center; cursor: pointer; transition: 0.2s; margin-bottom: 12px; }
.sf-upload-zone:hover { border-color: #1877f2; background: #f0f7ff; }
.sf-upload-zone.dragover { border-color: #1877f2; background: #e3f2fd; }
.sf-upload-icon { font-size: 48px; margin-bottom: 12px; }
.sf-upload-text { color: #65676b; font-size: 14px; }
.sf-upload-progress { display: none; margin-top: 12px; }
.sf-progress-bar { height: 8px; background: #e4e6eb; border-radius: 4px; overflow: hidden; }
.sf-progress-fill { height: 100%; background: #1877f2; width: 0%; transition: width 0.3s; }
.sf-preview-thumb { max-width: 100%; max-height: 150px; border-radius: 8px; margin-top: 12px; }
.sf-emoji-picker { display: none; position: absolute; bottom: 50px; left: 50px; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); padding: 12px; width: 280px; z-index: 100; }
.sf-emoji-picker.active { display: block; }
.sf-emoji-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; max-height: 200px; overflow-y: auto; }
.sf-emoji-btn { background: none; border: none; font-size: 22px; padding: 6px; cursor: pointer; border-radius: 6px; }
.sf-emoji-btn:hover { background: #f0f2f5; }
.sf-emoji-tabs { display: flex; gap: 4px; margin-bottom: 8px; border-bottom: 1px solid #e4e6eb; padding-bottom: 8px; }
.sf-emoji-tab { background: none; border: none; font-size: 18px; padding: 6px 10px; cursor: pointer; border-radius: 6px; opacity: 0.5; }
.sf-emoji-tab:hover, .sf-emoji-tab.active { opacity: 1; background: #f0f2f5; }
</style>
 
<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 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>
<div class="sf-composer-actions">
<div class="sf-action-buttons">
<button class="sf-action-btn" id="add-image-btn" title="Add Photo">🖼️</button>
<button class="sf-action-btn" id="add-emoji-btn" title="Add Emoji">😊</button>
<button class="sf-action-btn" id="add-video-btn" title="Add Video">🎬</button>
<button class="sf-action-btn" id="add-location-btn" title="Add Location">📍</button>
<div class="sf-emoji-picker" id="emoji-picker">
<div class="sf-emoji-tabs">
<button class="sf-emoji-tab active" data-cat="smileys">😀</button>
<button class="sf-emoji-tab" data-cat="gestures">👋</button>
<button class="sf-emoji-tab" data-cat="animals">🐱</button>
<button class="sf-emoji-tab" data-cat="food">🍕</button>
<button class="sf-emoji-tab" data-cat="activities">⚽</button>
<button class="sf-emoji-tab" data-cat="objects">💡</button>
<button class="sf-emoji-tab" data-cat="symbols">❤️</button>
</div>
</div>
<div class="sf-emoji-grid" id="emoji-grid"></div>
</div>
</div>
<button class="sf-post-btn" id="post-btn">Post</button>
</div>
</div>
<div id="location-preview">📍 <span id="location-text"></span> <button id="remove-location-btn" style="margin-left:10px;padding:2px 8px;font-size:12px;">✕</button></div>
<div id="image-preview"><img id="preview-img"><video id="preview-video" controls style="display:none"></video><button id="remove-image-btn" style="margin-left:10px;padding:5px 10px;">Remove</button></div>
<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 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 class="sf-modal" id="image-modal">
<div class="sf-modal-content">
<div class="sf-modal-title">Add Photo</div>
<div class="sf-tabs">
<div class="sf-tab active" data-tab="upload">Upload</div>
<div class="sf-tab" data-tab="url">From URL</div>
</div>
<div class="sf-tab-content active" id="tab-upload">
<div class="sf-upload-zone" id="upload-zone">
<div class="sf-upload-icon">📤</div>
<div class="sf-upload-text">Click to select or drag image here</div>
<input type="file" id="file-input" accept="image/*" style="display:none">
</div>
<div class="sf-upload-progress" id="upload-progress">
<div class="sf-progress-bar"><div class="sf-progress-fill" id="progress-fill"></div></div>
<p style="font-size:12px;color:#65676b;margin-top:8px;" id="upload-status">Uploading...</p>
</div>
<img class="sf-preview-thumb" id="upload-preview" style="display:none">
</div>
<div class="sf-tab-content" id="tab-url">
<input type="text" class="sf-modal-input" id="image-url-input" placeholder="Paste image URL...">
<img class="sf-preview-thumb" id="url-preview" style="display:none">
</div>
<div class="sf-modal-buttons">
<button class="sf-modal-btn sf-modal-btn-cancel" id="image-cancel-btn">Cancel</button>
<button class="sf-modal-btn sf-modal-btn-save" id="image-add-btn">Add Image</button>
</div>
</div>
</div>
<div class="sf-modal" id="video-modal">
<div class="sf-modal-content">
<div class="sf-modal-title">Add Video</div>
<div class="sf-tabs">
<div class="sf-tab active" data-vtab="vupload">Upload</div>
<div class="sf-tab" data-vtab="vurl">From URL</div>
</div>
<div class="sf-tab-content active" id="tab-vupload">
<div class="sf-upload-zone" id="video-upload-zone">
<div class="sf-upload-icon">🎬</div>
<div class="sf-upload-text">Click to select or drag video here</div>
<input type="file" id="video-file-input" accept="video/*" style="display:none">
</div>
<div class="sf-upload-progress" id="video-upload-progress">
<div class="sf-progress-bar"><div class="sf-progress-fill" id="video-progress-fill"></div></div>
<p style="font-size:12px;color:#65676b;margin-top:8px;" id="video-upload-status">Uploading...</p>
</div>
<video class="sf-preview-thumb" id="video-upload-preview" controls style="display:none;max-height:150px;"></video>
</div>
<div class="sf-tab-content" id="tab-vurl">
<input type="text" class="sf-modal-input" id="video-url-input" placeholder="Paste video URL (YouTube, Vimeo, or direct link)...">
<p style="font-size:12px;color:#65676b;margin-bottom:12px;">Supports YouTube, Vimeo, or direct video URLs</p>
</div>
<div class="sf-modal-buttons">
<button class="sf-modal-btn sf-modal-btn-cancel" id="video-cancel-btn">Cancel</button>
<button class="sf-modal-btn sf-modal-btn-save" id="video-add-btn">Add Video</button>
</div>
</div>
</div>
<div class="sf-modal" id="location-modal">
<div class="sf-modal-content">
<div class="sf-modal-title">Add Location</div>
<button class="sf-modal-btn sf-modal-btn-save" id="detect-location-btn" style="width:100%;margin-bottom:12px;">📍 Use Current Location</button>
<p style="text-align:center;color:#65676b;margin-bottom:12px;">— or —</p>
<input type="text" class="sf-modal-input" id="location-input" placeholder="Enter location manually...">
<div class="sf-modal-buttons">
<button class="sf-modal-btn sf-modal-btn-cancel" id="location-cancel-btn">Cancel</button>
<button class="sf-modal-btn sf-modal-btn-save" id="location-add-btn">Add Location</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded',function(){
var apiBase='/api.php';
var user='Guest';
var csrfToken='';
try{if(window.mw)user=mw.config.get('wgUserName')||'Guest';}catch(e){}
var photo=localStorage.getItem('sf_photo_'+user)||'';
var pendingImg='';
var pendingVideo='';
var pendingLocation='';
var uploadedFile=null;
var mediaType='image';
var emojis={
smileys:['😀','😃','😄','😁','😅','😂','🤣','😊','😇','🙂','😉','😌','😍','🥰','😘','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','😮','🤯','😳','🥺','😢','😭','😤','😠','😡','🤬','😈','👿','💀','☠️','💩','🤡','👹','👺','👻','👽','🤖'],
gestures:['👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💪','🦵','🦶','👂','👃','🧠','🫀','🫁','🦷','🦴','👀','👁️','👅','👄'],
animals:['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🐯','🦁','🐮','🐷','🐸','🐵','🐔','🐧','🐦','🐤','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄','🐝','🐛','🦋','🐌','🐞','🐜','🦟','🐢','🐍','🦎','🦂','🦀','🦑','🐙','🦐','🐠','🐟','🐬','🐳','🦈','🐊','🐅','🐆','🦓','🦍','🐘','🦛','🦏'],
food:['🍎','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🥑','🥦','🥬','🥒','🌶️','🫑','🌽','🥕','🫒','🧄','🧅','🥔','🍠','🥐','🥯','🍞','🥖','🥨','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🦴','🌭','🍔','🍟','🍕','🫓','🥪','🥙','🧆','🌮','🌯','🫔','🥗','🥘','🫕','🥫','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🦪','🍤','🍙','🍚','🍘','🍥','🥠','🥮','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪'],
activities:['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛼','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️','🤼','🤸','🤺','⛹️','🤾','🏌️','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚴','🚵','🎖️','🏆','🥇','🥈','🥉','🏅','🎪','🤹','🎭','🎨','🎬','🎤','🎧','🎼','🎹','🥁','🪘','🎷','🎺','🪗','🎸','🪕','🎻','🎲','♟️','🎯','🎳','🎮','🎰'],
objects:['💡','🔦','🕯️','🪔','📱','💻','🖥️','🖨️','⌨️','🖱️','💾','💿','📀','📷','📸','📹','🎥','📽️','🎞️','📞','☎️','📟','📠','📺','📻','🎙️','🎚️','🎛️','🧭','⏱️','⏲️','⏰','🕰️','⌛','⏳','📡','🔋','🔌','💰','💵','💴','💶','💷','🪙','💳','💎','⚖️','🪜','🧰','🪛','🔧','🔨','⚒️','🛠️','⛏️','🪚','🔩','⚙️','🪤','🧱','⛓️','🧲','🔫','💣','🧨','🪓','🔪','🗡️','⚔️','🛡️','🚬','⚰️','🪦','⚱️','🏺','🔮','📿','🧿','💈','⚗️','🔭','🔬','🕳️','🩹','🩺','💊','💉','🩸','🧬','🦠','🧫','🧪','🌡️','🧹','🧺','🧻','🚽','🚿','🛁','🛀','🧼','🪥','🪒','🧽','🪣','🧴','🛎️','🔑','🗝️','🚪','🪑','🛋️','🛏️','🛌','🧸','🖼️','🪞','🪟','🛍️','🛒','🎁','🎈','🎏','🎀','🪄','🪅','🎊','🎉','🎎','🏮','🎐','🧧','✉️','📩','📨','📧','💌','📥','📤','📦','🏷️','📪','📫','📬','📭','📮','📯','📜','📃','📄','📑','🧾','📊','📈','📉','🗒️','🗓️','📆','📅','🗑️','📇','🗃️','🗳️','🗄️','📋','📁','📂','🗂️','🗞️','📰','📓','📔','📒','📕','📗','📘','📙','📚','📖','🔖','🧷','🔗','📎','🖇️','📐','📏','🧮','📌','📍','✂️','🖊️','🖋️','✒️','🖌️','🖍️','📝','✏️','🔍','🔎','🔏','🔐','🔒','🔓'],
symbols:['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝','💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓','🆔','⚛️','🉑','☢️','☣️','📴','📳','🈶','🈚','🈸','🈺','🈷️','✴️','🆚','💮','🉐','㊙️','㊗️','🈴','🈵','🈹','🈲','🅰️','🅱️','🆎','🆑','🅾️','🆘','❌','⭕','🛑','⛔','📛','🚫','💯','💢','♨️','🚷','🚯','🚳','🚱','🔞','📵','🚭','❗','❕','❓','❔','‼️','⁉️','🔅','🔆','〽️','⚠️','🚸','🔱','⚜️','🔰','♻️','✅','🈯','💹','❇️','✳️','❎','🌐','💠','Ⓜ️','🌀','💤','🏧','🚾','♿','🅿️','🛗','🈳','🈂️','🛂','🛃','🛄','🛅','🚹','🚺','🚼','⚧️','🚻','🚮','🎦','📶','🈁','🔣','ℹ️','🔤','🔡','🔠','🆖','🆗','🆙','🆒','🆕','🆓','0️⃣','1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟','🔢','#️⃣','*️⃣','⏏️','▶️','⏸️','⏯️','⏹️','⏺️','⏭️','⏮️','⏩','⏪','⏫','⏬','◀️','🔼','🔽','➡️','⬅️','⬆️','⬇️','↗️','↘️','↙️','↖️','↕️','↔️','↪️','↩️','⤴️','⤵️','🔀','🔁','🔂','🔄','🔃','🎵','🎶','➕','➖','➗','✖️','🟰','♾️','💲','💱','™️','©️','®️','〰️','➰','➿','🔚','🔙','🔛','🔝','🔜','✔️','☑️','🔘','🔴','🟠','🟡','🟢','🔵','🟣','⚫','⚪','🟤','🔺','🔻','🔸','🔹','🔶','🔷','🔳','🔲','▪️','▫️','◾','◽','◼️','◻️','🟥','🟧','🟨','🟩','🟦','🟪','⬛','⬜','🟫','🔈','🔇','🔉','🔊','🔔','🔕','📣','📢','👁️‍🗨️','💬','💭','🗯️','♠️','♣️','♥️','♦️','🃏','🎴','🀄','🕐','🕑','🕒','🕓','🕔','🕕','🕖','🕗','🕘','🕙','🕚','🕛','🕜','🕝','🕞','🕟','🕠','🕡','🕢','🕣','🕤','🕥','🕦','🕧']
};
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 gel(id){return document.getElementById(id);}
function apiGet(params){
var url=apiBase+'?format=json';
for(var k in params)url+='&'+encodeURIComponent(k)+'='+encodeURIComponent(params[k]);
return fetch(url,{credentials:'same-origin'}).then(function(r){return r.json();});
}
function apiPost(params){
var form=new FormData();
form.append('format','json');
for(var k in params)form.append(k,params[k]);
return fetch(apiBase,{method:'POST',credentials:'same-origin',body:form}).then(function(r){return r.json();});
}
function getToken(){
return apiGet({action:'query',meta:'tokens',type:'csrf'}).then(function(d){
csrfToken=d.query.tokens.csrftoken;
return csrfToken;
});
}
function setAv(){
var el=gel('composer-avatar');
if(!el)return;
el.innerHTML=photo?'<img src="'+photo+'">':init(user);
el.innerHTML+='<div class="sf-avatar-edit">Edit</div>';
}
setAv();
var postContent=gel('post-content');
if(postContent)postContent.placeholder='What is on your mind, '+user+'?';
gel('composer-avatar').addEventListener('click',function(){
gel('profile-url-input').value=photo;
gel('profile-modal').classList.add('active');
});
gel('profile-cancel-btn').addEventListener('click',function(){gel('profile-modal').classList.remove('active');});
gel('profile-save-btn').addEventListener('click',function(){
photo=gel('profile-url-input').value.trim();
localStorage.setItem('sf_photo_'+user,photo);
setAv();
gel('profile-modal').classList.remove('active');
});
// Emoji picker
function renderEmojis(cat){
var grid=gel('emoji-grid');
grid.innerHTML='';
(emojis[cat]||[]).forEach(function(em){
var btn=document.createElement('button');
btn.className='sf-emoji-btn';
btn.textContent=em;
btn.addEventListener('click',function(){
var ta=gel('post-content');
var start=ta.selectionStart;
var end=ta.selectionEnd;
ta.value=ta.value.substring(0,start)+em+ta.value.substring(end);
ta.selectionStart=ta.selectionEnd=start+em.length;
ta.focus();
});
grid.appendChild(btn);
});
}
document.querySelectorAll('.sf-emoji-tab').forEach(function(tab){
tab.addEventListener('click',function(){
document.querySelectorAll('.sf-emoji-tab').forEach(function(t){t.classList.remove('active');});
tab.classList.add('active');
renderEmojis(tab.dataset.cat);
});
});
gel('add-emoji-btn').addEventListener('click',function(e){
e.stopPropagation();
var picker=gel('emoji-picker');
picker.classList.toggle('active');
if(picker.classList.contains('active'))renderEmojis('smileys');
});
document.addEventListener('click',function(e){
if(!(e.target.closest('.sf-emoji-picker')||e.target.closest('#add-emoji-btn'))){
gel('emoji-picker').classList.remove('active');
}
});
// Video modal tabs
var uploadedVideoFile=null;
document.querySelectorAll('[data-vtab]').forEach(function(tab){
tab.addEventListener('click',function(){
document.querySelectorAll('[data-vtab]').forEach(function(t){t.classList.remove('active');});
document.querySelectorAll('#video-modal .sf-tab-content').forEach(function(c){c.classList.remove('active');});
tab.classList.add('active');
gel('tab-'+tab.dataset.vtab).classList.add('active');
});
});
gel('add-video-btn').addEventListener('click',function(){
gel('video-url-input').value='';
gel('video-upload-preview').style.display='none';
gel('video-upload-progress').style.display='none';
uploadedVideoFile=null;
gel('video-modal').classList.add('active');
});
gel('video-cancel-btn').addEventListener('click',function(){gel('video-modal').classList.remove('active');});
var videoUploadZone=gel('video-upload-zone');
var videoFileInput=gel('video-file-input');
videoUploadZone.addEventListener('click',function(){videoFileInput.click();});
videoUploadZone.addEventListener('dragover',function(e){e.preventDefault();videoUploadZone.classList.add('dragover');});
videoUploadZone.addEventListener('dragleave',function(){videoUploadZone.classList.remove('dragover');});
videoUploadZone.addEventListener('drop',function(e){
e.preventDefault();
videoUploadZone.classList.remove('dragover');
if(e.dataTransfer.files.length>0)handleVideoFile(e.dataTransfer.files[0]);
});
videoFileInput.addEventListener('change',function(){
if(videoFileInput.files.length>0)handleVideoFile(videoFileInput.files[0]);
});
function handleVideoFile(file){
if(!file.type.startsWith('video/')){alert('Please select a video file');return;}
uploadedVideoFile=file;
var url=URL.createObjectURL(file);
gel('video-upload-preview').src=url;
gel('video-upload-preview').style.display='block';
}
function uploadVideo(file){
gel('video-upload-progress').style.display='block';
gel('video-progress-fill').style.width='10%';
gel('video-upload-status').textContent='Preparing upload...';
var filename='SocialVideo_'+user+'_'+Date.now()+'.'+file.name.split('.').pop();
getToken().then(function(token){
gel('video-progress-fill').style.width='30%';
gel('video-upload-status').textContent='Uploading to wiki...';
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');
var xhr=new XMLHttpRequest();
xhr.open('POST',apiBase,true);
xhr.withCredentials=true;
xhr.upload.onprogress=function(e){
if(e.lengthComputable){
var pct=30+Math.round((e.loaded/e.total)*60);
gel('video-progress-fill').style.width=pct+'%';
}
};
xhr.onload=function(){
if(xhr.status===200){
var resp=JSON.parse(xhr.responseText);
if(resp.upload){
if(resp.upload.imageinfo){
gel('video-progress-fill').style.width='100%';
gel('video-upload-status').textContent='Upload complete!';
pendingVideo=resp.upload.imageinfo.url;
pendingImg='';
mediaType='video';
gel('preview-img').style.display='none';
gel('preview-video').src=pendingVideo;
gel('preview-video').style.display='block';
gel('image-preview').style.display='block';
setTimeout(function(){gel('video-modal').classList.remove('active');},500);
}else if(resp.upload.warnings){
gel('video-upload-status').textContent='Warning: '+JSON.stringify(resp.upload.warnings);
}
}else if(resp.error){
gel('video-upload-status').textContent='Error: '+resp.error.info;
}
}else{
gel('video-upload-status').textContent='Upload failed';
}
};
xhr.onerror=function(){gel('video-upload-status').textContent='Upload failed';};
xhr.send(formData);
});
}
gel('video-add-btn').addEventListener('click',function(){
var activeTab=document.querySelector('[data-vtab].active').dataset.vtab;
if(activeTab==='vurl'){
var url=gel('video-url-input').value.trim();
if(url){
pendingVideo=url;
pendingImg='';
mediaType='video';
gel('preview-img').style.display='none';
gel('preview-video').style.display='block';
if(url.includes('youtube.com')||url.includes('youtu.be')){
var vid=url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&]+)/);
if(vid)gel('preview-video').poster='https://img.youtube.com/vi/'+vid[1]+'/0.jpg';
gel('preview-video').src='';
}else{
gel('preview-video').src=url;
}
gel('image-preview').style.display='block';
gel('video-modal').classList.remove('active');
}else{
alert('Please enter a video URL');
}
}else{
if(uploadedVideoFile){
uploadVideo(uploadedVideoFile);
}else{
alert('Please select a video to upload');
}
}
});
// Location modal
gel('add-location-btn').addEventListener('click',function(){
gel('location-input').value='';
gel('location-modal').classList.add('active');
});
gel('location-cancel-btn').addEventListener('click',function(){gel('location-modal').classList.remove('active');});
gel('detect-location-btn').addEventListener('click',function(){
if(navigator.geolocation){
this.textContent='Detecting...';
var btn=this;
navigator.geolocation.getCurrentPosition(function(pos){
fetch('https://nominatim.openstreetmap.org/reverse?format=json&lat='+pos.coords.latitude+'&lon='+pos.coords.longitude).then(function(r){return r.json();}).then(function(d){
var loc=d.display_name||pos.coords.latitude+', '+pos.coords.longitude;
loc=loc.split(',').slice(0,3).join(',');
gel('location-input').value=loc;
btn.textContent='📍 Use Current Location';
}).catch(function(){
gel('location-input').value=pos.coords.latitude+', '+pos.coords.longitude;
btn.textContent='📍 Use Current Location';
});
},function(){
alert('Could not get location');
btn.textContent='📍 Use Current Location';
});
}else{
alert('Geolocation not supported');
}
});
gel('location-add-btn').addEventListener('click',function(){
var loc=gel('location-input').value.trim();
if(loc){
pendingLocation=loc;
gel('location-text').textContent=loc;
gel('location-preview').style.display='block';
gel('location-modal').classList.remove('active');
}else{
alert('Please enter a location');
}
});
gel('remove-location-btn').addEventListener('click',function(){
pendingLocation='';
gel('location-preview').style.display='none';
});
// Image modal tabs
document.querySelectorAll('.sf-tab').forEach(function(tab){
tab.addEventListener('click',function(){
document.querySelectorAll('.sf-tab').forEach(function(t){t.classList.remove('active');});
document.querySelectorAll('.sf-tab-content').forEach(function(c){c.classList.remove('active');});
tab.classList.add('active');
gel('tab-'+tab.dataset.tab).classList.add('active');
});
});
gel('add-image-btn').addEventListener('click',function(){
gel('image-modal').classList.add('active');
gel('image-url-input').value='';
gel('url-preview').style.display='none';
gel('upload-preview').style.display='none';
gel('upload-progress').style.display='none';
uploadedFile=null;
});
gel('image-cancel-btn').addEventListener('click',function(){
gel('image-modal').classList.remove('active');
});
var uploadZone=gel('upload-zone');
var fileInput=gel('file-input');
uploadZone.addEventListener('click',function(){fileInput.click();});
uploadZone.addEventListener('dragover',function(e){e.preventDefault();uploadZone.classList.add('dragover');});
uploadZone.addEventListener('dragleave',function(){uploadZone.classList.remove('dragover');});
uploadZone.addEventListener('drop',function(e){
e.preventDefault();
uploadZone.classList.remove('dragover');
if(e.dataTransfer.files.length>0)handleFile(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change',function(){
if(fileInput.files.length>0)handleFile(fileInput.files[0]);
});
function handleFile(file){
if(!file.type.startsWith('image/')){alert('Please select an image file');return;}
uploadedFile=file;
var reader=new FileReader();
reader.onload=function(e){
gel('upload-preview').src=e.target.result;
gel('upload-preview').style.display='block';
};
reader.readAsDataURL(file);
}
gel('image-url-input').addEventListener('input',function(){
var url=this.value.trim();
if(url){
gel('url-preview').src=url;
gel('url-preview').style.display='block';
}else{
gel('url-preview').style.display='none';
}
});
gel('image-add-btn').addEventListener('click',function(){
var activeTab=document.querySelector('.sf-tab.active').dataset.tab;
if(activeTab==='url'){
var url=gel('image-url-input').value.trim();
if(url){
pendingImg=url;
pendingVideo='';
mediaType='image';
gel('preview-img').src=url;
gel('preview-img').style.display='block';
gel('preview-video').style.display='none';
gel('image-preview').style.display='block';
gel('image-modal').classList.remove('active');
}else{
alert('Please enter an image URL');
}
}else{
if(uploadedFile){
uploadImage(uploadedFile);
}else{
alert('Please select an image to upload');
}
}
});
function uploadImage(file){
gel('upload-progress').style.display='block';
gel('progress-fill').style.width='10%';
gel('upload-status').textContent='Preparing upload...';
var filename='SocialFeed_'+user+'_'+Date.now()+'.'+file.name.split('.').pop();
getToken().then(function(token){
gel('progress-fill').style.width='30%';
gel('upload-status').textContent='Uploading to wiki...';
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');
var xhr=new XMLHttpRequest();
xhr.open('POST',apiBase,true);
xhr.withCredentials=true;
xhr.upload.onprogress=function(e){
if(e.lengthComputable){
var pct=30+Math.round((e.loaded/e.total)*60);
gel('progress-fill').style.width=pct+'%';
}
};
xhr.onload=function(){
if(xhr.status===200){
var resp=JSON.parse(xhr.responseText);
if(resp.upload){
if(resp.upload.imageinfo){
gel('progress-fill').style.width='100%';
gel('upload-status').textContent='Upload complete!';
pendingImg=resp.upload.imageinfo.url;
pendingVideo='';
mediaType='image';
gel('preview-img').src=pendingImg;
gel('preview-img').style.display='block';
gel('preview-video').style.display='none';
gel('image-preview').style.display='block';
setTimeout(function(){gel('image-modal').classList.remove('active');},500);
}else if(resp.upload.warnings){
gel('upload-status').textContent='Warning: '+JSON.stringify(resp.upload.warnings);
}
}else if(resp.error){
gel('upload-status').textContent='Error: '+resp.error.info;
}
}else{
gel('upload-status').textContent='Upload failed';
}
};
xhr.onerror=function(){gel('upload-status').textContent='Upload failed';};
xhr.send(formData);
});
}
gel('remove-image-btn').addEventListener('click',function(){
pendingImg='';
pendingVideo='';
gel('image-preview').style.display='none';
});
gel('post-btn').addEventListener('click',function(){
var c=gel('post-content').value.trim();
if(!(c||pendingImg||pendingVideo)){alert('Write something or add media');return;}
var btn=this;btn.disabled=true;btn.textContent='Posting...';
getToken().then(function(token){
var params={action:'socialfeed',sfaction:'createpost',content:c||'(media)',token:token};
if(pendingImg)params.image_url=pendingImg;
if(pendingVideo)params.video_url=pendingVideo;
if(pendingLocation)params.location=pendingLocation;
return apiPost(params);
}).then(function(d){
if(d.error){alert('Error: '+d.error.info);btn.disabled=false;btn.textContent='Post';return;}
gel('post-content').value='';
pendingImg='';pendingVideo='';pendingLocation='';
gel('image-preview').style.display='none';
gel('location-preview').style.display='none';
btn.disabled=false;btn.textContent='Post';
load();
}).catch(function(e){alert('Error: '+e);btn.disabled=false;btn.textContent='Post';});
});
function load(){
apiGet({action:'socialfeed',sfaction:'getposts',limit:20}).then(function(d){
if(d.socialfeed)render(d.socialfeed.posts||[]);
}).catch(function(){
gel('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=gel('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.location)h+='<div class="sf-post-location">📍 '+esc(p.location)+'</div>';
if(p.image_url)h+='<img class="sf-post-image" src="'+p.image_url+'">';
if(p.video_url){
if(p.video_url.includes('youtube.com')||p.video_url.includes('youtu.be')){
var vid=p.video_url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&]+)/);
if(vid)h+='<iframe class="sf-post-video" src="https://www.youtube.com/embed/'+vid[1]+'" frameborder="0" allowfullscreen style="width:100%;height:315px;"></iframe>';
}else{
h+='<video class="sf-post-video" src="'+p.video_url+'" controls></video>';
}
}
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.addEventListener('click',function(){if(confirm('Delete?')){getToken().then(function(t){return apiPost({action:'socialfeed',sfaction:'deletepost',post_id:b.dataset.del,token:t});}).then(load);}});});
c.querySelectorAll('[data-like]').forEach(function(b){b.addEventListener('click',function(){getToken().then(function(t){return apiPost({action:'socialfeed',sfaction:'react',post_id:b.dataset.like,reaction_type:'like',token:t});}).then(load);});});
c.querySelectorAll('[data-cmt]').forEach(function(b){b.addEventListener('click',function(){var box=gel('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=gel('cmt-'+id);
apiGet({action:'socialfeed',sfaction:'getcomments',post_id:id}).then(function(d){
var cmts=(d.socialfeed?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;
gel('cinput-'+id).addEventListener('keypress',function(e){if(e.key==='Enter'){var v=this.value.trim();if(v){this.value='';getToken().then(function(t){return apiPost({action:'socialfeed',sfaction:'comment',post_id:id,content:v,token:t});}).then(function(){loadCmt(id);load();});}}});
});
}
load();
});
</script>
</div>
</html>

Latest revision as of 11:37, 3 February 2026

Loading Social Feed...