Skip to content

Commit d6407bb

Browse files
authored
Merge pull request #9 from MedVIC-Lab/publication-formatting
Publication formatting
2 parents b70f6f3 + 7dd686c commit d6407bb

File tree

4 files changed

+156
-25
lines changed

4 files changed

+156
-25
lines changed

.vitepress/config.mts

+5-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export default defineConfig({
2323
generateMembersPlugin(),
2424
generatePublicationsPlugin(),
2525
generateProjectsPlugin(),
26-
]
27-
}
26+
],
27+
ssr: {
28+
noExternal: [/\.css$/, /\?vue&type=style/, /^vuetify/],
29+
}
30+
},
2831
})

.vitepress/theme/index.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import DefaultTheme from 'vitepress/theme'
22
import './custom.css'
3+
import 'vuetify/styles'
4+
import '@mdi/font/css/materialdesignicons.css'
5+
import * as components from 'vuetify/components'
6+
import * as directives from 'vuetify/directives'
7+
import { createVuetify } from 'vuetify'
8+
9+
const vuetify = createVuetify({ components, directives })
310

411
// Dynamically import all Vue components from the layouts directory
512
const layouts = import.meta.glob('../../layouts/*.vue')
@@ -8,6 +15,9 @@ export default {
815
Layout: DefaultTheme.Layout,
916
extends: DefaultTheme,
1017
enhanceApp({ app }) {
18+
// Use Vuetify
19+
app.use(vuetify)
20+
1121
// Register each layout component
1222
for (const path in layouts) {
1323
layouts[path]().then((module) => {

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"devDependencies": {
3+
"@mdi/font": "^7.4.47",
34
"ts-node": "^10.9.2",
45
"vite": "^5.4.8",
56
"vitepress": "^1.4.0",
@@ -11,6 +12,7 @@
1112
"preview": "vitepress build && vitepress preview"
1213
},
1314
"dependencies": {
14-
"front-matter": "^4.0.2"
15+
"front-matter": "^4.0.2",
16+
"vuetify": "^3.7.6"
1517
}
1618
}

pages/publications.md

+138-22
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,85 @@ aside: false
33
---
44

55
<script setup>
6-
import { ref, onMounted } from 'vue'
6+
import { ref, onMounted, computed, watch } from 'vue'
7+
8+
const SORT_OPTIONS = {
9+
sort: ["year", "author", "title", "conference"],
10+
ascending: false // boolean for ascending/descending order, true = ascending
11+
}
712

813
const publications = ref([])
914

10-
onMounted(async () => {
11-
const response = await fetch('/assets/publications.json')
12-
const pubs = await response.json()
15+
const sorting = ref({
16+
sort: "author",
17+
ascending: false
18+
})
19+
20+
const previousSort = ref("author")
21+
22+
const search = ref("")
23+
const searchOpen = ref(false)
24+
25+
// helper
26+
function sortByAuthor(a, b, order) {
27+
const aFirstAuthor = a.authors.split(',')[0]
28+
const bFirstAuthor = b.authors.split(',')[0]
29+
const aLastName = aFirstAuthor.split(' ').pop()
30+
const bLastName = bFirstAuthor.split(' ').pop()
31+
return aLastName.localeCompare(bLastName) * order
32+
}
33+
34+
const sortedPublications = computed(() => {
35+
// filter search string first
36+
let filteredPubs = [...publications.value]
37+
if (search.value !== "") {
38+
filteredPubs = filteredPubs.filter((p) => p.title.toLowerCase().includes(search.value.toLowerCase()))
39+
}
40+
41+
return filteredPubs.sort((a, b) => {
42+
const sortKey = sorting.value.sort
43+
const order = sorting.value.ascending ? 1 : -1
44+
45+
if (sortKey === "year") {
46+
if (a.year !== b.year) {
47+
return (b.year - a.year) * order
48+
}
49+
return sortByAuthor(a, b, 1)
50+
}
51+
52+
if (sortKey === "author") {
53+
return sortByAuthor(a, b, order)
54+
}
55+
56+
if (sortKey === "title") {
57+
return a.title.localeCompare(b.title) * order
58+
}
1359

14-
// TODO: update with better sorting when functionality added to UI
15-
// sort by year, then by first author (alphabetical)
16-
publications.value = pubs.sort((a, b) => {
17-
if (a.year !== b.year) {
18-
return b.year - a.year; // Sort by year in descending order
60+
if (sortKey === "conference") {
61+
return a.conference.localeCompare(b.conference) * order
1962
}
20-
// Sort by first author alphabetically (last name)
21-
const aFirstAuthor = a.authors.split(',')[0]
22-
const bFirstAuthor = b.authors.split(',')[0]
23-
const aLastName = aFirstAuthor.split(' ').pop()
24-
const bLastName = bFirstAuthor.split(' ').pop()
2563

26-
return aLastName.localeCompare(bLastName);
64+
return 0
2765
})
2866
})
67+
68+
function handleSortToggle(v) {
69+
if (previousSort.value === v) {
70+
sorting.value.ascending = !sorting.value.ascending
71+
} else {
72+
previousSort.value = v
73+
sorting.value.ascending = false
74+
}
75+
}
76+
77+
function openSearchBar() {
78+
searchOpen.value = true
79+
}
80+
81+
onMounted(async () => {
82+
const response = await fetch('/assets/publications.json')
83+
publications.value = await response.json()
84+
})
2985
</script>
3086

3187
<style>
@@ -36,9 +92,9 @@ onMounted(async () => {
3692
}
3793

3894
.publication img {
39-
max-width: 200px;
95+
max-width: 150px;
4096
height: auto; /* Maintain aspect ratio */
41-
margin-left: 20px;
97+
margin-right: 20px;
4298
object-fit: contain; /* Ensure the image fits within the container while maintaining aspect ratio */
4399
}
44100

@@ -52,21 +108,81 @@ onMounted(async () => {
52108
}
53109

54110
.publication img {
55-
margin-left: 0;
111+
margin-right: 0;
56112
margin-bottom: 10px;
57113
}
58114
}
115+
116+
.v-icon-placeholder {
117+
width: 16px; /* Width of the icon */
118+
height: 16px; /* Height of the icon */
119+
display: inline-block;
120+
}
59121
</style>
60122

123+
<v-row
124+
justify="space-between"
125+
class="mb-4"
126+
>
127+
61128
# Publications
62129

130+
<v-btn-toggle
131+
v-model="sorting.sort"
132+
mandatory
133+
group
134+
>
135+
<v-btn value="year" @click="() => sorting.sort === 'year' && handleSortToggle('year')">
136+
Year
137+
<v-icon v-if="sorting.sort === 'year'" class="opacity-60">
138+
{{ (sorting.ascending) ? "mdi-arrow-up" : "mdi-arrow-down" }}
139+
</v-icon>
140+
<span v-else class="v-icon-placeholder"></span>
141+
</v-btn>
142+
<v-btn value="author" @click="handleSortToggle('author')">
143+
Author
144+
<v-icon v-if="sorting.sort === 'author'" class="opacity-60">
145+
{{ (sorting.ascending) ? "mdi-arrow-up" : "mdi-arrow-down" }}
146+
</v-icon>
147+
<span v-else class="v-icon-placeholder"></span>
148+
</v-btn>
149+
<v-btn value="title" @click="handleSortToggle('title')">
150+
Title
151+
<v-icon v-if="sorting.sort === 'title'" class="opacity-60">
152+
{{ (sorting.ascending) ? "mdi-arrow-up" : "mdi-arrow-down" }}
153+
</v-icon>
154+
<span v-else class="v-icon-placeholder"></span>
155+
</v-btn>
156+
<v-menu
157+
:close-on-content-click="false"
158+
location="bottom"
159+
>
160+
<template v-slot:activator="{ props }">
161+
<v-btn v-bind="props" icon="mdi-magnify" @click="() => {
162+
sorting.sort = previousSort // reset sorting to previous to ignore the click action
163+
openSearchBar()
164+
}">
165+
</v-btn>
166+
</template>
167+
<v-card min-width="300">
168+
<v-text-field
169+
v-model="search"
170+
hide-details
171+
label="Search"
172+
>
173+
</v-text-field>
174+
</v-card>
175+
</v-menu>
176+
</v-btn-toggle>
177+
</v-row>
178+
63179
<div class="container">
64-
<div v-for="publication in publications" :key="publication.title" class="publication">
180+
<div v-for="publication in sortedPublications" :key="publication.title" class="publication">
181+
<img v-if="publication.image" :src="`../assets/images/publications/${publication.image.src}`" :alt="publication.image.alt">
65182
<div class="publication-info">
66-
<a :href="publication.link" target="_blank">{{ publication.title }}</a>
67183
<p>{{ publication.authors }}</p>
184+
<a :href="publication.link" target="_blank">{{ publication.title }}</a>
68185
<p>{{ publication.conference }} ({{ publication.year }})</p>
69186
</div>
70-
<img v-if="publication.image" :src="`../assets/images/publications/${publication.image.src}`" :alt="publication.image.alt">
71187
</div>
72-
</div>
188+
</div>

0 commit comments

Comments
 (0)