@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.
- package/CHANGELOG.md +6 -0
- package/README.md +70 -0
- package/package.json +1 -1
- package/src/components/ImageCarousel.cy.js +22 -0
- package/src/components/ImageCarousel.vue +268 -0
- package/src/components/ImageElement.cy.js +46 -0
- package/src/components/ImageElement.vue +269 -0
- package/src/components/ImageGallery.cy.js +52 -0
- package/src/components/ImageGallery.vue +622 -0
- package/src/components/SingleTweet.cy.js +27 -0
- package/src/components/SingleTweet.vue +155 -0
- package/src/index.js +9 -0
- package/src/team.data.js +51 -0
|
@@ -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) {
|
package/src/team.data.js
ADDED
|
@@ -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
|
+
});
|