diff --git a/manager/templates/_macros.html b/manager/templates/_macros.html
new file mode 100644
index 0000000..665ec70
--- /dev/null
+++ b/manager/templates/_macros.html
@@ -0,0 +1,23 @@
+{# Reusable SimpleX link box: a "Link" and a "QR" toggle button, both hidden by default.
+ linkbtns(id) — just the two toggle buttons (for table action cells)
+ linkpanels(link,id) — the hidden link + QR containers (place where they can span)
+ linkbox(link,id) — buttons + panels together (for block contexts)
+ `id` must be unique on the page. JS lives in base.html (sxToggleLink/sxToggleQr/sxCopy). #}
+
+{% macro linkbtns(id) %}
+ Link
+ QR
+{% endmacro %}
+
+{% macro linkpanels(link, id) %}
+
+
+{% endmacro %}
+
+{% macro linkbox(link, id) %}
+ {{ linkbtns(id) }}
+ {{ linkpanels(link, id) }}
+{% endmacro %}
diff --git a/manager/templates/base.html b/manager/templates/base.html
index afabf5a..607b2ae 100644
--- a/manager/templates/base.html
+++ b/manager/templates/base.html
@@ -16,6 +16,7 @@
+
-
+ {{ ui.linkbox(profile.address, 'addr') }}
{% else %}
Start the profile to generate an address.
{% endif %}
@@ -176,20 +165,20 @@
{{ name }}
{% if invited %}
- ⏳ invited
+ invited
{% else %}
{{ mcnt }}
{% endif %}
-
-
-
-
-
-
+ {% if g.link %}
+ {{ ui.linkpanels(g.link, 'g' ~ gid) }}
+ {% endif %}
{% endmacro %}
@@ -378,21 +361,7 @@ async function sendMsg() {
}
}
-// Clipboard that also works over plain-HTTP LAN (navigator.clipboard needs a
-// secure context). Falls back to a hidden textarea + execCommand.
-function robustCopy(text) {
- if (navigator.clipboard && window.isSecureContext) {
- return navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
- }
- return Promise.resolve(fallbackCopy(text));
-}
-function fallbackCopy(text) {
- const ta = document.createElement('textarea');
- ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
- document.body.appendChild(ta); ta.focus(); ta.select();
- try { document.execCommand('copy'); } catch (e) {}
- document.body.removeChild(ta);
-}
+// robustCopy/fallbackCopy live in base.html (shared). Directory-website URL copy:
function copyAddr(btn, addr) {
robustCopy(addr).then(() => {
btn.innerHTML = ' ';
@@ -596,38 +565,7 @@ async function leaveGroup(groupId, name, btn) {
else { btn.disabled = false; btn.textContent = 'Leave'; alert('Failed to leave: ' + (data.detail || 'unknown')); }
}
-function copyGroupLinkBtn(gid, btn) {
- const url = document.getElementById('link-url-' + gid).textContent;
- robustCopy(url).then(() => {
- btn.innerHTML = ' ';
- setTimeout(() => btn.innerHTML = ' ', 1500);
- });
-}
-async function getGroupLink(groupId, btn) {
- const row = document.getElementById('link-row-' + groupId);
- if (row && row.style.display !== 'none') { row.style.display = 'none'; return; } // toggle off
- const orig = btn.textContent;
- btn.textContent = '…';
- try {
- const resp = await fetch(`/api/profiles/{{ profile.id }}/groups/${groupId}/link`, {
- headers: {'X-Token': _token()},
- });
- const data = await resp.json();
- btn.textContent = orig;
- if (data.link) {
- const a = document.getElementById('link-url-' + groupId);
- a.textContent = data.link;
- a.href = data.link;
- row.style.display = '';
- } else {
- btn.textContent = 'No link';
- setTimeout(() => btn.textContent = orig, 2000);
- }
- } catch (e) {
- btn.textContent = 'Error';
- setTimeout(() => btn.textContent = orig, 2000);
- }
-}
+// Group/channel links use the shared link box (sxToggleLink/sxToggleQr in base.html).
// ─────────────────────────────────────────────────────────────────────────────
function refreshLog(event) {