This commit is contained in:
Evgeny @ SimpleX Chat
2026-06-02 18:07:38 +00:00
parent e6aa70af21
commit 546306fa16
4 changed files with 237 additions and 22 deletions
@@ -128,6 +128,7 @@ struct ChannelWebAccessView: View {
data-channel-link="\(pg.groupLink)"
data-channel-id="\(pg.publicGroupId)"
data-relay-domains="\(relayDomains.joined(separator: ","))"
data-app-download-buttons="on"
></div>
<script src="https://simplex.chat/js/channel-preview.js"></script>
"""
@@ -179,6 +179,7 @@ private fun embedCode(groupRelays: List<GroupRelay>, groupInfo: GroupInfo): Stri
data-channel-link="${pg.groupLink}"
data-channel-id="${pg.publicGroupId}"
data-relay-domains="$domains"
data-app-download-buttons="on"
></div>
<script src="https://simplex.chat/js/channel-preview.js"></script>"""
}
+1
View File
@@ -15,6 +15,7 @@
data-channel-link="YOUR_CHANNEL_LINK"
data-channel-id="YOUR_CHANNEL_ID"
data-relay-domains="relay1.example.com"
data-app-download-buttons="on"
<!-- data-relay-scheme="https" -->
></div>
<script src="https://simplex.chat/js/channel-preview.js"></script>
+234 -22
View File
@@ -2,6 +2,8 @@
(function() {
#include "qrcode.js"
const STYLE = `
.simplex-preview-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
@@ -72,6 +74,7 @@ const STYLE = `
cursor: pointer;
text-decoration: none;
display: inline-block;
font-family: inherit;
}
.simplex-preview-join-btn:hover {
@@ -497,6 +500,122 @@ const STYLE = `
width: 100%;
}
.simplex-preview-conversion {
margin-top: 20px;
}
.simplex-preview-qr-container {
display: flex;
flex-direction: column;
align-items: center;
}
.simplex-preview-qr-container canvas {
border-radius: 8px;
}
.simplex-preview-qr-caption {
font-size: 13px;
color: #8b8786;
text-align: center;
margin: 8px 0 0;
}
.simplex-preview-badges {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
margin: 16px 0 0;
}
.simplex-preview-badges a {
display: block;
}
.simplex-preview-badges a img {
height: 36px;
width: auto;
display: block;
}
.simplex-preview-copy-section {
font-size: 13px;
color: #8b8786;
margin-top: 16px;
}
.simplex-preview-copy-section a {
color: #0088ff;
text-decoration: none;
}
.simplex-preview-copy-section a:hover {
text-decoration: underline;
}
.simplex-preview-copy-row {
display: flex;
align-items: center;
gap: 6px;
margin-top: 6px;
background: #f5f5f6;
border-radius: 8px;
padding: 6px 10px;
}
.simplex-preview-copy-link {
flex: 1;
font-size: 11px;
color: #333;
word-break: break-all;
font-family: monospace;
min-width: 0;
line-height: 1.3;
}
.simplex-preview-copy-btn {
flex-shrink: 0;
background: none;
border: none;
cursor: pointer;
padding: 2px;
color: #8b8786;
line-height: 1;
}
.simplex-preview-copy-btn:hover {
color: #333;
}
.simplex-preview-step-label {
font-size: 16px;
font-weight: 500;
text-align: center;
margin: 20px 0 8px;
color: #000;
}
.simplex-preview-open-btn {
display: block;
text-align: center;
background: #0053D0;
color: #fff;
border: none;
border-radius: 34px;
padding: 12px 32px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
text-decoration: none;
font-family: inherit;
}
.simplex-preview-open-btn:hover {
background: #0047b3;
}
@media (min-width: 1000px) {
.simplex-preview-info {
width: 320px;
@@ -562,11 +681,14 @@ const VOICE_ICON_SVG = `<svg class="simplex-preview-file-icon" viewBox="0 0 24 2
const FORWARD_ICON_SVG = `<svg width="14" height="14" viewBox="0 -960 960 960" fill="currentColor"><path transform="scale(-1,1) translate(-960,0)" d="m236.5-495.5 142.5 143q8.5 8.5 8.25 20.25T378.5-312q-9 8.5-21 8.5t-20.5-9L145.5-504q-9-8.5-9-20t9-20.5L338-737q9-9 20.75-8.75T379.5-737q8.5 8.5 8.5 20.5t-8.5 20.5l-143 143h403q84 0 139.75 56T835-357.5V-234q0 12.5-8.25 20.75T806.5-205q-12.5 0-20.75-8.25T777.5-234v-123.5q0-60-39-99t-99-39h-403Z"/></svg>`;
const COPY_ICON_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
function initChannelPreview(container) {
const relayDomains = (container.dataset.relayDomains || '').split(',').map(u => u.trim()).filter(Boolean);
const relayScheme = container.dataset.relayScheme || 'https';
const channelId = container.dataset.channelId || '';
const channelLink = container.dataset.channelLink || '';
const showAppBadges = container.dataset.appDownloadButtons !== 'off';
if (!relayDomains.length || !channelId) {
container.innerHTML = '<p class="simplex-preview-empty">Missing configuration: data-relay-domains and data-channel-id required.</p>';
@@ -586,7 +708,7 @@ function initChannelPreview(container) {
container.innerHTML = '<p class="simplex-preview-empty">Failed to load channel preview.</p>';
return;
}
render(container, data, channelLink);
render(container, data, channelLink, showAppBadges);
});
}
@@ -620,7 +742,7 @@ async function fetchPreview(relayScheme, relayDomains, channelLink, channelId) {
return linkMismatch ? 'link_mismatch' : null;
}
function render(container, data, channelLink) {
function render(container, data, channelLink, showAppBadges) {
const { channel, members, messages } = data;
const membersMap = {};
for (const m of members) {
@@ -652,13 +774,12 @@ function render(container, data, channelLink) {
const infoContent = document.createElement('div');
infoContent.className = 'simplex-preview-info-content';
renderInfoContent(infoContent, data, channelLink, members.length);
renderInfoContent(infoContent, data, channelLink, members.length, showAppBadges);
info.appendChild(infoContent);
container.appendChild(info);
header.addEventListener('click', (e) => {
if (e.target.closest('.simplex-preview-join-btn')) return;
header.addEventListener('click', () => {
if (window.innerWidth < 1000) {
info.classList.add('open');
main.style.overflow = 'hidden';
@@ -704,22 +825,16 @@ function renderHeader(channel, channelLink, subscriberCount) {
header.appendChild(info);
if (channelLink) {
const btn = document.createElement('a');
const btn = document.createElement('button');
btn.className = 'simplex-preview-join-btn';
btn.textContent = 'Join';
try {
btn.href = platformSimplexUri(channelLink);
} catch(e) {
btn.href = channelLink;
}
btn.target = '_blank';
header.appendChild(btn);
}
return header;
}
function renderInfoContent(container, data, channelLink, subscriberCount) {
function renderInfoContent(container, data, channelLink, subscriberCount, showAppBadges) {
const { channel } = data;
const avatar = document.createElement('img');
@@ -765,19 +880,116 @@ function renderInfoContent(container, data, channelLink, subscriberCount) {
}
if (channelLink) {
const btn = document.createElement('a');
btn.className = 'simplex-preview-join-btn';
btn.textContent = 'Join';
try {
btn.href = platformSimplexUri(channelLink);
} catch(e) {
btn.href = channelLink;
const conversion = document.createElement('div');
conversion.className = 'simplex-preview-conversion';
if (isMobile.any()) {
renderMobileConversion(conversion, channelLink, showAppBadges);
} else {
renderDesktopConversion(conversion, channelLink, showAppBadges);
}
btn.target = '_blank';
container.appendChild(btn);
container.appendChild(conversion);
}
}
function renderDesktopConversion(container, channelLink, showAppBadges) {
const qrContainer = document.createElement('div');
qrContainer.className = 'simplex-preview-qr-container';
const canvas = document.createElement('canvas');
qrContainer.appendChild(canvas);
const caption = document.createElement('p');
caption.className = 'simplex-preview-qr-caption';
caption.textContent = 'Scan from mobile app to join';
qrContainer.appendChild(caption);
container.appendChild(qrContainer);
try {
QRCode.toCanvas(canvas, channelLink, {
errorCorrectionLevel: 'M',
color: { dark: '#062D56' },
width: 400,
margin: 1
}).then(function() {
canvas.style.width = '200px';
canvas.style.height = '200px';
}).catch(function() {
qrContainer.style.display = 'none';
});
} catch(e) {
qrContainer.style.display = 'none';
}
if (showAppBadges) {
const badges = document.createElement('div');
badges.className = 'simplex-preview-badges';
badges.innerHTML =
'<a href="https://apps.apple.com/us/app/simplex-chat/id1605771084" target="_blank"><img src="https://simplex.chat/img/new/apple_store.svg" alt="App Store"></a>' +
'<a href="https://play.google.com/store/apps/details?id=chat.simplex.app" target="_blank"><img src="https://simplex.chat/img/new/google_play.svg" alt="Google Play"></a>';
container.appendChild(badges);
}
const copySection = document.createElement('div');
copySection.className = 'simplex-preview-copy-section';
copySection.innerHTML = 'Or copy link for <a href="https://simplex.chat/downloads" target="_blank">desktop app</a>:';
const copyRow = document.createElement('div');
copyRow.className = 'simplex-preview-copy-row';
const linkText = document.createElement('span');
linkText.className = 'simplex-preview-copy-link';
linkText.textContent = channelLink;
copyRow.appendChild(linkText);
const copyBtn = document.createElement('button');
copyBtn.className = 'simplex-preview-copy-btn';
copyBtn.innerHTML = COPY_ICON_SVG;
copyBtn.title = 'Copy to clipboard';
copyBtn.addEventListener('click', function() {
navigator.clipboard.writeText(channelLink).then(function() {
copyBtn.title = 'Copied!';
setTimeout(function() { copyBtn.title = 'Copy to clipboard'; }, 2000);
});
});
copyRow.appendChild(copyBtn);
copySection.appendChild(copyRow);
container.appendChild(copySection);
}
function renderMobileConversion(container, channelLink, showAppBadges) {
if (showAppBadges) {
const stepLabel = document.createElement('p');
stepLabel.className = 'simplex-preview-step-label';
stepLabel.textContent = 'Install SimpleX App';
container.appendChild(stepLabel);
const badges = document.createElement('div');
badges.className = 'simplex-preview-badges';
if (isMobile.Android()) {
badges.innerHTML =
'<a href="https://play.google.com/store/apps/details?id=chat.simplex.app" target="_blank"><img src="https://simplex.chat/img/new/google_play.svg" alt="Google Play"></a>' +
'<a href="https://simplex.chat/fdroid" target="_blank"><img src="https://simplex.chat/img/new/f_droid.svg" alt="F-Droid"></a>' +
'<a href="https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk" target="_blank"><img src="https://simplex.chat/img/design_3/android-dark.png" alt="APK Download"></a>';
} else if (isMobile.iOS()) {
badges.innerHTML =
'<a href="https://apps.apple.com/us/app/simplex-chat/id1605771084" target="_blank"><img src="https://simplex.chat/img/new/apple_store.svg" alt="App Store"></a>' +
'<a href="https://testflight.apple.com/join/DWuT2LQu" target="_blank"><img src="https://simplex.chat/img/design_3/testflight-dark.png" alt="TestFlight"></a>';
} else {
badges.innerHTML =
'<a href="https://apps.apple.com/us/app/simplex-chat/id1605771084" target="_blank"><img src="https://simplex.chat/img/new/apple_store.svg" alt="App Store"></a>' +
'<a href="https://play.google.com/store/apps/details?id=chat.simplex.app" target="_blank"><img src="https://simplex.chat/img/new/google_play.svg" alt="Google Play"></a>';
}
container.appendChild(badges);
}
const openBtn = document.createElement('a');
openBtn.className = 'simplex-preview-open-btn';
openBtn.textContent = 'Open SimpleX App';
openBtn.href = channelLink;
container.appendChild(openBtn);
}
function setupExpandCollapse(section, readMore) {
if (section._measured) return;
const lineHeight = parseFloat(getComputedStyle(section).lineHeight);