@markuxt/markuxt 0.1.15 → 0.1.16

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.
@@ -70,6 +70,34 @@
70
70
  <ContentRenderer :value="member" />
71
71
  </div>
72
72
  </div>
73
+
74
+ <!-- Publications -->
75
+ <div v-if="memberPublications.length > 0" class="profile-section animate-fade-in-up delay-400">
76
+ <div class="profile-section__header">
77
+ <FileText class="icon-inline" theme="outline" :size="22" fill="white" :stroke-width="2.8" />
78
+ <h3>{{ t('members.publications') }}</h3>
79
+ </div>
80
+ <div class="profile-section__body">
81
+ <div class="publications-grid">
82
+ <NuxtLink
83
+ v-for="pub in memberPublications"
84
+ :key="pub._id"
85
+ :to="pub._path"
86
+ class="publication-card"
87
+ >
88
+ <div class="publication-card__content">
89
+ <h4 class="publication-card__title">{{ pub.title }}</h4>
90
+ <p class="publication-card__authors">{{ formatAuthors(pub.authors) }}</p>
91
+ <div class="publication-card__meta">
92
+ <span class="publication-card__venue">{{ pub.venue }}</span>
93
+ <span class="publication-card__year">{{ pub.year }}</span>
94
+ </div>
95
+ </div>
96
+ <ArrowRight class="publication-card__arrow" theme="outline" :size="16" fill="currentColor" :stroke-width="2" />
97
+ </NuxtLink>
98
+ </div>
99
+ </div>
100
+ </div>
73
101
  </div>
74
102
  </div>
75
103
  </main>
@@ -93,6 +121,8 @@ import Mail from '@icon-park/vue-next/es/icons/Mail'
93
121
  import Google from '@icon-park/vue-next/es/icons/Google'
94
122
  import Search from '@icon-park/vue-next/es/icons/Search'
95
123
  import FileStaff from '@icon-park/vue-next/es/icons/FileStaff'
124
+ import FileText from '@icon-park/vue-next/es/icons/FileText'
125
+ import ArrowRight from '@icon-park/vue-next/es/icons/ArrowRight'
96
126
  import Help from '@icon-park/vue-next/es/icons/Help'
97
127
 
98
128
  const { t } = useI18n()
@@ -120,6 +150,40 @@ const { data: memberData } = await useAsyncData(`member-${slug.value}`, async ()
120
150
 
121
151
  const member = computed(() => memberData.value)
122
152
 
153
+ // Fetch all publications for filtering by member ORCID
154
+ const { data: allPublications } = await useAsyncData('publications', async () => {
155
+ try {
156
+ return await queryContent('/publications')
157
+ .where({ _hidden: { $ne: true } })
158
+ .find()
159
+ } catch (e) {
160
+ console.error('Error fetching publications:', e)
161
+ return []
162
+ }
163
+ })
164
+
165
+ // Filter publications by member's ORCID
166
+ const memberPublications = computed(() => {
167
+ if (!member.value?.orcid || !allPublications.value) return []
168
+
169
+ const memberOrcid = member.value.orcid.trim()
170
+
171
+ return (allPublications.value || [])
172
+ .filter(pub => {
173
+ const authorsOrcid = pub.authors_orcid
174
+ if (!Array.isArray(authorsOrcid)) return false
175
+ return authorsOrcid.some(orcid => orcid === memberOrcid)
176
+ })
177
+ .sort((a, b) => (b.year || 0) - (a.year || 0)) // Sort by year descending
178
+ })
179
+
180
+ // Format authors list for display
181
+ const formatAuthors = (authors: string[] | undefined) => {
182
+ if (!Array.isArray(authors) || authors.length === 0) return ''
183
+ if (authors.length <= 2) return authors.join(' & ')
184
+ return `${authors[0]} et al.`
185
+ }
186
+
123
187
  // Provide content ID for ProseImg/ProseVideo to resolve relative asset paths
124
188
  provide('contentId', computed(() => member.value?._id || ''))
125
189
 
@@ -416,6 +480,92 @@ useHead({
416
480
  box-shadow: var(--shadow-sm);
417
481
  }
418
482
 
483
+ /* Publications Grid */
484
+ .publications-grid {
485
+ display: flex;
486
+ flex-direction: column;
487
+ gap: var(--spacing-sm);
488
+ }
489
+
490
+ .publication-card {
491
+ display: flex;
492
+ align-items: center;
493
+ justify-content: space-between;
494
+ gap: var(--spacing-md);
495
+ padding: var(--spacing-lg);
496
+ background: var(--color-bg);
497
+ border: 1px solid var(--color-border);
498
+ border-radius: var(--radius-lg);
499
+ text-decoration: none;
500
+ transition: all var(--transition-base);
501
+ }
502
+
503
+ .publication-card:hover {
504
+ background: var(--color-bg-alt);
505
+ border-color: var(--color-secondary);
506
+ transform: translateX(4px);
507
+ box-shadow: var(--shadow-md);
508
+ }
509
+
510
+ .publication-card__content {
511
+ flex: 1;
512
+ min-width: 0;
513
+ }
514
+
515
+ .publication-card__title {
516
+ font-family: var(--font-display);
517
+ font-size: 1rem;
518
+ font-weight: 600;
519
+ color: var(--color-primary);
520
+ margin: 0 0 var(--spacing-xs) 0;
521
+ line-height: 1.4;
522
+ display: -webkit-box;
523
+ -webkit-line-clamp: 2;
524
+ line-clamp: 2;
525
+ -webkit-box-orient: vertical;
526
+ overflow: hidden;
527
+ }
528
+
529
+ .publication-card__authors {
530
+ font-size: 0.875rem;
531
+ color: var(--color-text-muted);
532
+ margin: 0 0 var(--spacing-xs) 0;
533
+ line-height: 1.4;
534
+ }
535
+
536
+ .publication-card__meta {
537
+ display: flex;
538
+ align-items: center;
539
+ gap: var(--spacing-sm);
540
+ flex-wrap: wrap;
541
+ }
542
+
543
+ .publication-card__venue {
544
+ font-size: 0.8125rem;
545
+ font-weight: 500;
546
+ color: var(--color-secondary);
547
+ }
548
+
549
+ .publication-card__year {
550
+ font-size: 0.8125rem;
551
+ font-weight: 600;
552
+ color: var(--color-accent);
553
+ background: rgba(0, 217, 255, 0.1);
554
+ padding: 2px 8px;
555
+ border-radius: var(--radius-sm);
556
+ }
557
+
558
+ .publication-card__arrow {
559
+ flex-shrink: 0;
560
+ color: var(--color-secondary);
561
+ transition: transform var(--transition-fast);
562
+ }
563
+
564
+ .publication-card:hover .publication-card__arrow {
565
+ transform: translateX(4px);
566
+ color: var(--color-accent);
567
+ }
568
+
419
569
  /* ContentRenderer Markdown Styling */
420
570
  .profile-section__body--content :deep(h2) {
421
571
  font-family: var(--font-display);