feat: list pages by tags + fix search permissions
This commit is contained in:
parent
5202eadebb
commit
b6fd070b0b
@ -65,7 +65,7 @@
|
|||||||
td {{ props.item.updatedAt | moment('calendar') }}
|
td {{ props.item.updatedAt | moment('calendar') }}
|
||||||
template(slot='no-data')
|
template(slot='no-data')
|
||||||
v-alert.ma-3(icon='mdi-alert', :value='true', outlined) No pages to display.
|
v-alert.ma-3(icon='mdi-alert', :value='true', outlined) No pages to display.
|
||||||
.text-xs-center.py-2.animated.fadeInDown(v-if='this.pageTotal > 1')
|
.text-center.py-2.animated.fadeInDown(v-if='this.pageTotal > 1')
|
||||||
v-pagination(v-model='pagination', :length='pageTotal')
|
v-pagination(v-model='pagination', :length='pageTotal')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
v-icon(v-if='isSelected(tag.tag)', color='primary') mdi-checkbox-intermediate
|
v-icon(v-if='isSelected(tag.tag)', color='primary') mdi-checkbox-intermediate
|
||||||
v-icon(v-else) mdi-checkbox-blank-outline
|
v-icon(v-else) mdi-checkbox-blank-outline
|
||||||
v-list-item-title {{tag.title}}
|
v-list-item-title {{tag.title}}
|
||||||
v-content
|
v-content.grey(:class='$vuetify.theme.dark ? `darken-4-d5` : `lighten-3`')
|
||||||
v-toolbar(color='primary', dark, flat, height='58')
|
v-toolbar(color='primary', dark, flat, height='58')
|
||||||
template(v-if='selection.length > 0')
|
template(v-if='selection.length > 0')
|
||||||
.overline.mr-3.animated.fadeInLeft Current Selection
|
.overline.mr-3.animated.fadeInLeft Current Selection
|
||||||
@ -41,6 +41,7 @@
|
|||||||
.overline.animated.fadeInRight Select one or more tags
|
.overline.animated.fadeInRight Select one or more tags
|
||||||
v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-l5` : `grey lighten-4`', flat, height='58')
|
v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-l5` : `grey lighten-4`', flat, height='58')
|
||||||
v-text-field.tags-search(
|
v-text-field.tags-search(
|
||||||
|
v-model='innerSearch'
|
||||||
label='Search within results...'
|
label='Search within results...'
|
||||||
solo
|
solo
|
||||||
hide-details
|
hide-details
|
||||||
@ -50,6 +51,7 @@
|
|||||||
height='40'
|
height='40'
|
||||||
prepend-icon='mdi-file-document-box-search-outline'
|
prepend-icon='mdi-file-document-box-search-outline'
|
||||||
append-icon='mdi-arrow-right'
|
append-icon='mdi-arrow-right'
|
||||||
|
clearable
|
||||||
)
|
)
|
||||||
template(v-if='locales.length > 1')
|
template(v-if='locales.length > 1')
|
||||||
v-divider.mx-3(vertical)
|
v-divider.mx-3(vertical)
|
||||||
@ -86,9 +88,62 @@
|
|||||||
v-btn(text, height='40'): v-icon(size='20') mdi-chevron-double-up
|
v-btn(text, height='40'): v-icon(size='20') mdi-chevron-double-up
|
||||||
v-btn(text, height='40'): v-icon(size='20') mdi-chevron-double-down
|
v-btn(text, height='40'): v-icon(size='20') mdi-chevron-double-down
|
||||||
v-divider
|
v-divider
|
||||||
.text-center.pt-10
|
.text-center.pt-10(v-if='selection.length < 1')
|
||||||
img(src='/svg/icon-price-tag.svg')
|
img(src='/svg/icon-price-tag.svg')
|
||||||
.subtitle-2.grey--text Select one or more tags on the left.
|
.subtitle-2.grey--text Select one or more tags on the left.
|
||||||
|
.px-5.py-2(v-else)
|
||||||
|
v-data-iterator(
|
||||||
|
:items='pages'
|
||||||
|
:items-per-page='4'
|
||||||
|
:search='innerSearch'
|
||||||
|
:loading='isLoading'
|
||||||
|
:options.sync='pagination'
|
||||||
|
hide-default-footer
|
||||||
|
ref='dude'
|
||||||
|
)
|
||||||
|
template(v-slot:loading)
|
||||||
|
.text-center.pt-10
|
||||||
|
v-progress-circular(
|
||||||
|
indeterminate
|
||||||
|
color='primary'
|
||||||
|
size='96'
|
||||||
|
width='2'
|
||||||
|
)
|
||||||
|
.subtitle-2.grey--text.mt-5 Retrieving page results...
|
||||||
|
template(v-slot:no-data)
|
||||||
|
.text-center.pt-10
|
||||||
|
img(src='/svg/icon-info.svg')
|
||||||
|
.subtitle-2.grey--text Couldn't find any page with the selected tags.
|
||||||
|
template(v-slot:no-results)
|
||||||
|
.text-center.pt-10
|
||||||
|
img(src='/svg/icon-info.svg')
|
||||||
|
.subtitle-2.grey--text Couldn't find any page matching the current filtering options.
|
||||||
|
template(v-slot:default='props')
|
||||||
|
v-row(align='stretch')
|
||||||
|
v-col(
|
||||||
|
v-for='item of props.items'
|
||||||
|
:key='`page-` + item.id'
|
||||||
|
cols='12'
|
||||||
|
lg='6'
|
||||||
|
)
|
||||||
|
v-card.radius-7(
|
||||||
|
@click='goTo(item)'
|
||||||
|
style='height:100%;'
|
||||||
|
:class='$vuetify.theme.dark ? `grey darken-4` : ``'
|
||||||
|
)
|
||||||
|
v-card-text
|
||||||
|
.d-flex.flex-row.align-center
|
||||||
|
.body-1: strong.primary--text {{item.title}}
|
||||||
|
v-spacer
|
||||||
|
.caption Last updated {{item.updatedAt | moment('from')}}
|
||||||
|
.body-2.grey--text {{item.description || '---'}}
|
||||||
|
v-divider.my-2
|
||||||
|
.d-flex.flex-row.align-center
|
||||||
|
v-chip(small, label, :color='$vuetify.theme.dark ? `grey darken-3-l5` : `grey lighten-4`').overline {{item.locale}}
|
||||||
|
.caption.ml-1 / {{item.path}}
|
||||||
|
.text-center.py-2.animated.fadeInDown(v-if='this.pageTotal > 1')
|
||||||
|
v-pagination(v-model='pagination.page', :length='pageTotal')
|
||||||
|
|
||||||
nav-footer
|
nav-footer
|
||||||
notify
|
notify
|
||||||
search-results
|
search-results
|
||||||
@ -100,6 +155,7 @@ import VueRouter from 'vue-router'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import tagsQuery from 'gql/common/common-pages-query-tags.gql'
|
import tagsQuery from 'gql/common/common-pages-query-tags.gql'
|
||||||
|
import pagesQuery from 'gql/common/common-pages-query-list.gql'
|
||||||
|
|
||||||
/* global siteLangs */
|
/* global siteLangs */
|
||||||
|
|
||||||
@ -113,17 +169,27 @@ export default {
|
|||||||
return {
|
return {
|
||||||
tags: [],
|
tags: [],
|
||||||
selection: [],
|
selection: [],
|
||||||
|
innerSearch: '',
|
||||||
locale: 'any',
|
locale: 'any',
|
||||||
locales: [],
|
locales: [],
|
||||||
orderBy: 'TITLE',
|
orderBy: 'title',
|
||||||
orderByItems: [
|
orderByItems: [
|
||||||
{ text: 'Creation Date', value: 'CREATED' },
|
{ text: 'Creation Date', value: 'createdAt' },
|
||||||
{ text: 'ID', value: 'ID' },
|
{ text: 'ID', value: 'id' },
|
||||||
{ text: 'Last Modified', value: 'UPDATED' },
|
{ text: 'Last Modified', value: 'updatedAt' },
|
||||||
{ text: 'Path', value: 'PATH' },
|
{ text: 'Path', value: 'path' },
|
||||||
{ text: 'Title', value: 'TITLE' }
|
{ text: 'Title', value: 'title' }
|
||||||
],
|
],
|
||||||
orderByDirection: 0,
|
orderByDirection: 0,
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
itemsPerPage: 12,
|
||||||
|
mustSort: true,
|
||||||
|
sortBy: ['title'],
|
||||||
|
sortDesc: [false]
|
||||||
|
},
|
||||||
|
pages: [],
|
||||||
|
isLoading: true,
|
||||||
scrollStyle: {
|
scrollStyle: {
|
||||||
vuescroll: {},
|
vuescroll: {},
|
||||||
scrollPanel: {
|
scrollPanel: {
|
||||||
@ -154,6 +220,9 @@ export default {
|
|||||||
},
|
},
|
||||||
tagsSelected () {
|
tagsSelected () {
|
||||||
return _.filter(this.tags, t => _.includes(this.selection, t.tag))
|
return _.filter(this.tags, t => _.includes(this.selection, t.tag))
|
||||||
|
},
|
||||||
|
pageTotal () {
|
||||||
|
return Math.ceil(this.pages.length / this.pagination.itemsPerPage)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -162,9 +231,11 @@ export default {
|
|||||||
},
|
},
|
||||||
orderBy (newValue, oldValue) {
|
orderBy (newValue, oldValue) {
|
||||||
this.rebuildURL()
|
this.rebuildURL()
|
||||||
|
this.pagination.sortBy = [newValue]
|
||||||
},
|
},
|
||||||
orderByDirection (newValue, oldValue) {
|
orderByDirection (newValue, oldValue) {
|
||||||
this.rebuildURL()
|
this.rebuildURL()
|
||||||
|
this.pagination.sortDesc = [newValue === 1]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
router,
|
router,
|
||||||
@ -186,6 +257,7 @@ export default {
|
|||||||
this.selection.push(tag)
|
this.selection.push(tag)
|
||||||
}
|
}
|
||||||
this.rebuildURL()
|
this.rebuildURL()
|
||||||
|
console.info(this.$refs.dude)
|
||||||
},
|
},
|
||||||
isSelected (tag) {
|
isSelected (tag) {
|
||||||
return _.includes(this.selection, tag)
|
return _.includes(this.selection, tag)
|
||||||
@ -204,6 +276,9 @@ export default {
|
|||||||
_.set(urlObj, 'query.dir', this.orderByDirection === 0 ? `asc` : `desc`)
|
_.set(urlObj, 'query.dir', this.orderByDirection === 0 ? `asc` : `desc`)
|
||||||
}
|
}
|
||||||
this.$router.push(urlObj)
|
this.$router.push(urlObj)
|
||||||
|
},
|
||||||
|
goTo (page) {
|
||||||
|
window.location.assign(`/${page.locale}/${page.path}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
@ -214,6 +289,24 @@ export default {
|
|||||||
watchLoading (isLoading) {
|
watchLoading (isLoading) {
|
||||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'tags-refresh')
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'tags-refresh')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
query: pagesQuery,
|
||||||
|
fetchPolicy: 'cache-and-network',
|
||||||
|
update: (data) => _.cloneDeep(data.pages.list),
|
||||||
|
watchLoading (isLoading) {
|
||||||
|
this.isLoading = isLoading
|
||||||
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'pages-refresh')
|
||||||
|
},
|
||||||
|
variables () {
|
||||||
|
return {
|
||||||
|
locale: this.locale === 'any' ? null : this.locale,
|
||||||
|
tags: this.selection
|
||||||
|
}
|
||||||
|
},
|
||||||
|
skip () {
|
||||||
|
return this.selection.length < 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
client/graph/common/common-pages-query-list.gql
Normal file
14
client/graph/common/common-pages-query-list.gql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
query ($limit: Int, $orderBy: PageOrderBy, $orderByDirection: PageOrderByDirection, $tags: [String!], $locale: String) {
|
||||||
|
pages {
|
||||||
|
list(limit: $limit, orderBy: $orderBy, orderByDirection: $orderByDirection, tags: $tags, locale: $locale) {
|
||||||
|
id
|
||||||
|
locale
|
||||||
|
path
|
||||||
|
title
|
||||||
|
description
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
client/static/svg/icon-info.svg
Normal file
1
client/static/svg/icon-info.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="256" height="256"><path fill="#fff" d="M64,14c27.6,0,50,22.4,50,50c0,27.6-22.4,50-50,50c-27.6,0-50-22.4-50-50C14,36.4,36.4,14,64,14"/><path fill="#e6e7e7" d="M64,14c-0.2,0-0.4,0-0.6,0c-1.5,0-3.1,0.1-4.6,0.3c-0.3,0-0.7,0.1-1,0.1 c24.6,3.1,43.7,24.1,43.7,49.6c0,25.5-19.1,46.5-43.7,49.6c0.5,0.1,1,0.1,1.6,0.2c1.2,0.1,2.5,0.2,3.7,0.2c0.3,0,0.6,0,0.9,0 c27.6,0,50-22.4,50-50C114,36.4,91.6,14,64,14"/><path fill="#454b54" d="M64,117c-29.2,0-53-23.8-53-53s23.8-53,53-53s53,23.8,53,53S93.2,117,64,117z M64,17 c-25.9,0-47,21.1-47,47s21.1,47,47,47s47-21.1,47-47S89.9,17,64,17z"/><path fill="#454b54" d="M64 42.7c-1.7 0-3 1.3-3 3s1.3 3 3 3c1.7 0 3-1.3 3-3S65.7 42.7 64 42.7zM64 93c-1.7 0-3-1.3-3-3V62.3c0-1.7 1.3-3 3-3 1.7 0 3 1.3 3 3V90C67 91.7 65.7 93 64 93z"/></svg>
|
After Width: | Height: | Size: 828 B |
@ -1,3 +1,4 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
const graphHelper = require('../../helpers/graph')
|
const graphHelper = require('../../helpers/graph')
|
||||||
|
|
||||||
/* global WIKI */
|
/* global WIKI */
|
||||||
@ -19,7 +20,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
async search (obj, args, context) {
|
async search (obj, args, context) {
|
||||||
if (WIKI.data.searchEngine) {
|
if (WIKI.data.searchEngine) {
|
||||||
return WIKI.data.searchEngine.query(args.query, args)
|
const resp = await WIKI.data.searchEngine.query(args.query, args)
|
||||||
|
return {
|
||||||
|
...resp,
|
||||||
|
results: _.filter(resp.results, r => {
|
||||||
|
return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
|
||||||
|
path: r.path,
|
||||||
|
locale: r.locale
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
results: [],
|
results: [],
|
||||||
@ -29,8 +39,8 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async list (obj, args, context, info) {
|
async list (obj, args, context, info) {
|
||||||
return WIKI.models.pages.query().column([
|
let results = await WIKI.models.pages.query().column([
|
||||||
'id',
|
'pages.id',
|
||||||
'path',
|
'path',
|
||||||
{ locale: 'localeCode' },
|
{ locale: 'localeCode' },
|
||||||
'title',
|
'title',
|
||||||
@ -41,10 +51,23 @@ module.exports = {
|
|||||||
'contentType',
|
'contentType',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
]).modify(queryBuilder => {
|
])
|
||||||
|
.eagerAlgorithm(WIKI.models.Objection.Model.JoinEagerAlgorithm)
|
||||||
|
.eager('tags(selectTags)', {
|
||||||
|
selectTags: builder => {
|
||||||
|
builder.select('tag')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.modify(queryBuilder => {
|
||||||
if (args.limit) {
|
if (args.limit) {
|
||||||
queryBuilder.limit(args.limit)
|
queryBuilder.limit(args.limit)
|
||||||
}
|
}
|
||||||
|
if (args.locale) {
|
||||||
|
queryBuilder.where('localeCode', args.locale)
|
||||||
|
}
|
||||||
|
if (args.tags && args.tags.length > 0) {
|
||||||
|
queryBuilder.whereIn('tags.tag', args.tags)
|
||||||
|
}
|
||||||
const orderDir = args.orderByDirection === 'DESC' ? 'desc' : 'asc'
|
const orderDir = args.orderByDirection === 'DESC' ? 'desc' : 'asc'
|
||||||
switch (args.orderBy) {
|
switch (args.orderBy) {
|
||||||
case 'CREATED':
|
case 'CREATED':
|
||||||
@ -60,10 +83,23 @@ module.exports = {
|
|||||||
queryBuilder.orderBy('updatedAt', orderDir)
|
queryBuilder.orderBy('updatedAt', orderDir)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
queryBuilder.orderBy('id', orderDir)
|
queryBuilder.orderBy('pages.id', orderDir)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
results = _.filter(results, r => {
|
||||||
|
return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
|
||||||
|
path: r.path,
|
||||||
|
locale: r.locale
|
||||||
|
})
|
||||||
|
}).map(r => ({
|
||||||
|
...r,
|
||||||
|
tags: _.map(r.tags, 'tag')
|
||||||
|
}))
|
||||||
|
if (args.tags && args.tags.length > 0) {
|
||||||
|
results = _.filter(results, r => _.every(args.tags, t => _.includes(r.tags, t)))
|
||||||
|
}
|
||||||
|
return results
|
||||||
},
|
},
|
||||||
async single (obj, args, context, info) {
|
async single (obj, args, context, info) {
|
||||||
let page = await WIKI.models.pages.getPageFromDb(args.id)
|
let page = await WIKI.models.pages.getPageFromDb(args.id)
|
||||||
|
@ -31,7 +31,9 @@ type PageQuery {
|
|||||||
limit: Int
|
limit: Int
|
||||||
orderBy: PageOrderBy
|
orderBy: PageOrderBy
|
||||||
orderByDirection: PageOrderByDirection
|
orderByDirection: PageOrderByDirection
|
||||||
): [PageListItem!]! @auth(requires: ["manage:system"])
|
tags: [String!]
|
||||||
|
locale: String
|
||||||
|
): [PageListItem!]! @auth(requires: ["manage:system", "read:pages"])
|
||||||
|
|
||||||
single(
|
single(
|
||||||
id: Int!
|
id: Int!
|
||||||
@ -177,6 +179,7 @@ type PageListItem {
|
|||||||
privateNS: String
|
privateNS: String
|
||||||
createdAt: Date!
|
createdAt: Date!
|
||||||
updatedAt: Date!
|
updatedAt: Date!
|
||||||
|
tags: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PageOrderBy {
|
enum PageOrderBy {
|
||||||
|
Loading…
Reference in New Issue
Block a user