feat(ui): add support and donation modal with CloudTips and crypto addresses
This commit is contained in:
@@ -6649,3 +6649,33 @@ msgstr "Разрешить только: "
|
||||
|
||||
msgid "Block: "
|
||||
msgstr "Блокировать: "
|
||||
|
||||
msgid "Support Development"
|
||||
msgstr "Поддержать разработку"
|
||||
|
||||
msgid "If Tachyon powers your daily networking and keeps your connection fast and secure, consider supporting ongoing development! ☕ 🧀 🌭"
|
||||
msgstr "Если Tachyon помогает вам и делает работу в сети комфортной, вы можете поддержать проект и автора! ☕ 🧀 🌭"
|
||||
|
||||
msgid "Credit Cards / SBP / Tinkoff Pay"
|
||||
msgstr "Карты РФ / СБП / Tinkoff Pay"
|
||||
|
||||
msgid "Quick donation via Russian bank cards or SBP"
|
||||
msgstr "Быстрый перевод картами РФ или через СБП"
|
||||
|
||||
msgid "Support on CloudTips"
|
||||
msgstr "Поддержать проект на CloudTips"
|
||||
|
||||
msgid "Cryptocurrency"
|
||||
msgstr "Криптовалюта"
|
||||
|
||||
msgid "Copy Address"
|
||||
msgstr "Скопировать"
|
||||
|
||||
msgid "Copied!"
|
||||
msgstr "Скопировано!"
|
||||
|
||||
msgid "Address copied to clipboard"
|
||||
msgstr "Адрес скопирован в буфер обмена"
|
||||
|
||||
msgid "Failed to copy address"
|
||||
msgstr "Не удалось скопировать адрес"
|
||||
|
||||
@@ -288,3 +288,15 @@ export function renderXIcon24() {
|
||||
svgEl('path', { d: 'm6 6 12 12' }),
|
||||
]);
|
||||
}
|
||||
|
||||
export function renderHeartIcon24() {
|
||||
return createIcon(
|
||||
'lucide lucide-heart-icon lucide-heart',
|
||||
[
|
||||
svgEl('path', {
|
||||
d: 'M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z',
|
||||
}),
|
||||
],
|
||||
{ width: '24', height: '24' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
import { renderButton } from '../../../../partials';
|
||||
import {
|
||||
renderCheckIcon24,
|
||||
renderCopyIcon24,
|
||||
renderLinkIcon24,
|
||||
} from '../../../../icons';
|
||||
import { showToast } from '../../../../helpers/showToast';
|
||||
|
||||
interface CryptoItem {
|
||||
name: string;
|
||||
badge: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
const CRYPTO_LIST: CryptoItem[] = [
|
||||
{
|
||||
name: 'Bitcoin',
|
||||
badge: 'BTC',
|
||||
address: 'bc1q9ehdv7y9g948jejyflkq7xmau3tytgunxgh35h',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum',
|
||||
badge: 'ETH / ERC-20',
|
||||
address: '0x6CB7a4547eD62EF64990D5C6B5D9fdA58EB223E6',
|
||||
},
|
||||
{
|
||||
name: 'TON',
|
||||
badge: 'TON',
|
||||
address: 'UQDPhLRjMz5KltDLACAT3YXXHDVEtIDOHky2i33ZIOtsMEoR',
|
||||
},
|
||||
{
|
||||
name: 'Solana',
|
||||
badge: 'SOL',
|
||||
address: '3csTGaNeU9KjhCKHEKAU3XLS5UfVjEeBVhXihpsETZwh',
|
||||
},
|
||||
];
|
||||
|
||||
const CLOUDTIPS_URL = 'https://pay.cloudtips.ru/p/48c57581';
|
||||
|
||||
async function copyTextToClipboard(text: string): Promise<boolean> {
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Fallback to execCommand
|
||||
}
|
||||
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
ta.style.top = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
ta.select();
|
||||
const successful = document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
return successful;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function renderSupportModal(): void {
|
||||
const introBlock = E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'margin-bottom: 16px; padding: 12px 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px; font-size: 13px; line-height: 1.5; color: var(--text-color-high, inherit);',
|
||||
},
|
||||
[
|
||||
E(
|
||||
'p',
|
||||
{ style: 'margin: 0;' },
|
||||
_(
|
||||
'If Tachyon powers your daily networking and keeps your connection fast and secure, consider supporting ongoing development! ☕ 🧀 🌭',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// CloudTips card
|
||||
const cloudTipsCard = E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'margin-bottom: 16px; padding: 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px;',
|
||||
},
|
||||
[
|
||||
E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;',
|
||||
},
|
||||
[
|
||||
E(
|
||||
'div',
|
||||
{ style: 'display: flex; flex-direction: column; gap: 2px;' },
|
||||
[
|
||||
E('div', { style: 'font-weight: bold; font-size: 14px;' }, [
|
||||
'💳 ',
|
||||
_('Credit Cards / SBP / Tinkoff Pay'),
|
||||
]),
|
||||
E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'font-size: 12px; color: var(--text-color-medium, #888);',
|
||||
},
|
||||
_('Quick donation via Russian bank cards or SBP'),
|
||||
),
|
||||
],
|
||||
),
|
||||
renderButton({
|
||||
classNames: ['cbi-button-action'],
|
||||
icon: renderLinkIcon24,
|
||||
text: _('Support on CloudTips'),
|
||||
onClick: () => {
|
||||
window.open(CLOUDTIPS_URL, '_blank', 'noopener,noreferrer');
|
||||
},
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Crypto list section
|
||||
const cryptoItemsContainer = E('div', {
|
||||
style: 'display: flex; flex-direction: column; gap: 10px;',
|
||||
});
|
||||
|
||||
CRYPTO_LIST.forEach((item) => {
|
||||
const addressCode = E(
|
||||
'code',
|
||||
{
|
||||
style:
|
||||
'font-family: monospace; font-size: 11px; word-break: break-all; flex: 1; padding: 6px 8px; background: var(--background-color-primary, rgba(0, 0, 0, 0.15)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 4px; user-select: all; line-height: 1.4;',
|
||||
},
|
||||
item.address,
|
||||
);
|
||||
|
||||
const copyBtn = renderButton({
|
||||
classNames: ['cbi-button'],
|
||||
icon: renderCopyIcon24,
|
||||
text: _('Copy Address'),
|
||||
onClick: async () => {
|
||||
const ok = await copyTextToClipboard(item.address);
|
||||
if (ok) {
|
||||
copyBtn.replaceChildren(
|
||||
renderCheckIcon24(),
|
||||
document.createTextNode(' ' + _('Copied!')),
|
||||
);
|
||||
copyBtn.classList.add('cbi-button-apply');
|
||||
setTimeout(() => {
|
||||
copyBtn.replaceChildren(
|
||||
renderCopyIcon24(),
|
||||
document.createTextNode(' ' + _('Copy Address')),
|
||||
);
|
||||
copyBtn.classList.remove('cbi-button-apply');
|
||||
}, 2000);
|
||||
showToast(_('Address copied to clipboard'), 'success');
|
||||
} else {
|
||||
showToast(_('Failed to copy address'), 'error');
|
||||
}
|
||||
},
|
||||
});
|
||||
copyBtn.style.whiteSpace = 'nowrap';
|
||||
copyBtn.style.flexShrink = '0';
|
||||
|
||||
const row = E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'padding: 10px 12px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px; display: flex; flex-direction: column; gap: 6px;',
|
||||
},
|
||||
[
|
||||
E(
|
||||
'div',
|
||||
{
|
||||
style: 'display: flex; align-items: center; gap: 8px;',
|
||||
},
|
||||
[
|
||||
E(
|
||||
'span',
|
||||
{ style: 'font-weight: bold; font-size: 13px;' },
|
||||
item.name,
|
||||
),
|
||||
E(
|
||||
'span',
|
||||
{
|
||||
class: 'badge cbi-value-title',
|
||||
style: 'font-size: 11px; padding: 2px 6px; opacity: 0.85;',
|
||||
},
|
||||
item.badge,
|
||||
),
|
||||
],
|
||||
),
|
||||
E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'display: flex; align-items: center; gap: 8px; flex-wrap: wrap;',
|
||||
},
|
||||
[addressCode, copyBtn],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
cryptoItemsContainer.appendChild(row);
|
||||
});
|
||||
|
||||
const cryptoSection = E('div', { style: 'margin-bottom: 16px;' }, [
|
||||
E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'font-weight: bold; font-size: 14px; margin-bottom: 8px; display: flex; align-items: center; gap: 6px;',
|
||||
},
|
||||
['🪙 ', _('Cryptocurrency')],
|
||||
),
|
||||
cryptoItemsContainer,
|
||||
]);
|
||||
|
||||
// Modal footer with Close button
|
||||
const footer = E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'display: flex; justify-content: flex-end; padding-top: 12px; border-top: 1px solid var(--border-color, rgba(128, 128, 128, 0.2));',
|
||||
},
|
||||
[
|
||||
renderButton({
|
||||
classNames: ['cbi-button'],
|
||||
text: _('Close'),
|
||||
onClick: () => {
|
||||
if (ui.hideModal) ui.hideModal();
|
||||
},
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
const modalContent = E(
|
||||
'div',
|
||||
{
|
||||
style:
|
||||
'max-width: 640px; width: 100%; box-sizing: border-box; padding: 4px;',
|
||||
},
|
||||
[introBlock, cloudTipsCard, cryptoSection, footer],
|
||||
);
|
||||
|
||||
ui.showModal(`💖 ${_('Support Development')}`, modalContent);
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import { renderBookOpenTextIcon24, renderSendIcon24 } from '../../../../icons';
|
||||
import {
|
||||
renderBookOpenTextIcon24,
|
||||
renderHeartIcon24,
|
||||
renderSendIcon24,
|
||||
} from '../../../../icons';
|
||||
import { renderButton } from '../../../../partials';
|
||||
import { insertIf } from '../../../../helpers';
|
||||
import { renderSupportModal } from './renderSupportModal';
|
||||
|
||||
export function renderWikiDisclaimer(kind: 'default' | 'error' | 'warning') {
|
||||
const iconWrap = E('span', {
|
||||
@@ -47,5 +52,11 @@ export function renderWikiDisclaimer(kind: 'default' | 'error' | 'warning') {
|
||||
'noopener,noreferrer',
|
||||
),
|
||||
}),
|
||||
renderButton({
|
||||
classNames: ['cbi-button-save'],
|
||||
icon: renderHeartIcon24,
|
||||
text: _('Support Development'),
|
||||
onClick: () => renderSupportModal(),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
class MockMutationObserver {
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
takeRecords() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
(globalThis as any).MutationObserver = MockMutationObserver;
|
||||
|
||||
function createDummyElement(tag: string) {
|
||||
return {
|
||||
tagName: tag.toUpperCase(),
|
||||
children: [] as any[],
|
||||
style: {} as Record<string, string>,
|
||||
textContent: '',
|
||||
innerHTML: '',
|
||||
setAttribute() {},
|
||||
getAttribute() {
|
||||
return null;
|
||||
},
|
||||
addEventListener() {},
|
||||
querySelectorAll: function () {
|
||||
return [];
|
||||
},
|
||||
querySelector: function () {
|
||||
return null;
|
||||
},
|
||||
replaceChildren: function (...nodes: any[]) {
|
||||
(this as any).children = nodes;
|
||||
},
|
||||
appendChild: function (node: any) {
|
||||
(this as any).children.push(node);
|
||||
},
|
||||
append: function (...nodes: any[]) {
|
||||
nodes.forEach((node) => (this as any).children.push(node));
|
||||
},
|
||||
classList: {
|
||||
add() {},
|
||||
remove() {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
(globalThis as any).document = {
|
||||
body: createDummyElement('body') as any,
|
||||
createElement: (tag: string) => createDummyElement(tag) as any,
|
||||
createElementNS: (_ns: string, tag: string) =>
|
||||
createDummyElement(tag) as any,
|
||||
createTextNode: (text: string) => ({ textContent: text }),
|
||||
getElementById: () => null,
|
||||
querySelector: () => null,
|
||||
querySelectorAll: () => [],
|
||||
execCommand: vi.fn().mockReturnValue(true),
|
||||
};
|
||||
|
||||
(globalThis as any).E = (tag: string, attrs?: any, children?: any) => {
|
||||
const el = (globalThis as any).document.createElement(tag);
|
||||
if (attrs) {
|
||||
const { style, ...rest } = attrs;
|
||||
Object.assign(el, rest);
|
||||
if (style && typeof style === 'string') {
|
||||
style.split(';').forEach((rule: string) => {
|
||||
const [k, v] = rule.split(':').map((s: string) => s.trim());
|
||||
if (k && v) {
|
||||
el.style[k] = v;
|
||||
}
|
||||
});
|
||||
} else if (style && typeof style === 'object') {
|
||||
Object.assign(el.style, style);
|
||||
}
|
||||
}
|
||||
if (children) {
|
||||
if (Array.isArray(children)) {
|
||||
children.forEach((c) => {
|
||||
if (c) el.appendChild(c);
|
||||
});
|
||||
} else {
|
||||
el.appendChild(children);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
};
|
||||
|
||||
(globalThis as any)._ = (str: string) => str;
|
||||
|
||||
const showModal = vi.fn();
|
||||
const hideModal = vi.fn();
|
||||
(globalThis as any).ui = {
|
||||
showModal,
|
||||
hideModal,
|
||||
};
|
||||
|
||||
return {
|
||||
showModal,
|
||||
hideModal,
|
||||
};
|
||||
});
|
||||
|
||||
import { renderSupportModal } from '../partials/renderSupportModal';
|
||||
import { renderWikiDisclaimer } from '../partials/renderWikiDisclaimer';
|
||||
|
||||
describe('renderSupportModal & renderWikiDisclaimer', () => {
|
||||
beforeEach(() => {
|
||||
mocks.showModal.mockReset();
|
||||
mocks.hideModal.mockReset();
|
||||
});
|
||||
|
||||
it('renders Support Development button inside renderWikiDisclaimer', () => {
|
||||
const disclaimer = renderWikiDisclaimer('default') as any;
|
||||
expect(disclaimer).toBeDefined();
|
||||
|
||||
// disclaimer has content, Open Project Page btn, Telegram Channel btn, Support Development btn
|
||||
const buttons = disclaimer.children.filter(
|
||||
(c: any) =>
|
||||
c.tagName === 'BUTTON' ||
|
||||
(c.classList && c.classList.toString().includes('btn')),
|
||||
);
|
||||
expect(buttons.length).toBe(3);
|
||||
});
|
||||
|
||||
it('opens support modal with title and content when renderSupportModal is called', () => {
|
||||
renderSupportModal();
|
||||
expect(mocks.showModal).toHaveBeenCalledTimes(1);
|
||||
|
||||
const title = mocks.showModal.mock.calls[0][0];
|
||||
expect(title).toContain('Support Development');
|
||||
|
||||
const modalContent = mocks.showModal.mock.calls[0][1];
|
||||
expect(modalContent).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1742,6 +1742,17 @@ function renderXIcon24() {
|
||||
svgEl("path", { d: "m6 6 12 12" })
|
||||
]);
|
||||
}
|
||||
function renderHeartIcon24() {
|
||||
return createIcon(
|
||||
"lucide lucide-heart-icon lucide-heart",
|
||||
[
|
||||
svgEl("path", {
|
||||
d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"
|
||||
})
|
||||
],
|
||||
{ width: "24", height: "24" }
|
||||
);
|
||||
}
|
||||
|
||||
// src/helpers/prettyBytes.ts
|
||||
function prettyBytes(n) {
|
||||
@@ -15311,6 +15322,222 @@ function normalizeCompiledVersion(version, commitSha) {
|
||||
return version;
|
||||
}
|
||||
|
||||
// src/tachyon/tabs/diagnostic/partials/renderSupportModal.ts
|
||||
var CRYPTO_LIST = [
|
||||
{
|
||||
name: "Bitcoin",
|
||||
badge: "BTC",
|
||||
address: "bc1q9ehdv7y9g948jejyflkq7xmau3tytgunxgh35h"
|
||||
},
|
||||
{
|
||||
name: "Ethereum",
|
||||
badge: "ETH / ERC-20",
|
||||
address: "0x6CB7a4547eD62EF64990D5C6B5D9fdA58EB223E6"
|
||||
},
|
||||
{
|
||||
name: "TON",
|
||||
badge: "TON",
|
||||
address: "UQDPhLRjMz5KltDLACAT3YXXHDVEtIDOHky2i33ZIOtsMEoR"
|
||||
},
|
||||
{
|
||||
name: "Solana",
|
||||
badge: "SOL",
|
||||
address: "3csTGaNeU9KjhCKHEKAU3XLS5UfVjEeBVhXihpsETZwh"
|
||||
}
|
||||
];
|
||||
var CLOUDTIPS_URL = "https://pay.cloudtips.ru/p/48c57581";
|
||||
async function copyTextToClipboard(text) {
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.left = "-9999px";
|
||||
ta.style.top = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
ta.select();
|
||||
const successful = document.execCommand("copy");
|
||||
document.body.removeChild(ta);
|
||||
return successful;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function renderSupportModal() {
|
||||
const introBlock = E(
|
||||
"div",
|
||||
{
|
||||
style: "margin-bottom: 16px; padding: 12px 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px; font-size: 13px; line-height: 1.5; color: var(--text-color-high, inherit);"
|
||||
},
|
||||
[
|
||||
E(
|
||||
"p",
|
||||
{ style: "margin: 0;" },
|
||||
_(
|
||||
"If Tachyon powers your daily networking and keeps your connection fast and secure, consider supporting ongoing development! ☕ 🧀 🌭"
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
const cloudTipsCard = E(
|
||||
"div",
|
||||
{
|
||||
style: "margin-bottom: 16px; padding: 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px;"
|
||||
},
|
||||
[
|
||||
E(
|
||||
"div",
|
||||
{
|
||||
style: "display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;"
|
||||
},
|
||||
[
|
||||
E(
|
||||
"div",
|
||||
{ style: "display: flex; flex-direction: column; gap: 2px;" },
|
||||
[
|
||||
E("div", { style: "font-weight: bold; font-size: 14px;" }, [
|
||||
"💳 ",
|
||||
_("Credit Cards / SBP / Tinkoff Pay")
|
||||
]),
|
||||
E(
|
||||
"div",
|
||||
{
|
||||
style: "font-size: 12px; color: var(--text-color-medium, #888);"
|
||||
},
|
||||
_("Quick donation via Russian bank cards or SBP")
|
||||
)
|
||||
]
|
||||
),
|
||||
renderButton({
|
||||
classNames: ["cbi-button-action"],
|
||||
icon: renderLinkIcon24,
|
||||
text: _("Support on CloudTips"),
|
||||
onClick: () => {
|
||||
window.open(CLOUDTIPS_URL, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
})
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
const cryptoItemsContainer = E("div", {
|
||||
style: "display: flex; flex-direction: column; gap: 10px;"
|
||||
});
|
||||
CRYPTO_LIST.forEach((item) => {
|
||||
const addressCode = E(
|
||||
"code",
|
||||
{
|
||||
style: "font-family: monospace; font-size: 11px; word-break: break-all; flex: 1; padding: 6px 8px; background: var(--background-color-primary, rgba(0, 0, 0, 0.15)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 4px; user-select: all; line-height: 1.4;"
|
||||
},
|
||||
item.address
|
||||
);
|
||||
const copyBtn = renderButton({
|
||||
classNames: ["cbi-button"],
|
||||
icon: renderCopyIcon24,
|
||||
text: _("Copy Address"),
|
||||
onClick: async () => {
|
||||
const ok = await copyTextToClipboard(item.address);
|
||||
if (ok) {
|
||||
copyBtn.replaceChildren(
|
||||
renderCheckIcon24(),
|
||||
document.createTextNode(" " + _("Copied!"))
|
||||
);
|
||||
copyBtn.classList.add("cbi-button-apply");
|
||||
setTimeout(() => {
|
||||
copyBtn.replaceChildren(
|
||||
renderCopyIcon24(),
|
||||
document.createTextNode(" " + _("Copy Address"))
|
||||
);
|
||||
copyBtn.classList.remove("cbi-button-apply");
|
||||
}, 2e3);
|
||||
showToast(_("Address copied to clipboard"), "success");
|
||||
} else {
|
||||
showToast(_("Failed to copy address"), "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
copyBtn.style.whiteSpace = "nowrap";
|
||||
copyBtn.style.flexShrink = "0";
|
||||
const row = E(
|
||||
"div",
|
||||
{
|
||||
style: "padding: 10px 12px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px; display: flex; flex-direction: column; gap: 6px;"
|
||||
},
|
||||
[
|
||||
E(
|
||||
"div",
|
||||
{
|
||||
style: "display: flex; align-items: center; gap: 8px;"
|
||||
},
|
||||
[
|
||||
E(
|
||||
"span",
|
||||
{ style: "font-weight: bold; font-size: 13px;" },
|
||||
item.name
|
||||
),
|
||||
E(
|
||||
"span",
|
||||
{
|
||||
class: "badge cbi-value-title",
|
||||
style: "font-size: 11px; padding: 2px 6px; opacity: 0.85;"
|
||||
},
|
||||
item.badge
|
||||
)
|
||||
]
|
||||
),
|
||||
E(
|
||||
"div",
|
||||
{
|
||||
style: "display: flex; align-items: center; gap: 8px; flex-wrap: wrap;"
|
||||
},
|
||||
[addressCode, copyBtn]
|
||||
)
|
||||
]
|
||||
);
|
||||
cryptoItemsContainer.appendChild(row);
|
||||
});
|
||||
const cryptoSection = E("div", { style: "margin-bottom: 16px;" }, [
|
||||
E(
|
||||
"div",
|
||||
{
|
||||
style: "font-weight: bold; font-size: 14px; margin-bottom: 8px; display: flex; align-items: center; gap: 6px;"
|
||||
},
|
||||
["🪙 ", _("Cryptocurrency")]
|
||||
),
|
||||
cryptoItemsContainer
|
||||
]);
|
||||
const footer = E(
|
||||
"div",
|
||||
{
|
||||
style: "display: flex; justify-content: flex-end; padding-top: 12px; border-top: 1px solid var(--border-color, rgba(128, 128, 128, 0.2));"
|
||||
},
|
||||
[
|
||||
renderButton({
|
||||
classNames: ["cbi-button"],
|
||||
text: _("Close"),
|
||||
onClick: () => {
|
||||
if (ui.hideModal) ui.hideModal();
|
||||
}
|
||||
})
|
||||
]
|
||||
);
|
||||
const modalContent = E(
|
||||
"div",
|
||||
{
|
||||
style: "max-width: 640px; width: 100%; box-sizing: border-box; padding: 4px;"
|
||||
},
|
||||
[introBlock, cloudTipsCard, cryptoSection, footer]
|
||||
);
|
||||
ui.showModal(`💖 ${_("Support Development")}`, modalContent);
|
||||
}
|
||||
|
||||
// src/tachyon/tabs/diagnostic/partials/renderWikiDisclaimer.ts
|
||||
function renderWikiDisclaimer(kind) {
|
||||
const iconWrap = E("span", {
|
||||
@@ -15352,6 +15579,12 @@ function renderWikiDisclaimer(kind) {
|
||||
"_blank",
|
||||
"noopener,noreferrer"
|
||||
)
|
||||
}),
|
||||
renderButton({
|
||||
classNames: ["cbi-button-save"],
|
||||
icon: renderHeartIcon24,
|
||||
text: _("Support Development"),
|
||||
onClick: () => renderSupportModal()
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -6649,3 +6649,33 @@ msgstr "Разрешить только: "
|
||||
|
||||
msgid "Block: "
|
||||
msgstr "Блокировать: "
|
||||
|
||||
msgid "Support Development"
|
||||
msgstr "Поддержать разработку"
|
||||
|
||||
msgid "If Tachyon powers your daily networking and keeps your connection fast and secure, consider supporting ongoing development! ☕ 🧀 🌭"
|
||||
msgstr "Если Tachyon помогает вам и делает работу в сети комфортной, вы можете поддержать проект и автора! ☕ 🧀 🌭"
|
||||
|
||||
msgid "Credit Cards / SBP / Tinkoff Pay"
|
||||
msgstr "Карты РФ / СБП / Tinkoff Pay"
|
||||
|
||||
msgid "Quick donation via Russian bank cards or SBP"
|
||||
msgstr "Быстрый перевод картами РФ или через СБП"
|
||||
|
||||
msgid "Support on CloudTips"
|
||||
msgstr "Поддержать проект на CloudTips"
|
||||
|
||||
msgid "Cryptocurrency"
|
||||
msgstr "Криптовалюта"
|
||||
|
||||
msgid "Copy Address"
|
||||
msgstr "Скопировать"
|
||||
|
||||
msgid "Copied!"
|
||||
msgstr "Скопировано!"
|
||||
|
||||
msgid "Address copied to clipboard"
|
||||
msgstr "Адрес скопирован в буфер обмена"
|
||||
|
||||
msgid "Failed to copy address"
|
||||
msgstr "Не удалось скопировать адрес"
|
||||
|
||||
Reference in New Issue
Block a user