srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/siteFooter.js
blob: eef193b6a6c589d62fdbb189a6a04455e6caf1fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import { attachTooltips } from './tooltip.js';

// The links every site is expected to carry: where the source lives, who made
// it, and what it does with your data. Kept to one line at the foot of the
// menu rather than a page of its own, because there is very little to say and
// a whole screen would overstate it.
//
// Edit these to point at the real destinations.
export const LINKS = {
    github: 'https://github.com/srdusr/typerpunk',
    privacy: '#privacy',
};

// Share is a Web Share invocation where the browser has one (every mobile
// browser, Safari, and Edge), and a clipboard copy everywhere else. Neither
// needs a network call or a third-party button, so there is no tracking
// script here and nothing to consent to.
async function share(result) {
    const url = window.location.origin;
    const text = result
        ? `I just typed ${Math.round(result.wpm)} wpm at ${Math.round(result.accuracy)}% accuracy on TyperPunk.`
        : 'TyperPunk - competitive typing in the terminal and the browser.';

    if (navigator.share) {
        try {
            await navigator.share({ title: 'TyperPunk', text, url });
            return 'shared';
        } catch {
            // A cancelled share sheet is not a failure; fall through to copy.
        }
    }
    try {
        await navigator.clipboard.writeText(`${text} ${url}`);
        return 'copied';
    } catch {
        return 'failed';
    }
}

/// `result` is optional: with one, Share offers the score just achieved;
/// without, it offers the site.
export function renderSiteFooter(root, { result, onShowPrivacy, onShowContribute } = {}) {
    const wrap = document.createElement('div');
    wrap.className = 'site-footer';
    wrap.innerHTML = `
        <button class="site-footer-link" data-action="share" data-tooltip="${result ? 'Share this result' : 'Share TyperPunk'}">Share</button>
        <span class="site-footer-sep">·</span>
        <a class="site-footer-link" href="${LINKS.github}" target="_blank" rel="noopener noreferrer" data-tooltip="Source on GitHub">GitHub</a>
        <span class="site-footer-sep">·</span>
        <button class="site-footer-link" data-action="contribute" data-tooltip="Submit a passage for other people to type">Contribute</button>
        <span class="site-footer-sep">·</span>
        <button class="site-footer-link" data-action="privacy" data-tooltip="What this site stores">Privacy</button>
        <span class="site-footer-sep">·</span>
        <span class="site-footer-version">v0.1.0</span>
    `;
    root.appendChild(wrap);
    attachTooltips(wrap);

    const shareBtn = wrap.querySelector('[data-action="share"]');
    shareBtn.addEventListener('click', async () => {
        const outcome = await share(result);
        shareBtn.textContent = outcome === 'copied' ? 'Copied' : outcome === 'failed' ? 'Copy failed' : 'Shared';
        setTimeout(() => { shareBtn.textContent = 'Share'; }, 1800);
    });
    wrap.querySelector('[data-action="privacy"]').addEventListener('click', () => onShowPrivacy?.());
    wrap.querySelector('[data-action="contribute"]').addEventListener('click', () => onShowContribute?.());

    return () => wrap.remove();
}