@ulu/frontend-vue 0.1.0-beta.14 → 0.1.0-beta.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.
- package/dist/{breakpoints-t2PT-Tjo.js → breakpoints-BRe-TsJk.js} +1 -1
- package/dist/frontend-vue.js +12 -11
- package/dist/{index-DKFsbs3Z.js → index-BSMuClr2.js} +1449 -1404
- package/lib/components/navigation/UluPager.vue +102 -0
- package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
- package/lib/composables/index.js +2 -1
- package/lib/composables/usePagination.js +122 -0
- package/package.json +1 -1
- package/types/composables/index.d.ts +1 -0
- package/types/composables/usePagination.d.ts +25 -0
- package/types/composables/usePagination.d.ts.map +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav v-if="items" class="pager" role="navigation" :aria-labelledby="headingId">
|
|
3
|
+
<component :is="titleElement" :id="headingId" class="hidden-visually">Pagination</component>
|
|
4
|
+
<ul class="pager__items js-pager__items">
|
|
5
|
+
<!-- First page link -->
|
|
6
|
+
<li v-if="items.first" class="pager__item pager__item--first">
|
|
7
|
+
<router-link :to="items.first.href" title="Go to first page" v-bind="items.first.attributes">
|
|
8
|
+
<span class="hidden-visually">First page</span>
|
|
9
|
+
<UluIcon icon="fas fa-angle-double-left" aria-hidden="true" />
|
|
10
|
+
</router-link>
|
|
11
|
+
</li>
|
|
12
|
+
|
|
13
|
+
<!-- Previous page link -->
|
|
14
|
+
<li v-if="items.previous" class="pager__item pager__item--previous">
|
|
15
|
+
<router-link :to="items.previous.href" title="Go to previous page" rel="prev" v-bind="items.previous.attributes">
|
|
16
|
+
<span class="hidden-visually">Previous page</span>
|
|
17
|
+
<UluIcon icon="fas fa-angle-left" aria-hidden="true" />
|
|
18
|
+
</router-link>
|
|
19
|
+
</li>
|
|
20
|
+
|
|
21
|
+
<!-- Ellipsis for previous pages -->
|
|
22
|
+
<li v-if="ellipses.previous" class="pager__item pager__item--ellipsis" role="presentation">…</li>
|
|
23
|
+
|
|
24
|
+
<!-- Page number links -->
|
|
25
|
+
<li v-for="(item, key) in items.pages" :key="key" :class="['pager__item', { 'is-active': current == key }]">
|
|
26
|
+
<router-link :to="item.href" :title="getPageTitle(key)" v-bind="item.attributes">
|
|
27
|
+
<span class="hidden-visually">
|
|
28
|
+
{{ current == key ? 'Current page' : 'Page' }}
|
|
29
|
+
</span>
|
|
30
|
+
{{ key }}
|
|
31
|
+
</router-link>
|
|
32
|
+
</li>
|
|
33
|
+
|
|
34
|
+
<!-- Ellipsis for next pages -->
|
|
35
|
+
<li v-if="ellipses.next" class="pager__item pager__item--ellipsis" role="presentation">…</li>
|
|
36
|
+
|
|
37
|
+
<!-- Next page link -->
|
|
38
|
+
<li v-if="items.next" class="pager__item pager__item--next">
|
|
39
|
+
<router-link :to="items.next.href" title="Go to next page" rel="next" v-bind="items.next.attributes">
|
|
40
|
+
<span class="hidden-visually">Next page</span>
|
|
41
|
+
<UluIcon icon="fas fa-angle-right" aria-hidden="true" />
|
|
42
|
+
</router-link>
|
|
43
|
+
</li>
|
|
44
|
+
|
|
45
|
+
<!-- Last page link -->
|
|
46
|
+
<li v-if="items.last" class="pager__item pager__item--last">
|
|
47
|
+
<router-link :to="items.last.href" title="Go to last page" v-bind="items.last.attributes">
|
|
48
|
+
<span class="hidden-visually">Last page</span>
|
|
49
|
+
<UluIcon icon="fas fa-angle-double-right" aria-hidden="true" />
|
|
50
|
+
</router-link>
|
|
51
|
+
</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</nav>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup>
|
|
57
|
+
import UluIcon from '../elements/UluIcon.vue';
|
|
58
|
+
|
|
59
|
+
let pagerCounter = 0;
|
|
60
|
+
|
|
61
|
+
const props = defineProps({
|
|
62
|
+
/**
|
|
63
|
+
* The HTML element to use for the visually hidden title.
|
|
64
|
+
*/
|
|
65
|
+
titleElement: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: 'h4'
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* List of pager items.
|
|
71
|
+
*/
|
|
72
|
+
items: {
|
|
73
|
+
type: Object,
|
|
74
|
+
default: () => ({})
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* The page number of the current page.
|
|
78
|
+
*/
|
|
79
|
+
current: {
|
|
80
|
+
type: Number,
|
|
81
|
+
default: 1
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Ellipses configuration.
|
|
85
|
+
*/
|
|
86
|
+
ellipses: {
|
|
87
|
+
type: Object,
|
|
88
|
+
default: () => ({})
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const headingId = `ulu-pager-${ pagerCounter++ }`;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generates the title for a page link.
|
|
96
|
+
* @param {string|number} key - The page number.
|
|
97
|
+
* @returns {string} The title for the page link.
|
|
98
|
+
*/
|
|
99
|
+
function getPageTitle(key) {
|
|
100
|
+
return props.current == key ? 'Current page' : `Go to page ${key}`;
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="BibliographyList">
|
|
3
|
+
<LayoutListPage title="Bibliography" icon="type:bibliography">
|
|
4
|
+
<template #intro>
|
|
5
|
+
<AppContent uid="bibliographyIntroduction" />
|
|
6
|
+
</template>
|
|
7
|
+
<template #default>
|
|
8
|
+
<UluFacetsSidebarLayout>
|
|
9
|
+
<template #sidebar>
|
|
10
|
+
<UluFacetsSearch v-model="searchValue" />
|
|
11
|
+
<UluFacetsSort v-model="selectedSort" :sort-types="sortTypes" />
|
|
12
|
+
<UluFacetsFilters :facets="facets" @facet-change="handleFacetChange" />
|
|
13
|
+
</template>
|
|
14
|
+
<template #main>
|
|
15
|
+
<UluFacetsResults :items="paginatedItems">
|
|
16
|
+
<template #item="{ item }">
|
|
17
|
+
<div class="source-item">
|
|
18
|
+
<h3>{{ item.title || "NO TITLE" }}</h3>
|
|
19
|
+
<div>
|
|
20
|
+
<PortableText v-if="item.citation" :value="item.citation" />
|
|
21
|
+
</div>
|
|
22
|
+
<small v-if="item.publicationDate">Published on: {{ item.publicationDate }}</small>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
</UluFacetsResults>
|
|
26
|
+
<UluPager
|
|
27
|
+
v-if="totalPages > 1"
|
|
28
|
+
:items="pagerItems"
|
|
29
|
+
:current="currentPage"
|
|
30
|
+
:ellipses="pagerEllipses"
|
|
31
|
+
class="mt-4"
|
|
32
|
+
/>
|
|
33
|
+
</template>
|
|
34
|
+
</UluFacetsSidebarLayout>
|
|
35
|
+
</template>
|
|
36
|
+
</LayoutListPage>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup>
|
|
41
|
+
import { ref } from "vue";
|
|
42
|
+
import { PortableText } from "@portabletext/vue";
|
|
43
|
+
import sources from "@/api/virtual/sources.js?virtual-module";
|
|
44
|
+
import {
|
|
45
|
+
useFacets,
|
|
46
|
+
usePagination,
|
|
47
|
+
UluFacetsSidebarLayout,
|
|
48
|
+
UluFacetsFilters,
|
|
49
|
+
UluFacetsSort,
|
|
50
|
+
UluFacetsSearch,
|
|
51
|
+
UluFacetsResults
|
|
52
|
+
} from "@ulu/frontend-vue";
|
|
53
|
+
|
|
54
|
+
const sorterDateLatest = (a, b) => new Date(b.publicationDate) - new Date(a.publicationDate);
|
|
55
|
+
|
|
56
|
+
const config = {
|
|
57
|
+
facetFields: [
|
|
58
|
+
{ name: "Chapters", uid: "chapters", open: false, getValue: item => item.chapters?.map(c => c.uuid) },
|
|
59
|
+
{ name: "Types", uid: "types", open: true },
|
|
60
|
+
{ name: "Topics", uid: "topics", open: true },
|
|
61
|
+
{ name: "Citation Type", uid: "citationType", open: true },
|
|
62
|
+
{ name: "Source Type", uid: "sourceType", open: true },
|
|
63
|
+
{ name: "Authors", uid: "authors", open: false },
|
|
64
|
+
{ name: "Source Name", uid: "sourceName", open: false }
|
|
65
|
+
],
|
|
66
|
+
extraSortTypes: {
|
|
67
|
+
newest: {
|
|
68
|
+
text: "Date (Newest)",
|
|
69
|
+
sort: items => [...items].sort(sorterDateLatest)
|
|
70
|
+
},
|
|
71
|
+
oldest: {
|
|
72
|
+
text: "Date (Oldest)",
|
|
73
|
+
sort: items => [...items].sort(sorterDateLatest).reverse()
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
initialSortType: "az",
|
|
77
|
+
// Remove quotes and stuff from beginning when sorting
|
|
78
|
+
getSortValue: item => item.title ? item.title.replace(/^[^A-Za-z0-9]+/, "") : "",
|
|
79
|
+
searchOptions: {
|
|
80
|
+
keys: ["title", "authors", "sourceName", "topics", "types", "citation"]
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const itemsPerPage = 20;
|
|
85
|
+
|
|
86
|
+
const {
|
|
87
|
+
facets,
|
|
88
|
+
searchValue,
|
|
89
|
+
selectedSort,
|
|
90
|
+
sortTypes,
|
|
91
|
+
displayItems,
|
|
92
|
+
handleFacetChange,
|
|
93
|
+
} = useFacets(ref(sources), config);
|
|
94
|
+
|
|
95
|
+
const {
|
|
96
|
+
currentPage,
|
|
97
|
+
totalPages,
|
|
98
|
+
paginatedItems,
|
|
99
|
+
pagerItems,
|
|
100
|
+
pagerEllipses
|
|
101
|
+
} = usePagination(displayItems, itemsPerPage);
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<style lang="scss">
|
|
105
|
+
// Add some basic styling for the item display
|
|
106
|
+
.source-item {
|
|
107
|
+
padding: 1rem;
|
|
108
|
+
border-bottom: 1px solid #eee;
|
|
109
|
+
|
|
110
|
+
h3 {
|
|
111
|
+
margin: 0 0 0.5rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
p {
|
|
115
|
+
margin: 0 0 0.5rem;
|
|
116
|
+
font-style: italic;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
</style>
|
package/lib/composables/index.js
CHANGED
|
@@ -8,4 +8,5 @@ export { useIcon } from './useIcon.js';
|
|
|
8
8
|
export { useModifiers } from './useModifiers.js';
|
|
9
9
|
export { useWindowResize } from './useWindowResize.js';
|
|
10
10
|
export { useRequiredInject } from './useRequiredInject.js';
|
|
11
|
-
export { useBreakpointManager } from './useBreakpointManager.js';
|
|
11
|
+
export { useBreakpointManager } from './useBreakpointManager.js';
|
|
12
|
+
export { usePagination } from './usePagination.js';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { computed, watch } from "vue";
|
|
2
|
+
import { useRoute, useRouter } from "vue-router";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A Vue composable for handling pagination logic.
|
|
6
|
+
* It interacts with vue-router to keep the current page in the URL query string.
|
|
7
|
+
*
|
|
8
|
+
* @param {import('vue').Ref<Array<any>>} items - A ref containing the full list of items to be paginated.
|
|
9
|
+
* @param {number} itemsPerPage - The number of items to display per page.
|
|
10
|
+
* @returns {{
|
|
11
|
+
* currentPage: import('vue').ComputedRef<number>,
|
|
12
|
+
* totalPages: import('vue').ComputedRef<number>,
|
|
13
|
+
* paginatedItems: import('vue').ComputedRef<Array<any>>,
|
|
14
|
+
* pagerItems: import('vue').ComputedRef<object|null>,
|
|
15
|
+
* pagerEllipses: import('vue').ComputedRef<{previous: boolean, next: boolean}>
|
|
16
|
+
* }} - An object containing reactive properties for pagination.
|
|
17
|
+
*/
|
|
18
|
+
export function usePagination(items, itemsPerPage) {
|
|
19
|
+
const route = useRoute();
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
|
|
22
|
+
const currentPage = computed(() => {
|
|
23
|
+
const page = parseInt(route.query.page || '1', 10);
|
|
24
|
+
return isNaN(page) || page < 1 ? 1 : page;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const totalPages = computed(() => {
|
|
28
|
+
if (!items.value || items.value.length === 0) return 1;
|
|
29
|
+
return Math.ceil(items.value.length / itemsPerPage);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
watch(totalPages, (newTotalPages) => {
|
|
33
|
+
if (currentPage.value > newTotalPages) {
|
|
34
|
+
router.push({ query: { ...route.query, page: newTotalPages } });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const paginatedItems = computed(() => {
|
|
39
|
+
const start = (currentPage.value - 1) * itemsPerPage;
|
|
40
|
+
const end = start + itemsPerPage;
|
|
41
|
+
return items.value.slice(start, end);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const pagerItems = computed(() => {
|
|
45
|
+
if (totalPages.value <= 1) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const items = {
|
|
50
|
+
pages: {}
|
|
51
|
+
};
|
|
52
|
+
const page = currentPage.value;
|
|
53
|
+
const total = totalPages.value;
|
|
54
|
+
const maxPagesToShow = 5;
|
|
55
|
+
|
|
56
|
+
const createRoute = (p) => {
|
|
57
|
+
return { query: { ...route.query, page: p } };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (page > 1) {
|
|
61
|
+
items.first = { href: createRoute(1) };
|
|
62
|
+
items.previous = { href: createRoute(page - 1) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (page < total) {
|
|
66
|
+
items.next = { href: createRoute(page + 1) };
|
|
67
|
+
items.last = { href: createRoute(total) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let startPage, endPage;
|
|
71
|
+
if (total <= maxPagesToShow) {
|
|
72
|
+
startPage = 1;
|
|
73
|
+
endPage = total;
|
|
74
|
+
} else {
|
|
75
|
+
const maxPagesBeforeCurrent = Math.floor(maxPagesToShow / 2);
|
|
76
|
+
const maxPagesAfterCurrent = Math.ceil(maxPagesToShow / 2) - 1;
|
|
77
|
+
if (page <= maxPagesBeforeCurrent) {
|
|
78
|
+
startPage = 1;
|
|
79
|
+
endPage = maxPagesToShow;
|
|
80
|
+
} else if (page + maxPagesAfterCurrent >= total) {
|
|
81
|
+
startPage = total - maxPagesToShow + 1;
|
|
82
|
+
endPage = total;
|
|
83
|
+
} else {
|
|
84
|
+
startPage = page - maxPagesBeforeCurrent;
|
|
85
|
+
endPage = page + maxPagesAfterCurrent;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (let i = startPage; i <= endPage; i++) {
|
|
90
|
+
items.pages[i] = { href: createRoute(i) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return items;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const pagerEllipses = computed(() => {
|
|
97
|
+
const ellipses = { previous: false, next: false };
|
|
98
|
+
if (!pagerItems.value || !pagerItems.value.pages) return ellipses;
|
|
99
|
+
|
|
100
|
+
const pageKeys = Object.keys(pagerItems.value.pages).map(Number);
|
|
101
|
+
if (pageKeys.length === 0) return ellipses;
|
|
102
|
+
|
|
103
|
+
const firstPageInPager = Math.min(...pageKeys);
|
|
104
|
+
const lastPageInPager = Math.max(...pageKeys);
|
|
105
|
+
|
|
106
|
+
if (firstPageInPager > 1) {
|
|
107
|
+
ellipses.previous = true;
|
|
108
|
+
}
|
|
109
|
+
if (lastPageInPager < totalPages.value) {
|
|
110
|
+
ellipses.next = true;
|
|
111
|
+
}
|
|
112
|
+
return ellipses;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
currentPage,
|
|
117
|
+
totalPages,
|
|
118
|
+
paginatedItems,
|
|
119
|
+
pagerItems,
|
|
120
|
+
pagerEllipses
|
|
121
|
+
};
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -3,4 +3,5 @@ export { useModifiers } from "./useModifiers.js";
|
|
|
3
3
|
export { useWindowResize } from "./useWindowResize.js";
|
|
4
4
|
export { useRequiredInject } from "./useRequiredInject.js";
|
|
5
5
|
export { useBreakpointManager } from "./useBreakpointManager.js";
|
|
6
|
+
export { usePagination } from "./usePagination.js";
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Vue composable for handling pagination logic.
|
|
3
|
+
* It interacts with vue-router to keep the current page in the URL query string.
|
|
4
|
+
*
|
|
5
|
+
* @param {import('vue').Ref<Array<any>>} items - A ref containing the full list of items to be paginated.
|
|
6
|
+
* @param {number} itemsPerPage - The number of items to display per page.
|
|
7
|
+
* @returns {{
|
|
8
|
+
* currentPage: import('vue').ComputedRef<number>,
|
|
9
|
+
* totalPages: import('vue').ComputedRef<number>,
|
|
10
|
+
* paginatedItems: import('vue').ComputedRef<Array<any>>,
|
|
11
|
+
* pagerItems: import('vue').ComputedRef<object|null>,
|
|
12
|
+
* pagerEllipses: import('vue').ComputedRef<{previous: boolean, next: boolean}>
|
|
13
|
+
* }} - An object containing reactive properties for pagination.
|
|
14
|
+
*/
|
|
15
|
+
export function usePagination(items: import("vue").Ref<Array<any>>, itemsPerPage: number): {
|
|
16
|
+
currentPage: import("vue").ComputedRef<number>;
|
|
17
|
+
totalPages: import("vue").ComputedRef<number>;
|
|
18
|
+
paginatedItems: import("vue").ComputedRef<Array<any>>;
|
|
19
|
+
pagerItems: import("vue").ComputedRef<object | null>;
|
|
20
|
+
pagerEllipses: import("vue").ComputedRef<{
|
|
21
|
+
previous: boolean;
|
|
22
|
+
next: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=usePagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePagination.d.ts","sourceRoot":"","sources":["../../lib/composables/usePagination.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,qCAVW,OAAO,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,gBAC7B,MAAM,GACJ;IACR,WAAW,EAAE,OAAO,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/C,UAAU,EAAE,OAAO,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C,cAAc,EAAE,OAAO,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,UAAU,EAAE,OAAO,KAAK,EAAE,WAAW,CAAC,MAAM,GAAC,IAAI,CAAC,CAAC;IACnD,aAAa,EAAE,OAAO,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAC,CAAC,CAAA;CAC7E,CA0GH"}
|