Style paginator component
This commit is contained in:
parent
9846fab54c
commit
27d17874f4
@ -90,7 +90,7 @@ work_history:
|
|||||||
displayed: true
|
displayed: true
|
||||||
address:
|
address:
|
||||||
name: eSOLUTIONS s.r.o.
|
name: eSOLUTIONS s.r.o.
|
||||||
location: Hroncová 2
|
location: Gorkého 8
|
||||||
zipcode: 040 01
|
zipcode: 040 01
|
||||||
city: Košice
|
city: Košice
|
||||||
country: Slovakia
|
country: Slovakia
|
||||||
@ -151,7 +151,7 @@ projects:
|
|||||||
This project is built with modern web technologies including: **CycleJS**, **Reactive Streams**, **D3**, **Jest**, **Webpack**.
|
This project is built with modern web technologies including: **CycleJS**, **Reactive Streams**, **D3**, **Jest**, **Webpack**.
|
||||||
displayed: true
|
displayed: true
|
||||||
image:
|
image:
|
||||||
image_description: " responzIO main controller"
|
image_description: ' responzIO main controller'
|
||||||
source: /images/uploads/responzio.png
|
source: /images/uploads/responzio.png
|
||||||
name: responzIO
|
name: responzIO
|
||||||
- description: >-
|
- description: >-
|
||||||
@ -300,14 +300,16 @@ presentations:
|
|||||||
affect the future of the world.
|
affect the future of the world.
|
||||||
- displayed: true
|
- displayed: true
|
||||||
name: Spreading the web
|
name: Spreading the web
|
||||||
description: A presentation about the rising number of use cases for utilizing
|
description:
|
||||||
|
A presentation about the rising number of use cases for utilizing
|
||||||
web technologies outside of the web platform such as native mobile
|
web technologies outside of the web platform such as native mobile
|
||||||
applications and robotics.
|
applications and robotics.
|
||||||
link: https://michalvankodev.github.io/spreading-the-web/#/
|
link: https://michalvankodev.github.io/spreading-the-web/#/
|
||||||
- displayed: true
|
- displayed: true
|
||||||
name: Docker
|
name: Docker
|
||||||
link: http://michalvankodev.github.io/dockerpresentation/#/
|
link: http://michalvankodev.github.io/dockerpresentation/#/
|
||||||
description: An introduction to Docker containerization technology and how it
|
description:
|
||||||
|
An introduction to Docker containerization technology and how it
|
||||||
differs from virtualization.
|
differs from virtualization.
|
||||||
education:
|
education:
|
||||||
- description: |-
|
- description: |-
|
||||||
|
43
package-lock.json
generated
43
package-lock.json
generated
@ -8,7 +8,6 @@
|
|||||||
"name": "michalvankodev",
|
"name": "michalvankodev",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mobily/ts-belt": "^3.10.0",
|
|
||||||
"@vanilla-extract/css": "^1.6.8",
|
"@vanilla-extract/css": "^1.6.8",
|
||||||
"@vanilla-extract/sprinkles": "^1.4.0",
|
"@vanilla-extract/sprinkles": "^1.4.0",
|
||||||
"@vanilla-extract/vite-plugin": "^3.1.4",
|
"@vanilla-extract/vite-plugin": "^3.1.4",
|
||||||
@ -42,7 +41,8 @@
|
|||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.2",
|
||||||
"vite": "^2.8.6",
|
"vite": "^2.8.6",
|
||||||
"vitest": "^0.7.10"
|
"vitest": "^0.7.13",
|
||||||
|
"vitest-svelte-kit": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@ -447,14 +447,6 @@
|
|||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@mobily/ts-belt": {
|
|
||||||
"version": "3.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mobily/ts-belt/-/ts-belt-3.10.0.tgz",
|
|
||||||
"integrity": "sha512-F3XLU3zMDzJOf9KlKgnNOz5rdAtMG/UBxEDU4UNA4ewKFRd5DsbIIJmeAifLudNwcXmoIgtZ39KwVjPaL/CjgA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -4025,9 +4017,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "0.7.10",
|
"version": "0.7.13",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.7.10.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.7.13.tgz",
|
||||||
"integrity": "sha512-We5a7cnY2aUpX4tAO+w2KRhJiJ4FznfWjYKkqWoAqs4x4pKgyRsMJNZ7OSY/lFHOoRz3yv0mgwfVlZiRc0/mmA==",
|
"integrity": "sha512-UCHeJEOK+qCBa/e4UtkCfv0wIZ125T4Nf2R0J/46v/Wnv6bt9zGfAyKAI6siYFhvLvg20MgDIreROtVgedHFWw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
@ -4042,7 +4034,7 @@
|
|||||||
"vitest": "vitest.mjs"
|
"vitest": "vitest.mjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v14.16.0"
|
"node": ">=v14.19.1"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
@ -4068,6 +4060,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest-svelte-kit": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest-svelte-kit/-/vitest-svelte-kit-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-bQ1GcCAk600YV1xOiJBhltGE/HO/j6FozNY2BFq2GP1mHh3pj0KrGZlyx0kVlXx+BSKDXQHuYZtwlHwNlvv0fQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||||
@ -4498,11 +4496,6 @@
|
|||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@mobily/ts-belt": {
|
|
||||||
"version": "3.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mobily/ts-belt/-/ts-belt-3.10.0.tgz",
|
|
||||||
"integrity": "sha512-F3XLU3zMDzJOf9KlKgnNOz5rdAtMG/UBxEDU4UNA4ewKFRd5DsbIIJmeAifLudNwcXmoIgtZ39KwVjPaL/CjgA=="
|
|
||||||
},
|
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -6943,9 +6936,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vitest": {
|
"vitest": {
|
||||||
"version": "0.7.10",
|
"version": "0.7.13",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.7.10.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.7.13.tgz",
|
||||||
"integrity": "sha512-We5a7cnY2aUpX4tAO+w2KRhJiJ4FznfWjYKkqWoAqs4x4pKgyRsMJNZ7OSY/lFHOoRz3yv0mgwfVlZiRc0/mmA==",
|
"integrity": "sha512-UCHeJEOK+qCBa/e4UtkCfv0wIZ125T4Nf2R0J/46v/Wnv6bt9zGfAyKAI6siYFhvLvg20MgDIreROtVgedHFWw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
@ -6957,6 +6950,12 @@
|
|||||||
"vite": "^2.8.6"
|
"vite": "^2.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vitest-svelte-kit": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest-svelte-kit/-/vitest-svelte-kit-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-bQ1GcCAk600YV1xOiJBhltGE/HO/j6FozNY2BFq2GP1mHh3pj0KrGZlyx0kVlXx+BSKDXQHuYZtwlHwNlvv0fQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
"svgstore": "svgstore -o static/build/icons-sprite.svg src/svg/**.svg"
|
"svgstore": "svgstore -o static/build/icons-sprite.svg src/svg/**.svg"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mobily/ts-belt": "^3.10.0",
|
|
||||||
"@vanilla-extract/css": "^1.6.8",
|
"@vanilla-extract/css": "^1.6.8",
|
||||||
"@vanilla-extract/sprinkles": "^1.4.0",
|
"@vanilla-extract/sprinkles": "^1.4.0",
|
||||||
"@vanilla-extract/vite-plugin": "^3.1.4",
|
"@vanilla-extract/vite-plugin": "^3.1.4",
|
||||||
@ -50,6 +49,7 @@
|
|||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.2",
|
||||||
"vite": "^2.8.6",
|
"vite": "^2.8.6",
|
||||||
"vitest": "^0.7.10"
|
"vitest": "^0.7.13",
|
||||||
|
"vitest-svelte-kit": "^0.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<ul class={tagsListClass}>
|
<ul class={tagsListClass}>
|
||||||
{#each post.tags as tag}
|
{#each post.tags as tag}
|
||||||
<li class={tagsListLiClass}>
|
<li class={tagsListLiClass}>
|
||||||
<a href="/blog?tag={tag}">{tag}</a>
|
<a href="/blog/tags/{tag}">{tag}</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
21
src/components/paginator/Paginator.css.ts
Normal file
21
src/components/paginator/Paginator.css.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { sprinkles } from '$lib/styles/sprinkles.css'
|
||||||
|
|
||||||
|
export const listClass = sprinkles({
|
||||||
|
listStyle: 'none',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const listItemClass = sprinkles({
|
||||||
|
paddingX: '1x',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const activePage = sprinkles({
|
||||||
|
//fontStyle: 'italic',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
paddingX: '2x',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const pageLinkClass = sprinkles({
|
||||||
|
paddingX: '1x',
|
||||||
|
})
|
52
src/components/paginator/Paginator.svelte
Normal file
52
src/components/paginator/Paginator.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
activePage,
|
||||||
|
listClass,
|
||||||
|
listItemClass,
|
||||||
|
pageLinkClass,
|
||||||
|
} from './Paginator.css'
|
||||||
|
|
||||||
|
import { getPaginatorPages, createHref } from './paginatorUtils'
|
||||||
|
|
||||||
|
// TODO styles
|
||||||
|
export const Divider = 'divider'
|
||||||
|
|
||||||
|
export let href: string
|
||||||
|
export let page: number
|
||||||
|
export let pageSize: number
|
||||||
|
export let totalCount: number
|
||||||
|
export let filters: Record<string, string>
|
||||||
|
let paginatorPages: (number | typeof Divider)[]
|
||||||
|
|
||||||
|
$: paginatorPages = getPaginatorPages({ page, pageSize, totalCount })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class={listClass}>
|
||||||
|
{#if page !== 1}
|
||||||
|
<li class="{listItemClass} ">
|
||||||
|
<a class={pageLinkClass} href={createHref(href, filters, page - 1)}
|
||||||
|
><</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{#each paginatorPages as pageNumber}
|
||||||
|
{#if pageNumber === Divider}
|
||||||
|
<li class={listItemClass}>...</li>
|
||||||
|
{:else if page === pageNumber}
|
||||||
|
<li class="{listItemClass} {activePage}">{pageNumber}</li>
|
||||||
|
{:else}
|
||||||
|
<li class="{listItemClass} ">
|
||||||
|
<a class={pageLinkClass} href={createHref(href, filters, pageNumber)}
|
||||||
|
>{pageNumber}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{#if page !== paginatorPages.length}
|
||||||
|
<li class="{listItemClass} ">
|
||||||
|
<a class={pageLinkClass} href={createHref(href, filters, page + 1)}
|
||||||
|
>></a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
52
src/components/paginator/paginatorUtils.test.ts
Normal file
52
src/components/paginator/paginatorUtils.test.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
import { Divider, getPaginatorPages } from './paginatorUtils'
|
||||||
|
|
||||||
|
describe('Paginator component', () => {
|
||||||
|
describe('Paginator generates feasable pages to display', () => {
|
||||||
|
test('Page: 1/5', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 1, totalCount: 5, pageSize: 1 })
|
||||||
|
).toEqual([1, 2, 3, 4, 5])
|
||||||
|
})
|
||||||
|
test('Page: 4/7', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 4, totalCount: 7, pageSize: 1 })
|
||||||
|
).toEqual([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
})
|
||||||
|
test('Page: 4/8', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 4, totalCount: 8, pageSize: 1 })
|
||||||
|
).toEqual([1, 2, 3, 4, 5, 6, Divider, 8])
|
||||||
|
})
|
||||||
|
test('Page: 1/10', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 1, totalCount: 10, pageSize: 1 })
|
||||||
|
).toEqual([1, 2, 3, 4, 5, 6, Divider, 10])
|
||||||
|
})
|
||||||
|
test('Page: 2/10', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 2, totalCount: 10, pageSize: 1 })
|
||||||
|
).toEqual([1, 2, 3, 4, 5, 6, Divider, 10])
|
||||||
|
})
|
||||||
|
test('Page: 5/10', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 5, totalCount: 10, pageSize: 1 })
|
||||||
|
).toEqual([1, Divider, 3, 4, 5, 6, 7, Divider, 10])
|
||||||
|
})
|
||||||
|
test('Page: 7/10', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 7, totalCount: 10, pageSize: 1 })
|
||||||
|
).toEqual([1, Divider, 5, 6, 7, 8, 9, 10])
|
||||||
|
})
|
||||||
|
test('Page: 8/10', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 8, totalCount: 10, pageSize: 1 })
|
||||||
|
).toEqual([1, Divider, 5, 6, 7, 8, 9, 10])
|
||||||
|
})
|
||||||
|
test('Page: 10/10', () => {
|
||||||
|
expect(
|
||||||
|
getPaginatorPages({ page: 10, totalCount: 10, pageSize: 1 })
|
||||||
|
).toEqual([1, Divider, 5, 6, 7, 8, 9, 10])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
50
src/components/paginator/paginatorUtils.ts
Normal file
50
src/components/paginator/paginatorUtils.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { toParams } from '$lib/pagination/searchParams'
|
||||||
|
import { last, range } from 'ramda'
|
||||||
|
|
||||||
|
export const Divider = 'divider'
|
||||||
|
|
||||||
|
export function getPaginatorPages({
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
totalCount,
|
||||||
|
}: {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalCount: number
|
||||||
|
}) {
|
||||||
|
const maxLinksLength = 7
|
||||||
|
const linksAroundActive = 2
|
||||||
|
const totalPages = Math.ceil(totalCount / pageSize)
|
||||||
|
const daco = range(1, totalPages + 1).reduce((acc, link) => {
|
||||||
|
const isFirst = link === 1
|
||||||
|
const isLast = link === totalPages
|
||||||
|
const isPageOnStart = page <= 3 && link < maxLinksLength
|
||||||
|
const isPageOnEnd =
|
||||||
|
page >= totalPages - 3 && link > totalPages - maxLinksLength + 1
|
||||||
|
|
||||||
|
if ([isFirst, isLast, isPageOnStart, isPageOnEnd].some((value) => value)) {
|
||||||
|
return [...acc, link]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link < page - linksAroundActive || link > page + linksAroundActive) {
|
||||||
|
if (last(acc) === Divider) {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
return [...acc, Divider]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...acc, link]
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return daco
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createHref(
|
||||||
|
href: string,
|
||||||
|
filters: Record<string, string>,
|
||||||
|
pageNumber: number
|
||||||
|
) {
|
||||||
|
const filtersPath = toParams(filters)
|
||||||
|
console.log(filtersPath, filters)
|
||||||
|
return `/${href}/${filtersPath ? filtersPath + '/' : ''}page/${pageNumber}`
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
import { identity } from 'ramda'
|
import { identity, drop, take, pipe } from 'ramda'
|
||||||
import { flow, A } from '@mobily/ts-belt'
|
|
||||||
const { drop, take } = A
|
|
||||||
|
|
||||||
export interface PaginationQuery {
|
export interface PaginationQuery {
|
||||||
offset?: number
|
offset?: number
|
||||||
@ -14,7 +12,9 @@ export interface PaginationResult<ItemType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function dropAndTake<Item>({ offset = 0, limit = Infinity }) {
|
export function dropAndTake<Item>({ offset = 0, limit = Infinity }) {
|
||||||
return flow(drop<Item>(offset), take<Item>(limit))
|
return pipe(drop<Item>(offset), take<Item>(limit)) as (
|
||||||
|
items: Item[]
|
||||||
|
) => Item[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterByPropContains<Item>(filters: Record<string, string>) {
|
export function filterByPropContains<Item>(filters: Record<string, string>) {
|
||||||
|
@ -42,22 +42,37 @@ describe('get search params', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should parse values into searchParams for first page', () => {
|
test('should parse values into searchParams for first page', () => {
|
||||||
const params = 'tags/News/page/1'
|
const params = {
|
||||||
expect(getPaginationSearchParams(7, params).toString()).toEqual(
|
pageSize: 7,
|
||||||
|
page: 1,
|
||||||
|
filters: {
|
||||||
|
tags: 'News',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect(getPaginationSearchParams(params).toString()).toEqual(
|
||||||
'limit=7&offset=0&tags=News'
|
'limit=7&offset=0&tags=News'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should parse values into searchParams for third page', () => {
|
test('should parse values into searchParams for third page', () => {
|
||||||
const params = 'tags/News/page/3'
|
const params = {
|
||||||
expect(getPaginationSearchParams(7, params).toString()).toEqual(
|
pageSize: 7,
|
||||||
|
page: 3,
|
||||||
|
filters: {
|
||||||
|
tags: 'News',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect(getPaginationSearchParams(params).toString()).toEqual(
|
||||||
'limit=7&offset=14&tags=News'
|
'limit=7&offset=14&tags=News'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should return first page without any params specified', () => {
|
test('should return first page without any params specified', () => {
|
||||||
const params = ''
|
const params = {
|
||||||
expect(getPaginationSearchParams(7, params).toString()).toEqual(
|
pageSize: 7,
|
||||||
|
page: 1,
|
||||||
|
}
|
||||||
|
expect(getPaginationSearchParams(params).toString()).toEqual(
|
||||||
'limit=7&offset=0'
|
'limit=7&offset=0'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -34,12 +34,26 @@ export function parseParams(params: string) {
|
|||||||
return Object.fromEntries(splits)
|
return Object.fromEntries(splits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toParams(records: Record<string, string>) {
|
||||||
|
return Object.entries(records)
|
||||||
|
.map(([key, value]) => `${key}/${value}`)
|
||||||
|
.join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationSearchParams {
|
||||||
|
pageSize: number
|
||||||
|
page: number
|
||||||
|
filters?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert svelte `load` params into a `URLSearchParams` so they can be used to fetch endpoints with pagination queries
|
* Convert svelte `load` params into a `URLSearchParams` so they can be used to fetch endpoints with pagination queries
|
||||||
*/
|
*/
|
||||||
export function getPaginationSearchParams(pageSize: number, params: string) {
|
export function getPaginationSearchParams({
|
||||||
const { page = 1, ...filters } = parseParams(params)
|
pageSize,
|
||||||
|
page,
|
||||||
|
filters,
|
||||||
|
}: PaginationSearchParams) {
|
||||||
const offset = pageSize * (page - 1)
|
const offset = pageSize * (page - 1)
|
||||||
const limit = pageSize
|
const limit = pageSize
|
||||||
return new URLSearchParams({ limit, offset, ...filters })
|
return new URLSearchParams({ limit, offset, ...filters })
|
||||||
|
@ -1,28 +1,47 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import { getPaginationSearchParams } from '$lib/pagination/searchParams'
|
import {
|
||||||
|
getPaginationSearchParams,
|
||||||
|
parseParams,
|
||||||
|
} from '$lib/pagination/searchParams'
|
||||||
|
|
||||||
|
const pageSize = 7
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@sveltejs/kit').Load}
|
* @type {import('@sveltejs/kit').Load}
|
||||||
*/
|
*/
|
||||||
export async function load({ fetch, params }) {
|
export async function load({ fetch, params }) {
|
||||||
console.log('params', params)
|
console.log('params', params)
|
||||||
const searchParams = getPaginationSearchParams(7, params.params)
|
const { page = 1, ...filters } = parseParams(params.params)
|
||||||
|
const searchParams = getPaginationSearchParams({ pageSize, page, filters })
|
||||||
console.log('searchpprsm', searchParams)
|
console.log('searchpprsm', searchParams)
|
||||||
const articleResponse = await fetch(
|
const articleResponse = await fetch(
|
||||||
`/blog/articles?${searchParams.toString()}`
|
`/blog/articles?${searchParams.toString()}`
|
||||||
).then((r) => r.json())
|
).then((r) => r.json())
|
||||||
|
|
||||||
return { props: { posts: articleResponse.posts } }
|
return {
|
||||||
|
props: {
|
||||||
|
posts: articleResponse.posts,
|
||||||
|
page: Number(page),
|
||||||
|
pageSize,
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ArticleFooter from '../../components/blog/ArticleFooter.svelte'
|
import ArticleFooter from '../../components/blog/ArticleFooter.svelte'
|
||||||
|
import Paginator from '../../components/paginator/Paginator.svelte'
|
||||||
import { postListClass, seeAllClass } from './index.css'
|
import { postListClass, seeAllClass } from './index.css'
|
||||||
import type { PostContent } from './_content'
|
import type { PostContent } from './_content'
|
||||||
import type { PaginationResult } from '$lib/pagination/pagination'
|
import type { PaginationResult } from '$lib/pagination/pagination'
|
||||||
|
|
||||||
export let posts: PaginationResult<PostContent>
|
export let posts: PaginationResult<PostContent>
|
||||||
export let tagQuery: string
|
export let filters: Record<string, string>
|
||||||
|
export let page: number
|
||||||
|
export let pageSize: number
|
||||||
|
let totalPages = Math.ceil(posts.totalCount / pageSize)
|
||||||
|
// TODO display filter name
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -33,18 +52,28 @@
|
|||||||
<p class="no-posts">You've found void in the space.</p>
|
<p class="no-posts">You've found void in the space.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<h1>
|
<h1>
|
||||||
Recent
|
{#if filters.tags}
|
||||||
{#if tagQuery}
|
<em>{filters.tags}</em>
|
||||||
<em>{tagQuery}</em>
|
{:else}
|
||||||
|
Blog
|
||||||
{/if}
|
{/if}
|
||||||
posts
|
posts
|
||||||
</h1>
|
</h1>
|
||||||
{#if tagQuery}
|
{#if filters.tags}
|
||||||
<div class={seeAllClass}>
|
<div class={seeAllClass}>
|
||||||
<a href="/blog">See all posts</a>
|
<a href="/blog">See all posts</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
<header>
|
||||||
|
<Paginator
|
||||||
|
href="blog"
|
||||||
|
{page}
|
||||||
|
{pageSize}
|
||||||
|
{filters}
|
||||||
|
totalCount={posts.totalCount}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
<ul class="post-list {postListClass}">
|
<ul class="post-list {postListClass}">
|
||||||
{#each posts.items as post}
|
{#each posts.items as post}
|
||||||
<li>
|
<li>
|
||||||
@ -60,3 +89,12 @@
|
|||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
<footer>
|
||||||
|
<Paginator
|
||||||
|
href="blog"
|
||||||
|
{page}
|
||||||
|
{pageSize}
|
||||||
|
{filters}
|
||||||
|
totalCount={posts.totalCount}
|
||||||
|
/>
|
||||||
|
</footer>
|
||||||
|
@ -24,8 +24,8 @@ export async function getFeed() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const blogListing = await getBlogListing()
|
const blogListing = await getBlogListing({})
|
||||||
blogListing.forEach((post) => {
|
blogListing.items.forEach((post) => {
|
||||||
feed.addItem({
|
feed.addItem({
|
||||||
title: post.title,
|
title: post.title,
|
||||||
id: `https://michalvanko.dev/blog/${post.slug}`,
|
id: `https://michalvanko.dev/blog/${post.slug}`,
|
||||||
|
@ -10,6 +10,7 @@ const config = {
|
|||||||
kit: {
|
kit: {
|
||||||
adapter: adapterStatic(),
|
adapter: adapterStatic(),
|
||||||
vite: { plugins: [vanillaExtractPlugin()] },
|
vite: { plugins: [vanillaExtractPlugin()] },
|
||||||
|
prerender: { default: true }
|
||||||
},
|
},
|
||||||
preprocess: preprocess({
|
preprocess: preprocess({
|
||||||
sourceMap: dev,
|
sourceMap: dev,
|
||||||
|
3
vitest.config.js
Normal file
3
vitest.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { extractFromSvelteConfig } from "vitest-svelte-kit"
|
||||||
|
|
||||||
|
export default extractFromSvelteConfig()
|
Loading…
Reference in New Issue
Block a user