mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-04-16 12:35:54 +00:00
- Implemented a function to detect RTL languages and updated the Vue component to apply the appropriate class based on the language. - Enhanced the subscription page styles to support RTL layout, ensuring proper text alignment and direction for Arabic and Persian languages.
168 lines
5.9 KiB
JavaScript
168 lines
5.9 KiB
JavaScript
(function () {
|
|
// Vue app for Subscription page
|
|
const el = document.getElementById('subscription-data');
|
|
if (!el) return;
|
|
const textarea = document.getElementById('subscription-links');
|
|
const rawLinks = (textarea?.value || '').split('\n').filter(Boolean);
|
|
|
|
const data = {
|
|
sId: el.getAttribute('data-sid') || '',
|
|
subUrl: el.getAttribute('data-sub-url') || '',
|
|
subJsonUrl: el.getAttribute('data-subjson-url') || '',
|
|
download: el.getAttribute('data-download') || '',
|
|
upload: el.getAttribute('data-upload') || '',
|
|
used: el.getAttribute('data-used') || '',
|
|
total: el.getAttribute('data-total') || '',
|
|
remained: el.getAttribute('data-remained') || '',
|
|
expireMs: (parseInt(el.getAttribute('data-expire') || '0', 10) || 0) * 1000,
|
|
lastOnlineMs: (parseInt(el.getAttribute('data-lastonline') || '0', 10) || 0),
|
|
downloadByte: parseInt(el.getAttribute('data-downloadbyte') || '0', 10) || 0,
|
|
uploadByte: parseInt(el.getAttribute('data-uploadbyte') || '0', 10) || 0,
|
|
totalByte: parseInt(el.getAttribute('data-totalbyte') || '0', 10) || 0,
|
|
datepicker: el.getAttribute('data-datepicker') || 'gregorian',
|
|
};
|
|
|
|
// Normalize lastOnline to milliseconds if it looks like seconds
|
|
if (data.lastOnlineMs && data.lastOnlineMs < 10_000_000_000) {
|
|
data.lastOnlineMs *= 1000;
|
|
}
|
|
|
|
function renderLink(item) {
|
|
return (
|
|
Vue.h('a-list-item', {}, [
|
|
Vue.h('a-space', { props: { size: 'small' } }, [
|
|
Vue.h('a-button', { props: { size: 'small' }, on: { click: () => copy(item) } }, [Vue.h('a-icon', { props: { type: 'copy' } })]),
|
|
Vue.h('span', { class: 'break-all' }, item)
|
|
])
|
|
])
|
|
);
|
|
}
|
|
|
|
function copy(text) {
|
|
ClipboardManager.copyText(text).then(ok => {
|
|
const messageType = ok ? 'success' : 'error';
|
|
Vue.prototype.$message[messageType](ok ? 'Copied' : 'Copy failed');
|
|
});
|
|
}
|
|
|
|
function open(url) {
|
|
window.location.href = url;
|
|
}
|
|
|
|
function isRtlLanguage(lang) {
|
|
if (!lang) return false;
|
|
return ['fa', 'ar'].some(prefix => lang.toLowerCase().startsWith(prefix));
|
|
}
|
|
|
|
function drawQR(value) {
|
|
try {
|
|
new QRious({ element: document.getElementById('qrcode'), value, size: 220 });
|
|
} catch (e) {
|
|
console.warn(e);
|
|
}
|
|
}
|
|
|
|
// Try to extract a human label (email/ps) from different link types
|
|
function linkName(link, idx) {
|
|
try {
|
|
if (link.startsWith('vmess://')) {
|
|
const json = JSON.parse(atob(link.replace('vmess://', '')));
|
|
if (json.ps) return json.ps;
|
|
if (json.add && json.id) return json.add; // fallback host
|
|
} else if (link.startsWith('vless://') || link.startsWith('trojan://')) {
|
|
const hashIdx = link.indexOf('#');
|
|
if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
|
|
const qIdx = link.indexOf('?');
|
|
if (qIdx !== -1) {
|
|
const qs = new URL('http://x/?' + link.substring(qIdx + 1, hashIdx !== -1 ? hashIdx : undefined)).searchParams;
|
|
if (qs.get('remark')) return qs.get('remark');
|
|
if (qs.get('email')) return qs.get('email');
|
|
}
|
|
const at = link.indexOf('@');
|
|
const protSep = link.indexOf('://');
|
|
if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at);
|
|
} else if (link.startsWith('ss://')) {
|
|
const hashIdx = link.indexOf('#');
|
|
if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
|
|
}
|
|
} catch (e) { /* ignore and fallback */ }
|
|
return 'Link ' + (idx + 1);
|
|
}
|
|
|
|
const app = new Vue({
|
|
delimiters: ['[[', ']]'],
|
|
el: '#app',
|
|
data: {
|
|
themeSwitcher,
|
|
app: data,
|
|
links: rawLinks,
|
|
lang: '',
|
|
viewportWidth: (typeof window !== 'undefined' ? window.innerWidth : 1024),
|
|
},
|
|
async mounted() {
|
|
this.lang = LanguageManager.getLanguage();
|
|
const tpl = document.getElementById('subscription-data');
|
|
const sj = tpl ? tpl.getAttribute('data-subjson-url') : '';
|
|
if (sj) this.app.subJsonUrl = sj;
|
|
drawQR(this.app.subUrl);
|
|
try {
|
|
const elJson = document.getElementById('qrcode-subjson');
|
|
if (elJson && this.app.subJsonUrl) {
|
|
new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 });
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
this._onResize = () => { this.viewportWidth = window.innerWidth; };
|
|
window.addEventListener('resize', this._onResize);
|
|
},
|
|
beforeDestroy() {
|
|
if (this._onResize) window.removeEventListener('resize', this._onResize);
|
|
},
|
|
computed: {
|
|
isRtl() {
|
|
return isRtlLanguage(this.lang || LanguageManager.getLanguage());
|
|
},
|
|
isMobile() {
|
|
return this.viewportWidth < 576;
|
|
},
|
|
isUnlimited() {
|
|
return !this.app.totalByte;
|
|
},
|
|
isActive() {
|
|
const now = Date.now();
|
|
const expiryOk = !this.app.expireMs || this.app.expireMs >= now;
|
|
const trafficOk = !this.app.totalByte || (this.app.uploadByte + this.app.downloadByte) <= this.app.totalByte;
|
|
return expiryOk && trafficOk;
|
|
},
|
|
shadowrocketUrl() {
|
|
const rawUrl = this.app.subUrl + '?flag=shadowrocket';
|
|
const base64Url = btoa(rawUrl);
|
|
const remark = encodeURIComponent(this.app.sId || 'Subscription');
|
|
return `shadowrocket://add/sub/${base64Url}?remark=${remark}`;
|
|
},
|
|
v2boxUrl() {
|
|
return `v2box://install-sub?url=${encodeURIComponent(this.app.subUrl)}&name=${encodeURIComponent(this.app.sId)}`;
|
|
},
|
|
streisandUrl() {
|
|
return `streisand://import/${encodeURIComponent(this.app.subUrl)}`;
|
|
},
|
|
v2raytunUrl() {
|
|
return this.app.subUrl;
|
|
},
|
|
npvtunUrl() {
|
|
return this.app.subUrl;
|
|
},
|
|
happUrl() {
|
|
return `happ://add/${this.app.subUrl}`;
|
|
}
|
|
},
|
|
methods: {
|
|
renderLink,
|
|
copy,
|
|
open,
|
|
linkName,
|
|
i18nLabel(key) {
|
|
return '{{ i18n "' + key + '" }}';
|
|
},
|
|
},
|
|
});
|
|
})();
|