311 lines
10 KiB
JavaScript
311 lines
10 KiB
JavaScript
|
|
/**
|
||
|
|
* Mirrors talkpro static site: /api/site-links, APK default, in-app + App Store modals.
|
||
|
|
* Strings come from #site-links-i18n (JSON) rendered by DownloadCTA.astro.
|
||
|
|
*/
|
||
|
|
(function () {
|
||
|
|
const DEFAULT_APK = 'https://talkspro.xyz/download';
|
||
|
|
let apkDownloadUrl = DEFAULT_APK;
|
||
|
|
|
||
|
|
function getApkDownloadUrl() {
|
||
|
|
return apkDownloadUrl;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getI18n() {
|
||
|
|
const el = document.getElementById('site-links-i18n');
|
||
|
|
if (!el || !el.textContent.trim()) return {};
|
||
|
|
try {
|
||
|
|
return JSON.parse(el.textContent);
|
||
|
|
} catch {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function detectInAppBrowser() {
|
||
|
|
const ua = navigator.userAgent || '';
|
||
|
|
if (/MicroMessenger/i.test(ua)) return 'wechat';
|
||
|
|
try {
|
||
|
|
if (typeof window.WeixinJSBridge !== 'undefined') return 'wechat';
|
||
|
|
} catch {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
if (/QQ\//i.test(ua) && /MQQBrowser/i.test(ua)) return 'qq';
|
||
|
|
if (/Weibo/i.test(ua)) return 'weibo';
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function isIOSDevice() {
|
||
|
|
const ua = navigator.userAgent || '';
|
||
|
|
if (/iPhone|iPad|iPod/i.test(ua)) return true;
|
||
|
|
if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) return true;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isAndroidDevice() {
|
||
|
|
const ua = navigator.userAgent || '';
|
||
|
|
return /Android/i.test(ua) && !isIOSDevice();
|
||
|
|
}
|
||
|
|
|
||
|
|
const MODAL_WRAP =
|
||
|
|
'position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;';
|
||
|
|
const MODAL_BACK = 'position:absolute;inset:0;background:rgba(0,0,0,.45);cursor:pointer;';
|
||
|
|
const MODAL_PANEL =
|
||
|
|
'position:relative;max-width:420px;width:100%;margin:auto;padding:20px 22px;border-radius:12px;background:#fff;color:#1a1a1a;font:15px/1.45 system-ui,-apple-system,sans-serif;box-shadow:0 12px 40px rgba(0,0,0,.18);';
|
||
|
|
const MODAL_TITLE = 'margin:0 0 10px;font-size:18px;font-weight:700;line-height:1.25;';
|
||
|
|
const MODAL_BODY = 'margin:0 0 18px;font-size:14px;color:#444;';
|
||
|
|
const MODAL_ACTIONS = 'display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;';
|
||
|
|
const BTN =
|
||
|
|
'cursor:pointer;border-radius:8px;padding:10px 14px;font-size:14px;font-weight:600;border:1px solid #ccc;background:#f5f5f5;color:#1a1a1a;';
|
||
|
|
const BTN_PRIMARY = 'border-color:#1a6cff;background:#1a6cff;color:#fff;';
|
||
|
|
|
||
|
|
function initInAppBrowserModal() {
|
||
|
|
if (document.getElementById('inapp-browser-modal')) return;
|
||
|
|
const wrap = document.createElement('div');
|
||
|
|
wrap.id = 'inapp-browser-modal';
|
||
|
|
wrap.setAttribute('role', 'dialog');
|
||
|
|
wrap.setAttribute('aria-modal', 'true');
|
||
|
|
wrap.setAttribute('aria-hidden', 'true');
|
||
|
|
wrap.style.cssText = MODAL_WRAP + 'display:none;';
|
||
|
|
wrap.innerHTML =
|
||
|
|
'<div data-inapp-close style="' +
|
||
|
|
MODAL_BACK +
|
||
|
|
'"></div>' +
|
||
|
|
'<div style="' +
|
||
|
|
MODAL_PANEL +
|
||
|
|
'">' +
|
||
|
|
'<h3 id="inapp-modal-title" style="' +
|
||
|
|
MODAL_TITLE +
|
||
|
|
'"></h3>' +
|
||
|
|
'<p id="inapp-modal-body" style="' +
|
||
|
|
MODAL_BODY +
|
||
|
|
'"></p>' +
|
||
|
|
'<div style="' +
|
||
|
|
MODAL_ACTIONS +
|
||
|
|
'">' +
|
||
|
|
'<button type="button" id="inapp-copy-apk" style="' +
|
||
|
|
BTN +
|
||
|
|
BTN_PRIMARY +
|
||
|
|
'"></button>' +
|
||
|
|
'<button type="button" id="inapp-try-chrome" style="' +
|
||
|
|
BTN +
|
||
|
|
'" hidden></button>' +
|
||
|
|
'<button type="button" id="inapp-got-it" data-inapp-close style="' +
|
||
|
|
BTN +
|
||
|
|
'"></button>' +
|
||
|
|
'</div></div>';
|
||
|
|
document.body.appendChild(wrap);
|
||
|
|
|
||
|
|
const close = () => {
|
||
|
|
wrap.style.display = 'none';
|
||
|
|
wrap.setAttribute('aria-hidden', 'true');
|
||
|
|
};
|
||
|
|
wrap.querySelectorAll('[data-inapp-close]').forEach((el) => {
|
||
|
|
el.addEventListener('click', close);
|
||
|
|
});
|
||
|
|
|
||
|
|
const i18n = () => getI18n();
|
||
|
|
document.getElementById('inapp-copy-apk').addEventListener('click', async () => {
|
||
|
|
const btn = document.getElementById('inapp-copy-apk');
|
||
|
|
const dict = i18n();
|
||
|
|
const done = dict.inapp_browser_copied || 'Copied';
|
||
|
|
try {
|
||
|
|
await navigator.clipboard.writeText(getApkDownloadUrl());
|
||
|
|
const prev = btn.textContent;
|
||
|
|
btn.textContent = done;
|
||
|
|
setTimeout(() => {
|
||
|
|
btn.textContent = dict.inapp_browser_copy || prev;
|
||
|
|
}, 1600);
|
||
|
|
} catch {
|
||
|
|
window.prompt(dict.inapp_browser_copy || 'Copy', getApkDownloadUrl());
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
document.getElementById('inapp-try-chrome').addEventListener('click', () => {
|
||
|
|
const enc = encodeURIComponent(getApkDownloadUrl());
|
||
|
|
const intent =
|
||
|
|
getApkDownloadUrl().replace(/^https:/i, 'intent:') +
|
||
|
|
'#Intent;' +
|
||
|
|
'scheme=https;action=android.intent.action.VIEW;category=android.intent.category.BROWSABLE;' +
|
||
|
|
'package=com.android.chrome;S.browser_fallback_url=' +
|
||
|
|
enc +
|
||
|
|
';end';
|
||
|
|
window.location.href = intent;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function openInAppBrowserModal() {
|
||
|
|
const modal = document.getElementById('inapp-browser-modal');
|
||
|
|
if (!modal) return;
|
||
|
|
const dict = getI18n();
|
||
|
|
const tryChrome = modal.querySelector('#inapp-try-chrome');
|
||
|
|
const showChrome = isAndroidDevice() && !!detectInAppBrowser();
|
||
|
|
if (tryChrome) {
|
||
|
|
tryChrome.hidden = !showChrome;
|
||
|
|
tryChrome.setAttribute('aria-hidden', showChrome ? 'false' : 'true');
|
||
|
|
}
|
||
|
|
modal.style.display = 'flex';
|
||
|
|
modal.setAttribute('aria-hidden', 'false');
|
||
|
|
const titleEl = modal.querySelector('#inapp-modal-title');
|
||
|
|
const bodyEl = modal.querySelector('#inapp-modal-body');
|
||
|
|
if (titleEl) titleEl.textContent = dict.inapp_browser_title || '';
|
||
|
|
if (bodyEl) {
|
||
|
|
const iosWechat = isIOSDevice() && detectInAppBrowser() === 'wechat';
|
||
|
|
const bodyKey = iosWechat ? 'inapp_browser_body_ios' : 'inapp_browser_body';
|
||
|
|
bodyEl.innerHTML =
|
||
|
|
dict[bodyKey] || dict.inapp_browser_body || '';
|
||
|
|
}
|
||
|
|
const copyBtn = modal.querySelector('#inapp-copy-apk');
|
||
|
|
const gotBtn = modal.querySelector('#inapp-got-it');
|
||
|
|
if (copyBtn) copyBtn.textContent = dict.inapp_browser_copy || '';
|
||
|
|
if (tryChrome) tryChrome.textContent = dict.inapp_browser_try_chrome || '';
|
||
|
|
if (gotBtn) gotBtn.textContent = dict.inapp_browser_got_it || 'OK';
|
||
|
|
}
|
||
|
|
|
||
|
|
function initAppStoreSoonModal() {
|
||
|
|
if (document.getElementById('app-store-soon-modal')) return;
|
||
|
|
const wrap = document.createElement('div');
|
||
|
|
wrap.id = 'app-store-soon-modal';
|
||
|
|
wrap.setAttribute('role', 'dialog');
|
||
|
|
wrap.setAttribute('aria-modal', 'true');
|
||
|
|
wrap.setAttribute('aria-hidden', 'true');
|
||
|
|
wrap.style.cssText = MODAL_WRAP + 'display:none;';
|
||
|
|
wrap.innerHTML =
|
||
|
|
'<div data-app-soon-close style="' +
|
||
|
|
MODAL_BACK +
|
||
|
|
'"></div>' +
|
||
|
|
'<div style="' +
|
||
|
|
MODAL_PANEL +
|
||
|
|
'">' +
|
||
|
|
'<h3 id="app-soon-title" style="' +
|
||
|
|
MODAL_TITLE +
|
||
|
|
'"></h3>' +
|
||
|
|
'<p id="app-soon-body" style="' +
|
||
|
|
MODAL_BODY +
|
||
|
|
'"></p>' +
|
||
|
|
'<div style="' +
|
||
|
|
MODAL_ACTIONS +
|
||
|
|
'">' +
|
||
|
|
'<button type="button" data-app-soon-close style="' +
|
||
|
|
BTN +
|
||
|
|
BTN_PRIMARY +
|
||
|
|
'"></button>' +
|
||
|
|
'</div></div>';
|
||
|
|
document.body.appendChild(wrap);
|
||
|
|
const close = () => {
|
||
|
|
wrap.style.display = 'none';
|
||
|
|
wrap.setAttribute('aria-hidden', 'true');
|
||
|
|
};
|
||
|
|
wrap.querySelectorAll('[data-app-soon-close]').forEach((el) => {
|
||
|
|
el.addEventListener('click', close);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function openAppStoreSoonModal() {
|
||
|
|
const modal = document.getElementById('app-store-soon-modal');
|
||
|
|
if (!modal) return;
|
||
|
|
const dict = getI18n();
|
||
|
|
const tEl = modal.querySelector('#app-soon-title');
|
||
|
|
const bEl = modal.querySelector('#app-soon-body');
|
||
|
|
const ok = modal.querySelector('button[data-app-soon-close]');
|
||
|
|
if (tEl) tEl.textContent = dict.app_store_soon_title || '';
|
||
|
|
if (bEl) bEl.innerHTML = dict.app_store_soon_body || '';
|
||
|
|
if (ok) ok.textContent = dict.app_store_soon_ok || 'OK';
|
||
|
|
modal.style.display = 'flex';
|
||
|
|
modal.setAttribute('aria-hidden', 'false');
|
||
|
|
}
|
||
|
|
|
||
|
|
function bindBrowserDownloadLinks() {
|
||
|
|
document.querySelectorAll('a.store-badge--browser').forEach((a) => {
|
||
|
|
const openForInApp = (e) => {
|
||
|
|
if (!detectInAppBrowser()) return;
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
openInAppBrowserModal();
|
||
|
|
};
|
||
|
|
a.addEventListener('click', openForInApp);
|
||
|
|
a.addEventListener(
|
||
|
|
'touchend',
|
||
|
|
(e) => {
|
||
|
|
if (!detectInAppBrowser()) return;
|
||
|
|
e.preventDefault();
|
||
|
|
openInAppBrowserModal();
|
||
|
|
},
|
||
|
|
{ passive: false }
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function bindAppStoreAppleBadges() {
|
||
|
|
document.querySelectorAll('a.store-badge--apple[data-app-soon]').forEach((a) => {
|
||
|
|
const stopAndOpen = (e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
openAppStoreSoonModal();
|
||
|
|
};
|
||
|
|
a.addEventListener('click', stopAndOpen);
|
||
|
|
a.addEventListener(
|
||
|
|
'touchend',
|
||
|
|
(e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
openAppStoreSoonModal();
|
||
|
|
},
|
||
|
|
{ passive: false }
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadSiteLinksThenBind() {
|
||
|
|
if (window.location.protocol === 'file:') {
|
||
|
|
bindBrowserDownloadLinks();
|
||
|
|
bindAppStoreAppleBadges();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
fetch('/api/site-links', { credentials: 'same-origin' })
|
||
|
|
.then((r) => {
|
||
|
|
if (!r.ok) throw new Error('bad status');
|
||
|
|
return r.json();
|
||
|
|
})
|
||
|
|
.then((j) => {
|
||
|
|
const apk = String(j.apk_download_url || '').trim();
|
||
|
|
const ios = String(j.app_store_url || '').trim();
|
||
|
|
if (apk.startsWith('https://')) apkDownloadUrl = apk;
|
||
|
|
document.querySelectorAll('a.store-badge--browser').forEach((a) => {
|
||
|
|
if (apk.startsWith('https://')) a.setAttribute('href', apk);
|
||
|
|
});
|
||
|
|
document.querySelectorAll('a.store-badge--apple').forEach((a) => {
|
||
|
|
if (ios.startsWith('https://')) {
|
||
|
|
a.setAttribute('href', ios);
|
||
|
|
a.removeAttribute('data-app-soon');
|
||
|
|
a.setAttribute('rel', 'noopener noreferrer');
|
||
|
|
a.setAttribute('target', '_blank');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const meta = document.getElementById('site-links-meta');
|
||
|
|
if (meta) {
|
||
|
|
const dict = getI18n();
|
||
|
|
const u = String(j.updated_at || '').trim();
|
||
|
|
if (u) {
|
||
|
|
meta.textContent =
|
||
|
|
(dict.linksMetaPrefix || 'Store links file updated: ') +
|
||
|
|
u +
|
||
|
|
(dict.linksMetaSuffix || ' (GMT+8)');
|
||
|
|
meta.hidden = false;
|
||
|
|
} else {
|
||
|
|
meta.hidden = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
/* keep defaults */
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
bindBrowserDownloadLinks();
|
||
|
|
bindAppStoreAppleBadges();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
initInAppBrowserModal();
|
||
|
|
initAppStoreSoonModal();
|
||
|
|
loadSiteLinksThenBind();
|
||
|
|
})();
|