v0.165.1: native Megosztás… button in the share modal (Web Share API)

Feature-detected navigator.share opens the OS share sheet with title+text+URL only
(no QR files: — narrow support drops the URL when given file+URL). Hidden unless
supported; Link másolása stays the universal fallback and catches the non-cancel
rejection; AbortError is silent. Template JS + tests only. 2 red-proofs verified red.
This commit is contained in:
2026-07-24 12:43:14 +02:00
parent 570fb30147
commit a04afc367b
6 changed files with 161 additions and 98 deletions
@@ -0,0 +1,66 @@
package web
import (
"strings"
"testing"
)
// Native "Megosztás…" button in the share modal (v0.165.1). Renders the ADMIN launcher template with
// sharing enabled and asserts the Web Share API wiring: hidden-by-default button, feature-detect
// reveal, a title/text/url-only payload (never a QR file), and the non-cancel fallback to copy.
func renderAdminLauncherModal(t *testing.T) string {
t.Helper()
return renderBackupPage(t, "launcher", map[string]interface{}{
"Page": "launcher", "Title": "Indítópult", "Domain": "demo-felhom.eu",
"Version": "test",
"ShareEnabled": true,
"ShareURL": "https://felhom.demo-felhom.eu/s/REDACTED-TOKEN",
"SharePasswordSet": false,
})
}
// Group A — markup + payload shape.
// COMPANION red-proof (REPORT): drop the `style="display:none"` from the button (reveal it in markup)
// → the hidden-by-default assertion FAILS.
func TestShareModal_NativeShareButton(t *testing.T) {
html := renderAdminLauncherModal(t)
if !strings.Contains(html, `id="share-native-btn"`) {
t.Error("the native share button must be present in the modal")
}
// Hidden by default in the markup — JS only ever reveals it.
if !strings.Contains(html, `id="share-native-btn" class="btn btn-outline" style="display:none"`) {
t.Error("the native share button must be display:none in the initial markup")
}
// Feature-detect reveal in the IIFE.
if !strings.Contains(html, "if(navigator.share)") {
t.Error("missing navigator.share feature-detect reveal")
}
// Payload carries title/text/url and NOTHING else — no files key.
for _, k := range []string{"title:'Indítópult", "text:'Az otthoni alkalmazások egy helyen.'", "url:inp.value"} {
if !strings.Contains(html, k) {
t.Errorf("nativeShare payload missing %q", k)
}
}
if strings.Contains(html, "files:") {
t.Error("nativeShare must NOT attach files (the QR) to the share payload")
}
// The universal fallback is not demoted.
if !strings.Contains(html, "Link másolása") {
t.Error("the copy button (universal fallback) must remain")
}
}
// Group B — fallback wiring.
// COMPANION red-proof (REPORT): swap the catch to swallow all errors (drop the copyShareLink() call)
// → the fallback-presence assertion FAILS.
func TestShareModal_NativeShareFallback(t *testing.T) {
html := renderAdminLauncherModal(t)
if !strings.Contains(html, "AbortError") {
t.Error("a user-cancel (AbortError) must be silently ignored")
}
if !strings.Contains(html, "copyShareLink();") {
t.Error("a non-abort rejection must fall back to copyShareLink()")
}
}
@@ -59,6 +59,7 @@
<div class="share-link-row">
<input id="share-link-input" type="text" readonly value="{{.ShareURL}}">
<button type="button" id="share-copy-btn" class="btn btn-outline" onclick="copyShareLink()">Link másolása</button>
<button type="button" id="share-native-btn" class="btn btn-outline" style="display:none" onclick="nativeShare()">Megosztás…</button>
</div>
</div>
<div class="share-qr">
@@ -124,9 +125,19 @@ function copyShareLink(){
if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(inp.value).then(done,function(){inp.select();done();});}
else{inp.select();try{document.execCommand('copy');}catch(e){}done();}
}
// Native OS share sheet (Web Share API, feature-detected). URL + title/text only — never the QR as a
// file: file-share support is narrow and several targets drop the URL when handed file+URL. On a
// non-cancel rejection, fall back to the universal clipboard copy so the user still keeps the link.
function nativeShare(){
var inp=document.getElementById('share-link-input');
if(!inp||!navigator.share)return;
navigator.share({title:'Indítópult — {{.Domain}}',text:'Az otthoni alkalmazások egy helyen.',url:inp.value})
.catch(function(e){if(e&&e.name==='AbortError')return;copyShareLink();});
}
(function(){
var m=document.getElementById('share-modal');
if(m){m.addEventListener('click',function(e){if(e.target===m)closeShareModal();});}
if(navigator.share){var nb=document.getElementById('share-native-btn');if(nb)nb.style.display='';}
{{if .ShareFlash}}openShareModal();{{end}}
})();
</script>