mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
feat: add pagination for search page
- it wasn't really making sense to have one query for all users/posts, future hashtags, because we change the first/offset when the user paginates, which would unneccesarily refetch all other resources. - the solution was to separate them into their own queries and only refetch when the user wants to paginate the resources.
This commit is contained in:
parent
79c1cc02c1
commit
e8492b59f4
@ -64,7 +64,7 @@ Factory.define('basicUser')
|
||||
password: '1234',
|
||||
role: 'user',
|
||||
about: faker.lorem.paragraph,
|
||||
termsAndConditionsAgreedVersion: '0.0.1',
|
||||
termsAndConditionsAgreedVersion: '0.0.4',
|
||||
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
|
||||
@ -929,7 +929,24 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
])
|
||||
|
||||
await Promise.all([...Array(30).keys()].map(() => Factory.build('user')))
|
||||
|
||||
await Promise.all(
|
||||
[...Array(30).keys()].map(index => Factory.build('user', { name: `Jenny${index}` })),
|
||||
)
|
||||
await Promise.all(
|
||||
[...Array(30).keys()].map(() =>
|
||||
Factory.build(
|
||||
'post',
|
||||
{ content: `Jenny ${faker.lorem.sentence()}` },
|
||||
{
|
||||
categoryIds: ['cat1'],
|
||||
author: jennyRostock,
|
||||
image: Factory.build('image', {
|
||||
url: faker.image.unsplash.objects(),
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
await Promise.all(
|
||||
[...Array(30).keys()].map(() =>
|
||||
Factory.build(
|
||||
|
||||
@ -87,6 +87,8 @@ export default shield(
|
||||
findPosts: allow,
|
||||
findUsers: allow,
|
||||
searchResults: allow,
|
||||
searchPosts: allow,
|
||||
searchUsers: allow,
|
||||
embed: allow,
|
||||
Category: allow,
|
||||
Tag: allow,
|
||||
|
||||
@ -5,6 +5,89 @@ import { queryString } from './searches/queryString'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
searchPosts: async (_parent, args, context, _resolveInfo) => {
|
||||
const { query, postsOffset, firstPosts } = args
|
||||
const { id: userId } = context.user
|
||||
|
||||
const postCypher = `
|
||||
CALL db.index.fulltext.queryNodes('post_fulltext_search', $query)
|
||||
YIELD node as posts, score
|
||||
MATCH (posts)<-[:WROTE]-(author:User)
|
||||
WHERE score >= 0.0
|
||||
AND NOT (
|
||||
author.deleted = true OR author.disabled = true
|
||||
OR posts.deleted = true OR posts.disabled = true
|
||||
OR (:User {id: $userId})-[:MUTED]->(author)
|
||||
)
|
||||
WITH posts, author,
|
||||
[(posts)<-[:COMMENTS]-(comment:Comment) | comment] as comments,
|
||||
[(posts)<-[:SHOUTED]-(user:User) | user] as shouter
|
||||
RETURN posts {
|
||||
.*,
|
||||
__typename: labels(posts)[0],
|
||||
author: properties(author),
|
||||
commentsCount: toString(size(comments)),
|
||||
shoutedCount: toString(size(shouter))
|
||||
}
|
||||
SKIP $postsOffset
|
||||
LIMIT $firstPosts
|
||||
`
|
||||
|
||||
const myQuery = queryString(query)
|
||||
|
||||
const session = context.driver.session()
|
||||
const searchResultPromise = session.readTransaction(async transaction => {
|
||||
const postTransactionResponse = await transaction.run(postCypher, {
|
||||
query: myQuery,
|
||||
postsOffset,
|
||||
firstPosts,
|
||||
userId,
|
||||
})
|
||||
return postTransactionResponse
|
||||
})
|
||||
try {
|
||||
const postResults = await searchResultPromise
|
||||
log(postResults)
|
||||
return postResults.records.map(record => record.get('posts'))
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
searchUsers: async (_parent, args, context, _resolveInfo) => {
|
||||
const { query, usersOffset, firstUsers } = args
|
||||
const { id: userId } = context.user
|
||||
|
||||
const userCypher = `
|
||||
CALL db.index.fulltext.queryNodes('user_fulltext_search', $query)
|
||||
YIELD node as users, score
|
||||
MATCH (users)
|
||||
WHERE score >= 0.0
|
||||
AND NOT (users.deleted = true OR users.disabled = true)
|
||||
RETURN users {.*, __typename: labels(users)[0]}
|
||||
SKIP $usersOffset
|
||||
LIMIT $firstUsers
|
||||
`
|
||||
const myQuery = queryString(query)
|
||||
|
||||
const session = context.driver.session()
|
||||
const searchResultPromise = session.readTransaction(async transaction => {
|
||||
const userTransactionResponse = await transaction.run(userCypher, {
|
||||
query: myQuery,
|
||||
usersOffset,
|
||||
firstUsers,
|
||||
userId,
|
||||
})
|
||||
return userTransactionResponse
|
||||
})
|
||||
|
||||
try {
|
||||
const userResults = await searchResultPromise
|
||||
log(userResults)
|
||||
return userResults.records.map(record => record.get('users'))
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
searchResults: async (_parent, args, context, _resolveInfo) => {
|
||||
const { query, limit } = args
|
||||
const { id: thisUserId } = context.user
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
union SearchResult = Post | User
|
||||
|
||||
type Query {
|
||||
searchPosts(query: String!, firstPosts: Int, postsOffset: Int): [Post]!
|
||||
searchUsers(query: String!, firstUsers: Int, usersOffset: Int): [User]!
|
||||
searchResults(query: String!, limit: Int = 5): [SearchResult]!
|
||||
}
|
||||
|
||||
@ -9,38 +9,54 @@
|
||||
icon="tasks"
|
||||
:message="$t('search.no-results', { search })"
|
||||
/>
|
||||
<masonry-grid v-else-if="activeTab === 'Post'">
|
||||
<masonry-grid-item v-for="resource in activeResources" :key="resource.key">
|
||||
<post-teaser :post="resource" />
|
||||
</masonry-grid-item>
|
||||
</masonry-grid>
|
||||
<template v-else-if="activeTab === 'Post'">
|
||||
<masonry-grid>
|
||||
<masonry-grid-item v-for="resource in activeResources" :key="resource.key">
|
||||
<post-teaser :post="resource" />
|
||||
</masonry-grid-item>
|
||||
</masonry-grid>
|
||||
<pagination-buttons
|
||||
:hasNext="hasMorePosts"
|
||||
:hasPrevious="hasPreviousPosts"
|
||||
@back="previousPosts"
|
||||
@next="nextPosts"
|
||||
/>
|
||||
</template>
|
||||
<ul v-else-if="activeTab === 'User'" class="user-list">
|
||||
<li v-for="resource in activeResources" :key="resource.key" class="item">
|
||||
<base-card :wideContent="true">
|
||||
<user-teaser :user="resource" />
|
||||
</base-card>
|
||||
</li>
|
||||
<pagination-buttons
|
||||
:hasNext="hasMoreUsers"
|
||||
:hasPrevious="hasPreviousUsers"
|
||||
@back="previousUsers"
|
||||
@next="nextUsers"
|
||||
/>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { searchQuery } from '~/graphql/Search.js'
|
||||
import { searchPosts, searchUsers } from '~/graphql/Search.js'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem'
|
||||
import PostTeaser from '~/components/PostTeaser/PostTeaser'
|
||||
import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation'
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TabNavigation,
|
||||
HcEmpty,
|
||||
MasonryGrid,
|
||||
MasonryGridItem,
|
||||
PostTeaser,
|
||||
TabNavigation,
|
||||
PaginationButtons,
|
||||
UserTeaser,
|
||||
},
|
||||
props: {
|
||||
@ -49,10 +65,18 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const pageSize = 25
|
||||
return {
|
||||
posts: [],
|
||||
users: [],
|
||||
activeTab: null,
|
||||
pageSize,
|
||||
firstPosts: pageSize,
|
||||
firstUsers: pageSize,
|
||||
postsOffset: 0,
|
||||
usersOffset: 0,
|
||||
hasMorePosts: false,
|
||||
hasMoreUsers: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -75,30 +99,72 @@ export default {
|
||||
},
|
||||
]
|
||||
},
|
||||
hasPreviousPosts() {
|
||||
return this.postsOffset > 0
|
||||
},
|
||||
hasPreviousUsers() {
|
||||
return this.usersOffset > 0
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
switchTab(tab) {
|
||||
this.activeTab = tab
|
||||
},
|
||||
previousPosts() {
|
||||
this.postsOffset = Math.max(this.postsOffset - this.pageSize, 0)
|
||||
},
|
||||
nextPosts() {
|
||||
this.postsOffset += this.pageSize
|
||||
},
|
||||
previousUsers() {
|
||||
this.usersOffset = Math.max(this.usersOffset - this.pageSize, 0)
|
||||
},
|
||||
nextUsers() {
|
||||
this.usersOffset += this.pageSize
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
searchResults: {
|
||||
searchPosts: {
|
||||
query() {
|
||||
return searchQuery
|
||||
return searchPosts
|
||||
},
|
||||
variables() {
|
||||
const { firstPosts, postsOffset, search } = this
|
||||
return {
|
||||
query: this.search,
|
||||
limit: 37,
|
||||
query: search,
|
||||
firstPosts,
|
||||
postsOffset,
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return !this.search
|
||||
},
|
||||
update({ searchResults }) {
|
||||
this.posts = searchResults.filter(result => result.__typename === 'Post')
|
||||
this.users = searchResults.filter(result => result.__typename === 'User')
|
||||
if (searchResults.length) this.activeTab = searchResults[0].__typename
|
||||
update({ searchPosts }) {
|
||||
this.posts = searchPosts
|
||||
this.hasMorePosts = this.posts.length >= this.pageSize
|
||||
if (searchPosts.length) this.activeTab = 'Post'
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
searchUsers: {
|
||||
query() {
|
||||
return searchUsers
|
||||
},
|
||||
variables() {
|
||||
const { firstUsers, usersOffset, search } = this
|
||||
return {
|
||||
query: search,
|
||||
firstUsers,
|
||||
usersOffset,
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return !this.search
|
||||
},
|
||||
update({ searchUsers }) {
|
||||
this.users = searchUsers
|
||||
this.hasMoreUsers = this.users.length >= this.pageSize
|
||||
if (!searchPosts.length && searchUsers.length) this.activeTab = 'User'
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
|
||||
@ -6,8 +6,13 @@ export const searchQuery = gql`
|
||||
${postFragment}
|
||||
${tagsCategoriesAndPinnedFragment}
|
||||
|
||||
query($query: String!, $limit: Int = 5) {
|
||||
searchResults(query: $query, limit: $limit) {
|
||||
query($query: String!, $firstPosts: Int, $firstUsers: Int, $offset: Int) {
|
||||
searchResults(
|
||||
query: $query
|
||||
firstPosts: $firstPosts
|
||||
firstUsers: $firstUsers
|
||||
offset: $offset
|
||||
) {
|
||||
__typename
|
||||
... on Post {
|
||||
...post
|
||||
@ -24,3 +29,33 @@ export const searchQuery = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const searchPosts = gql`
|
||||
${userFragment}
|
||||
${postFragment}
|
||||
${tagsCategoriesAndPinnedFragment}
|
||||
|
||||
query($query: String!, $firstPosts: Int, $postsOffset: Int) {
|
||||
searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) {
|
||||
__typename
|
||||
...post
|
||||
...tagsCategoriesAndPinned
|
||||
commentsCount
|
||||
shoutedCount
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const searchUsers = gql`
|
||||
${userFragment}
|
||||
|
||||
query($query: String!, $firstUsers: Int, $usersOffset: Int) {
|
||||
searchUsers(query: $query, firstUsers: $firstUsers, usersOffset: $usersOffset) {
|
||||
__typename
|
||||
...user
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user