1
This commit is contained in:
310
public/site-links-client.js
Normal file
310
public/site-links-client.js
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 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();
|
||||
})();
|
||||
Reference in New Issue
Block a user