1919 - along with this program. If not, see <http://www.gnu.org/licenses/>.
2020 -->
2121<template >
22- <div class =" addon-info" >
23- <h3 >Outlook Add-in</h3 >
24- <div v-if =" licenseStore.addinVersion" class =" addon-info__details" >
25- <p ><strong >Latest version:</strong > {{ licenseStore.addinVersion.Version }}</p >
26- <p v-if =" licenseStore.addinVersion.ReleaseDate" >
27- <strong >Release date:</strong > {{ formatDate(licenseStore.addinVersion.ReleaseDate) }}
28- </p >
29- <div class =" addon-info__links" >
30- <a v-if =" licenseStore.addinVersion.UrlBinary"
31- :href =" licenseStore.addinVersion.UrlBinary"
32- class =" button primary"
33- target =" _blank"
34- rel =" noopener" >
35- Download
36- </a >
37- <a v-if =" licenseStore.addinVersion.UrlManual"
38- :href =" licenseStore.addinVersion.UrlManual"
39- target =" _blank"
40- rel =" noopener"
41- class =" addon-info__link" >
42- Manual ↗
43- </a >
44- <a v-if =" licenseStore.addinVersion.UrlReleaseNotes"
45- :href =" licenseStore.addinVersion.UrlReleaseNotes"
46- target =" _blank"
47- rel =" noopener"
48- class =" addon-info__link" >
49- Release Notes ↗
50- </a >
22+ <div class =" product-releases" >
23+ <div v-if =" loading" class =" product-releases__loading" >
24+ <span class =" icon-loading" />
25+ Loading release info...
26+ </div >
27+ <div v-else-if =" Object.keys(releases).length === 0" class =" product-releases__empty" >
28+ No release information available.
29+ </div >
30+ <div v-else class =" product-releases__grid" >
31+ <div v-for =" product in products"
32+ :key =" product.slug"
33+ class =" product-card" >
34+ <div v-if =" releases[product.slug]" class =" product-card__content" >
35+ <h3 >{{ product.label }}</h3 >
36+ <p >
37+ <strong >Latest version:</strong >
38+ {{ extractVersion(releases[product.slug].title) }}
39+ </p >
40+ <p v-if =" releases[product.slug].date" >
41+ <strong >Release date:</strong >
42+ {{ formatDate(releases[product.slug].date) }}
43+ </p >
44+ <div v-if =" releases[product.slug].tags?.length" class =" product-card__tags" >
45+ <span v-for =" tag in releases[product.slug].tags"
46+ :key =" tag"
47+ class =" product-card__tag" >
48+ {{ tag }}
49+ </span >
50+ </div >
51+ <div class =" product-card__actions" >
52+ <button class =" product-card__notes-toggle"
53+ @click =" toggleNotes(product.slug)" >
54+ {{ expandedNotes[product.slug] ? 'Hide release notes' : 'Show release notes' }}
55+ </button >
56+ </div >
57+ <div v-if =" expandedNotes[product.slug]"
58+ class =" product-card__release-notes"
59+ v-html =" releases[product.slug].content" />
60+ </div >
5161 </div >
5262 </div >
53- <p v-else class =" addon-info__empty" >
54- No add-in version information available.
55- </p >
5663 </div >
5764</template >
5865
5966<script setup lang="ts">
60- import { useLicenseStore } from ' ../../stores/license'
67+ import { ref , reactive , onMounted } from ' vue'
68+ import type { ReleaseEntry } from ' ../../types/releases'
69+ import { fetchLatestReleases } from ' ../../services/releasesApi'
6170import { formatDate } from ' ../../utils/date-utils'
6271
63- const licenseStore = useLicenseStore ()
72+ const products = [
73+ { slug: ' outlook-cross-platform' , label: ' Sendent for Outlook (Cross-Platform)' },
74+ { slug: ' ms-teams' , label: ' Sendent for MS Teams' },
75+ { slug: ' outlook-windows' , label: ' Sendent for Outlook (Windows-Only)' },
76+ ]
77+
78+ const releases = ref <Record <string , ReleaseEntry >>({})
79+ const loading = ref (true )
80+ const expandedNotes = reactive <Record <string , boolean >>({})
81+
82+ /**
83+ * Extracts a version number from a release title like "Release Notes v2.3.0"
84+ * @param title
85+ */
86+ function extractVersion(title : string ): string {
87+ const match = title .match (/ v? (\d + \. \d + (?:\. \d + )? )/ i )
88+ return match ? match [1 ] : title
89+ }
90+
91+ /**
92+ * @param slug
93+ */
94+ function toggleNotes(slug : string ) {
95+ expandedNotes [slug ] = ! expandedNotes [slug ]
96+ }
97+
98+ onMounted (async () => {
99+ try {
100+ releases .value = await fetchLatestReleases ()
101+ } catch {
102+ // Silently fail — the empty state handles this
103+ } finally {
104+ loading .value = false
105+ }
106+ })
64107 </script >
65108
66109<style scoped>
67- .addon-info {
110+ .product-releases__loading {
111+ display : flex ;
112+ align-items : center ;
113+ gap : 8px ;
114+ padding : 12px 0 ;
115+ color : var (--color-text-maxcontrast );
116+ }
117+
118+ .product-releases__empty {
119+ color : var (--color-text-maxcontrast );
120+ padding : 12px 0 ;
121+ }
122+
123+ .product-releases__grid {
124+ display : flex ;
125+ flex-wrap : wrap ;
126+ gap : 16px ;
68127 margin-bottom : 24px ;
69128}
70129
71- .addon-info h3 {
72- font-size : 16px ;
130+ .product-card {
131+ flex : 1 ;
132+ min-width : 280px ;
133+ max-width : 400px ;
134+ border : 1px solid var (--color-border );
135+ border-radius : var (--border-radius-large );
136+ padding : 16px ;
137+ }
138+
139+ .product-card h3 {
140+ font-size : 15px ;
73141 font-weight : 600 ;
74- margin-bottom : 12px ;
142+ margin-bottom : 8px ;
143+ }
144+
145+ .product-card p {
146+ margin : 4px 0 ;
147+ font-size : 14px ;
75148}
76149
77- .addon-info__links {
150+ .product-card__tags {
78151 display : flex ;
79- gap : 16 px ;
80- margin-top : 8 px ;
81- align-items : center ;
152+ gap : 6 px ;
153+ margin-top : 6 px ;
154+ flex-wrap : wrap ;
82155}
83156
84- .addon-info__link {
85- display : inline-flex ;
86- align-items : center ;
87- gap : 4px ;
157+ .product-card__tag {
158+ display : inline-block ;
159+ padding : 2px 8px ;
160+ font-size : 12px ;
161+ border-radius : var (--border-radius-pill );
162+ background : var (--color-primary-element-light );
163+ color : var (--color-primary-element );
88164}
89165
90- .addon-info__empty {
91- color : var (--color-text-maxcontrast );
166+ .product-card__actions {
167+ margin-top : 10px ;
168+ }
169+
170+ .product-card__notes-toggle {
171+ background : none ;
172+ border : 1px solid var (--color-border-dark );
173+ border-radius : var (--border-radius );
174+ padding : 4px 12px ;
175+ font-size : 13px ;
176+ cursor : pointer ;
177+ color : var (--color-primary-element );
178+ }
179+
180+ .product-card__notes-toggle :hover {
181+ background : var (--color-background-hover );
182+ }
183+
184+ .product-card__release-notes {
185+ margin-top : 12px ;
186+ padding : 12px ;
187+ background : var (--color-background-hover );
188+ border-radius : var (--border-radius );
189+ font-size : 14px ;
190+ line-height : 1.6 ;
191+ overflow-x : auto ;
192+ }
193+
194+ .product-card__release-notes :deep(h2 ) {
195+ font-size : 14px ;
196+ font-weight : 600 ;
197+ margin : 12px 0 6px ;
198+ }
199+
200+ .product-card__release-notes :deep(h2 :first-child ) {
201+ margin-top : 0 ;
202+ }
203+
204+ .product-card__release-notes :deep(ul ) {
205+ padding-left : 20px ;
206+ margin : 6px 0 ;
207+ }
208+
209+ .product-card__release-notes :deep(li ) {
210+ margin : 4px 0 ;
211+ }
212+
213+ .product-card__release-notes :deep(a ) {
214+ color : var (--color-primary-element );
92215}
93- </style >
216+ </style >
0 commit comments