jekyll-privatus-analytics 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +397 -0
- data/lib/jekyll-privatus-analytics.rb +17 -0
- data/lib/jekyll_privatus_analytics/arguments.rb +43 -0
- data/lib/jekyll_privatus_analytics/attributes.rb +61 -0
- data/lib/jekyll_privatus_analytics/configuration.rb +62 -0
- data/lib/jekyll_privatus_analytics/injector.rb +42 -0
- data/lib/jekyll_privatus_analytics/liquid_tags.rb +56 -0
- data/lib/jekyll_privatus_analytics/page_props.rb +40 -0
- data/lib/jekyll_privatus_analytics/tag.rb +122 -0
- data/lib/jekyll_privatus_analytics/version.rb +5 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6b60c001e65be88754b3059dbd76bcff86a12294dc2342e312e510bcf2f9c4c3
|
|
4
|
+
data.tar.gz: b64e4965df21ebf2db19513b66c7a9bcf551ae11731eac16b05652d36dbc713a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e2e06265a7859a5f30e655c7ca92def88a0fc45d94a8536270696eedce07f4085d8ebe1baf62e948ccfbfe09d803bd07e88636d1c23ac23b8f45c16c3fb63376
|
|
7
|
+
data.tar.gz: c6ec30b675124db9f75bd994d26c425d560a292db796ec839655d38ed5c0c528bf7e2f56f5d8f93df7f6dcfd95be23d28a48c2f7433d0710344d0e2bf1499f2a
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 - 2026-10-25
|
|
4
|
+
|
|
5
|
+
- Initial release.
|
|
6
|
+
- `{% privatus_analytics %}` Liquid tag, with per-call settings.
|
|
7
|
+
- `{% privatus_event %}` for custom events and `{% privatus_click %}` for click map names.
|
|
8
|
+
- Optional `auto_inject` that adds the tag to the head of every page and document.
|
|
9
|
+
- Settings under `privatus_analytics` in _config.yml. The tag is on only when `JEKYLL_ENV` is
|
|
10
|
+
`production`, unless `enabled` is set.
|
|
11
|
+
- Page props from `props` in _config.yml, merged with `privatus_props` from front matter.
|
|
12
|
+
- With `queue_stub: true`, `{% privatus_analytics %}` now prints the queue stub and nothing else when the
|
|
13
|
+
tag is off (development, `enabled: false` or no site id), so `privatus.track` calls never fail. Before, it
|
|
14
|
+
printed nothing.
|
|
15
|
+
- With `queue_stub: true`, automatic injection (`auto_inject: true`) now also adds the queue stub alone to
|
|
16
|
+
every page when the tag is off (no tracker, no page props script). A page that already prints the stub with
|
|
17
|
+
`{% privatus_analytics %}` does not get a second one. With the queue stub off nothing is added, as before.
|
|
18
|
+
- `example/`: a tiny site for the Privatus conformance tests, built with the plugin and served by nginx.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Two Phase LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# Privatus Analytics for Jekyll
|
|
2
|
+
|
|
3
|
+
A [Jekyll](https://jekyllrb.com) plugin that adds the cookieless
|
|
4
|
+
[Privatus Analytics](https://privatusanalytics.com) tracking script to your
|
|
5
|
+
site with a Liquid tag, or adds it to every page for you. It also has Liquid
|
|
6
|
+
tags for custom events and click maps.
|
|
7
|
+
|
|
8
|
+
Privatus Analytics counts visits, sources, pages and conversions without
|
|
9
|
+
cookies, local storage or fingerprinting, so analytics does not need a consent
|
|
10
|
+
banner by itself. Check with your own counsel for your country.
|
|
11
|
+
|
|
12
|
+
The plugin only prints the browser script and HTML attributes. It never sends
|
|
13
|
+
events from your computer or build server and sets no cookies. You need a
|
|
14
|
+
Privatus Analytics account to see reports, and there is a free plan. Sign up at
|
|
15
|
+
https://privatusanalytics.com.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
* Ruby 3.1 or later.
|
|
20
|
+
* Jekyll 3.10 to 4.x.
|
|
21
|
+
* A Privatus Analytics site id (it starts with `pa_`). You find it under
|
|
22
|
+
Site settings, Tracking.
|
|
23
|
+
|
|
24
|
+
### A note on GitHub Pages
|
|
25
|
+
|
|
26
|
+
The classic GitHub Pages build (the one that runs when you pick a branch under
|
|
27
|
+
Settings, Pages, with no workflow of your own) only runs a short, fixed list of
|
|
28
|
+
plugins. This plugin is not on that list, so **it does not run in classic
|
|
29
|
+
GitHub Pages builds**. Your site still builds, but without the tag.
|
|
30
|
+
|
|
31
|
+
You can still host on GitHub Pages. Build the site with GitHub Actions instead:
|
|
32
|
+
in your repository go to Settings, Pages, and set Source to "GitHub Actions",
|
|
33
|
+
then use the Jekyll starter workflow GitHub offers there. With Actions, any
|
|
34
|
+
plugin in your `Gemfile` runs. The starter workflow already sets
|
|
35
|
+
`JEKYLL_ENV=production`, which this plugin needs (see below).
|
|
36
|
+
|
|
37
|
+
If you must stay on the classic build, paste the plain script tag from your
|
|
38
|
+
Privatus site settings into your layout's `<head>` instead.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Add the gem to the plugins group of your `Gemfile`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
group :jekyll_plugins do
|
|
46
|
+
gem "jekyll-privatus-analytics"
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then run `bundle install`. Gems in the `jekyll_plugins` group load by
|
|
51
|
+
themselves. If you do not use that group, list the plugin in `_config.yml`
|
|
52
|
+
instead:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
plugins:
|
|
56
|
+
- jekyll-privatus-analytics
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For the full setup guide and every tracker option, see the [Privatus Analytics
|
|
60
|
+
docs](https://privatusanalytics.com/docs).
|
|
61
|
+
|
|
62
|
+
Set your site id in `_config.yml`:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
privatus_analytics:
|
|
66
|
+
site_id: pa_YOURSITEID
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
or with an environment variable on your build machine:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
PRIVATUS_SITE_ID=pa_YOURSITEID
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Add the tag
|
|
76
|
+
|
|
77
|
+
Put the tag in the `<head>` of your layout, for example `_layouts/default.html`
|
|
78
|
+
(or `_includes/head.html` in many themes):
|
|
79
|
+
|
|
80
|
+
```liquid
|
|
81
|
+
<head>
|
|
82
|
+
<title>{{ page.title }}</title>
|
|
83
|
+
{% privatus_analytics %}
|
|
84
|
+
</head>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When you build with `JEKYLL_ENV=production` it prints:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID"></script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
JEKYLL_ENV=production bundle exec jekyll build
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The tag prints nothing when there is no site id, and nothing outside the
|
|
98
|
+
production environment. Plain `jekyll serve` and `jekyll build` run in the
|
|
99
|
+
`development` environment, so your own visits while you write are never
|
|
100
|
+
counted. (With `queue_stub: true` it still adds the small queue stub, see
|
|
101
|
+
below.)
|
|
102
|
+
|
|
103
|
+
### Or let the plugin add it
|
|
104
|
+
|
|
105
|
+
If you would rather not touch your layouts (for example with a theme from a
|
|
106
|
+
gem), switch on automatic injection:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
privatus_analytics:
|
|
110
|
+
site_id: pa_YOURSITEID
|
|
111
|
+
auto_inject: true
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
After each page and post is rendered, the plugin adds the tag just before
|
|
115
|
+
`</head>`. It skips files that are not HTML, pages without a `<head>`, and
|
|
116
|
+
pages that already have the tag in their head. Static files (files without
|
|
117
|
+
front matter) are copied as they are, so they do not get the tag.
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
Every setting is optional except the site id.
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
privatus_analytics:
|
|
125
|
+
site_id: pa_YOURSITEID # or the PRIVATUS_SITE_ID environment variable
|
|
126
|
+
script_host: https://privatusstats.com # or PRIVATUS_SCRIPT_HOST
|
|
127
|
+
modules: [engage, auto, vitals, clicks, ask]
|
|
128
|
+
excluded_paths: [/drafts/*]
|
|
129
|
+
domains: [www.example.com]
|
|
130
|
+
spa: auto # auto, history, hash or off
|
|
131
|
+
manual: false # true turns off the automatic pageview
|
|
132
|
+
enabled_environments: [production]
|
|
133
|
+
enabled: # true or false forces the tag on or off
|
|
134
|
+
auto_inject: false
|
|
135
|
+
extra_attributes:
|
|
136
|
+
data-mask: /orders/*->/orders/:id
|
|
137
|
+
props:
|
|
138
|
+
site_section: docs
|
|
139
|
+
queue_stub: false
|
|
140
|
+
nonce:
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Setting | Default | What it does |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `site_id` | `PRIVATUS_SITE_ID` | The `data-site` attribute. Nothing prints without it |
|
|
146
|
+
| `script_host` | `https://privatusstats.com` | Where `/js/pa.js` is served. Change it only if you host your own copy |
|
|
147
|
+
| `modules` | tracker default (`engage`) | The `data-modules` attribute. Setting it replaces the default, so keep `engage` for time on page and scroll depth. `[]` loads none |
|
|
148
|
+
| `excluded_paths` | none | The `data-exclude` attribute. Path patterns the tracker never counts |
|
|
149
|
+
| `domains` | all hosts | The `data-domains` attribute. Count only these hostnames |
|
|
150
|
+
| `spa` | `auto` | The `data-spa` attribute |
|
|
151
|
+
| `manual` | `false` | The `data-manual` attribute |
|
|
152
|
+
| `enabled_environments` | `[production]` | Values of `JEKYLL_ENV` that get the tag |
|
|
153
|
+
| `enabled` | not set | `true` or `false` forces the tag on or off in every environment |
|
|
154
|
+
| `auto_inject` | `false` | Adds the tag to the head of every page for you |
|
|
155
|
+
| `extra_attributes` | `{}` | Any other `data-*` attribute |
|
|
156
|
+
| `props` | `{}` | Page props sent with every pageview and event. See below |
|
|
157
|
+
| `queue_stub` | `false` | Prints a small script first, so `privatus.track` calls made before the tracker loads are kept and sent later |
|
|
158
|
+
| `nonce` | none | A Content Security Policy nonce for the tags. Static sites rarely have one |
|
|
159
|
+
|
|
160
|
+
Each tracker attribute is explained in the [script attributes
|
|
161
|
+
reference](https://privatusanalytics.com/docs/tracker/attributes), and the
|
|
162
|
+
modules in [Modules](https://privatusanalytics.com/docs/tracker/modules).
|
|
163
|
+
|
|
164
|
+
You can also override a setting for one layout or page. Lists are written with
|
|
165
|
+
commas:
|
|
166
|
+
|
|
167
|
+
```liquid
|
|
168
|
+
{% privatus_analytics modules="engage,auto" excluded_paths="/preview/*" queue_stub=true %}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Development and test
|
|
172
|
+
|
|
173
|
+
Builds that are not `JEKYLL_ENV=production` do not get the tag, so your own
|
|
174
|
+
clicking around never reaches your reports. To see the tag while you build,
|
|
175
|
+
set `enabled: true` (or use a separate config file, like
|
|
176
|
+
`jekyll serve --config _config.yml,_config.dev.yml`). The tracker also ignores
|
|
177
|
+
`localhost` unless you pass `data-allow-local` or `data-debug` (see [Debug
|
|
178
|
+
mode](https://privatusanalytics.com/docs/tracker/debug)):
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
privatus_analytics:
|
|
182
|
+
enabled: true
|
|
183
|
+
extra_attributes:
|
|
184
|
+
data-debug: "true"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Leaving people out
|
|
188
|
+
|
|
189
|
+
Use `excluded_paths` for pages you do not want counted, like drafts. The
|
|
190
|
+
`domains` setting makes sure only your real site is counted, not previews or
|
|
191
|
+
copies on other hosts.
|
|
192
|
+
|
|
193
|
+
## Product analytics: see which features people use
|
|
194
|
+
|
|
195
|
+
Privatus can show which buttons, links and forms people really use. You add a
|
|
196
|
+
few attributes to your HTML. The tracker reads them in the browser and sends
|
|
197
|
+
the events. Your build still sends nothing.
|
|
198
|
+
|
|
199
|
+
### Name events well
|
|
200
|
+
|
|
201
|
+
Use a short "Noun verb" name, like `Report exported` or `Newsletter joined`.
|
|
202
|
+
Put the details in props, not in the name. So write `Report exported` with
|
|
203
|
+
`format="csv"`, not `Report exported as CSV`. Then all exports add up in one
|
|
204
|
+
place, and you can split them by format.
|
|
205
|
+
|
|
206
|
+
### Events on buttons, links and forms
|
|
207
|
+
|
|
208
|
+
`{% privatus_event %}` prints the attributes inside a tag you write yourself:
|
|
209
|
+
|
|
210
|
+
```liquid
|
|
211
|
+
<button {% privatus_event "Report exported" format="csv" source="toolbar" %}>Export</button>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
It prints:
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<button data-privatus-event="Report exported" data-privatus-prop-format="csv" data-privatus-prop-source="toolbar">Export</button>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Values in quotes are text. `true`, `false` and numbers stay as they are.
|
|
221
|
+
Anything else is read as a Liquid variable, so you can use page data:
|
|
222
|
+
|
|
223
|
+
```liquid
|
|
224
|
+
<a href="{{ page.pdf }}" {% privatus_event "Download started" format="pdf" guide=page.slug %}>Download</a>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
On a form, the event is sent when the form is submitted:
|
|
228
|
+
|
|
229
|
+
```liquid
|
|
230
|
+
<form action="https://example.com/join" {% privatus_event "Newsletter joined" list="weekly" %}>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The rules:
|
|
234
|
+
|
|
235
|
+
* The name is trimmed and cut to 120 characters. An empty name prints nothing.
|
|
236
|
+
* Prop keys are made lowercase. Anything that is not a letter, a digit or `_`
|
|
237
|
+
becomes `_`, so `report-type` becomes `report_type`. Keys are cut to 64
|
|
238
|
+
characters.
|
|
239
|
+
* At most 30 props. Values become text: `true`, `false`, numbers as written,
|
|
240
|
+
lists joined with commas. Empty (`nil`) values are left out. Values are cut
|
|
241
|
+
to 500 characters.
|
|
242
|
+
* Everything is escaped for HTML.
|
|
243
|
+
* The attributes print in every environment, even when the tag is off. They do
|
|
244
|
+
nothing without the tracker, so your pages look the same in development and
|
|
245
|
+
production.
|
|
246
|
+
|
|
247
|
+
The tags work in HTML files and in Markdown pages, as long as they sit inside
|
|
248
|
+
HTML you write in the page.
|
|
249
|
+
|
|
250
|
+
### Click maps
|
|
251
|
+
|
|
252
|
+
A click map shows where people click on a page. Give the important parts a
|
|
253
|
+
name so they stay easy to find, even when the page changes:
|
|
254
|
+
|
|
255
|
+
```liquid
|
|
256
|
+
<a href="/pricing" {% privatus_click "pricing.cta" %}>See plans</a>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
A name may use letters, digits and `_ . : -`, up to 100 characters. Anything
|
|
260
|
+
else prints nothing, and in development the plugin writes a warning to the
|
|
261
|
+
build log.
|
|
262
|
+
|
|
263
|
+
### Page props: section, version and other groups
|
|
264
|
+
|
|
265
|
+
Page props are sent with every pageview and event on the page. Use them to
|
|
266
|
+
split your reports by section, product, docs version or language.
|
|
267
|
+
|
|
268
|
+
Set them for the whole site in `_config.yml`:
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
privatus_analytics:
|
|
272
|
+
props:
|
|
273
|
+
site_section: docs
|
|
274
|
+
docs_version: "2.4"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
and change them for one page in its front matter. They are merged over the site
|
|
278
|
+
props, and an empty value removes one:
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
---
|
|
282
|
+
title: Getting started
|
|
283
|
+
privatus_props:
|
|
284
|
+
section: tutorials
|
|
285
|
+
docs_version:
|
|
286
|
+
---
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
With Jekyll's front matter defaults you can set them for a whole folder:
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
defaults:
|
|
293
|
+
- scope:
|
|
294
|
+
path: blog
|
|
295
|
+
values:
|
|
296
|
+
privatus_props:
|
|
297
|
+
section: blog
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The tag then prints a small script before the tracker and points the tracker
|
|
301
|
+
at it:
|
|
302
|
+
|
|
303
|
+
```html
|
|
304
|
+
<script>window.privatusPageProps=function(){return {"site_section":"docs","section":"tutorials"}}</script>
|
|
305
|
+
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID" data-props="privatusPageProps"></script>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Keys follow the same rules as event props. Numbers and true or false stay as
|
|
309
|
+
they are, and lists stay lists (up to 30 items). The props are escaped so they
|
|
310
|
+
are safe inside a script.
|
|
311
|
+
|
|
312
|
+
**Never put names, emails, user ids or anything that points to one person in
|
|
313
|
+
props.** Privatus counts visitors without any id. Put groups (like the section
|
|
314
|
+
or the plan), not people. The server also removes emails and long ids it
|
|
315
|
+
finds.
|
|
316
|
+
|
|
317
|
+
### Track from your own JavaScript
|
|
318
|
+
|
|
319
|
+
For things that are not a click or a submit, call the tracker from your
|
|
320
|
+
JavaScript, as the [JavaScript API
|
|
321
|
+
docs](https://privatusanalytics.com/docs/tracker/javascript-api) explain:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
privatus.track("Feature used", { feature: "search" })
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
If your code may run before the tracker has loaded, switch on the queue stub.
|
|
328
|
+
It prints a tiny script before the tracker that keeps your calls in a list and
|
|
329
|
+
sends them once the tracker is ready:
|
|
330
|
+
|
|
331
|
+
```yaml
|
|
332
|
+
privatus_analytics:
|
|
333
|
+
queue_stub: true
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
or for one layout: `{% privatus_analytics queue_stub=true %}`. The stub is
|
|
337
|
+
added even when the tag is off (in development, with `enabled: false` or with
|
|
338
|
+
no site id), so your `privatus.track` calls never fail in development: they
|
|
339
|
+
wait in the list and are never sent. Then only the stub is added, with no
|
|
340
|
+
tracker and no page props. This works with the `{% privatus_analytics %}` tag
|
|
341
|
+
and with automatic injection (`auto_inject: true`). A page that uses both
|
|
342
|
+
still gets the stub only once. With the queue stub off, nothing is added when
|
|
343
|
+
the tag is off.
|
|
344
|
+
|
|
345
|
+
### See the results
|
|
346
|
+
|
|
347
|
+
* [Funnels](https://privatusanalytics.com/docs/features/funnels): how many
|
|
348
|
+
people go from one step to the next.
|
|
349
|
+
* [Click maps](https://privatusanalytics.com/docs/features/click-maps): where
|
|
350
|
+
people click.
|
|
351
|
+
* [Goals](https://privatusanalytics.com/docs/features/goals): turn an event
|
|
352
|
+
into a goal to count conversions.
|
|
353
|
+
* [Events](https://privatusanalytics.com/docs/events): every custom event and
|
|
354
|
+
its props.
|
|
355
|
+
|
|
356
|
+
## Privacy
|
|
357
|
+
|
|
358
|
+
The script sets no cookies and stores nothing in the browser. The event, click
|
|
359
|
+
and props tags only print HTML attributes and a small script. Keep people out
|
|
360
|
+
of your props and event props: no names, emails or user ids. See the [privacy
|
|
361
|
+
documentation](https://privatusanalytics.com/docs/privacy) for what is
|
|
362
|
+
collected.
|
|
363
|
+
|
|
364
|
+
If you have a privacy policy, it should say which tools handle your visitors' data. You can paste this:
|
|
365
|
+
|
|
366
|
+
> This website uses [Privatus Analytics](https://privatusanalytics.com/privacy-analytics) to count visits. It
|
|
367
|
+
> does not use cookies and does not follow you from one website to another. Your IP address is used for a moment
|
|
368
|
+
> to count visits and is never stored.
|
|
369
|
+
|
|
370
|
+
Change it to fit your site. The [privacy policy generator](https://privatusanalytics.com/tools/privacy-policy-generator)
|
|
371
|
+
makes a longer version with your site's name in it.
|
|
372
|
+
|
|
373
|
+
## Development
|
|
374
|
+
|
|
375
|
+
The tests run in Docker so your local Ruby does not matter:
|
|
376
|
+
|
|
377
|
+
```sh
|
|
378
|
+
./run-docker.sh
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
It runs the Minitest suite (with real builds of tiny Jekyll sites), RuboCop,
|
|
382
|
+
and `gem build`.
|
|
383
|
+
|
|
384
|
+
The `example/` folder holds a tiny site for the [conformance
|
|
385
|
+
tests](https://github.com/Privatus-Analytics/conformance), which open it in a
|
|
386
|
+
real browser with the real tracker. Run them from a copy of that repo with
|
|
387
|
+
`./run.sh ../jekyll`.
|
|
388
|
+
|
|
389
|
+
## Contributing
|
|
390
|
+
|
|
391
|
+
Bug reports and pull requests are welcome at
|
|
392
|
+
https://github.com/Privatus-Analytics/jekyll.
|
|
393
|
+
|
|
394
|
+
## License
|
|
395
|
+
|
|
396
|
+
MIT. See [LICENSE.txt](LICENSE.txt). Privatus Analytics is a product of Two
|
|
397
|
+
Phase LLC, Wyoming, USA.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jekyll"
|
|
4
|
+
require "jekyll_privatus_analytics/version"
|
|
5
|
+
require "jekyll_privatus_analytics/attributes"
|
|
6
|
+
require "jekyll_privatus_analytics/page_props"
|
|
7
|
+
require "jekyll_privatus_analytics/configuration"
|
|
8
|
+
require "jekyll_privatus_analytics/tag"
|
|
9
|
+
require "jekyll_privatus_analytics/arguments"
|
|
10
|
+
require "jekyll_privatus_analytics/liquid_tags"
|
|
11
|
+
require "jekyll_privatus_analytics/injector"
|
|
12
|
+
|
|
13
|
+
# Adds the cookieless Privatus Analytics tracking script to Jekyll sites.
|
|
14
|
+
#
|
|
15
|
+
# The plugin only prints the browser script and HTML attributes. It never sends events from the build.
|
|
16
|
+
module JekyllPrivatusAnalytics
|
|
17
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllPrivatusAnalytics
|
|
4
|
+
# Reads Liquid tag arguments like: "Export CSV" format="csv" rows=page.rows beta=true
|
|
5
|
+
#
|
|
6
|
+
# Quoted text stays text. true, false, nil and numbers become those values.
|
|
7
|
+
# Anything else is looked up as a Liquid variable, like page.title.
|
|
8
|
+
class Arguments
|
|
9
|
+
TOKEN = /(?:([A-Za-z0-9_-]+)\s*=\s*)?("(?:[^"]*)"|'(?:[^']*)'|[^\s"']+)/
|
|
10
|
+
NUMBER = /\A-?\d+(\.\d+)?\z/
|
|
11
|
+
LITERALS = { "true" => true, "false" => false, "nil" => nil, "null" => nil }.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :positional, :named
|
|
14
|
+
|
|
15
|
+
def initialize(markup)
|
|
16
|
+
@positional = []
|
|
17
|
+
@named = []
|
|
18
|
+
markup.to_s.scan(TOKEN) do |key, value|
|
|
19
|
+
key ? @named << [key, value] : @positional << value
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The first unnamed argument, resolved.
|
|
24
|
+
def first(context)
|
|
25
|
+
positional.empty? ? nil : resolve(positional.first, context)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The named arguments as a hash, resolved.
|
|
29
|
+
def options(context)
|
|
30
|
+
named.to_h { |key, value| [key, resolve(value, context)] }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def resolve(token, context)
|
|
36
|
+
return token[1..-2] if token.start_with?('"', "'")
|
|
37
|
+
return LITERALS[token] if LITERALS.key?(token)
|
|
38
|
+
return (token.include?(".") ? token.to_f : token.to_i) if token.match?(NUMBER)
|
|
39
|
+
|
|
40
|
+
context[token]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module JekyllPrivatusAnalytics
|
|
6
|
+
# Builds the data attributes the tracker reads for custom events and click maps.
|
|
7
|
+
module Attributes
|
|
8
|
+
MAX_NAME = 120
|
|
9
|
+
MAX_KEY = 64
|
|
10
|
+
MAX_PROPS = 30
|
|
11
|
+
MAX_VALUE = 500
|
|
12
|
+
CLICK_NAME = /\A[A-Za-z0-9_.:-]{1,100}\z/
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# { "data-privatus-event" => name, "data-privatus-prop-<key>" => value, ... }, or {} for an empty name.
|
|
17
|
+
def event(name, props = {})
|
|
18
|
+
name = name.to_s.strip[0, MAX_NAME].to_s.strip
|
|
19
|
+
return {} if name.empty?
|
|
20
|
+
|
|
21
|
+
attrs = { "data-privatus-event" => name }
|
|
22
|
+
normalize(props).each { |key, value| attrs["data-privatus-prop-#{key}"] = string_value(value) }
|
|
23
|
+
attrs
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# { "data-privatus-click" => name }, or {} when the name is not valid.
|
|
27
|
+
def click(name)
|
|
28
|
+
valid_click?(name) ? { "data-privatus-click" => name.to_s } : {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Whether a click map name is valid: letters, digits, _ . : - and at most 100 characters.
|
|
32
|
+
def valid_click?(name)
|
|
33
|
+
name.to_s.match?(CLICK_NAME)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Turns an attribute hash into HTML, with every value escaped.
|
|
37
|
+
def to_html(attrs)
|
|
38
|
+
attrs.map { |name, value| %(#{name}="#{CGI.escapeHTML(value.to_s)}") }.join(" ")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Lowercases keys, swaps anything outside a-z 0-9 _ for _, cuts them to 64 characters,
|
|
42
|
+
# drops nil values and keeps the first 30 props.
|
|
43
|
+
def normalize(props)
|
|
44
|
+
clean = {}
|
|
45
|
+
(props || {}).to_h.each do |key, value|
|
|
46
|
+
key = normalize_key(key)
|
|
47
|
+
clean[key] = value unless key.empty? || value.nil?
|
|
48
|
+
end
|
|
49
|
+
clean.first(MAX_PROPS).to_h
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def normalize_key(key)
|
|
53
|
+
key.to_s.downcase.gsub(/[^a-z0-9_]/, "_")[0, MAX_KEY]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def string_value(value)
|
|
57
|
+
text = value.is_a?(Array) ? value.compact.join(",") : value.to_s
|
|
58
|
+
text[0, MAX_VALUE]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllPrivatusAnalytics
|
|
4
|
+
# Reads the `privatus_analytics:` settings from _config.yml.
|
|
5
|
+
class Configuration
|
|
6
|
+
DEFAULT_HOST = "https://privatusstats.com"
|
|
7
|
+
SPA_MODES = %w[auto history hash off].freeze
|
|
8
|
+
DEFAULT_ENVIRONMENTS = %w[production].freeze
|
|
9
|
+
|
|
10
|
+
def self.for_site(site)
|
|
11
|
+
new(site.config["privatus_analytics"])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(settings = {})
|
|
15
|
+
@settings = settings.is_a?(Hash) ? settings.transform_keys(&:to_s) : {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# A raw setting, for example self["modules"].
|
|
19
|
+
def [](name)
|
|
20
|
+
@settings[name.to_s]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The site id (starts with pa_). Falls back to ENV["PRIVATUS_SITE_ID"].
|
|
24
|
+
def site_id
|
|
25
|
+
value = self["site_id"].to_s.strip
|
|
26
|
+
value = ENV.fetch("PRIVATUS_SITE_ID", "").strip if value.empty?
|
|
27
|
+
value.empty? ? nil : value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Origin that serves /js/pa.js. Falls back to ENV["PRIVATUS_SCRIPT_HOST"].
|
|
31
|
+
def script_host
|
|
32
|
+
value = self["script_host"].to_s.strip
|
|
33
|
+
value = ENV.fetch("PRIVATUS_SCRIPT_HOST", "").strip if value.empty?
|
|
34
|
+
value = DEFAULT_HOST if value.empty?
|
|
35
|
+
value.chomp("/")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# `enabled: true` or `false` forces it. Otherwise on when JEKYLL_ENV is in enabled_environments.
|
|
39
|
+
def enabled?(environment = Jekyll.env)
|
|
40
|
+
enabled = self["enabled"]
|
|
41
|
+
return enabled == true unless enabled.nil?
|
|
42
|
+
|
|
43
|
+
environments = self["enabled_environments"]
|
|
44
|
+
environments = DEFAULT_ENVIRONMENTS if environments.nil?
|
|
45
|
+
Array(environments).map(&:to_s).include?(environment.to_s)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def auto_inject?
|
|
49
|
+
self["auto_inject"] == true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def extra_attributes
|
|
53
|
+
value = self["extra_attributes"]
|
|
54
|
+
value.is_a?(Hash) ? value : {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def props
|
|
58
|
+
value = self["props"]
|
|
59
|
+
value.is_a?(Hash) ? value : {}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllPrivatusAnalytics
|
|
4
|
+
# Adds the tag just before </head> in every HTML page and document.
|
|
5
|
+
# Off unless `auto_inject: true` is set under privatus_analytics in _config.yml.
|
|
6
|
+
module Injector
|
|
7
|
+
HEAD_CLOSE = %r{</head>}i
|
|
8
|
+
# A real queue stub script (with or without a nonce). Shown as text in a code block,
|
|
9
|
+
# the "<script" is escaped, so it does not match.
|
|
10
|
+
STUB_SCRIPT = /<script\b[^>]*>#{Regexp.escape(Tag::QUEUE_STUB[0, 40])}/
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def call(item)
|
|
15
|
+
configuration = Configuration.for_site(item.site)
|
|
16
|
+
return unless configuration.auto_inject? && html?(item)
|
|
17
|
+
|
|
18
|
+
tag = Tag.new(configuration, "page_props" => item.data["privatus_props"])
|
|
19
|
+
# When the tag is off, to_s is the queue stub alone (queue_stub on) or "", and props are not used.
|
|
20
|
+
item.output = inject(item.output, tag.to_s)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def html?(item)
|
|
24
|
+
item.output.is_a?(String) && item.output_ext == ".html"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Only the head is checked for the tag, so a page that shows the snippet as text still gets it.
|
|
28
|
+
# The queue stub alone (the tag is off) is skipped when the page already prints a stub anywhere,
|
|
29
|
+
# for example from {% privatus_analytics %}, so a page never gets two.
|
|
30
|
+
def inject(html, tag)
|
|
31
|
+
match = HEAD_CLOSE.match(html)
|
|
32
|
+
return html if tag.empty? || match.nil? || match.pre_match.include?(Tag::MARKER)
|
|
33
|
+
return html if !tag.include?(Tag::MARKER) && html.match?(STUB_SCRIPT)
|
|
34
|
+
|
|
35
|
+
"#{match.pre_match}#{tag}\n#{match}#{match.post_match}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Jekyll::Hooks.register [:pages, :documents], :post_render do |item|
|
|
41
|
+
JekyllPrivatusAnalytics::Injector.call(item)
|
|
42
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllPrivatusAnalytics
|
|
4
|
+
# {% privatus_analytics %} prints the script tag. Named arguments override settings for this call:
|
|
5
|
+
# {% privatus_analytics modules="engage,auto" queue_stub=true %}
|
|
6
|
+
class AnalyticsTag < Liquid::Tag
|
|
7
|
+
def initialize(tag_name, markup, parse_context)
|
|
8
|
+
super
|
|
9
|
+
@arguments = Arguments.new(markup)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def render(context)
|
|
13
|
+
site = context.registers[:site]
|
|
14
|
+
page = context.registers[:page] || {}
|
|
15
|
+
overrides = @arguments.options(context)
|
|
16
|
+
overrides["page_props"] = page["privatus_props"]
|
|
17
|
+
Tag.new(Configuration.for_site(site), overrides).to_s
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# {% privatus_event "Export CSV" format="csv" source="toolbar" %} prints the event data attributes.
|
|
22
|
+
# They print in every environment. They do nothing without the tracker.
|
|
23
|
+
class EventTag < Liquid::Tag
|
|
24
|
+
def initialize(tag_name, markup, parse_context)
|
|
25
|
+
super
|
|
26
|
+
@arguments = Arguments.new(markup)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def render(context)
|
|
30
|
+
Attributes.to_html(Attributes.event(@arguments.first(context), @arguments.options(context)))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# {% privatus_click "pricing.cta" %} prints the click map name attribute, or nothing when not valid.
|
|
35
|
+
class ClickTag < Liquid::Tag
|
|
36
|
+
def initialize(tag_name, markup, parse_context)
|
|
37
|
+
super
|
|
38
|
+
@arguments = Arguments.new(markup)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def render(context)
|
|
42
|
+
name = @arguments.first(context)
|
|
43
|
+
return Attributes.to_html(Attributes.click(name)) if Attributes.valid_click?(name)
|
|
44
|
+
|
|
45
|
+
unless Jekyll.env == "production"
|
|
46
|
+
Jekyll.logger.warn("Privatus:", "click map name #{name.inspect} is not valid. Use letters, digits " \
|
|
47
|
+
"and _ . : - only, at most 100 characters. Nothing was printed.")
|
|
48
|
+
end
|
|
49
|
+
""
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Liquid::Template.register_tag("privatus_analytics", JekyllPrivatusAnalytics::AnalyticsTag)
|
|
55
|
+
Liquid::Template.register_tag("privatus_event", JekyllPrivatusAnalytics::EventTag)
|
|
56
|
+
Liquid::Template.register_tag("privatus_click", JekyllPrivatusAnalytics::ClickTag)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module JekyllPrivatusAnalytics
|
|
6
|
+
# Turns page props (docs version, section and so on) into JSON that is safe inside a <script>.
|
|
7
|
+
# Keys follow the same rules as event props. Numbers, booleans and lists keep their JSON types.
|
|
8
|
+
module PageProps
|
|
9
|
+
MAX_ITEMS = 30
|
|
10
|
+
UNSAFE = /[<>&
]/
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# A JSON object string, or nil when there are no props to send.
|
|
15
|
+
def json(props)
|
|
16
|
+
clean = Attributes.normalize(props).filter_map do |key, value|
|
|
17
|
+
value = json_value(value)
|
|
18
|
+
[key, value] unless value.nil?
|
|
19
|
+
end
|
|
20
|
+
return nil if clean.empty?
|
|
21
|
+
|
|
22
|
+
JSON.generate(clean.to_h).gsub(UNSAFE) { |char| format("\\u%04x", char.ord) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def json_value(value)
|
|
26
|
+
return value.compact.first(MAX_ITEMS).filter_map { |item| scalar(item) } if value.is_a?(Array)
|
|
27
|
+
|
|
28
|
+
scalar(value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def scalar(value)
|
|
32
|
+
case value
|
|
33
|
+
when nil then nil
|
|
34
|
+
when true, false, Integer then value
|
|
35
|
+
when Float then value.finite? ? value : nil
|
|
36
|
+
else value.to_s.scrub[0, Attributes::MAX_VALUE]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module JekyllPrivatusAnalytics
|
|
6
|
+
# Builds the script tag. When the tag is off it prints only the queue stub (with queue_stub), or nothing.
|
|
7
|
+
#
|
|
8
|
+
# Before the tracker tag it can also print the queue stub (queue_stub) and the
|
|
9
|
+
# page props script (props), each with the nonce when there is one.
|
|
10
|
+
class Tag
|
|
11
|
+
LIST_OPTIONS = { "modules" => "data-modules", "excluded_paths" => "data-exclude",
|
|
12
|
+
"domains" => "data-domains" }.freeze
|
|
13
|
+
PROPS_FUNCTION = "privatusPageProps"
|
|
14
|
+
MARKER = "/js/pa.js"
|
|
15
|
+
QUEUE_STUB = "window.privatus=window.privatus||{q:[]};['track','pageview','props','optOut','optIn','ready']" \
|
|
16
|
+
".forEach(function(m){window.privatus[m]=window.privatus[m]||function(){window.privatus.q.push(" \
|
|
17
|
+
"[m,Array.prototype.slice.call(arguments)])}})"
|
|
18
|
+
|
|
19
|
+
# overrides: any setting by name, plus "page_props" (the page's privatus_props front matter).
|
|
20
|
+
# Props are merged in this order: _config.yml, front matter, the "props" override. nil removes one.
|
|
21
|
+
def initialize(configuration, overrides = {})
|
|
22
|
+
@configuration = configuration
|
|
23
|
+
@overrides = (overrides || {}).transform_keys(&:to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render?
|
|
27
|
+
!site_id.nil? && enabled?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The tag is off (disabled, or no site id): only the queue stub when queue_stub is on, else "".
|
|
31
|
+
# Then privatus.track calls in your own JavaScript queue up and never fail. Props are not used.
|
|
32
|
+
def to_s
|
|
33
|
+
return off_html unless render?
|
|
34
|
+
|
|
35
|
+
props_json = PageProps.json(page_props)
|
|
36
|
+
html = []
|
|
37
|
+
html << inline_script(QUEUE_STUB) if option("queue_stub") == true
|
|
38
|
+
html << inline_script("window.#{PROPS_FUNCTION}=function(){return #{props_json}}") if props_json
|
|
39
|
+
html << script_tag(props_json ? { "data-props" => PROPS_FUNCTION } : {})
|
|
40
|
+
html.join("\n")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def attributes
|
|
44
|
+
attrs = { "defer" => true, "src" => "#{script_host}#{MARKER}", "data-site" => site_id }
|
|
45
|
+
add_options(attrs)
|
|
46
|
+
@configuration.extra_attributes.each { |name, value| attrs[name.to_s] = value unless value.nil? }
|
|
47
|
+
attrs["nonce"] = nonce if nonce
|
|
48
|
+
attrs
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def page_props
|
|
52
|
+
[@configuration.props, @overrides["page_props"], @overrides["props"]].reduce({}) do |merged, layer|
|
|
53
|
+
merged.merge(keyed(layer))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def off_html
|
|
58
|
+
option("queue_stub") == true ? inline_script(QUEUE_STUB) : ""
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def site_id
|
|
64
|
+
value = @overrides["site_id"].to_s.strip
|
|
65
|
+
value.empty? ? @configuration.site_id : value
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def script_host
|
|
69
|
+
value = @overrides["script_host"].to_s.strip
|
|
70
|
+
value.empty? ? @configuration.script_host : value.chomp("/")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def enabled?
|
|
74
|
+
@overrides.key?("enabled") ? @overrides["enabled"] == true : @configuration.enabled?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def nonce
|
|
78
|
+
value = option("nonce").to_s
|
|
79
|
+
value.empty? ? nil : value
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def script_tag(more)
|
|
83
|
+
parts = attributes.merge(more).map do |name, value|
|
|
84
|
+
value == true ? name : %(#{name}="#{CGI.escapeHTML(value.to_s)}")
|
|
85
|
+
end
|
|
86
|
+
"<script #{parts.join(' ')}></script>"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def inline_script(code)
|
|
90
|
+
opening = nonce ? %(<script nonce="#{CGI.escapeHTML(nonce)}">) : "<script>"
|
|
91
|
+
"#{opening}#{code}</script>"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def keyed(props)
|
|
95
|
+
return {} unless props.is_a?(Hash)
|
|
96
|
+
|
|
97
|
+
props.transform_keys { |key| Attributes.normalize_key(key) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def add_options(attrs)
|
|
101
|
+
LIST_OPTIONS.each do |name, attribute|
|
|
102
|
+
values = list(option(name))
|
|
103
|
+
# nil keeps the tracker default. An empty modules list loads none.
|
|
104
|
+
attrs[attribute] = values.join(",") if values && (name == "modules" || !values.empty?)
|
|
105
|
+
end
|
|
106
|
+
spa = option("spa").to_s
|
|
107
|
+
attrs["data-spa"] = spa if Configuration::SPA_MODES.include?(spa) && spa != "auto"
|
|
108
|
+
attrs["data-manual"] = "true" if option("manual") == true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def option(name)
|
|
112
|
+
@overrides.key?(name) ? @overrides[name] : @configuration[name]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# nil stays nil. Anything else becomes a clean array of strings.
|
|
116
|
+
def list(value)
|
|
117
|
+
return nil if value.nil?
|
|
118
|
+
|
|
119
|
+
Array(value).flat_map { |item| item.to_s.split(",") }.map(&:strip).reject(&:empty?)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jekyll-privatus-analytics
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Two Phase LLC
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: jekyll
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.10'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '5'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '3.10'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5'
|
|
33
|
+
description: Liquid tags for the Privatus Analytics tracking script, custom events
|
|
34
|
+
and click maps, with optional automatic injection into every page. The script sets
|
|
35
|
+
no cookies, so analytics does not need a consent banner by itself.
|
|
36
|
+
email:
|
|
37
|
+
- james@privatusanalytics.com
|
|
38
|
+
executables: []
|
|
39
|
+
extensions: []
|
|
40
|
+
extra_rdoc_files: []
|
|
41
|
+
files:
|
|
42
|
+
- CHANGELOG.md
|
|
43
|
+
- LICENSE.txt
|
|
44
|
+
- README.md
|
|
45
|
+
- lib/jekyll-privatus-analytics.rb
|
|
46
|
+
- lib/jekyll_privatus_analytics/arguments.rb
|
|
47
|
+
- lib/jekyll_privatus_analytics/attributes.rb
|
|
48
|
+
- lib/jekyll_privatus_analytics/configuration.rb
|
|
49
|
+
- lib/jekyll_privatus_analytics/injector.rb
|
|
50
|
+
- lib/jekyll_privatus_analytics/liquid_tags.rb
|
|
51
|
+
- lib/jekyll_privatus_analytics/page_props.rb
|
|
52
|
+
- lib/jekyll_privatus_analytics/tag.rb
|
|
53
|
+
- lib/jekyll_privatus_analytics/version.rb
|
|
54
|
+
homepage: https://privatusanalytics.com
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
homepage_uri: https://privatusanalytics.com
|
|
59
|
+
source_code_uri: https://github.com/Privatus-Analytics/jekyll
|
|
60
|
+
changelog_uri: https://github.com/Privatus-Analytics/jekyll/blob/main/CHANGELOG.md
|
|
61
|
+
bug_tracker_uri: https://github.com/Privatus-Analytics/jekyll/issues
|
|
62
|
+
documentation_uri: https://privatusanalytics.com/docs
|
|
63
|
+
rubygems_mfa_required: 'true'
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '3.1'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 3.5.22
|
|
80
|
+
signing_key:
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Cookieless Privatus Analytics tracking script for Jekyll sites.
|
|
83
|
+
test_files: []
|