@eox/pages-theme-eox 0.5.5 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eox/pages-theme-eox",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Vitepress Theme with EOX branding",
6
6
  "main": "src/index.js",
package/src/Layout.vue CHANGED
@@ -107,6 +107,7 @@
107
107
  :href="withBase(item.link)"
108
108
  :target="item.target"
109
109
  :rel="item.rel"
110
+ @click="trackEvent(['CTA', 'Click', 'Nav', item.text])"
110
111
  >
111
112
  <span>{{ item.text }}</span>
112
113
  <i
@@ -172,6 +173,14 @@
172
173
  : 'border no-elevate white-text'
173
174
  "
174
175
  :style="i === 0 ? 'margin-left: 0 !important' : ''"
176
+ @click="
177
+ trackEvent([
178
+ 'CTA',
179
+ 'Click',
180
+ `Hero ${action.theme === 'brand' ? 'primary' : action.theme === 'secondary' ? 'secondary' : 'alt'}`,
181
+ action.text,
182
+ ])
183
+ "
175
184
  >
176
185
  <span>{{ action.text }}</span>
177
186
  <i v-if="action.theme !== 'alt'" class="mdi mdi-arrow-right"></i>
@@ -207,6 +216,7 @@
207
216
  :href="theme.nav.find((i) => i.link.includes('contact')).link"
208
217
  class="button small border no-margin"
209
218
  style="color: var(--on-surface)"
219
+ @click="trackEvent(['CTA', 'Click', 'Footer', action.text])"
210
220
  >{{ theme.nav.find((i) => i.link.includes("contact")).text }}</a
211
221
  >
212
222
  <p v-html="theme.footer.copyright"></p>
@@ -229,15 +239,32 @@
229
239
  <p class="bold">Legal</p>
230
240
  <p>
231
241
  <a
232
- href="https://eox.at/impressum"
242
+ :href="
243
+ theme.theme.brandConfig?.legal?.about ||
244
+ 'https://eox.at/impressum'
245
+ "
246
+ target="_blank"
247
+ class="link"
248
+ >About</a
249
+ >
250
+ </p>
251
+ <p>
252
+ <a
253
+ :href="
254
+ theme.theme.brandConfig?.legal?.termsAndConditions ||
255
+ 'https://eox.at/impressum'
256
+ "
233
257
  target="_blank"
234
258
  class="link"
235
- >About & Terms</a
259
+ >Terms & Conditions</a
236
260
  >
237
261
  </p>
238
262
  <p>
239
263
  <a
240
- href="https://eox.at/privacy-notice"
264
+ :href="
265
+ theme.theme.brandConfig?.legal?.privacyPolicy ||
266
+ 'https://eox.at/privacy-notice'
267
+ "
241
268
  target="_blank"
242
269
  class="link"
243
270
  >Privacy</a
@@ -250,6 +277,7 @@
250
277
  <div class="large-space"></div>
251
278
  </div>
252
279
  </footer>
280
+ <CookieBanner v-if="theme.theme.brandConfig?.analytics"></CookieBanner>
253
281
  <slot name="layout-bottom"></slot>
254
282
  </template>
255
283
  </Layout>
@@ -257,8 +285,9 @@
257
285
 
258
286
  <script setup>
259
287
  import DefaultTheme from "vitepress/theme";
260
- const { Layout } = DefaultTheme;
261
288
  import { useData, withBase } from "vitepress";
289
+ import { trackEvent } from "./helpers";
290
+ const { Layout } = DefaultTheme;
262
291
  const { frontmatter, page, site, theme } = useData();
263
292
 
264
293
  if (!import.meta.env.SSR) {
@@ -368,6 +397,8 @@ header > img.background-image {
368
397
  object-fit: cover;
369
398
  opacity: 0.4;
370
399
  z-index: 0;
400
+ border-top-left-radius: 0 !important;
401
+ border-top-right-radius: 0 !important;
371
402
  }
372
403
  header > .hero-container {
373
404
  max-width: 1200px;
@@ -19,12 +19,18 @@
19
19
  class="button extra small-margin"
20
20
  :class="dark !== undefined ? 'surface' : 'primary'"
21
21
  :href="primaryLink"
22
+ @click="
23
+ trackEvent(['CTA', 'Click', 'CTA Section Primary', primaryButton])
24
+ "
22
25
  >{{ primaryButton }} <i class="mdi mdi-arrow-right"></i
23
26
  ></a>
24
27
  <a
25
28
  v-if="secondaryButton"
26
29
  class="button extra small-margin secondary"
27
30
  :href="secondaryLink"
31
+ @click="
32
+ trackEvent(['CTA', 'Click', 'CTA Section Secondary', secondaryButton])
33
+ "
28
34
  >{{ secondaryButton }}<i class="mdi mdi-arrow-right"></i
29
35
  ></a>
30
36
  <a
@@ -32,6 +38,7 @@
32
38
  class="button border extra small-margin"
33
39
  :class="dark !== undefined ? 'white-text' : ''"
34
40
  :href="altLink"
41
+ @click="trackEvent(['CTA', 'Click', 'CTA Section Alt', altButton])"
35
42
  >{{ altButton }}</a
36
43
  >
37
44
  </div>
@@ -42,6 +49,7 @@
42
49
  </template>
43
50
 
44
51
  <script setup>
52
+ import { trackEvent } from "../helpers";
45
53
  const props = defineProps([
46
54
  "dark",
47
55
  "primaryButton",
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <div
3
+ class="cookie-banner card surface medium-margin medium-padding medium-elevate no-round"
4
+ >
5
+ <p class="small-text">
6
+ We use cookies to improve your experience and for marketing. Read our
7
+ <a
8
+ :href="theme.theme.brandConfig?.legal?.privacyPolicy"
9
+ target="_blank"
10
+ class="link"
11
+ >
12
+ Privacy Policy
13
+ </a>
14
+ for more information.
15
+ </p>
16
+ <nav>
17
+ <button class="small" @click="acceptCookies">Accept all</button>
18
+ <button class="small" @click="declineCookies">Reject all</button>
19
+ </nav>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ import { onMounted, watch } from "vue";
25
+ import { useData, useRouter } from "vitepress";
26
+ import { trackEvent } from "../helpers";
27
+
28
+ const { theme } = useData();
29
+ const router = useRouter();
30
+
31
+ /**
32
+ * Show/hide banner
33
+ *
34
+ * @param show Boolean
35
+ */
36
+ const showBanner = (show) => {
37
+ document.querySelector(".cookie-banner").style.display = show
38
+ ? "block"
39
+ : "none";
40
+ };
41
+
42
+ /**
43
+ * Accept cookies
44
+ */
45
+ const acceptCookies = () => {
46
+ _paq.push(["rememberCookieConsentGiven"]);
47
+ showBanner(false);
48
+ trackPageScrolling(true);
49
+ trackRouterNavigation(true);
50
+ };
51
+
52
+ /**
53
+ * Decline cookies
54
+ */
55
+ const declineCookies = () => {
56
+ _paq.push(["forgetCookieConsentGiven"]);
57
+ _paq.push(["optUserOut"]);
58
+ showBanner(false);
59
+ trackPageScrolling(false);
60
+ trackRouterNavigation(false);
61
+ };
62
+
63
+ /**
64
+ * Track page scrolling depth
65
+ *
66
+ * @param enabled Boolean
67
+ */
68
+ const trackPageScrolling = (enabled) => {
69
+ function getScrollPercent() {
70
+ const h = document.documentElement,
71
+ b = document.body,
72
+ st = "scrollTop",
73
+ sh = "scrollHeight";
74
+ return ((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100;
75
+ }
76
+ const scrollTargets = {
77
+ 25: false,
78
+ 50: false,
79
+ 75: false,
80
+ 100: false,
81
+ };
82
+ const scrollListener = () => {
83
+ Object.keys(scrollTargets).forEach((target) => {
84
+ if (getScrollPercent() >= parseInt(target) && !scrollTargets[target]) {
85
+ scrollTargets[target] = true;
86
+ trackEvent(["Interaction", "Scroll Depth", "Page"`${target}%`]);
87
+ }
88
+ });
89
+ };
90
+ if (enabled) {
91
+ document.addEventListener("scroll", scrollListener);
92
+ } else {
93
+ document.removeEventListener("scroll", scrollListener);
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Track user navigation from one page to another
99
+ *
100
+ * @param enabled Boolean
101
+ */
102
+ const trackRouterNavigation = (enabled) => {
103
+ const trackNavigation = () => {
104
+ _paq.push(["setCustomUrl", router.route.path]);
105
+ _paq.push(["setDocumentTitle", router.route.data.title]);
106
+ _paq.push(["trackPageView"]);
107
+ _paq.push(["enableLinkTracking"]);
108
+ };
109
+ if (enabled) {
110
+ watch(() => router.route.data.relativePath, trackNavigation, {
111
+ immediate: true,
112
+ });
113
+ } else {
114
+ watch(
115
+ () => router.route.data.relativePath,
116
+ () => {},
117
+ { immediate: true },
118
+ );
119
+ }
120
+ };
121
+
122
+ onMounted(() => {
123
+ setTimeout(() => {
124
+ // Basic tracking setup
125
+ if (
126
+ !document.cookie.includes("mtm_cookie_consent") &&
127
+ !document.cookie.includes("mtm_consent_removed")
128
+ ) {
129
+ showBanner(true);
130
+ }
131
+ });
132
+ });
133
+ </script>
134
+
135
+ <style>
136
+ .cookie-banner {
137
+ display: none;
138
+ position: fixed;
139
+ bottom: 0;
140
+ right: 0;
141
+ max-width: 512px;
142
+ z-index: 9999;
143
+ }
144
+ </style>
package/src/helpers.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Track Matomo event with array of four primary components
3
+ *
4
+ * - Category (Required) – This describes the type of events you want to track. For example, Link Clicks, Videos, Outbound Links, and Form Events.
5
+ * - Action (Required) – This is the specific action that is taken. For example, with the Video category, you might have a Play, Pause and Complete action.
6
+ * - Name (Optional – Recommended) – This is usually the title of the element that is being interacted with, to aid with analysis. For example, it could be the name of a Video that was played or the specific form that is being submitted.
7
+ * - Value (Optional) – This is a numeric value and is often added dynamically. It could be the cost of a product that is added to a cart, or the completion percentage of a video.
8
+ *
9
+ * See https://matomo.org/faq/reports/the-anatomy-of-an-event/
10
+ *
11
+ * @param {Array<string>} eventDetails
12
+ */
13
+ export const trackEvent = (eventDetails) => {
14
+ if (!import.meta.env.SSR) {
15
+ _paq.push(["trackEvent", ...eventDetails]);
16
+ }
17
+ };
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ import FeatureSection from "./components/FeatureSection.vue";
3
3
  import FeaturesGallery from "./components/FeaturesGallery.vue";
4
4
  import PricingTable from "./components/PricingTable.vue";
5
5
  import CTASection from "./components/CTASection.vue";
6
+ import CookieBanner from "./components/CookieBanner.vue";
6
7
  import Layout from "./Layout.vue";
7
8
  import "./style.css";
8
9
 
@@ -14,6 +15,7 @@ export default {
14
15
  app.component("FeaturesGallery", FeaturesGallery);
15
16
  app.component("PricingTable", PricingTable);
16
17
  app.component("CTASection", CTASection);
18
+ app.component("CookieBanner", CookieBanner);
17
19
 
18
20
  router.onAfterRouteChanged = () => {
19
21
  if (!import.meta.env.SSR) {
@@ -8,7 +8,88 @@ export const generate = (brandConfig) => ({
8
8
  appearance: false,
9
9
  cleanUrls: true,
10
10
  description: brandConfig.meta?.description,
11
- head: [["link", { rel: "icon", href: brandConfig.meta?.favicon }]],
11
+ head: [
12
+ ["link", { rel: "icon", href: brandConfig.meta?.favicon }],
13
+ // Open Graph / Facebook
14
+ ["meta", { property: "og:type", content: "website" }],
15
+ [
16
+ "meta",
17
+ {
18
+ property: "og:url",
19
+ content: brandConfig.home,
20
+ },
21
+ ],
22
+ ["meta", { property: "og:title", content: brandConfig.meta?.title }],
23
+ [
24
+ "meta",
25
+ {
26
+ property: "og:description",
27
+ content: brandConfig.meta?.description,
28
+ },
29
+ ],
30
+ // TODO
31
+ // [
32
+ // "meta",
33
+ // {
34
+ // property: "og:image",
35
+ // content:
36
+ // "https://earthcode.esa.int/img/EarthCODE_Herobanner_1920x1080.jpg",
37
+ // },
38
+ // ],
39
+ // Twitter
40
+ ["meta", { property: "twitter:card", content: "summary_large_image" }],
41
+ [
42
+ "meta",
43
+ {
44
+ property: "twitter:url",
45
+ content: brandConfig.home,
46
+ },
47
+ ],
48
+ ["meta", { property: "twitter:title", content: brandConfig.meta?.title }],
49
+ [
50
+ "meta",
51
+ {
52
+ property: "twitter:description",
53
+ content: brandConfig.meta?.description,
54
+ },
55
+ ],
56
+ // TODO
57
+ // [
58
+ // "meta",
59
+ // {
60
+ // property: "twitter:image",
61
+ // content:
62
+ // "https://earthcode.esa.int/img/EarthCODE_Herobanner_1920x1080.jpg",
63
+ // },
64
+ // ],
65
+ ...(brandConfig.analytics
66
+ ? [
67
+ [
68
+ "script",
69
+ {},
70
+ `
71
+ var _paq = (window._paq = window._paq || []);
72
+ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
73
+ _paq.push(["requireCookieConsent"]);
74
+ _paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
75
+ _paq.push(["trackPageView"]);
76
+ _paq.push(["enableLinkTracking"]);
77
+ (function () {
78
+ var u = "https://nix.eox.at/piwik/";
79
+ _paq.push(["setTrackerUrl", u + "matomo.php"]);
80
+ _paq.push(["setSiteId", "${brandConfig.analytics.siteId}"]);
81
+ var d = document,
82
+ g = d.createElement("script"),
83
+ s = d.getElementsByTagName("script")[0];
84
+ g.async = true;
85
+ g.src = u + "matomo.js";
86
+ s.parentNode.insertBefore(g, s);
87
+ })();
88
+ `,
89
+ ],
90
+ ]
91
+ : []),
92
+ ],
12
93
  srcExclude: ["README.md"],
13
94
  themeConfig: {
14
95
  siteTitle: false,
@@ -16,6 +97,7 @@ export const generate = (brandConfig) => ({
16
97
  primaryColor: brandConfig.theme?.primary_color,
17
98
  secondaryColor:
18
99
  brandConfig.theme?.secondary_color || brandConfig.theme?.primary_color,
100
+ brandConfig,
19
101
  },
20
102
  logo: brandConfig.logo,
21
103
  footer: {