Skip to content

Commit ef714c2

Browse files
Add Plausible analytics + a 'Something's missing' doc feedback button (#62)
* feat: add Plausible analytics (privacy-friendly, cookieless) Single shared property with the main site; distinguish docs traffic via the Hostname filter. The script ID is a public client-side identifier, not a secret, so it lives inline in the VitePress head config. * feat: add 'Something's missing' doc feedback button Renders in the right aside (after the page outline) on content pages, not the LandingLayout homepage. The click fires a Plausible 'Doc Incomplete' event with the page path as the metric; an optional 'Add details' step opens a prefilled GitHub issue for context, so no free text (and no PII) is ever sent to Plausible.
1 parent 30d200a commit ef714c2

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

docs/.vitepress/config.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export default withMermaid(defineConfig({
3838
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
3939
['meta', { name: 'twitter:site', content: '@any_cable' }],
4040
['meta', { name: 'keywords', content: 'anycable, websockets, real-time, realtime server, delivery guarantees, reliable streams, presence, action-cable, ruby, rails, hotwire, laravel, nodejs, python, go' }],
41+
// Privacy-friendly analytics by Plausible (cookieless, no PII). The script ID is a
42+
// public client-side identifier, not a secret; spoofing is blocked via hostname Shields.
43+
['script', { async: '', src: 'https://plausible.io/js/pa-sk7JDKlDe3CHAdBbykSWN.js' }],
44+
['script', {}, 'window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};\n plausible.init()'],
4145
],
4246

4347
themeConfig: {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<script setup lang="ts">
2+
import { ref, computed } from 'vue'
3+
import { useData, useRoute } from 'vitepress'
4+
5+
const route = useRoute()
6+
const { page } = useData()
7+
8+
const sent = ref(false)
9+
10+
// Built once per page, SSR-safe (no `window`/`location` at render time).
11+
const issueUrl = computed(() => {
12+
const path = route.path
13+
const source = page.value.relativePath // e.g. "quickstart.md"
14+
const body = [
15+
"What's missing, unclear, or wrong on this page?",
16+
'',
17+
'',
18+
'---',
19+
`Page: https://docs.anycable.io${path}`,
20+
`Source: docs/${source}`,
21+
].join('\n')
22+
const params = new URLSearchParams({
23+
title: `Docs feedback: ${path}`,
24+
body,
25+
})
26+
return `https://github.com/anycable/docs.anycable.io/issues/new?${params.toString()}`
27+
})
28+
29+
// Fire the lightweight signal. Free text never goes to Plausible (PII/terms).
30+
function track(event: string) {
31+
const plausible = (globalThis as any).plausible
32+
if (typeof plausible === 'function') {
33+
plausible(event, { props: { path: route.path } })
34+
}
35+
}
36+
37+
function report() {
38+
track('Doc Incomplete')
39+
sent.value = true
40+
}
41+
</script>
42+
43+
<template>
44+
<div class="doc-feedback">
45+
<template v-if="!sent">
46+
<span class="doc-feedback-q">Something missing or unclear on this page?</span>
47+
<button class="doc-feedback-btn" type="button" @click="report">
48+
Something's missing
49+
</button>
50+
</template>
51+
<template v-else>
52+
<span class="doc-feedback-q">Thanks, noted. Want to add specifics?</span>
53+
<a
54+
class="doc-feedback-btn"
55+
:href="issueUrl"
56+
target="_blank"
57+
rel="noopener"
58+
@click="track('Doc Feedback Detail')"
59+
>
60+
Add details on GitHub →
61+
</a>
62+
</template>
63+
</div>
64+
</template>
65+
66+
<style scoped>
67+
.doc-feedback {
68+
margin-top: 1.25rem;
69+
padding-top: 1rem;
70+
border-top: 1px solid var(--vp-c-divider);
71+
font-size: 0.75rem;
72+
line-height: 1.5;
73+
}
74+
75+
.doc-feedback-q {
76+
display: block;
77+
margin-bottom: 0.5rem;
78+
color: var(--vp-c-text-2);
79+
}
80+
81+
.doc-feedback-btn {
82+
display: block;
83+
width: 100%;
84+
box-sizing: border-box;
85+
padding: 0.4rem 0.6rem;
86+
border: 1px solid var(--vp-c-divider);
87+
border-radius: 8px;
88+
color: var(--vp-c-text-1);
89+
background: var(--vp-c-bg-soft);
90+
font-size: 0.75rem;
91+
font-weight: 500;
92+
text-align: center;
93+
cursor: pointer;
94+
text-decoration: none;
95+
transition: border-color 0.2s, color 0.2s;
96+
}
97+
98+
.doc-feedback-btn:hover {
99+
border-color: var(--vp-c-brand-1);
100+
color: var(--vp-c-brand-1);
101+
}
102+
</style>

docs/.vitepress/theme/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { h } from 'vue'
33
import type { Theme } from 'vitepress'
44
import DefaultTheme from 'vitepress/theme'
55
import AvailableSince from './components/AvailableSince.vue'
6+
import DocFeedback from './components/DocFeedback.vue'
67
import LandingLayout from './layouts/LandingLayout.vue'
78
import './style.css'
89

@@ -11,6 +12,9 @@ export default {
1112
Layout: () => {
1213
return h(DefaultTheme.Layout, null, {
1314
// https://vitepress.dev/guide/extending-default-theme#layout-slots
15+
// Right aside (toolbar), under the page outline. Doc pages only, not the
16+
// LandingLayout homepage.
17+
'aside-outline-after': () => h(DocFeedback),
1418
})
1519
},
1620
enhanceApp({ app, router, siteData }) {

0 commit comments

Comments
 (0)