@eox/pages-theme-eox 1.8.0 → 1.9.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.
@@ -0,0 +1,155 @@
1
+ <template>
2
+ <div class="single-tweet-container">
3
+ <div class="tweet-card">
4
+ <!-- Embed Mode (Cookies accepted or SSR) -->
5
+ <div v-if="cookiesAllowed" class="tweet-embed-wrapper">
6
+ <blockquote class="twitter-tweet" data-theme="light">
7
+ <a :href="`https://twitter.com/x/status/${id}`"></a>
8
+ </blockquote>
9
+ </div>
10
+
11
+ <!-- No Consent Fallback -->
12
+ <div v-else class="fallback-card">
13
+ <svg class="fallback-icon" viewBox="0 0 24 24" fill="currentColor">
14
+ <path
15
+ d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
16
+ />
17
+ </svg>
18
+ <div class="fallback-text">
19
+ <p class="fallback-title">Cookie Consent Required</p>
20
+ <p class="fallback-desc">
21
+ Please accept optional cookies to load the interactive Tweet. You
22
+ can review your preferences on the
23
+ <a href="/cookie-settings" class="cookie-link">Cookie Settings</a>
24
+ page.
25
+ </p>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup>
33
+ import { ref, onMounted, watch } from "vue";
34
+
35
+ const props = defineProps({
36
+ id: {
37
+ type: String,
38
+ required: true,
39
+ },
40
+ mockConsent: {
41
+ type: Boolean,
42
+ default: null,
43
+ },
44
+ });
45
+
46
+ const cookiesAllowed = ref(false);
47
+
48
+ const checkConsent = () => {
49
+ if (props.mockConsent !== null) {
50
+ cookiesAllowed.value = props.mockConsent;
51
+ return;
52
+ }
53
+ if (typeof document !== "undefined") {
54
+ // If no mtm cookie banner is present or user has consented
55
+ cookiesAllowed.value =
56
+ document.cookie.includes("mtm_cookie_consent") ||
57
+ !document.querySelector(".cookie-banner");
58
+ }
59
+ };
60
+
61
+ onMounted(() => {
62
+ checkConsent();
63
+
64
+ // Dynamically load Twitter widgets script if allowed
65
+ watch(
66
+ cookiesAllowed,
67
+ (allowed) => {
68
+ if (allowed && typeof window !== "undefined") {
69
+ if (window.twttr) {
70
+ window.twttr.widgets.load();
71
+ } else {
72
+ const script = document.createElement("script");
73
+ script.src = "https://platform.twitter.com/widgets.js";
74
+ script.async = true;
75
+ script.charset = "utf-8";
76
+ document.head.appendChild(script);
77
+ }
78
+ }
79
+ },
80
+ { immediate: true },
81
+ );
82
+ });
83
+ </script>
84
+
85
+ <style scoped>
86
+ .single-tweet-container {
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ margin: 1.5rem 0;
91
+ width: 100%;
92
+ }
93
+
94
+ .tweet-card {
95
+ width: 100%;
96
+ max-width: 500px;
97
+ min-height: 150px;
98
+ display: flex;
99
+ flex-direction: column;
100
+ justify-content: center;
101
+ }
102
+
103
+ .tweet-embed-wrapper {
104
+ width: 100%;
105
+ display: flex;
106
+ justify-content: center;
107
+ }
108
+
109
+ .fallback-card {
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: center;
113
+ justify-content: center;
114
+ padding: 2rem;
115
+ background-color: #f1f5f9;
116
+ border: 1px solid #e2e8f0;
117
+ border-radius: 8px;
118
+ text-align: center;
119
+ color: #475569;
120
+ gap: 1rem;
121
+ }
122
+
123
+ .fallback-icon {
124
+ width: 40px;
125
+ height: 40px;
126
+ color: #94a3b8;
127
+ }
128
+
129
+ .fallback-text {
130
+ font-size: 0.875rem;
131
+ }
132
+
133
+ .fallback-title {
134
+ font-weight: 600;
135
+ color: #1e293b;
136
+ margin: 0 0 0.25rem 0;
137
+ }
138
+
139
+ .fallback-desc {
140
+ color: #64748b;
141
+ font-size: 0.75rem;
142
+ line-height: 1.5;
143
+ margin: 0;
144
+ }
145
+
146
+ .cookie-link {
147
+ color: var(--primary, #004170);
148
+ font-weight: 500;
149
+ text-decoration: none;
150
+ }
151
+
152
+ .cookie-link:hover {
153
+ text-decoration: underline;
154
+ }
155
+ </style>
package/src/index.js CHANGED
@@ -13,6 +13,10 @@ import CookieSettings from "./components/CookieSettings.vue";
13
13
  import NotFound from "./components/NotFound.vue";
14
14
  import DataTable from "./components/DataTable.vue";
15
15
  import Tutorial from "./components/Tutorial.vue";
16
+ import ImageElement from "./components/ImageElement.vue";
17
+ import ImageGallery from "./components/ImageGallery.vue";
18
+ import ImageCarousel from "./components/ImageCarousel.vue";
19
+ import SingleTweet from "./components/SingleTweet.vue";
16
20
  import Layout from "./Layout.vue";
17
21
  import "./style.css";
18
22
 
@@ -34,6 +38,11 @@ export default {
34
38
  app.component("NotFound", NotFound);
35
39
  app.component("DataTable", DataTable);
36
40
  app.component("Tutorial", Tutorial);
41
+ app.component("ImageElement", ImageElement);
42
+ app.component("ImageGallery", ImageGallery);
43
+ app.component("GalleryGrid", ImageGallery);
44
+ app.component("ImageCarousel", ImageCarousel);
45
+ app.component("SingleTweet", SingleTweet);
37
46
 
38
47
  router.onAfterRouteChanged = () => {
39
48
  if (!import.meta.env.SSR) {
@@ -0,0 +1,51 @@
1
+ import { createContentLoader } from "vitepress";
2
+
3
+ export default createContentLoader("team/*.md", {
4
+ transform(rawData) {
5
+ return rawData
6
+ .filter((page) => {
7
+ // Exclude guest members
8
+ return !(page.frontmatter.guest === true);
9
+ })
10
+ .map((page) => {
11
+ // Find job description inside the meta array
12
+ const metaList = page.frontmatter.meta || [];
13
+ const jobDescMeta = Array.isArray(metaList)
14
+ ? metaList.find((m) => m && m.name === "jobDescription")
15
+ : null;
16
+ const role = jobDescMeta ? jobDescMeta.content : "";
17
+
18
+ // Transform sns array of objects into a single object of key-value pairs
19
+ const sns = {};
20
+ const snsList = page.frontmatter.sns || [];
21
+ if (Array.isArray(snsList)) {
22
+ snsList.forEach((item) => {
23
+ if (item && typeof item === "object") {
24
+ Object.entries(item).forEach(([platform, url]) => {
25
+ sns[platform] = url;
26
+ });
27
+ }
28
+ });
29
+ }
30
+
31
+ // Return the clean schema
32
+ return {
33
+ name: page.frontmatter.title || "",
34
+ id: page.frontmatter.id || "",
35
+ image: page.frontmatter.image || "",
36
+ bio: page.frontmatter.description || "",
37
+ role,
38
+ sns,
39
+ url: page.url,
40
+ };
41
+ })
42
+ .sort((a, b) => {
43
+ // "0_new" (id: new_member) should be in the 1st position.
44
+ if (a.id === "new_member") return -1;
45
+ if (b.id === "new_member") return 1;
46
+
47
+ // Otherwise sort alphabetically by ID (which maps to filename / last name)
48
+ return a.id.localeCompare(b.id);
49
+ });
50
+ },
51
+ });