Mobile Publisher ·
موبائل پبلشر — staff tool ·
Book portal
Dr. Wasif — Mobile Publisher
:root {
–bg: #0b1f33;
–card: #0b1f33;
–card2: #112240;
–blue: #2176ab;
–blue-light: #3385FF;
–gold: #C9A84C;
–gold-light: #E8C96A;
–text: #E8EDF5;
–muted: #8899AA;
–border: #1E3050;
–danger: #FF4444;
–success: #22C55E;
–warning: #F59E0B;
–radius: 14px;
–radius-sm: 8px;
}
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
body { background: var(–bg); color: var(–text); font-family: ‘Outfit’, -apple-system, BlinkMacSystemFont, ‘Segoe UI’, sans-serif; min-height: 100vh; padding-bottom: 40px; }
.header { background: linear-gradient(135deg, #0b1f33 0%, #112240 100%); border-bottom: 1px solid var(–border); padding: 16px 20px 14px; position: sticky; top: 0; z-index: 100; display: flex; align-items: center; gap: 12px; }
.header-logo { width: 38px; height: 38px; background: linear-gradient(135deg, var(–blue), #0044CC); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; }
.header-text h1 { font-size: 15px; font-weight: 700; color: var(–text); line-height: 1.2; }
.header-text p { font-size: 11px; color: var(–gold); font-weight: 500; letter-spacing: 0.5px; text-transform: uppercase; }
.header-badge { margin-left: auto; background: rgba(0,103,255,0.15); border: 1px solid rgba(0,103,255,0.3); color: var(–blue-light); font-size: 10px; font-weight: 600; padding: 4px 8px; border-radius: 20px; letter-spacing: 0.5px; text-transform: uppercase; }
/* LOGIN */
#loginScreen { padding: 40px 20px; display: flex; flex-direction: column; align-items: center; gap: 20px; }
.login-icon { width: 80px; height: 80px; background: linear-gradient(135deg, var(–blue), #0044CC); border-radius: 24px; display: flex; align-items: center; justify-content: center; font-size: 36px; box-shadow: 0 8px 32px rgba(0,103,255,0.3); }
.login-title { font-size: 22px; font-weight: 700; text-align: center; }
.login-sub { font-size: 13px; color: var(–muted); text-align: center; line-height: 1.5; }
.login-card { background: var(–card); border: 1px solid var(–border); border-radius: var(–radius); padding: 24px 20px; width: 100%; max-width: 400px; display: flex; flex-direction: column; gap: 16px; }
/* FORM */
.field-group { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: 11px; font-weight: 600; color: var(–gold); letter-spacing: 0.8px; text-transform: uppercase; }
input[type=”text”], input[type=”password”], textarea, select { background: var(–card2); border: 1px solid var(–border); border-radius: var(–radius-sm); color: var(–text); font-family: inherit; font-size: 15px; padding: 12px 14px; width: 100%; outline: none; transition: border-color 0.2s; -webkit-appearance: none; appearance: none; }
input:focus, textarea:focus, select:focus { border-color: var(–blue); box-shadow: 0 0 0 3px rgba(0,103,255,0.15); }
textarea { min-height: 120px; resize: vertical; line-height: 1.6; }
select { background-image: url(“data:image/svg+xml,%3Csvg xmlns=’http://www.w3.org/2000/svg’ width=’12’ height=’8′ viewBox=’0 0 12 8’%3E%3Cpath d=’M1 1l5 5 5-5′ stroke=’%238899AA’ stroke-width=’1.5′ fill=’none’ stroke-linecap=’round’/%3E%3C/svg%3E”); background-repeat: no-repeat; background-position: right 14px center; padding-right: 36px; }
textarea.urdu-input { font-family: ‘Noto Nastaliq Urdu’, serif; direction: rtl; text-align: right; font-size: 16px; line-height: 2; }
/* BUTTONS */
.btn { border: none; border-radius: var(–radius-sm); cursor: pointer; font-family: inherit; font-weight: 600; padding: 14px 20px; font-size: 15px; transition: all 0.2s; width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; }
.btn-primary { background: linear-gradient(135deg, var(–blue), #0044CC); color: #fff; box-shadow: 0 4px 16px rgba(0,103,255,0.35); }
.btn-primary:active { transform: scale(0.97); }
.btn-gold { background: linear-gradient(135deg, var(–gold), #A07830); color: #fff; }
.btn-outline { background: transparent; border: 1px solid var(–border); color: var(–muted); }
.btn-danger { background: rgba(255,68,68,0.15); border: 1px solid rgba(255,68,68,0.3); color: var(–danger); }
.btn-sm { padding: 8px 14px; font-size: 13px; width: auto; border-radius: 6px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* APP */
#appScreen { display: none; padding: 0 0 20px; }
.tabs { display: flex; background: var(–card); border-bottom: 1px solid var(–border); overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab { flex: 1; min-width: 80px; padding: 12px 8px; text-align: center; font-size: 11px; font-weight: 600; color: var(–muted); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s; white-space: nowrap; letter-spacing: 0.3px; }
.tab.active { color: var(–blue-light); border-bottom-color: var(–blue); }
.tab.live-tab { color: #FF4444; }
.tab.live-tab.active { color: #FF6666; border-bottom-color: #FF4444; }
.live-dot { width: 8px; height: 8px; background: #FF4444; border-radius: 50%; display: inline-block; margin-right: 5px; animation: livePulse 1.2s infinite; }
@keyframes livePulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.4;transform:scale(0.8)} }
.live-badge { background: #FF4444; color: #fff; font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 4px; letter-spacing: 0.5px; display: inline-flex; align-items: center; gap: 4px; }
.stream-option-card { background: var(–card2); border: 2px solid var(–border); border-radius: var(–radius); padding: 16px; cursor: pointer; transition: all 0.2s; margin-bottom: 10px; display: flex; align-items: center; gap: 14px; }
.stream-option-card:hover, .stream-option-card.selected { border-color: var(–blue); background: rgba(0,103,255,0.08); }
.stream-option-icon { font-size: 32px; flex-shrink: 0; }
.stream-option-text h3 { font-size: 14px; font-weight: 700; color: var(–text); margin-bottom: 3px; }
.stream-option-text p { font-size: 12px; color: var(–muted); line-height: 1.4; }
.stream-status-card { background: rgba(255,68,68,0.08); border: 1px solid rgba(255,68,68,0.25); border-radius: var(–radius); padding: 20px; text-align: center; margin-bottom: 16px; }
.stream-status-title { font-size: 18px; font-weight: 700; color: #FF6666; margin-bottom: 6px; }
.stream-status-sub { font-size: 13px; color: var(–muted); }
.tab-icon { font-size: 16px; display: block; margin-bottom: 2px; }
.tab-panel { display: none; padding: 20px; }
.tab-panel.active { display: block; }
.section-card { background: var(–card); border: 1px solid var(–border); border-radius: var(–radius); padding: 18px; margin-bottom: 16px; }
.section-title { font-size: 13px; font-weight: 700; color: var(–gold); letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
.section-title .urdu { font-family: ‘Noto Nastaliq Urdu’, serif; font-size: 13px; color: var(–muted); text-transform: none; letter-spacing: 0; direction: rtl; }
.type-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.type-pill { background: var(–card2); border: 2px solid var(–border); border-radius: 10px; padding: 10px 8px; text-align: center; cursor: pointer; transition: all 0.2s; font-size: 12px; font-weight: 600; color: var(–muted); }
.type-pill .pill-icon { font-size: 20px; display: block; margin-bottom: 4px; }
.type-pill.selected { border-color: var(–blue); background: rgba(0,103,255,0.12); color: var(–blue-light); }
.lang-toggle { display: flex; background: var(–card2); border: 1px solid var(–border); border-radius: var(–radius-sm); overflow: hidden; }
.lang-btn { flex: 1; padding: 9px; text-align: center; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.2s; color: var(–muted); border: none; background: transparent; font-family: inherit; }
.lang-btn.active { background: var(–blue); color: #fff; }
.media-upload-area { border: 2px dashed var(–border); border-radius: var(–radius); padding: 28px 20px; text-align: center; cursor: pointer; transition: all 0.2s; background: var(–card2); position: relative; }
.media-upload-area input[type=”file”] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.upload-icon { font-size: 36px; margin-bottom: 8px; }
.upload-text { font-size: 14px; font-weight: 600; color: var(–text); margin-bottom: 4px; }
.upload-sub { font-size: 12px; color: var(–muted); }
#mediaPreviewGrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 12px; }
.preview-item { position: relative; border-radius: 8px; overflow: hidden; aspect-ratio: 1; background: var(–card2); }
.preview-item img, .preview-item video { width: 100%; height: 100%; object-fit: cover; }
.preview-item .remove-btn { position: absolute; top: 4px; right: 4px; background: rgba(0,0,0,0.7); border: none; border-radius: 50%; color: #fff; font-size: 14px; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.preview-item .video-badge { position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.7); color: #fff; font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; }
.preview-item .file-size { position: absolute; bottom: 4px; right: 4px; background: rgba(0,0,0,0.7); color: var(–muted); font-size: 9px; padding: 2px 5px; border-radius: 4px; }
.consent-box { background: rgba(201,168,76,0.08); border: 1px solid rgba(201,168,76,0.25); border-radius: var(–radius-sm); padding: 14px; }
.consent-box p { font-size: 12px; color: var(–muted); line-height: 1.6; margin-bottom: 10px; }
.consent-box .urdu-consent { font-family: ‘Noto Nastaliq Urdu’, serif; direction: rtl; font-size: 12px; color: var(–muted); line-height: 1.8; margin-bottom: 10px; display: block; }
.checkbox-row { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.checkbox-row input[type=”checkbox”] { width: 20px; height: 20px; flex-shrink: 0; accent-color: var(–gold); margin-top: 1px; cursor: pointer; }
.checkbox-row label { font-size: 13px; font-weight: 600; color: var(–gold-light); cursor: pointer; line-height: 1.4; }
.alert { border-radius: var(–radius-sm); padding: 12px 14px; font-size: 13px; font-weight: 500; margin-bottom: 12px; display: flex; align-items: flex-start; gap: 8px; line-height: 1.4; }
.alert-error { background: rgba(255,68,68,0.1); border: 1px solid rgba(255,68,68,0.25); color: #FF8888; }
.alert-success { background: rgba(34,197,94,0.1); border: 1px solid rgba(34,197,94,0.25); color: #4ADE80; }
.alert-info { background: rgba(0,103,255,0.1); border: 1px solid rgba(0,103,255,0.25); color: #60A5FA; }
.alert-warning { background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.25); color: #FCD34D; }
.progress-wrap { background: var(–card2); border-radius: 4px; height: 6px; overflow: hidden; margin: 8px 0; }
.progress-bar { height: 100%; background: linear-gradient(90deg, var(–blue), var(–blue-light)); border-radius: 4px; transition: width 0.3s ease; width: 0%; }
.progress-text { font-size: 12px; color: var(–muted); text-align: center; }
.post-card { background: var(–card); border: 1px solid var(–border); border-radius: var(–radius); padding: 14px; margin-bottom: 12px; }
.post-card-title { font-size: 14px; font-weight: 600; color: var(–text); line-height: 1.3; margin-bottom: 8px; }
.post-card-meta { font-size: 11px; color: var(–muted); display: flex; gap: 10px; flex-wrap: wrap; }
.post-status-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; margin-right: 4px; }
.dot-published { background: var(–success); }
.dot-draft { background: var(–warning); }
.post-card-actions { display: flex; gap: 8px; margin-top: 10px; }
.settings-row { display: flex; align-items: center; justify-content: space-between; padding: 14px 0; border-bottom: 1px solid var(–border); }
.settings-row:last-child { border-bottom: none; }
.settings-label { font-size: 14px; font-weight: 500; }
.settings-sub { font-size: 11px; color: var(–muted); margin-top: 2px; }
.toggle-switch { position: relative; width: 44px; height: 24px; cursor: pointer; flex-shrink: 0; }
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider { position: absolute; inset: 0; background: var(–border); border-radius: 24px; transition: 0.2s; }
.toggle-slider:before { content: ”; position: absolute; width: 18px; height: 18px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: 0.2s; }
.toggle-switch input:checked + .toggle-slider { background: var(–blue); }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(20px); }
.char-counter { font-size: 11px; color: var(–muted); text-align: right; margin-top: 4px; }
.char-counter.warn { color: var(–warning); }
.char-counter.over { color: var(–danger); }
.spinner { width: 18px; height: 18px; border: 2px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
#successOverlay { display: none; position: fixed; inset: 0; background: rgba(11,31,51,0.95); z-index: 200; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 40px; text-align: center; }
#successOverlay.show { display: flex; }
.success-icon { font-size: 64px; animation: pop 0.4s ease; }
@keyframes pop { 0% { transform: scale(0); } 80% { transform: scale(1.1); } 100% { transform: scale(1); } }
.success-title { font-size: 24px; font-weight: 700; color: var(–success); }
.success-sub { font-size: 14px; color: var(–muted); line-height: 1.6; }
.info-chip { display: inline-flex; align-items: center; gap: 4px; background: rgba(0,103,255,0.1); border: 1px solid rgba(0,103,255,0.2); color: var(–blue-light); font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 20px; }
.required-star { color: var(–danger); margin-left: 2px; }
.optional-tag { color: var(–muted); font-size: 10px; font-weight: 400; margin-left: 4px; }
@media (min-width: 480px) {
.type-grid { grid-template-columns: repeat(4, 1fr); }
#mediaPreviewGrid { grid-template-columns: repeat(4, 1fr); }
}
🔐
Secure Login
Log in with your WordPress username and password to publish directly to the website.
✍️ NEW POST
📋 MY POSTS
🔴 GO LIVE
⚙️ SETTINGS
📌 Post Type پوسٹ کی قسم
🔬 Procedure
🩻 Investigation
⭐ Feedback
📝 General
🌐 Language زبان
🇬🇧 English
🇵🇰 اردو
🌐 Both
📝 Content مواد
English Content *
0 / 5000
📸 Photos & Videos تصاویر اور ویڈیوز
⚖️ Patient Consent مریض کی اجازت
Before publishing any patient-related content (photos, videos, case details, or feedback), confirm that the patient has given explicit verbal or written consent for their case to be shared for educational and professional purposes.
مریض نے اپنی تصاویر، ویڈیوز، یا کیس کی تفصیل شیئر کرنے کی اجازت دی ہے — تعلیمی اور پیشہ ورانہ مقاصد کے لیے۔
I confirm the patient has given informed consent. Patient identity is anonymised where required.
Consent Reference (optional)
🚀 Publish Options
Status
💾 Save as Draft (review before publishing)
🌐 Publish Immediately
🚀 Publish Post
⚠️
YouTube Mobile Live requires 1,000 subscribers. Your channel currently has 1 subscriber. Use StreamYard below to go live from your phone right now — it works with any subscriber count.
Option 1 — Go Live NOW (Any Subscriber Count)
StreamYard lets you broadcast from your phone to YouTube with zero subscriber requirement. Free plan available.
After going live, paste your YouTube Video/Stream ID here to embed it on your website:
🌐 Embed
Find the ID in your YouTube Studio → Live → copy the last part of the URL
📺 Option 2 — YouTube Live Direct (1,000 subs required)
Once your channel reaches 1,000 subscribers, you can go live directly from the YouTube app on your phone.
Current subscribers: 1 / 1,000 needed
How to grow faster: Share your YouTube channel link on WhatsApp, Facebook, and with patients.
👤 Account
drwasifmalik.com
WordPress site
🌐 Live
const WP_URL = ‘https://drwasifmalik.com’;
const WP_API = WP_URL + ‘/wp-json/wp/v2’;
let state = {
nonce: null,
userName: null,
postType: ‘procedure’,
lang: ‘en’,
mediaFiles: [],
uploading: false
};
// ── INIT ──
window.addEventListener(‘load’, () => {
checkExistingSession();
});
async function checkExistingSession() {
// Method 1: Check if WordPress injected a nonce via wp.apiFetch (fastest — works when already logged in)
try {
if (typeof wp !== ‘undefined’ && wp.apiFetch && wp.apiFetch.nonceMiddleware && wp.apiFetch.nonceMiddleware.nonce) {
const nonce = wp.apiFetch.nonceMiddleware.nonce;
const userResp = await fetch(WP_API + ‘/users/me’, {
credentials: ‘include’,
headers: { ‘X-WP-Nonce’: nonce }
});
if (userResp.ok) {
const user = await userResp.json();
state.nonce = nonce;
state.userName = user.name;
showApp();
return;
}
}
} catch(e) {}
// Method 2: Extract nonce from inline page scripts
try {
const scripts = Array.from(document.querySelectorAll(‘script’));
for (const s of scripts) {
const m = s.textContent.match(/createNonceMiddleware(s*[“‘]([a-f0-9]+)[“‘]/);
if (m) {
const nonce = m[1];
const userResp = await fetch(WP_API + ‘/users/me’, {
credentials: ‘include’,
headers: { ‘X-WP-Nonce’: nonce }
});
if (userResp.ok) {
const user = await userResp.json();
state.nonce = nonce;
state.userName = user.name;
showApp();
return;
}
}
}
} catch(e) {}
// Not logged in — show login screen
document.getElementById(‘headerBadge’).textContent = ‘Login Required’;
document.getElementById(‘loginScreen’).style.display = ‘flex’;
}
// ── LOGIN ──
async function doLogin() {
const user = document.getElementById(‘loginUser’).value.trim();
const pass = document.getElementById(‘loginPass’).value.trim();
const alertEl = document.getElementById(‘loginAlert’);
const btn = document.getElementById(‘loginBtn’);
if (!user || !pass) {
alertEl.innerHTML = ‘
⚠️ Please enter username and password.
‘;
return;
}
alertEl.innerHTML = ‘
🔄 Signing in…
‘;
btn.disabled = true;
try {
// Step 1: Log in via wp-login.php form submission
const formData = new FormData();
formData.append(‘log’, user);
formData.append(‘pwd’, pass);
formData.append(‘wp-submit’, ‘Log In’);
formData.append(‘redirect_to’, ‘/wp-admin/’);
formData.append(‘testcookie’, ‘1’);
const loginResp = await fetch(WP_URL + ‘/wp-login.php’, {
method: ‘POST’,
body: formData,
credentials: ‘include’,
redirect: ‘follow’
});
// Step 2: Get nonce (proves login worked)
const nonceResp = await fetch(WP_URL + ‘/wp-admin/admin-ajax.php?action=rest-nonce’, {
credentials: ‘include’
});
if (!nonceResp.ok) throw new Error(‘Login failed — could not get session token.’);
const nonce = await nonceResp.text();
if (!nonce || nonce.length < 5 || nonce.includes('<') || nonce === '0') {
throw new Error('Invalid credentials. Please check your username and password.');
}
// Step 3: Verify with REST API
const userResp = await fetch(WP_API + '/users/me', {
credentials: 'include',
headers: { 'X-WP-Nonce': nonce }
});
if (!userResp.ok) throw new Error('Authentication failed. Please try again.');
const userData = await userResp.json();
state.nonce = nonce;
state.userName = userData.name;
showApp();
loadPosts();
loadCategories();
} catch(e) {
alertEl.innerHTML = `
❌ ${e.message}
`;
btn.disabled = false;
}
}
function showApp() {
document.getElementById(‘loginScreen’).style.display = ‘none’;
document.getElementById(‘appScreen’).style.display = ‘block’;
document.getElementById(‘settingsUsername’).textContent = state.userName || ‘Dr. Wasif’;
document.getElementById(‘headerBadge’).textContent = ‘🟢 Live’;
document.getElementById(‘headerBadge’).style.cssText = ‘background:rgba(34,197,94,0.15); border-color:rgba(34,197,94,0.3); color:#4ADE80’;
loadPosts();
loadCategories();
}
function doLogout() {
if (!confirm(‘Sign out?’)) return;
state.nonce = null; state.userName = null;
// Navigate to WP logout
window.location.href = WP_URL + ‘/wp-login.php?action=logout&_wpnonce=’ + state.nonce;
}
// ── TABS ──
function switchTab(name) {
document.querySelectorAll(‘.tab’).forEach(t => t.classList.remove(‘active’));
document.querySelectorAll(‘.tab-panel’).forEach(p => p.classList.remove(‘active’));
document.getElementById(‘tab-‘ + name).classList.add(‘active’);
document.getElementById(‘panel-‘ + name).classList.add(‘active’);
if (name === ‘posts’) loadPosts();
}
// ── POST TYPE ──
function selectType(el, type) {
document.querySelectorAll(‘.type-pill’).forEach(p => p.classList.remove(‘selected’));
el.classList.add(‘selected’);
state.postType = type;
updatePublishBtn();
}
// ── LANGUAGE ──
function setLang(lang, btn) {
document.querySelectorAll(‘.lang-btn’).forEach(b => b.classList.remove(‘active’));
btn.classList.add(‘active’);
state.lang = lang;
const showEn = lang === ‘en’ || lang === ‘both’;
const showUr = lang === ‘ur’ || lang === ‘both’;
document.getElementById(‘enTitleGroup’).style.display = showEn ? ‘flex’ : ‘none’;
document.getElementById(‘enBodyGroup’).style.display = showEn ? ‘flex’ : ‘none’;
document.getElementById(‘urTitleGroup’).style.display = showUr ? ‘block’ : ‘none’;
document.getElementById(‘urBodyGroup’).style.display = showUr ? ‘block’ : ‘none’;
}
// ── CHAR COUNTER ──
function updateCharCount(el, id, max) {
const len = el.value.length;
const c = document.getElementById(id);
c.textContent = len + ‘ / ‘ + max;
c.className = ‘char-counter’ + (len >= max ? ‘ over’ : len > max * 0.9 ? ‘ warn’ : ”);
}
// ── MEDIA ──
function handleMediaSelect(input) {
Array.from(input.files).forEach(file => {
if (state.mediaFiles.length >= 10) return;
state.mediaFiles.push(file);
});
renderMediaPreviews();
input.value = ”;
updateMediaInfo();
}
function renderMediaPreviews() {
const grid = document.getElementById(‘mediaPreviewGrid’);
grid.innerHTML = ”;
state.mediaFiles.forEach((file, idx) => {
const isVideo = file.type.startsWith(‘video/’);
const url = URL.createObjectURL(file);
const sizeText = file.size > 1048576 ? (file.size/1048576).toFixed(1)+’MB’ : Math.round(file.size/1024)+’KB’;
const item = document.createElement(‘div’);
item.className = ‘preview-item’;
item.innerHTML = isVideo
? `
▶ VIDEO ${sizeText} ✕ `
: `
${sizeText} ✕ `;
grid.appendChild(item);
});
}
function removeMedia(idx) {
state.mediaFiles.splice(idx, 1);
renderMediaPreviews();
updateMediaInfo();
}
function updateMediaInfo() {
const info = document.getElementById(‘mediaInfo’);
if (!state.mediaFiles.length) { info.innerHTML = ”; return; }
const photos = state.mediaFiles.filter(f => f.type.startsWith(‘image/’)).length;
const videos = state.mediaFiles.filter(f => f.type.startsWith(‘video/’)).length;
const mb = (state.mediaFiles.reduce((a,f) => a+f.size, 0)/1048576).toFixed(1);
info.innerHTML = `
📎 ${state.mediaFiles.length} file(s): ${photos} photo(s), ${videos} video(s) — ${mb} MB
`;
}
// ── PUBLISH BTN STATE ──
function updatePublishBtn() {
const btn = document.getElementById(‘publishBtn’);
const consentOk = document.getElementById(‘consentCheck’).checked;
btn.disabled = !consentOk;
}
// ── CATEGORIES ──
let categoryMap = {};
async function loadCategories() {
try {
const r = await fetch(WP_API + ‘/categories?per_page=50’, { credentials: ‘include’, headers: { ‘X-WP-Nonce’: state.nonce } });
if (r.ok) { const cats = await r.json(); cats.forEach(c => { categoryMap[c.slug] = c.id; }); }
} catch(e) {}
}
// ── UPLOAD MEDIA ──
async function uploadMediaFile(file, idx, total) {
setProgress(Math.round((idx/total)*60), `Uploading ${file.type.startsWith(‘video/’) ? ‘video’ : ‘photo’} ${idx+1} of ${total}…`);
const fd = new FormData();
fd.append(‘file’, file, file.name || `upload-${Date.now()}`);
const r = await fetch(WP_API + ‘/media’, {
method: ‘POST’,
credentials: ‘include’,
headers: { ‘X-WP-Nonce’: state.nonce },
body: fd
});
if (!r.ok) { const e = await r.json(); throw new Error(‘Upload failed: ‘ + (e.message || r.status)); }
return (await r.json()).id;
}
// ── PUBLISH ──
async function publishPost() {
if (state.uploading) return;
const alertEl = document.getElementById(‘composeAlert’);
alertEl.innerHTML = ”;
const needEn = state.lang === ‘en’ || state.lang === ‘both’;
const needUr = state.lang === ‘ur’ || state.lang === ‘both’;
const title = document.getElementById(‘postTitle’).value.trim();
const body = document.getElementById(‘postBody’).value.trim();
const titleUr = document.getElementById(‘postTitleUr’).value.trim();
const bodyUr = document.getElementById(‘postBodyUr’).value.trim();
if (needEn && !title) { alertEl.innerHTML = ‘
⚠️ English title is required.
‘; alertEl.scrollIntoView({behavior:’smooth’}); return; }
if (needEn && !body) { alertEl.innerHTML = ‘
⚠️ English content is required.
‘; alertEl.scrollIntoView({behavior:’smooth’}); return; }
if (needUr && !titleUr) { alertEl.innerHTML = ‘
⚠️ Urdu title is required.
‘; alertEl.scrollIntoView({behavior:’smooth’}); return; }
if (!document.getElementById(‘consentCheck’).checked) { alertEl.innerHTML = ‘
⚠️ Patient consent must be confirmed.
‘; alertEl.scrollIntoView({behavior:’smooth’}); return; }
state.uploading = true;
const btn = document.getElementById(‘publishBtn’);
btn.disabled = true;
btn.innerHTML = ‘
Publishing…’;
document.getElementById(‘uploadProgress’).style.display = ‘block’;
setProgress(5, ‘Starting…’);
try {
// Upload media
const mediaIds = [];
for (let i = 0; i < state.mediaFiles.length; i++) {
mediaIds.push(await uploadMediaFile(state.mediaFiles[i], i, state.mediaFiles.length));
}
setProgress(65, 'Building post…');
// Build content
const typeLabels = { procedure:'🔬 Procedure', investigation:'🩻 Patient Investigation', feedback:'⭐ Patient Feedback', general:'📝 General' };
const consentRef = document.getElementById('consentRef').value.trim();
let content = `
${typeLabels[state.postType]}
nn`;
if (needEn && body) content += body.replace(/n/g, ‘
‘) + ‘nn’;
if (needUr && bodyUr) content += `
${bodyUr.replace(/n/g,’ ‘)}
nn`;
if (document.getElementById(‘consentCheck’).checked) content += `n
✅ Patient consent obtained for educational publication.${consentRef ? ‘ Ref: ‘+consentRef : ”}
`;
if (mediaIds.length > 0) {
content += ‘nnn
‘;
mediaIds.forEach(id => { content += ` `; });
content += ‘ n’;
}
setProgress(75, ‘Publishing…’);
// Category
const catSlug = { procedure:’procedures’, investigation:’investigations’, feedback:’patient-stories’, general:’neurosurgery-news’ }[state.postType];
const catIds = categoryMap[catSlug] ? [categoryMap[catSlug]] : [];
// Title
let finalTitle = needEn ? title : titleUr;
if (needEn && needUr && title && titleUr) finalTitle = title + ‘ — ‘ + titleUr;
// Tags
const tagNames = document.getElementById(‘postTags’).value.split(‘,’).map(t=>t.trim()).filter(Boolean);
const tagIds = [];
for (const tagName of tagNames) {
try {
const tr = await fetch(WP_API + ‘/tags’, { method:’POST’, credentials:’include’, headers:{‘X-WP-Nonce’:state.nonce,’Content-Type’:’application/json’}, body:JSON.stringify({name:tagName}) });
if (tr.ok) { tagIds.push((await tr.json()).id); }
else {
const sr = await fetch(WP_API + ‘/tags?search=’ + encodeURIComponent(tagName), { credentials:’include’, headers:{‘X-WP-Nonce’:state.nonce} });
if (sr.ok) { const ex = await sr.json(); if (ex.length) tagIds.push(ex[0].id); }
}
} catch(e) {}
}
setProgress(88, ‘Finalising…’);
const postData = { title: finalTitle, content, status: document.getElementById(‘postStatus’).value, categories: catIds, tags: tagIds };
const firstImgIdx = state.mediaFiles.findIndex(f => f.type.startsWith(‘image/’));
if (firstImgIdx >= 0 && mediaIds[firstImgIdx]) postData.featured_media = mediaIds[firstImgIdx];
const pr = await fetch(WP_API + ‘/posts’, { method:’POST’, credentials:’include’, headers:{‘X-WP-Nonce’:state.nonce,’Content-Type’:’application/json’}, body:JSON.stringify(postData) });
if (!pr.ok) { const e = await pr.json(); throw new Error(‘Post failed: ‘ + (e.message || pr.status)); }
const post = await pr.json();
setProgress(100, ‘Done!’);
setTimeout(() => showSuccess(post), 500);
resetForm();
} catch(e) {
alertEl.innerHTML = `
❌ ${e.message}
`;
alertEl.scrollIntoView({behavior:’smooth’});
document.getElementById(‘uploadProgress’).style.display = ‘none’;
} finally {
state.uploading = false;
btn.disabled = false;
btn.innerHTML = ‘
🚀 Publish Post ‘;
updatePublishBtn();
}
}
function setProgress(pct, text) {
document.getElementById(‘progressBar’).style.width = pct + ‘%’;
document.getElementById(‘progressText’).textContent = text;
}
function showSuccess(post) {
const isDraft = post.status === ‘draft’;
document.getElementById(‘successMsg’).textContent = isDraft ? ‘Saved as draft. Review it in WordPress Admin before publishing.’ : ‘Your post is now live on drwasifmalik.com!’;
document.getElementById(‘viewPostBtn’).href = isDraft ? `https://drwasifmalik.com/wp-admin/post.php?post=${post.id}&action=edit` : post.link;
document.getElementById(‘viewPostBtn’).textContent = isDraft ? ‘✏️ Edit Draft’ : ‘👁️ View Post’;
document.getElementById(‘successOverlay’).classList.add(‘show’);
document.getElementById(‘uploadProgress’).style.display = ‘none’;
}
function dismissSuccess() {
document.getElementById(‘successOverlay’).classList.remove(‘show’);
switchTab(‘compose’);
}
function resetForm() {
[‘postTitle’,’postBody’,’postTitleUr’,’postBodyUr’,’postTags’,’consentRef’].forEach(id => { document.getElementById(id).value = ”; });
document.getElementById(‘consentCheck’).checked = false;
document.getElementById(‘mediaPreviewGrid’).innerHTML = ”;
document.getElementById(‘mediaInfo’).innerHTML = ”;
document.getElementById(‘composeAlert’).innerHTML = ”;
document.getElementById(‘titleCount’).textContent = ‘0 / 120’;
document.getElementById(‘bodyCount’).textContent = ‘0 / 5000’;
state.mediaFiles = [];
}
// ── POSTS LIST ──
async function loadPosts() {
if (!state.nonce) return;
const list = document.getElementById(‘postsList’);
list.innerHTML = ‘
🔄 Loading…
‘;
try {
const r = await fetch(WP_API + ‘/posts?per_page=15&orderby=date&order=desc’, { credentials:’include’, headers:{‘X-WP-Nonce’:state.nonce} });
if (!r.ok) throw new Error(‘Failed’);
const posts = await r.json();
if (!posts.length) { list.innerHTML = ‘
‘; return; }
list.innerHTML = posts.map(p => {
const date = new Date(p.date).toLocaleDateString(‘en-PK’,{day:’numeric’,month:’short’,year:’numeric’});
const isDraft = p.status === ‘draft’;
const title = p.title.rendered.replace(/]+>/g,”);
return `
${title}
${isDraft?’Draft’:’Published’}📅 ${date}
${isDraft?’✏️ Edit’:’👁️ View’} ${isDraft?`
🚀 Publish `:”}
🗑️ Delete `;
}).join(”);
} catch(e) { list.innerHTML = ‘
❌ Could not load posts.
‘; }
}
async function publishDraft(id, btn) {
if (!confirm(‘Publish this draft to the live website now?’)) return;
btn.disabled = true; btn.textContent = ‘⏳…’;
try {
const r = await fetch(WP_API + ‘/posts/’ + id, { method:’POST’, credentials:’include’, headers:{‘X-WP-Nonce’:state.nonce,’Content-Type’:’application/json’}, body:JSON.stringify({status:’publish’}) });
if (r.ok) { btn.textContent = ‘✅ Published!’; setTimeout(loadPosts, 1000); }
else { btn.textContent = ‘❌ Failed’; btn.disabled = false; }
} catch(e) { btn.textContent = ‘❌ Error’; btn.disabled = false; }
}
// ── EMBED LIVE STREAM ──
async function embedLiveStream() {
const videoId = document.getElementById(‘streamVideoId’).value.trim();
const title = document.getElementById(‘streamTitle’).value.trim() || ‘Live Stream — Dr. Wasif Rizwan Malik’;
const desc = document.getElementById(‘streamDesc’).value.trim();
const statusEl = document.getElementById(’embedStatus’);
if (!videoId) {
statusEl.innerHTML = ‘
❌ Please paste your YouTube Video ID first.
‘;
return;
}
// Extract video ID if full URL was pasted
const idMatch = videoId.match(/(?:v=|youtu.be/|live/)([w-]{11})/);
const cleanId = idMatch ? idMatch[1] : videoId;
statusEl.innerHTML = ‘
⏳ Updating live page on your website…
‘;
const liveContent = `
🔴 LIVE NOW — ${title}
${desc ? `
${desc}
` : ”}
`;
try {
const r = await fetch(WP_API.replace(‘/posts’, ‘/pages’) + ‘/1618’, {
method: ‘POST’, credentials: ‘include’,
headers: { ‘X-WP-Nonce’: state.nonce, ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ title: ‘🔴 Live — ‘ + title, content: liveContent, status: ‘publish’ })
});
if (r.ok) {
statusEl.innerHTML = ‘
‘;
} else {
statusEl.innerHTML = ‘
❌ Could not update live page. Share the stream link manually.
‘;
}
} catch(e) {
statusEl.innerHTML = ‘
❌ Error: ‘ + e.message + ‘
‘;
}
}
async function deletePost(id, title, btn) {
if (!confirm(`⚠️ Permanently delete this post?nn”${title}”nnThis cannot be undone.`)) return;
btn.disabled = true; btn.textContent = ‘⏳…’;
try {
// First move to trash (status=trash), then force-delete
const r = await fetch(WP_API + ‘/posts/’ + id + ‘?force=true’, {
method: ‘DELETE’,
credentials: ‘include’,
headers: { ‘X-WP-Nonce’: state.nonce }
});
if (r.ok) {
btn.closest(‘.post-card’).style.transition = ‘opacity 0.3s’;
btn.closest(‘.post-card’).style.opacity = ‘0’;
setTimeout(loadPosts, 400);
} else {
const err = await r.json();
alert(‘❌ Could not delete: ‘ + (err.message || r.status));
btn.textContent = ‘🗑️ Delete’;
btn.disabled = false;
}
} catch(e) {
alert(‘❌ Error: ‘ + e.message);
btn.textContent = ‘🗑️ Delete’;
btn.disabled = false;
}
}