@markuxt/markuxt 0.1.4

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.
Files changed (94) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +168 -0
  3. package/app.config.d.ts +33 -0
  4. package/nuxt.config.ts +170 -0
  5. package/package.json +43 -0
  6. package/src/components/AppFooter.vue +225 -0
  7. package/src/components/AppHeader.vue +342 -0
  8. package/src/components/Hero.vue +438 -0
  9. package/src/components/Icon.vue +131 -0
  10. package/src/components/LanguageSwitcher.vue +71 -0
  11. package/src/components/MemberCard.vue +198 -0
  12. package/src/components/MembersGrid.vue +129 -0
  13. package/src/components/MermaidDiagram.vue +99 -0
  14. package/src/components/NewsCard.vue +119 -0
  15. package/src/components/PublicationCard.vue +245 -0
  16. package/src/components/SectionTitle.vue +75 -0
  17. package/src/components/content/ProseImg.vue +29 -0
  18. package/src/components/content/ProsePre.vue +58 -0
  19. package/src/components/content/ProseVideo.vue +45 -0
  20. package/src/composables/resolveContentImage.ts +35 -0
  21. package/src/content-transformers/binary-assets.ts +20 -0
  22. package/src/error.vue +58 -0
  23. package/src/layouts/default.vue +37 -0
  24. package/src/middleware/navigation-guard.ts +22 -0
  25. package/src/pages/index.vue +232 -0
  26. package/src/pages/members/[...slug].vue +542 -0
  27. package/src/pages/members/index.vue +147 -0
  28. package/src/pages/news/[...slug].vue +280 -0
  29. package/src/pages/news/index.vue +102 -0
  30. package/src/pages/positions/[...slug].vue +425 -0
  31. package/src/pages/positions/index.vue +266 -0
  32. package/src/pages/projects/[...slug].vue +441 -0
  33. package/src/pages/projects/index.vue +499 -0
  34. package/src/pages/publications/[...slug].vue +435 -0
  35. package/src/pages/publications/index.vue +145 -0
  36. package/src/plugins/mathml-components.ts +33 -0
  37. package/src/plugins/suppress-warnings.ts +40 -0
  38. package/src/public/_markuxt/components/AppFooter.vue +225 -0
  39. package/src/public/_markuxt/components/AppHeader.vue +342 -0
  40. package/src/public/_markuxt/components/Hero.vue +430 -0
  41. package/src/public/_markuxt/components/Icon.vue +131 -0
  42. package/src/public/_markuxt/components/LanguageSwitcher.vue +71 -0
  43. package/src/public/_markuxt/components/MemberCard.vue +198 -0
  44. package/src/public/_markuxt/components/MembersGrid.vue +129 -0
  45. package/src/public/_markuxt/components/MermaidDiagram.vue +99 -0
  46. package/src/public/_markuxt/components/NewsCard.vue +119 -0
  47. package/src/public/_markuxt/components/PublicationCard.vue +245 -0
  48. package/src/public/_markuxt/components/SectionTitle.vue +75 -0
  49. package/src/public/_markuxt/components/content/ProseImg.vue +29 -0
  50. package/src/public/_markuxt/components/content/ProsePre.vue +58 -0
  51. package/src/public/_markuxt/components/content/ProseVideo.vue +45 -0
  52. package/src/public/_markuxt/composables/resolveContentImage.ts +35 -0
  53. package/src/public/_markuxt/content-transformers/binary-assets.ts +20 -0
  54. package/src/public/_markuxt/error.vue +58 -0
  55. package/src/public/_markuxt/layouts/default.vue +37 -0
  56. package/src/public/_markuxt/middleware/navigation-guard.ts +22 -0
  57. package/src/public/_markuxt/pages/index.vue +232 -0
  58. package/src/public/_markuxt/pages/members/[...slug].vue +542 -0
  59. package/src/public/_markuxt/pages/members/index.vue +147 -0
  60. package/src/public/_markuxt/pages/news/[...slug].vue +280 -0
  61. package/src/public/_markuxt/pages/news/index.vue +102 -0
  62. package/src/public/_markuxt/pages/positions/[...slug].vue +425 -0
  63. package/src/public/_markuxt/pages/positions/index.vue +266 -0
  64. package/src/public/_markuxt/pages/projects/[...slug].vue +441 -0
  65. package/src/public/_markuxt/pages/projects/index.vue +499 -0
  66. package/src/public/_markuxt/pages/publications/[...slug].vue +435 -0
  67. package/src/public/_markuxt/pages/publications/index.vue +145 -0
  68. package/src/public/_markuxt/plugins/mathml-components.ts +33 -0
  69. package/src/public/_markuxt/plugins/suppress-warnings.ts +40 -0
  70. package/src/public/_markuxt/server/plugins/content-locale.ts +47 -0
  71. package/src/public/_markuxt/server/plugins/fix-content-anchors.ts +63 -0
  72. package/src/public/_markuxt/styles/_animations.css +99 -0
  73. package/src/public/_markuxt/styles/_base.css +31 -0
  74. package/src/public/_markuxt/styles/_code.css +109 -0
  75. package/src/public/_markuxt/styles/_components.css +109 -0
  76. package/src/public/_markuxt/styles/_layout.css +220 -0
  77. package/src/public/_markuxt/styles/_markdown.css +52 -0
  78. package/src/public/_markuxt/styles/_tokens.css +62 -0
  79. package/src/public/_markuxt/styles/_typography.css +144 -0
  80. package/src/public/_markuxt/styles/_utilities.css +110 -0
  81. package/src/public/_markuxt/styles/main.css +784 -0
  82. package/src/public/images/logo.png +0 -0
  83. package/src/server/plugins/content-locale.ts +47 -0
  84. package/src/server/plugins/fix-content-anchors.ts +63 -0
  85. package/src/styles/_animations.css +99 -0
  86. package/src/styles/_base.css +31 -0
  87. package/src/styles/_code.css +109 -0
  88. package/src/styles/_components.css +109 -0
  89. package/src/styles/_layout.css +220 -0
  90. package/src/styles/_markdown.css +52 -0
  91. package/src/styles/_tokens.css +62 -0
  92. package/src/styles/_typography.css +144 -0
  93. package/src/styles/_utilities.css +110 -0
  94. package/src/styles/main.css +784 -0
@@ -0,0 +1,280 @@
1
+ <template>
2
+ <div class="news-post-page">
3
+ <!-- Back Button -->
4
+ <div class="container">
5
+ <NuxtLink to="/news" class="back-link">
6
+ {{ t('news.backTo') }}
7
+ </NuxtLink>
8
+ </div>
9
+
10
+ <article class="news-post" v-if="news">
11
+ <header class="news-post__header">
12
+ <div class="container">
13
+ <time class="news-post__date" :datetime="news.date">
14
+ {{ formattedDate }}
15
+ </time>
16
+ <h1 class="news-post__title">{{ news.title }}</h1>
17
+ <div class="news-post__tags" v-if="news.tags && news.tags.length">
18
+ <span v-for="tag in news.tags" :key="tag" class="badge">
19
+ {{ tag }}
20
+ </span>
21
+ </div>
22
+ </div>
23
+ </header>
24
+
25
+ <div class="news-post__content">
26
+ <div class="container">
27
+ <ContentRenderer :value="news" />
28
+ </div>
29
+ </div>
30
+ </article>
31
+
32
+ <!-- Related News -->
33
+ <div class="section" v-if="news && relatedNews.length > 0">
34
+ <div class="container">
35
+ <h3 class="related-news__title">{{ t('news.related') }}</h3>
36
+ <div class="related-news__grid">
37
+ <NewsCard
38
+ v-for="item in relatedNews"
39
+ :key="item._path"
40
+ :news="item"
41
+ />
42
+ </div>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Not Found -->
47
+ <div v-if="!news" class="not-found-page">
48
+ <div class="container">
49
+ <div class="not-found">
50
+ <Help class="icon-inline" theme="outline" :size="80" fill="var(--color-accent)" :stroke-width="3" />
51
+ <h1>{{ t('news.notFound') }}</h1>
52
+ <p>{{ t('news.notFoundDesc') }}</p>
53
+ <NuxtLink to="/news" class="btn btn-primary">{{ t('news.browseAll') }}</NuxtLink>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </template>
59
+
60
+ <script setup lang="ts">
61
+ import Help from '@icon-park/vue-next/es/icons/Help'
62
+
63
+ const { t, locale } = useI18n()
64
+ const route = useRoute()
65
+
66
+ // Compute slug to support catch-all route [...slug] (array or string)
67
+ const slug = computed(() => {
68
+ const slugParam = route.params.slug
69
+ return Array.isArray(slugParam) ? slugParam.join('/') : slugParam
70
+ })
71
+
72
+ // Fetch current news post (watch slug for navigation)
73
+ const { data: news } = await useAsyncData(() => `news-${slug.value}`, async () => {
74
+ try {
75
+ const fullPath = `/news/${slug.value}`
76
+ return await queryContent(fullPath).findOne()
77
+ } catch (e) {
78
+ console.error('Error fetching news:', e)
79
+ return null
80
+ }
81
+ }, { watch: [slug] })
82
+
83
+ // Provide content ID for ProseImg/ProseVideo to resolve relative asset paths
84
+ provide('contentId', computed(() => news.value?._id || ''))
85
+
86
+ // Fetch related news (exclude current)
87
+ const { data: allNews } = await useAsyncData('news-related', () =>
88
+ queryContent('/news')
89
+ .where({ _hidden: { $ne: true } })
90
+ .sort({ date: -1 })
91
+ .limit(6)
92
+ .where({ _extension: 'md' }).find()
93
+ )
94
+
95
+ const relatedNews = computed(() => {
96
+ if (!allNews.value || !news.value) return []
97
+ const currentPath = news.value._path
98
+ return allNews.value
99
+ .filter(item => item._path !== currentPath)
100
+ .slice(0, 3)
101
+ })
102
+
103
+ const formattedDate = computed(() => {
104
+ if (!news.value?.date) return ''
105
+ const date = new Date(news.value.date)
106
+ return date.toLocaleDateString(locale.value === 'zh-CN' ? 'zh-CN' : 'en-US', {
107
+ year: 'numeric',
108
+ month: 'long',
109
+ day: 'numeric'
110
+ })
111
+ })
112
+
113
+ // SEO
114
+ useHead({
115
+ title: computed(() => news.value ? news.value.title : 'News Post Not Found'),
116
+ meta: computed(() => [
117
+ { name: 'description', content: news.value?.description || `News post from ${t('site.shortName')}.` }
118
+ ])
119
+ })
120
+ </script>
121
+
122
+ <style scoped>
123
+ .news-post-page {
124
+ min-height: 100vh;
125
+ }
126
+
127
+ .back-link {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ padding: var(--spacing-sm) 0;
131
+ font-size: 0.875rem;
132
+ font-weight: 500;
133
+ color: var(--color-text-muted);
134
+ text-decoration: none;
135
+ transition: color var(--transition-fast);
136
+ }
137
+
138
+ .back-link:hover {
139
+ color: var(--color-secondary);
140
+ }
141
+
142
+ /* News Post */
143
+ .news-post {
144
+ margin-bottom: var(--spacing-3xl);
145
+ }
146
+
147
+ .news-post__header {
148
+ padding: var(--spacing-3xl) 0 var(--spacing-xl);
149
+ border-bottom: 1px solid var(--color-border);
150
+ }
151
+
152
+ .news-post__date {
153
+ display: block;
154
+ font-family: var(--font-display);
155
+ font-size: 0.875rem;
156
+ font-weight: 500;
157
+ color: var(--color-secondary);
158
+ text-transform: uppercase;
159
+ letter-spacing: 0.05em;
160
+ margin-bottom: var(--spacing-md);
161
+ }
162
+
163
+ .news-post__title {
164
+ font-family: var(--font-display);
165
+ font-size: clamp(2rem, 5vw, 3rem);
166
+ font-weight: 700;
167
+ color: var(--color-primary);
168
+ margin-bottom: var(--spacing-lg);
169
+ line-height: 1.2;
170
+ }
171
+
172
+ .news-post__tags {
173
+ display: flex;
174
+ flex-wrap: wrap;
175
+ gap: var(--spacing-sm);
176
+ }
177
+
178
+ .news-post__content {
179
+ padding: var(--spacing-2xl) 0;
180
+ }
181
+
182
+ .news-post__content :deep(p) {
183
+ font-size: 1.0625rem;
184
+ line-height: 1.7;
185
+ margin-bottom: var(--spacing-md);
186
+ }
187
+
188
+ .news-post__content :deep(h2) {
189
+ font-family: var(--font-display);
190
+ font-size: 1.75rem;
191
+ font-weight: 600;
192
+ color: var(--color-primary);
193
+ margin-top: var(--spacing-2xl);
194
+ margin-bottom: var(--spacing-md);
195
+ }
196
+
197
+ .news-post__content :deep(h3) {
198
+ font-family: var(--font-display);
199
+ font-size: 1.375rem;
200
+ font-weight: 600;
201
+ color: var(--color-primary);
202
+ margin-top: var(--spacing-xl);
203
+ margin-bottom: var(--spacing-sm);
204
+ }
205
+
206
+ .news-post__content :deep(ul),
207
+ .news-post__content :deep(ol) {
208
+ margin-bottom: var(--spacing-md);
209
+ padding-left: var(--spacing-xl);
210
+ }
211
+
212
+ .news-post__content :deep(li) {
213
+ margin-bottom: var(--spacing-xs);
214
+ line-height: 1.6;
215
+ }
216
+
217
+ /* Related News */
218
+ .related-news__title {
219
+ font-family: var(--font-display);
220
+ font-size: 1.5rem;
221
+ font-weight: 600;
222
+ color: var(--color-primary);
223
+ margin-bottom: var(--spacing-xl);
224
+ }
225
+
226
+ .related-news__grid {
227
+ display: grid;
228
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
229
+ gap: var(--spacing-xl);
230
+ }
231
+
232
+ @media (max-width: 640px) {
233
+ .related-news__grid {
234
+ grid-template-columns: 1fr;
235
+ }
236
+ }
237
+
238
+ /* Not Found */
239
+ .not-found-page {
240
+ min-height: 60vh;
241
+ display: flex;
242
+ align-items: center;
243
+ }
244
+
245
+ .not-found {
246
+ text-align: center;
247
+ padding: var(--spacing-4xl) 0;
248
+ max-width: 400px;
249
+ margin: 0 auto;
250
+ }
251
+
252
+ .not-found__icon {
253
+ width: 80px;
254
+ height: 80px;
255
+ margin: 0 auto var(--spacing-xl);
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ font-family: var(--font-display);
260
+ font-size: 3rem;
261
+ font-weight: 800;
262
+ color: var(--color-accent);
263
+ background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-secondary) 100%);
264
+ border-radius: var(--radius-xl);
265
+ }
266
+
267
+ .not-found h1 {
268
+ font-family: var(--font-display);
269
+ font-size: clamp(2rem, 4vw, 2.5rem);
270
+ font-weight: 700;
271
+ color: var(--color-primary);
272
+ margin-bottom: var(--spacing-md);
273
+ }
274
+
275
+ .not-found p {
276
+ font-size: 1rem;
277
+ color: var(--color-text-muted);
278
+ margin-bottom: var(--spacing-xl);
279
+ }
280
+ </style>
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <div class="news-page">
3
+ <div class="section">
4
+ <div class="container">
5
+ <SectionTitle
6
+ :overline="t('news.overline')"
7
+ :title="t('news.title')"
8
+ :description="t('news.description')"
9
+ />
10
+
11
+ <div class="news-list" v-if="safeNewsItems.length > 0">
12
+ <NewsCard
13
+ v-for="item in safeNewsItems"
14
+ :key="item._path"
15
+ :news="item"
16
+ />
17
+ </div>
18
+
19
+ <div class="news-empty" v-else>
20
+ <div class="news-empty__icon">📰</div>
21
+ <h3>{{ t('news.noNews') }}</h3>
22
+ <p>{{ t('news.checkBack') }}</p>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ interface NewsItem {
31
+ title: string
32
+ date: string
33
+ description?: string
34
+ tags?: string[]
35
+ _path: string
36
+ }
37
+
38
+ const { t } = useI18n()
39
+
40
+ // Fetch all news
41
+ const { data: newsItems } = await useAsyncData('news', () =>
42
+ queryContent('/news')
43
+ .where({ _hidden: { $ne: true } })
44
+ .sort({ date: -1 })
45
+ .where({ _extension: 'md' }).find()
46
+ )
47
+
48
+ const safeNewsItems = computed(() =>
49
+ (newsItems.value ?? []).filter((item: any) => typeof item.date === 'string') as unknown as NewsItem[]
50
+ )
51
+
52
+ useHead({
53
+ title: t('news.pageTitle'),
54
+ meta: [
55
+ { name: 'description', content: t('news.pageDescription') }
56
+ ]
57
+ })
58
+ </script>
59
+
60
+ <style scoped>
61
+ .news-page {
62
+ padding-top: var(--spacing-xl);
63
+ }
64
+
65
+ .news-list {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
68
+ gap: var(--spacing-xl);
69
+ }
70
+
71
+ .news-empty {
72
+ text-align: center;
73
+ padding: var(--spacing-4xl) var(--spacing-lg);
74
+ background: var(--color-bg-alt);
75
+ border-radius: var(--radius-2xl);
76
+ border: 1px dashed var(--color-border);
77
+ }
78
+
79
+ .news-empty__icon {
80
+ font-size: 4rem;
81
+ margin-bottom: var(--spacing-lg);
82
+ }
83
+
84
+ .news-empty h3 {
85
+ font-family: var(--font-display);
86
+ font-size: 1.5rem;
87
+ font-weight: 600;
88
+ color: var(--color-primary);
89
+ margin-bottom: var(--spacing-md);
90
+ }
91
+
92
+ .news-empty p {
93
+ font-size: 1.0625rem;
94
+ color: var(--color-text-muted);
95
+ }
96
+
97
+ @media (max-width: 640px) {
98
+ .news-list {
99
+ grid-template-columns: 1fr;
100
+ }
101
+ }
102
+ </style>