refactor: simplify base.js (#3473)
This commit is contained in:
parent
98e9a61b98
commit
d142d76148
10 changed files with 751 additions and 753 deletions
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
173
lnbits/static/js/api.js
Normal file
173
lnbits/static/js/api.js
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
window._lnbitsApi = {
|
||||
request(method, url, apiKey, data, options = {}) {
|
||||
return axios({
|
||||
method: method,
|
||||
url: url,
|
||||
headers: {
|
||||
'X-Api-Key': apiKey
|
||||
},
|
||||
data: data,
|
||||
...options
|
||||
})
|
||||
},
|
||||
getServerHealth() {
|
||||
return this.request('get', '/api/v1/health')
|
||||
},
|
||||
async createInvoice(
|
||||
wallet,
|
||||
amount,
|
||||
memo,
|
||||
unit = 'sat',
|
||||
lnurlWithdraw = null,
|
||||
fiatProvider = null,
|
||||
internalMemo = null,
|
||||
payment_hash = null
|
||||
) {
|
||||
const data = {
|
||||
out: false,
|
||||
amount: amount,
|
||||
memo: memo,
|
||||
unit: unit,
|
||||
lnurl_withdraw: lnurlWithdraw,
|
||||
fiat_provider: fiatProvider,
|
||||
payment_hash: payment_hash
|
||||
}
|
||||
if (internalMemo) {
|
||||
data.extra = {
|
||||
internal_memo: String(internalMemo)
|
||||
}
|
||||
}
|
||||
return this.request('post', '/api/v1/payments', wallet.inkey, data)
|
||||
},
|
||||
payInvoice(wallet, bolt11, internalMemo = null) {
|
||||
const data = {
|
||||
out: true,
|
||||
bolt11: bolt11
|
||||
}
|
||||
if (internalMemo) {
|
||||
data.extra = {
|
||||
internal_memo: String(internalMemo)
|
||||
}
|
||||
}
|
||||
return this.request('post', '/api/v1/payments', wallet.adminkey, data)
|
||||
},
|
||||
cancelInvoice(wallet, paymentHash) {
|
||||
return this.request('post', '/api/v1/payments/cancel', wallet.adminkey, {
|
||||
payment_hash: paymentHash
|
||||
})
|
||||
},
|
||||
settleInvoice(wallet, preimage) {
|
||||
return this.request('post', `/api/v1/payments/settle`, wallet.adminkey, {
|
||||
preimage: preimage
|
||||
})
|
||||
},
|
||||
createAccount(name) {
|
||||
return this.request('post', '/api/v1/account', null, {
|
||||
name: name
|
||||
})
|
||||
},
|
||||
register(username, email, password, password_repeat) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth/register',
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
password_repeat
|
||||
}
|
||||
})
|
||||
},
|
||||
reset(reset_key, password, password_repeat) {
|
||||
return axios({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/auth/reset',
|
||||
data: {
|
||||
reset_key,
|
||||
password,
|
||||
password_repeat
|
||||
}
|
||||
})
|
||||
},
|
||||
login(username, password) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth',
|
||||
data: {username, password}
|
||||
})
|
||||
},
|
||||
loginByProvider(provider, headers, data) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: `/api/v1/auth/${provider}`,
|
||||
headers: headers,
|
||||
data
|
||||
})
|
||||
},
|
||||
loginUsr(usr) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth/usr',
|
||||
data: {usr}
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth/logout'
|
||||
})
|
||||
},
|
||||
getAuthenticatedUser() {
|
||||
return this.request('get', '/api/v1/auth')
|
||||
},
|
||||
getWallet(wallet) {
|
||||
return this.request('get', '/api/v1/wallet', wallet.inkey)
|
||||
},
|
||||
createWallet(wallet, name) {
|
||||
return this.request('post', '/api/v1/wallet', wallet.adminkey, {
|
||||
name: name
|
||||
}).then(res => {
|
||||
window.location = '/wallet?wal=' + res.data.id
|
||||
})
|
||||
},
|
||||
updateWallet(name, wallet) {
|
||||
return this.request('patch', '/api/v1/wallet', wallet.adminkey, {
|
||||
name: name
|
||||
})
|
||||
},
|
||||
resetWalletKeys(wallet) {
|
||||
return this.request('put', `/api/v1/wallet/reset/${wallet.id}`).then(
|
||||
res => {
|
||||
return res.data
|
||||
}
|
||||
)
|
||||
},
|
||||
deleteWallet(wallet) {
|
||||
return this.request('delete', `/api/v1/wallet/${wallet.id}`).then(_ => {
|
||||
let url = new URL(window.location.href)
|
||||
url.searchParams.delete('wal')
|
||||
window.location = url
|
||||
})
|
||||
},
|
||||
getPayments(wallet, params) {
|
||||
return this.request(
|
||||
'get',
|
||||
'/api/v1/payments/paginated?' + params,
|
||||
wallet.inkey
|
||||
)
|
||||
},
|
||||
getPayment(wallet, paymentHash) {
|
||||
return this.request('get', '/api/v1/payments/' + paymentHash, wallet.inkey)
|
||||
},
|
||||
updateBalance(credit, wallet_id) {
|
||||
return this.request('PUT', '/users/api/v1/balance', null, {
|
||||
amount: credit,
|
||||
id: wallet_id
|
||||
})
|
||||
},
|
||||
getCurrencies() {
|
||||
return this.request('GET', '/api/v1/currencies').then(response => {
|
||||
return ['sats', ...response.data]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,182 +1,7 @@
|
|||
window.LNbits = {
|
||||
g: window.g,
|
||||
api: {
|
||||
request(method, url, apiKey, data, options = {}) {
|
||||
return axios({
|
||||
method: method,
|
||||
url: url,
|
||||
headers: {
|
||||
'X-Api-Key': apiKey
|
||||
},
|
||||
data: data,
|
||||
...options
|
||||
})
|
||||
},
|
||||
getServerHealth() {
|
||||
return this.request('get', '/api/v1/health')
|
||||
},
|
||||
async createInvoice(
|
||||
wallet,
|
||||
amount,
|
||||
memo,
|
||||
unit = 'sat',
|
||||
lnurlWithdraw = null,
|
||||
fiatProvider = null,
|
||||
internalMemo = null,
|
||||
payment_hash = null
|
||||
) {
|
||||
const data = {
|
||||
out: false,
|
||||
amount: amount,
|
||||
memo: memo,
|
||||
unit: unit,
|
||||
lnurl_withdraw: lnurlWithdraw,
|
||||
fiat_provider: fiatProvider,
|
||||
payment_hash: payment_hash
|
||||
}
|
||||
if (internalMemo) {
|
||||
data.extra = {
|
||||
internal_memo: String(internalMemo)
|
||||
}
|
||||
}
|
||||
return this.request('post', '/api/v1/payments', wallet.inkey, data)
|
||||
},
|
||||
payInvoice(wallet, bolt11, internalMemo = null) {
|
||||
const data = {
|
||||
out: true,
|
||||
bolt11: bolt11
|
||||
}
|
||||
if (internalMemo) {
|
||||
data.extra = {
|
||||
internal_memo: String(internalMemo)
|
||||
}
|
||||
}
|
||||
return this.request('post', '/api/v1/payments', wallet.adminkey, data)
|
||||
},
|
||||
cancelInvoice(wallet, paymentHash) {
|
||||
return this.request('post', '/api/v1/payments/cancel', wallet.adminkey, {
|
||||
payment_hash: paymentHash
|
||||
})
|
||||
},
|
||||
settleInvoice(wallet, preimage) {
|
||||
return this.request('post', `/api/v1/payments/settle`, wallet.adminkey, {
|
||||
preimage: preimage
|
||||
})
|
||||
},
|
||||
createAccount(name) {
|
||||
return this.request('post', '/api/v1/account', null, {
|
||||
name: name
|
||||
})
|
||||
},
|
||||
register(username, email, password, password_repeat) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth/register',
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
password_repeat
|
||||
}
|
||||
})
|
||||
},
|
||||
reset(reset_key, password, password_repeat) {
|
||||
return axios({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/auth/reset',
|
||||
data: {
|
||||
reset_key,
|
||||
password,
|
||||
password_repeat
|
||||
}
|
||||
})
|
||||
},
|
||||
login(username, password) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth',
|
||||
data: {username, password}
|
||||
})
|
||||
},
|
||||
loginByProvider(provider, headers, data) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: `/api/v1/auth/${provider}`,
|
||||
headers: headers,
|
||||
data
|
||||
})
|
||||
},
|
||||
loginUsr(usr) {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth/usr',
|
||||
data: {usr}
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
return axios({
|
||||
method: 'POST',
|
||||
url: '/api/v1/auth/logout'
|
||||
})
|
||||
},
|
||||
getAuthenticatedUser() {
|
||||
return this.request('get', '/api/v1/auth')
|
||||
},
|
||||
getWallet(wallet) {
|
||||
return this.request('get', '/api/v1/wallet', wallet.inkey)
|
||||
},
|
||||
createWallet(wallet, name) {
|
||||
return this.request('post', '/api/v1/wallet', wallet.adminkey, {
|
||||
name: name
|
||||
}).then(res => {
|
||||
window.location = '/wallet?wal=' + res.data.id
|
||||
})
|
||||
},
|
||||
updateWallet(name, wallet) {
|
||||
return this.request('patch', '/api/v1/wallet', wallet.adminkey, {
|
||||
name: name
|
||||
})
|
||||
},
|
||||
resetWalletKeys(wallet) {
|
||||
return this.request('put', `/api/v1/wallet/reset/${wallet.id}`).then(
|
||||
res => {
|
||||
return res.data
|
||||
}
|
||||
)
|
||||
},
|
||||
deleteWallet(wallet) {
|
||||
return this.request('delete', `/api/v1/wallet/${wallet.id}`).then(_ => {
|
||||
let url = new URL(window.location.href)
|
||||
url.searchParams.delete('wal')
|
||||
window.location = url
|
||||
})
|
||||
},
|
||||
getPayments(wallet, params) {
|
||||
return this.request(
|
||||
'get',
|
||||
'/api/v1/payments/paginated?' + params,
|
||||
wallet.inkey
|
||||
)
|
||||
},
|
||||
getPayment(wallet, paymentHash) {
|
||||
return this.request(
|
||||
'get',
|
||||
'/api/v1/payments/' + paymentHash,
|
||||
wallet.inkey
|
||||
)
|
||||
},
|
||||
updateBalance(credit, wallet_id) {
|
||||
return this.request('PUT', '/users/api/v1/balance', null, {
|
||||
amount: credit,
|
||||
id: wallet_id
|
||||
})
|
||||
},
|
||||
getCurrencies() {
|
||||
return this.request('GET', '/api/v1/currencies').then(response => {
|
||||
return ['sats', ...response.data]
|
||||
})
|
||||
}
|
||||
},
|
||||
utils: window._lnbitsUtils,
|
||||
api: window._lnbitsApi,
|
||||
events: {
|
||||
onInvoicePaid(wallet, cb) {
|
||||
ws = new WebSocket(`${websocketUrl}/${wallet.inkey}`)
|
||||
|
|
@ -288,544 +113,5 @@ window.LNbits = {
|
|||
}
|
||||
return obj
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
confirmDialog(msg) {
|
||||
return Quasar.Dialog.create({
|
||||
message: msg,
|
||||
ok: {
|
||||
flat: true,
|
||||
color: 'orange'
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
color: 'grey'
|
||||
}
|
||||
})
|
||||
},
|
||||
async digestMessage(message) {
|
||||
const msgUint8 = new TextEncoder().encode(message)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hashHex = hashArray
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
return hashHex
|
||||
},
|
||||
formatDate(timestamp) {
|
||||
return Quasar.date.formatDate(
|
||||
new Date(timestamp * 1000),
|
||||
window.dateFormat
|
||||
)
|
||||
},
|
||||
formatDateString(isoDateString) {
|
||||
return Quasar.date.formatDate(new Date(isoDateString), window.dateFormat)
|
||||
},
|
||||
formatCurrency(value, currency) {
|
||||
return new Intl.NumberFormat(window.LOCALE, {
|
||||
style: 'currency',
|
||||
currency: currency || 'sat'
|
||||
}).format(value)
|
||||
},
|
||||
formatSat(value) {
|
||||
return new Intl.NumberFormat(window.LOCALE).format(value)
|
||||
},
|
||||
formatMsat(value) {
|
||||
return this.formatSat(value / 1000)
|
||||
},
|
||||
notifyApiError(error) {
|
||||
if (!error.response) {
|
||||
return console.error(error)
|
||||
}
|
||||
const types = {
|
||||
400: 'warning',
|
||||
401: 'warning',
|
||||
500: 'negative'
|
||||
}
|
||||
Quasar.Notify.create({
|
||||
timeout: 5000,
|
||||
type: types[error.response.status] || 'warning',
|
||||
message:
|
||||
error.response.data.message || error.response.data.detail || null,
|
||||
caption:
|
||||
[error.response.status, ' ', error.response.statusText]
|
||||
.join('')
|
||||
.toUpperCase() || null,
|
||||
icon: null
|
||||
})
|
||||
},
|
||||
search(data, q, field, separator) {
|
||||
try {
|
||||
const queries = q.toLowerCase().split(separator || ' ')
|
||||
return data.filter(obj => {
|
||||
let matches = 0
|
||||
_.each(queries, q => {
|
||||
if (obj[field].indexOf(q) !== -1) matches++
|
||||
})
|
||||
return matches === queries.length
|
||||
})
|
||||
} catch (err) {
|
||||
return data
|
||||
}
|
||||
},
|
||||
prepareFilterQuery(tableConfig, props) {
|
||||
tableConfig.filter = tableConfig.filter || {}
|
||||
if (props) {
|
||||
tableConfig.pagination = props.pagination
|
||||
Object.assign(tableConfig.filter, props.filter)
|
||||
}
|
||||
const pagination = tableConfig.pagination
|
||||
tableConfig.loading = true
|
||||
const query = {
|
||||
limit: pagination.rowsPerPage,
|
||||
offset: (pagination.page - 1) * pagination.rowsPerPage,
|
||||
sortby: pagination.sortBy ?? '',
|
||||
direction: pagination.descending ? 'desc' : 'asc',
|
||||
...tableConfig.filter
|
||||
}
|
||||
if (tableConfig.search) {
|
||||
query.search = tableConfig.search
|
||||
}
|
||||
return new URLSearchParams(query)
|
||||
},
|
||||
exportCSV(columns, data, fileName) {
|
||||
const wrapCsvValue = (val, formatFn) => {
|
||||
let formatted = formatFn !== void 0 ? formatFn(val) : val
|
||||
|
||||
formatted =
|
||||
formatted === void 0 || formatted === null ? '' : String(formatted)
|
||||
|
||||
formatted = formatted.split('"').join('""')
|
||||
|
||||
return `"${formatted}"`
|
||||
}
|
||||
|
||||
const content = [
|
||||
columns.map(col => {
|
||||
return wrapCsvValue(col.label)
|
||||
})
|
||||
]
|
||||
.concat(
|
||||
data.map(row => {
|
||||
return columns
|
||||
.map(col => {
|
||||
return wrapCsvValue(
|
||||
typeof col.field === 'function'
|
||||
? col.field(row)
|
||||
: row[col.field === void 0 ? col.name : col.field],
|
||||
col.format
|
||||
)
|
||||
})
|
||||
.join(',')
|
||||
})
|
||||
)
|
||||
.join('\r\n')
|
||||
|
||||
const status = Quasar.exportFile(
|
||||
`${fileName || 'table-export'}.csv`,
|
||||
content,
|
||||
'text/csv'
|
||||
)
|
||||
|
||||
if (status !== true) {
|
||||
Quasar.Notify.create({
|
||||
message: 'Browser denied file download...',
|
||||
color: 'negative',
|
||||
icon: null
|
||||
})
|
||||
}
|
||||
},
|
||||
convertMarkdown(text) {
|
||||
const converter = new showdown.Converter()
|
||||
converter.setFlavor('github')
|
||||
converter.setOption('simpleLineBreaks', true)
|
||||
return converter.makeHtml(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.g) {
|
||||
window.g = Vue.reactive({
|
||||
offline: !navigator.onLine,
|
||||
visibleDrawer: false,
|
||||
extensions: [],
|
||||
user: null,
|
||||
wallet: {},
|
||||
fiatBalance: 0,
|
||||
exchangeRate: 0,
|
||||
fiatTracking: false,
|
||||
wallets: [],
|
||||
payments: [],
|
||||
langs: [],
|
||||
walletEventListeners: [],
|
||||
updatePayments: false,
|
||||
updatePaymentsHash: ''
|
||||
})
|
||||
}
|
||||
|
||||
window.windowMixin = {
|
||||
i18n: window.i18n,
|
||||
data() {
|
||||
return {
|
||||
g: window.g,
|
||||
toggleSubs: true,
|
||||
mobileSimple: true,
|
||||
walletFlip: true,
|
||||
showAddWalletDialog: {show: false},
|
||||
isUserAuthorized: false,
|
||||
isSatsDenomination: WINDOW_SETTINGS['LNBITS_DENOMINATION'] == 'sats',
|
||||
allowedThemes: WINDOW_SETTINGS['LNBITS_THEME_OPTIONS'],
|
||||
walletEventListeners: [],
|
||||
darkChoice: this.$q.localStorage.has('lnbits.darkMode')
|
||||
? this.$q.localStorage.getItem('lnbits.darkMode')
|
||||
: true,
|
||||
borderChoice: this.$q.localStorage.has('lnbits.border')
|
||||
? this.$q.localStorage.getItem('lnbits.border')
|
||||
: USE_DEFAULT_BORDER,
|
||||
gradientChoice: this.$q.localStorage.has('lnbits.gradientBg')
|
||||
? this.$q.localStorage.getItem('lnbits.gradientBg')
|
||||
: USE_DEFAULT_GRADIENT,
|
||||
themeChoice: this.$q.localStorage.has('lnbits.theme')
|
||||
? this.$q.localStorage.getItem('lnbits.theme')
|
||||
: USE_DEFAULT_THEME,
|
||||
reactionChoice: this.$q.localStorage.has('lnbits.reactions')
|
||||
? this.$q.localStorage.getItem('lnbits.reactions')
|
||||
: USE_DEFAULT_REACTION,
|
||||
bgimageChoice: this.$q.localStorage.has('lnbits.backgroundImage')
|
||||
? this.$q.localStorage.getItem('lnbits.backgroundImage')
|
||||
: USE_DEFAULT_BGIMAGE,
|
||||
...WINDOW_SETTINGS
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
flipWallets(smallScreen) {
|
||||
this.walletFlip = !this.walletFlip
|
||||
if (this.walletFlip && smallScreen) {
|
||||
this.g.visibleDrawer = false
|
||||
}
|
||||
this.$q.localStorage.set('lnbits.walletFlip', this.walletFlip)
|
||||
},
|
||||
goToWallets() {
|
||||
this.$router.push({
|
||||
path: '/wallets'
|
||||
})
|
||||
},
|
||||
submitAddWallet() {
|
||||
if (
|
||||
this.showAddWalletDialog.name &&
|
||||
this.showAddWalletDialog.name.length > 0
|
||||
) {
|
||||
LNbits.api.createWallet(
|
||||
this.g.user.wallets[0],
|
||||
this.showAddWalletDialog.name
|
||||
)
|
||||
this.showAddWalletDialog = {show: false}
|
||||
} else {
|
||||
this.$q.notify({
|
||||
message: 'Please enter a name for the wallet',
|
||||
color: 'negative'
|
||||
})
|
||||
}
|
||||
},
|
||||
simpleMobile() {
|
||||
this.$q.localStorage.set('lnbits.mobileSimple', !this.mobileSimple)
|
||||
this.refreshRoute()
|
||||
},
|
||||
paymentEvents() {
|
||||
this.g.walletEventListeners = this.g.walletEventListeners || []
|
||||
this.g.user.wallets.forEach(wallet => {
|
||||
if (!this.g.walletEventListeners.includes(wallet.id)) {
|
||||
this.g.walletEventListeners.push(wallet.id)
|
||||
LNbits.events.onInvoicePaid(wallet, data => {
|
||||
const walletIndex = this.g.user.wallets.findIndex(
|
||||
w => w.id === wallet.id
|
||||
)
|
||||
if (walletIndex !== -1) {
|
||||
//needed for balance being deducted
|
||||
let satBalance = data.wallet_balance
|
||||
if (data.payment.amount < 0) {
|
||||
satBalance = data.wallet_balance += data.payment.amount / 1000
|
||||
}
|
||||
//update the wallet
|
||||
Object.assign(this.g.user.wallets[walletIndex], {
|
||||
sat: satBalance,
|
||||
msat: data.wallet_balance * 1000,
|
||||
fsat: data.wallet_balance.toLocaleString()
|
||||
})
|
||||
//update the current wallet
|
||||
if (this.g.wallet.id === data.payment.wallet_id) {
|
||||
Object.assign(this.g.wallet, this.g.user.wallets[walletIndex])
|
||||
|
||||
//if on the wallet page and payment is incoming trigger the eventReaction
|
||||
if (
|
||||
data.payment.amount > 0 &&
|
||||
window.location.pathname === '/wallet'
|
||||
) {
|
||||
eventReaction(data.wallet_balance * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.g.updatePaymentsHash = data.payment.payment_hash
|
||||
this.g.updatePayments = !this.g.updatePayments
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
selectWallet(wallet) {
|
||||
Object.assign(this.g.wallet, wallet)
|
||||
// this.wallet = this.g.wallet
|
||||
this.g.updatePayments = !this.g.updatePayments
|
||||
this.balance = parseInt(wallet.balance_msat / 1000)
|
||||
const currentPath = this.$route.path
|
||||
if (currentPath !== '/wallet') {
|
||||
this.$router.push({
|
||||
path: '/wallet',
|
||||
query: {wal: this.g.wallet.id}
|
||||
})
|
||||
} else {
|
||||
this.$router.replace({
|
||||
path: '/wallet',
|
||||
query: {wal: this.g.wallet.id}
|
||||
})
|
||||
}
|
||||
},
|
||||
formatBalance(amount) {
|
||||
if (LNBITS_DENOMINATION != 'sats') {
|
||||
return LNbits.utils.formatCurrency(amount / 100, LNBITS_DENOMINATION)
|
||||
} else {
|
||||
return LNbits.utils.formatSat(amount) + ' sats'
|
||||
}
|
||||
},
|
||||
changeTheme(newValue) {
|
||||
document.body.setAttribute('data-theme', newValue)
|
||||
this.$q.localStorage.set('lnbits.theme', newValue)
|
||||
this.themeChoice = newValue
|
||||
},
|
||||
applyGradient() {
|
||||
if (this.gradientChoice) {
|
||||
document.body.classList.add('gradient-bg')
|
||||
this.$q.localStorage.set('lnbits.gradientBg', true)
|
||||
// Ensure dark mode is enabled when gradient background is applied
|
||||
if (!this.$q.dark.isActive) {
|
||||
this.toggleDarkMode()
|
||||
}
|
||||
} else {
|
||||
document.body.classList.remove('gradient-bg')
|
||||
this.$q.localStorage.set('lnbits.gradientBg', false)
|
||||
}
|
||||
},
|
||||
applyBackgroundImage() {
|
||||
if (this.bgimageChoice == 'null') this.bgimageChoice = ''
|
||||
if (this.bgimageChoice == '') {
|
||||
document.body.classList.remove('bg-image')
|
||||
} else {
|
||||
document.body.classList.add('bg-image')
|
||||
document.body.style.setProperty(
|
||||
'--background',
|
||||
`url(${this.bgimageChoice})`
|
||||
)
|
||||
}
|
||||
this.$q.localStorage.set('lnbits.backgroundImage', this.bgimageChoice)
|
||||
},
|
||||
applyBorder() {
|
||||
// Remove any existing border classes
|
||||
document.body.classList.forEach(cls => {
|
||||
if (cls.endsWith('-border')) {
|
||||
document.body.classList.remove(cls)
|
||||
}
|
||||
})
|
||||
this.$q.localStorage.setItem('lnbits.border', this.borderChoice)
|
||||
document.body.classList.add(this.borderChoice)
|
||||
},
|
||||
toggleDarkMode() {
|
||||
this.$q.dark.toggle()
|
||||
this.darkChoice = this.$q.dark.isActive
|
||||
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
|
||||
if (!this.$q.dark.isActive) {
|
||||
this.gradientChoice = false
|
||||
this.applyGradient()
|
||||
}
|
||||
},
|
||||
copyText(text, message, position) {
|
||||
Quasar.copyToClipboard(text).then(() => {
|
||||
Quasar.Notify.create({
|
||||
message: message || 'Copied to clipboard!',
|
||||
position: position || 'bottom'
|
||||
})
|
||||
})
|
||||
},
|
||||
async checkUsrInUrl() {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const usr = params.get('usr')
|
||||
if (!usr) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.isUserAuthorized) {
|
||||
await LNbits.api.loginUsr(usr)
|
||||
}
|
||||
|
||||
params.delete('usr')
|
||||
const cleanQueryPrams = params.size ? `?${params.toString()}` : ''
|
||||
|
||||
window.history.replaceState(
|
||||
{},
|
||||
document.title,
|
||||
window.location.pathname + cleanQueryPrams
|
||||
)
|
||||
} finally {
|
||||
this.isUserAuthorized = !!this.$q.cookies.get(
|
||||
'is_lnbits_user_authorized'
|
||||
)
|
||||
}
|
||||
},
|
||||
async logout() {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
'Do you really want to logout?' +
|
||||
' Please visit "My Account" page to check your credentials!'
|
||||
)
|
||||
.onOk(async () => {
|
||||
try {
|
||||
await LNbits.api.logout()
|
||||
window.location = '/'
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
themeParams() {
|
||||
const url = new URL(window.location.href)
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const fields = ['theme', 'dark', 'gradient']
|
||||
const toBoolean = value =>
|
||||
value.trim().toLowerCase() === 'true' || value === '1'
|
||||
|
||||
// Check if any of the relevant parameters ('theme', 'dark', 'gradient') are present in the URL.
|
||||
if (fields.some(param => params.has(param))) {
|
||||
const theme = params.get('theme')
|
||||
const darkMode = params.get('dark')
|
||||
const gradient = params.get('gradient')
|
||||
const border = params.get('border')
|
||||
|
||||
if (theme && this.allowedThemes.includes(theme.trim().toLowerCase())) {
|
||||
const normalizedTheme = theme.trim().toLowerCase()
|
||||
document.body.setAttribute('data-theme', normalizedTheme)
|
||||
this.$q.localStorage.set('lnbits.theme', normalizedTheme)
|
||||
}
|
||||
|
||||
if (darkMode) {
|
||||
const isDark = toBoolean(darkMode)
|
||||
this.$q.localStorage.set('lnbits.darkMode', isDark)
|
||||
if (!isDark) {
|
||||
this.$q.localStorage.set('lnbits.gradientBg', false)
|
||||
}
|
||||
}
|
||||
|
||||
if (gradient) {
|
||||
const isGradient = toBoolean(gradient)
|
||||
this.$q.localStorage.set('lnbits.gradientBg', isGradient)
|
||||
if (isGradient) {
|
||||
this.$q.localStorage.set('lnbits.darkMode', true)
|
||||
}
|
||||
}
|
||||
if (border) {
|
||||
this.$q.localStorage.set('lnbits.border', border)
|
||||
}
|
||||
|
||||
// Remove processed parameters
|
||||
fields.forEach(param => params.delete(param))
|
||||
|
||||
window.history.replaceState(null, null, url.pathname)
|
||||
}
|
||||
},
|
||||
refreshRoute() {
|
||||
const path = window.location.pathname
|
||||
console.log(path)
|
||||
|
||||
this.$router.push('/temp').then(() => {
|
||||
this.$router.replace({path})
|
||||
})
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$q.dark.set(
|
||||
this.$q.localStorage.has('lnbits.darkMode')
|
||||
? this.$q.localStorage.getItem('lnbits.darkMode')
|
||||
: true
|
||||
)
|
||||
Chart.defaults.color = this.$q.dark.isActive ? '#fff' : '#000'
|
||||
this.changeTheme(this.themeChoice)
|
||||
this.applyBorder()
|
||||
if (this.$q.dark.isActive) {
|
||||
this.applyGradient()
|
||||
}
|
||||
this.applyBackgroundImage()
|
||||
|
||||
let locale = this.$q.localStorage.getItem('lnbits.lang')
|
||||
if (locale) {
|
||||
window.LOCALE = locale
|
||||
window.i18n.global.locale = locale
|
||||
}
|
||||
|
||||
this.g.langs = window.langs ?? []
|
||||
|
||||
addEventListener('offline', event => {
|
||||
this.g.offline = true
|
||||
})
|
||||
|
||||
addEventListener('online', event => {
|
||||
this.g.offline = false
|
||||
})
|
||||
|
||||
if (window.user) {
|
||||
this.g.user = Vue.reactive(window.LNbits.map.user(window.user))
|
||||
}
|
||||
if (window.wallet) {
|
||||
this.g.wallet = Vue.reactive(window.LNbits.map.wallet(window.wallet))
|
||||
}
|
||||
if (window.extensions) {
|
||||
this.g.extensions = Vue.reactive(window.extensions)
|
||||
}
|
||||
await this.checkUsrInUrl()
|
||||
this.themeParams()
|
||||
this.walletFlip = this.$q.localStorage.getItem('lnbits.walletFlip')
|
||||
if (
|
||||
this.$q.screen.gt.sm ||
|
||||
this.$q.localStorage.getItem('lnbits.mobileSimple') == false
|
||||
) {
|
||||
this.mobileSimple = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.g.user) {
|
||||
this.paymentEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.decryptLnurlPayAES = (success_action, preimage) => {
|
||||
let keyb = new Uint8Array(
|
||||
preimage.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))
|
||||
)
|
||||
|
||||
return crypto.subtle
|
||||
.importKey('raw', keyb, {name: 'AES-CBC', length: 256}, false, ['decrypt'])
|
||||
.then(key => {
|
||||
let ivb = Uint8Array.from(window.atob(success_action.iv), c =>
|
||||
c.charCodeAt(0)
|
||||
)
|
||||
let ciphertextb = Uint8Array.from(
|
||||
window.atob(success_action.ciphertext),
|
||||
c => c.charCodeAt(0)
|
||||
)
|
||||
|
||||
return crypto.subtle.decrypt({name: 'AES-CBC', iv: ivb}, key, ciphertextb)
|
||||
})
|
||||
.then(valueb => {
|
||||
let decoder = new TextDecoder('utf-8')
|
||||
return decoder.decode(valueb)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
46
lnbits/static/js/globals.js
Normal file
46
lnbits/static/js/globals.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
window.langs = [
|
||||
{value: 'en', label: 'English', display: '🇬🇧 EN'},
|
||||
{value: 'de', label: 'Deutsch', display: '🇩🇪 DE'},
|
||||
{value: 'es', label: 'Español', display: '🇪🇸 ES'},
|
||||
{value: 'jp', label: '日本語', display: '🇯🇵 JP'},
|
||||
{value: 'cn', label: '中文', display: '🇨🇳 CN'},
|
||||
{value: 'fr', label: 'Français', display: '🇫🇷 FR'},
|
||||
{value: 'it', label: 'Italiano', display: '🇮🇹 IT'},
|
||||
{value: 'pi', label: 'Pirate', display: '🏴☠️ PI'},
|
||||
{value: 'nl', label: 'Nederlands', display: '🇳🇱 NL'},
|
||||
{value: 'we', label: 'Cymraeg', display: '🏴 CY'},
|
||||
{value: 'pl', label: 'Polski', display: '🇵🇱 PL'},
|
||||
{value: 'pt', label: 'Português', display: '🇵🇹 PT'},
|
||||
{value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR'},
|
||||
{value: 'cs', label: 'Česky', display: '🇨🇿 CS'},
|
||||
{value: 'sk', label: 'Slovensky', display: '🇸🇰 SK'},
|
||||
{value: 'kr', label: '한국어', display: '🇰🇷 KR'},
|
||||
{value: 'fi', label: 'Suomi', display: '🇫🇮 FI'}
|
||||
]
|
||||
window.LOCALE = 'en'
|
||||
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
||||
window.i18n = new VueI18n.createI18n({
|
||||
locale: window.LOCALE,
|
||||
fallbackLocale: window.LOCALE,
|
||||
messages: window.localisation
|
||||
})
|
||||
const websocketPrefix =
|
||||
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
|
||||
const websocketUrl = `${websocketPrefix}${window.location.host}/api/v1/ws`
|
||||
|
||||
window.g = Vue.reactive({
|
||||
offline: !navigator.onLine,
|
||||
visibleDrawer: false,
|
||||
extensions: [],
|
||||
user: null,
|
||||
wallet: {},
|
||||
fiatBalance: 0,
|
||||
exchangeRate: 0,
|
||||
fiatTracking: false,
|
||||
wallets: [],
|
||||
payments: [],
|
||||
langs: [],
|
||||
walletEventListeners: [],
|
||||
updatePayments: false,
|
||||
updatePaymentsHash: ''
|
||||
})
|
||||
23
lnbits/static/js/lnurl.js
Normal file
23
lnbits/static/js/lnurl.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
window.decryptLnurlPayAES = (success_action, preimage) => {
|
||||
let keyb = new Uint8Array(
|
||||
preimage.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))
|
||||
)
|
||||
|
||||
return crypto.subtle
|
||||
.importKey('raw', keyb, {name: 'AES-CBC', length: 256}, false, ['decrypt'])
|
||||
.then(key => {
|
||||
let ivb = Uint8Array.from(window.atob(success_action.iv), c =>
|
||||
c.charCodeAt(0)
|
||||
)
|
||||
let ciphertextb = Uint8Array.from(
|
||||
window.atob(success_action.ciphertext),
|
||||
c => c.charCodeAt(0)
|
||||
)
|
||||
|
||||
return crypto.subtle.decrypt({name: 'AES-CBC', iv: ivb}, key, ciphertextb)
|
||||
})
|
||||
.then(valueb => {
|
||||
let decoder = new TextDecoder('utf-8')
|
||||
return decoder.decode(valueb)
|
||||
})
|
||||
}
|
||||
148
lnbits/static/js/utils.js
Normal file
148
lnbits/static/js/utils.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
window._lnbitsUtils = {
|
||||
confirmDialog(msg) {
|
||||
return Quasar.Dialog.create({
|
||||
message: msg,
|
||||
ok: {
|
||||
flat: true,
|
||||
color: 'orange'
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
color: 'grey'
|
||||
}
|
||||
})
|
||||
},
|
||||
async digestMessage(message) {
|
||||
const msgUint8 = new TextEncoder().encode(message)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
return hashHex
|
||||
},
|
||||
formatDate(timestamp) {
|
||||
return Quasar.date.formatDate(new Date(timestamp * 1000), window.dateFormat)
|
||||
},
|
||||
formatDateString(isoDateString) {
|
||||
return Quasar.date.formatDate(new Date(isoDateString), window.dateFormat)
|
||||
},
|
||||
formatCurrency(value, currency) {
|
||||
return new Intl.NumberFormat(window.LOCALE, {
|
||||
style: 'currency',
|
||||
currency: currency || 'sat'
|
||||
}).format(value)
|
||||
},
|
||||
formatSat(value) {
|
||||
return new Intl.NumberFormat(window.LOCALE).format(value)
|
||||
},
|
||||
formatMsat(value) {
|
||||
return this.formatSat(value / 1000)
|
||||
},
|
||||
notifyApiError(error) {
|
||||
if (!error.response) {
|
||||
return console.error(error)
|
||||
}
|
||||
const types = {
|
||||
400: 'warning',
|
||||
401: 'warning',
|
||||
500: 'negative'
|
||||
}
|
||||
Quasar.Notify.create({
|
||||
timeout: 5000,
|
||||
type: types[error.response.status] || 'warning',
|
||||
message:
|
||||
error.response.data.message || error.response.data.detail || null,
|
||||
caption:
|
||||
[error.response.status, ' ', error.response.statusText]
|
||||
.join('')
|
||||
.toUpperCase() || null,
|
||||
icon: null
|
||||
})
|
||||
},
|
||||
search(data, q, field, separator) {
|
||||
try {
|
||||
const queries = q.toLowerCase().split(separator || ' ')
|
||||
return data.filter(obj => {
|
||||
let matches = 0
|
||||
_.each(queries, q => {
|
||||
if (obj[field].indexOf(q) !== -1) matches++
|
||||
})
|
||||
return matches === queries.length
|
||||
})
|
||||
} catch (err) {
|
||||
return data
|
||||
}
|
||||
},
|
||||
prepareFilterQuery(tableConfig, props) {
|
||||
tableConfig.filter = tableConfig.filter || {}
|
||||
if (props) {
|
||||
tableConfig.pagination = props.pagination
|
||||
Object.assign(tableConfig.filter, props.filter)
|
||||
}
|
||||
const pagination = tableConfig.pagination
|
||||
tableConfig.loading = true
|
||||
const query = {
|
||||
limit: pagination.rowsPerPage,
|
||||
offset: (pagination.page - 1) * pagination.rowsPerPage,
|
||||
sortby: pagination.sortBy ?? '',
|
||||
direction: pagination.descending ? 'desc' : 'asc',
|
||||
...tableConfig.filter
|
||||
}
|
||||
if (tableConfig.search) {
|
||||
query.search = tableConfig.search
|
||||
}
|
||||
return new URLSearchParams(query)
|
||||
},
|
||||
exportCSV(columns, data, fileName) {
|
||||
const wrapCsvValue = (val, formatFn) => {
|
||||
let formatted = formatFn !== void 0 ? formatFn(val) : val
|
||||
|
||||
formatted =
|
||||
formatted === void 0 || formatted === null ? '' : String(formatted)
|
||||
|
||||
formatted = formatted.split('"').join('""')
|
||||
|
||||
return `"${formatted}"`
|
||||
}
|
||||
|
||||
const content = [
|
||||
columns.map(col => {
|
||||
return wrapCsvValue(col.label)
|
||||
})
|
||||
]
|
||||
.concat(
|
||||
data.map(row => {
|
||||
return columns
|
||||
.map(col => {
|
||||
return wrapCsvValue(
|
||||
typeof col.field === 'function'
|
||||
? col.field(row)
|
||||
: row[col.field === void 0 ? col.name : col.field],
|
||||
col.format
|
||||
)
|
||||
})
|
||||
.join(',')
|
||||
})
|
||||
)
|
||||
.join('\r\n')
|
||||
|
||||
const status = Quasar.exportFile(
|
||||
`${fileName || 'table-export'}.csv`,
|
||||
content,
|
||||
'text/csv'
|
||||
)
|
||||
|
||||
if (status !== true) {
|
||||
Quasar.Notify.create({
|
||||
message: 'Browser denied file download...',
|
||||
color: 'negative',
|
||||
icon: null
|
||||
})
|
||||
}
|
||||
},
|
||||
convertMarkdown(text) {
|
||||
const converter = new showdown.Converter()
|
||||
converter.setFlavor('github')
|
||||
converter.setOption('simpleLineBreaks', true)
|
||||
return converter.makeHtml(text)
|
||||
}
|
||||
}
|
||||
344
lnbits/static/js/windowMixin.js
Normal file
344
lnbits/static/js/windowMixin.js
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
window.windowMixin = {
|
||||
i18n: window.i18n,
|
||||
data() {
|
||||
return {
|
||||
g: window.g,
|
||||
toggleSubs: true,
|
||||
mobileSimple: true,
|
||||
walletFlip: true,
|
||||
showAddWalletDialog: {show: false},
|
||||
isUserAuthorized: false,
|
||||
isSatsDenomination: WINDOW_SETTINGS['LNBITS_DENOMINATION'] == 'sats',
|
||||
allowedThemes: WINDOW_SETTINGS['LNBITS_THEME_OPTIONS'],
|
||||
walletEventListeners: [],
|
||||
darkChoice: this.$q.localStorage.has('lnbits.darkMode')
|
||||
? this.$q.localStorage.getItem('lnbits.darkMode')
|
||||
: true,
|
||||
borderChoice: this.$q.localStorage.has('lnbits.border')
|
||||
? this.$q.localStorage.getItem('lnbits.border')
|
||||
: USE_DEFAULT_BORDER,
|
||||
gradientChoice: this.$q.localStorage.has('lnbits.gradientBg')
|
||||
? this.$q.localStorage.getItem('lnbits.gradientBg')
|
||||
: USE_DEFAULT_GRADIENT,
|
||||
themeChoice: this.$q.localStorage.has('lnbits.theme')
|
||||
? this.$q.localStorage.getItem('lnbits.theme')
|
||||
: USE_DEFAULT_THEME,
|
||||
reactionChoice: this.$q.localStorage.has('lnbits.reactions')
|
||||
? this.$q.localStorage.getItem('lnbits.reactions')
|
||||
: USE_DEFAULT_REACTION,
|
||||
bgimageChoice: this.$q.localStorage.has('lnbits.backgroundImage')
|
||||
? this.$q.localStorage.getItem('lnbits.backgroundImage')
|
||||
: USE_DEFAULT_BGIMAGE,
|
||||
...WINDOW_SETTINGS
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
flipWallets(smallScreen) {
|
||||
this.walletFlip = !this.walletFlip
|
||||
if (this.walletFlip && smallScreen) {
|
||||
this.g.visibleDrawer = false
|
||||
}
|
||||
this.$q.localStorage.set('lnbits.walletFlip', this.walletFlip)
|
||||
},
|
||||
goToWallets() {
|
||||
this.$router.push({
|
||||
path: '/wallets'
|
||||
})
|
||||
},
|
||||
submitAddWallet() {
|
||||
if (
|
||||
this.showAddWalletDialog.name &&
|
||||
this.showAddWalletDialog.name.length > 0
|
||||
) {
|
||||
LNbits.api.createWallet(
|
||||
this.g.user.wallets[0],
|
||||
this.showAddWalletDialog.name
|
||||
)
|
||||
this.showAddWalletDialog = {show: false}
|
||||
} else {
|
||||
this.$q.notify({
|
||||
message: 'Please enter a name for the wallet',
|
||||
color: 'negative'
|
||||
})
|
||||
}
|
||||
},
|
||||
simpleMobile() {
|
||||
this.$q.localStorage.set('lnbits.mobileSimple', !this.mobileSimple)
|
||||
this.refreshRoute()
|
||||
},
|
||||
paymentEvents() {
|
||||
this.g.walletEventListeners = this.g.walletEventListeners || []
|
||||
this.g.user.wallets.forEach(wallet => {
|
||||
if (!this.g.walletEventListeners.includes(wallet.id)) {
|
||||
this.g.walletEventListeners.push(wallet.id)
|
||||
LNbits.events.onInvoicePaid(wallet, data => {
|
||||
const walletIndex = this.g.user.wallets.findIndex(
|
||||
w => w.id === wallet.id
|
||||
)
|
||||
if (walletIndex !== -1) {
|
||||
//needed for balance being deducted
|
||||
let satBalance = data.wallet_balance
|
||||
if (data.payment.amount < 0) {
|
||||
satBalance = data.wallet_balance += data.payment.amount / 1000
|
||||
}
|
||||
//update the wallet
|
||||
Object.assign(this.g.user.wallets[walletIndex], {
|
||||
sat: satBalance,
|
||||
msat: data.wallet_balance * 1000,
|
||||
fsat: data.wallet_balance.toLocaleString()
|
||||
})
|
||||
//update the current wallet
|
||||
if (this.g.wallet.id === data.payment.wallet_id) {
|
||||
Object.assign(this.g.wallet, this.g.user.wallets[walletIndex])
|
||||
|
||||
//if on the wallet page and payment is incoming trigger the eventReaction
|
||||
if (
|
||||
data.payment.amount > 0 &&
|
||||
window.location.pathname === '/wallet'
|
||||
) {
|
||||
eventReaction(data.wallet_balance * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.g.updatePaymentsHash = data.payment.payment_hash
|
||||
this.g.updatePayments = !this.g.updatePayments
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
selectWallet(wallet) {
|
||||
Object.assign(this.g.wallet, wallet)
|
||||
// this.wallet = this.g.wallet
|
||||
this.g.updatePayments = !this.g.updatePayments
|
||||
this.balance = parseInt(wallet.balance_msat / 1000)
|
||||
const currentPath = this.$route.path
|
||||
if (currentPath !== '/wallet') {
|
||||
this.$router.push({
|
||||
path: '/wallet',
|
||||
query: {wal: this.g.wallet.id}
|
||||
})
|
||||
} else {
|
||||
this.$router.replace({
|
||||
path: '/wallet',
|
||||
query: {wal: this.g.wallet.id}
|
||||
})
|
||||
}
|
||||
},
|
||||
formatBalance(amount) {
|
||||
if (LNBITS_DENOMINATION != 'sats') {
|
||||
return LNbits.utils.formatCurrency(amount / 100, LNBITS_DENOMINATION)
|
||||
} else {
|
||||
return LNbits.utils.formatSat(amount) + ' sats'
|
||||
}
|
||||
},
|
||||
changeTheme(newValue) {
|
||||
document.body.setAttribute('data-theme', newValue)
|
||||
this.$q.localStorage.set('lnbits.theme', newValue)
|
||||
this.themeChoice = newValue
|
||||
},
|
||||
applyGradient() {
|
||||
if (this.gradientChoice) {
|
||||
document.body.classList.add('gradient-bg')
|
||||
this.$q.localStorage.set('lnbits.gradientBg', true)
|
||||
// Ensure dark mode is enabled when gradient background is applied
|
||||
if (!this.$q.dark.isActive) {
|
||||
this.toggleDarkMode()
|
||||
}
|
||||
} else {
|
||||
document.body.classList.remove('gradient-bg')
|
||||
this.$q.localStorage.set('lnbits.gradientBg', false)
|
||||
}
|
||||
},
|
||||
applyBackgroundImage() {
|
||||
if (this.bgimageChoice == 'null') this.bgimageChoice = ''
|
||||
if (this.bgimageChoice == '') {
|
||||
document.body.classList.remove('bg-image')
|
||||
} else {
|
||||
document.body.classList.add('bg-image')
|
||||
document.body.style.setProperty(
|
||||
'--background',
|
||||
`url(${this.bgimageChoice})`
|
||||
)
|
||||
}
|
||||
this.$q.localStorage.set('lnbits.backgroundImage', this.bgimageChoice)
|
||||
},
|
||||
applyBorder() {
|
||||
// Remove any existing border classes
|
||||
document.body.classList.forEach(cls => {
|
||||
if (cls.endsWith('-border')) {
|
||||
document.body.classList.remove(cls)
|
||||
}
|
||||
})
|
||||
this.$q.localStorage.setItem('lnbits.border', this.borderChoice)
|
||||
document.body.classList.add(this.borderChoice)
|
||||
},
|
||||
toggleDarkMode() {
|
||||
this.$q.dark.toggle()
|
||||
this.darkChoice = this.$q.dark.isActive
|
||||
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
|
||||
if (!this.$q.dark.isActive) {
|
||||
this.gradientChoice = false
|
||||
this.applyGradient()
|
||||
}
|
||||
},
|
||||
copyText(text, message, position) {
|
||||
Quasar.copyToClipboard(text).then(() => {
|
||||
Quasar.Notify.create({
|
||||
message: message || 'Copied to clipboard!',
|
||||
position: position || 'bottom'
|
||||
})
|
||||
})
|
||||
},
|
||||
async checkUsrInUrl() {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const usr = params.get('usr')
|
||||
if (!usr) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.isUserAuthorized) {
|
||||
await LNbits.api.loginUsr(usr)
|
||||
}
|
||||
|
||||
params.delete('usr')
|
||||
const cleanQueryPrams = params.size ? `?${params.toString()}` : ''
|
||||
|
||||
window.history.replaceState(
|
||||
{},
|
||||
document.title,
|
||||
window.location.pathname + cleanQueryPrams
|
||||
)
|
||||
} finally {
|
||||
this.isUserAuthorized = !!this.$q.cookies.get(
|
||||
'is_lnbits_user_authorized'
|
||||
)
|
||||
}
|
||||
},
|
||||
async logout() {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
'Do you really want to logout?' +
|
||||
' Please visit "My Account" page to check your credentials!'
|
||||
)
|
||||
.onOk(async () => {
|
||||
try {
|
||||
await LNbits.api.logout()
|
||||
window.location = '/'
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
themeParams() {
|
||||
const url = new URL(window.location.href)
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const fields = ['theme', 'dark', 'gradient']
|
||||
const toBoolean = value =>
|
||||
value.trim().toLowerCase() === 'true' || value === '1'
|
||||
|
||||
// Check if any of the relevant parameters ('theme', 'dark', 'gradient') are present in the URL.
|
||||
if (fields.some(param => params.has(param))) {
|
||||
const theme = params.get('theme')
|
||||
const darkMode = params.get('dark')
|
||||
const gradient = params.get('gradient')
|
||||
const border = params.get('border')
|
||||
|
||||
if (theme && this.allowedThemes.includes(theme.trim().toLowerCase())) {
|
||||
const normalizedTheme = theme.trim().toLowerCase()
|
||||
document.body.setAttribute('data-theme', normalizedTheme)
|
||||
this.$q.localStorage.set('lnbits.theme', normalizedTheme)
|
||||
}
|
||||
|
||||
if (darkMode) {
|
||||
const isDark = toBoolean(darkMode)
|
||||
this.$q.localStorage.set('lnbits.darkMode', isDark)
|
||||
if (!isDark) {
|
||||
this.$q.localStorage.set('lnbits.gradientBg', false)
|
||||
}
|
||||
}
|
||||
|
||||
if (gradient) {
|
||||
const isGradient = toBoolean(gradient)
|
||||
this.$q.localStorage.set('lnbits.gradientBg', isGradient)
|
||||
if (isGradient) {
|
||||
this.$q.localStorage.set('lnbits.darkMode', true)
|
||||
}
|
||||
}
|
||||
if (border) {
|
||||
this.$q.localStorage.set('lnbits.border', border)
|
||||
}
|
||||
|
||||
// Remove processed parameters
|
||||
fields.forEach(param => params.delete(param))
|
||||
|
||||
window.history.replaceState(null, null, url.pathname)
|
||||
}
|
||||
},
|
||||
refreshRoute() {
|
||||
const path = window.location.pathname
|
||||
console.log(path)
|
||||
|
||||
this.$router.push('/temp').then(() => {
|
||||
this.$router.replace({path})
|
||||
})
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$q.dark.set(
|
||||
this.$q.localStorage.has('lnbits.darkMode')
|
||||
? this.$q.localStorage.getItem('lnbits.darkMode')
|
||||
: true
|
||||
)
|
||||
Chart.defaults.color = this.$q.dark.isActive ? '#fff' : '#000'
|
||||
this.changeTheme(this.themeChoice)
|
||||
this.applyBorder()
|
||||
if (this.$q.dark.isActive) {
|
||||
this.applyGradient()
|
||||
}
|
||||
this.applyBackgroundImage()
|
||||
|
||||
let locale = this.$q.localStorage.getItem('lnbits.lang')
|
||||
if (locale) {
|
||||
window.LOCALE = locale
|
||||
window.i18n.global.locale = locale
|
||||
}
|
||||
|
||||
this.g.langs = window.langs ?? []
|
||||
|
||||
addEventListener('offline', event => {
|
||||
console.log('offline', event)
|
||||
this.g.offline = true
|
||||
})
|
||||
|
||||
addEventListener('online', event => {
|
||||
console.log('back online', event)
|
||||
this.g.offline = false
|
||||
})
|
||||
|
||||
if (window.user) {
|
||||
this.g.user = Vue.reactive(window.LNbits.map.user(window.user))
|
||||
}
|
||||
if (window.wallet) {
|
||||
this.g.wallet = Vue.reactive(window.LNbits.map.wallet(window.wallet))
|
||||
}
|
||||
if (window.extensions) {
|
||||
this.g.extensions = Vue.reactive(window.extensions)
|
||||
}
|
||||
await this.checkUsrInUrl()
|
||||
this.themeParams()
|
||||
this.walletFlip = this.$q.localStorage.getItem('lnbits.walletFlip')
|
||||
if (
|
||||
this.$q.screen.gt.sm ||
|
||||
this.$q.localStorage.getItem('lnbits.mobileSimple') == false
|
||||
) {
|
||||
this.mobileSimple = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.g.user) {
|
||||
this.paymentEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,9 +33,14 @@
|
|||
"i18n/sk.js",
|
||||
"i18n/kr.js",
|
||||
"i18n/fi.js",
|
||||
"js/utils.js",
|
||||
"js/api.js",
|
||||
"js/globals.js",
|
||||
"js/base.js",
|
||||
"js/windowMixin.js",
|
||||
"js/event-reactions.js",
|
||||
"js/bolt11-decoder.js"
|
||||
"js/bolt11-decoder.js",
|
||||
"js/lnurl.js"
|
||||
],
|
||||
"components": [
|
||||
"js/pages/payments.js",
|
||||
|
|
|
|||
|
|
@ -477,7 +477,6 @@
|
|||
{% endblock %}
|
||||
</q-layout>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const WINDOW_SETTINGS = {{ WINDOW_SETTINGS | tojson }}
|
||||
Object.keys(WINDOW_SETTINGS).forEach(key => {
|
||||
|
|
@ -487,39 +486,8 @@
|
|||
{% include('components.vue') %} {% include('pages/payments.vue') %} {% block
|
||||
vue_templates %}{% endblock %} {% for url in INCLUDED_JS %}
|
||||
<script src="{{ static_url_for('static', url) }}"></script>
|
||||
{% endfor %}
|
||||
<script type="text/javascript">
|
||||
window.langs = [
|
||||
{value: 'en', label: 'English', display: '🇬🇧 EN'},
|
||||
{value: 'de', label: 'Deutsch', display: '🇩🇪 DE'},
|
||||
{value: 'es', label: 'Español', display: '🇪🇸 ES'},
|
||||
{value: 'jp', label: '日本語', display: '🇯🇵 JP'},
|
||||
{value: 'cn', label: '中文', display: '🇨🇳 CN'},
|
||||
{value: 'fr', label: 'Français', display: '🇫🇷 FR'},
|
||||
{value: 'it', label: 'Italiano', display: '🇮🇹 IT'},
|
||||
{value: 'pi', label: 'Pirate', display: '🏴☠️ PI'},
|
||||
{value: 'nl', label: 'Nederlands', display: '🇳🇱 NL'},
|
||||
{value: 'we', label: 'Cymraeg', display: '🏴 CY'},
|
||||
{value: 'pl', label: 'Polski', display: '🇵🇱 PL'},
|
||||
{value: 'pt', label: 'Português', display: '🇵🇹 PT'},
|
||||
{value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR'},
|
||||
{value: 'cs', label: 'Česky', display: '🇨🇿 CS'},
|
||||
{value: 'sk', label: 'Slovensky', display: '🇸🇰 SK'},
|
||||
{value: 'kr', label: '한국어', display: '🇰🇷 KR'},
|
||||
{value: 'fi', label: 'Suomi', display: '🇫🇮 FI'}
|
||||
]
|
||||
window.LOCALE = 'en'
|
||||
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
||||
window.i18n = new VueI18n.createI18n({
|
||||
locale: window.LOCALE,
|
||||
fallbackLocale: window.LOCALE,
|
||||
messages: window.localisation
|
||||
})
|
||||
const websocketPrefix =
|
||||
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
|
||||
const websocketUrl = `${websocketPrefix}${window.location.host}/api/v1/ws`
|
||||
</script>
|
||||
{% block scripts %}{% endblock %} {% for url in INCLUDED_COMPONENTS %}
|
||||
{% endfor %} {% block scripts %}{% endblock %} {% for url in
|
||||
INCLUDED_COMPONENTS %}
|
||||
<script src="{{ static_url_for('static', url) }}"></script>
|
||||
{% endfor %}
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -85,9 +85,14 @@
|
|||
"i18n/sk.js",
|
||||
"i18n/kr.js",
|
||||
"i18n/fi.js",
|
||||
"js/utils.js",
|
||||
"js/api.js",
|
||||
"js/globals.js",
|
||||
"js/base.js",
|
||||
"js/windowMixin.js",
|
||||
"js/event-reactions.js",
|
||||
"js/bolt11-decoder.js"
|
||||
"js/bolt11-decoder.js",
|
||||
"js/lnurl.js"
|
||||
],
|
||||
"components": [
|
||||
"js/pages/payments.js",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue