Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
b6fd070b
You need to sign in or sign up before continuing.
Commit
b6fd070b
authored
Sep 08, 2019
by
Nick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: list pages by tags + fix search permissions
parent
5202eade
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
184 additions
and
36 deletions
+184
-36
admin-pages.vue
client/components/admin/admin-pages.vue
+1
-1
tags.vue
client/components/tags.vue
+101
-8
common-pages-query-list.gql
client/graph/common/common-pages-query-list.gql
+14
-0
icon-info.svg
client/static/svg/icon-info.svg
+2
-0
page.js
server/graph/resolvers/page.js
+62
-26
page.graphql
server/graph/schemas/page.graphql
+4
-1
No files found.
client/components/admin/admin-pages.vue
View file @
b6fd070b
...
@@ -65,7 +65,7 @@
...
@@ -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
>
...
...
client/components/tags.vue
View file @
b6fd070b
...
@@ -15,7 +15,7 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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'
...
@@ -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 {
...
@@ -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 {
...
@@ -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 {
...
@@ -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 {
...
@@ -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 {
...
@@ -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 {
...
@@ -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
}
}
}
}
}
}
}
...
...
client/graph/common/common-pages-query-list.gql
0 → 100644
View file @
b6fd070b
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
}
}
}
client/static/svg/icon-info.svg
0 → 100644
View file @
b6fd070b
<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>
\ No newline at end of file
server/graph/resolvers/page.js
View file @
b6fd070b
const
_
=
require
(
'lodash'
)
const
graphHelper
=
require
(
'../../helpers/graph'
)
const
graphHelper
=
require
(
'../../helpers/graph'
)
/* global WIKI */
/* global WIKI */
...
@@ -19,7 +20,16 @@ module.exports = {
...
@@ -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 = {
...
@@ -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,29 +51,55 @@ module.exports = {
...
@@ -41,29 +51,55 @@ module.exports = {
'contentType'
,
'contentType'
,
'createdAt'
,
'createdAt'
,
'updatedAt'
'updatedAt'
]).
modify
(
queryBuilder
=>
{
])
if
(
args
.
limit
)
{
.
eagerAlgorithm
(
WIKI
.
models
.
Objection
.
Model
.
JoinEagerAlgorithm
)
queryBuilder
.
limit
(
args
.
limit
)
.
eager
(
'tags(selectTags)'
,
{
}
selectTags
:
builder
=>
{
const
orderDir
=
args
.
orderByDirection
===
'DESC'
?
'desc'
:
'asc'
builder
.
select
(
'tag'
)
switch
(
args
.
orderBy
)
{
}
case
'CREATED'
:
})
queryBuilder
.
orderBy
(
'createdAt'
,
orderDir
)
.
modify
(
queryBuilder
=>
{
break
if
(
args
.
limit
)
{
case
'PATH'
:
queryBuilder
.
limit
(
args
.
limit
)
queryBuilder
.
orderBy
(
'path'
,
orderDir
)
}
break
if
(
args
.
locale
)
{
case
'TITLE'
:
queryBuilder
.
where
(
'localeCode'
,
args
.
locale
)
queryBuilder
.
orderBy
(
'title'
,
orderDir
)
}
break
if
(
args
.
tags
&&
args
.
tags
.
length
>
0
)
{
case
'UPDATED'
:
queryBuilder
.
whereIn
(
'tags.tag'
,
args
.
tags
)
queryBuilder
.
orderBy
(
'updatedAt'
,
orderDir
)
}
break
const
orderDir
=
args
.
orderByDirection
===
'DESC'
?
'desc'
:
'asc'
default
:
switch
(
args
.
orderBy
)
{
queryBuilder
.
orderBy
(
'id'
,
orderDir
)
case
'CREATED'
:
break
queryBuilder
.
orderBy
(
'createdAt'
,
orderDir
)
}
break
})
case
'PATH'
:
queryBuilder
.
orderBy
(
'path'
,
orderDir
)
break
case
'TITLE'
:
queryBuilder
.
orderBy
(
'title'
,
orderDir
)
break
case
'UPDATED'
:
queryBuilder
.
orderBy
(
'updatedAt'
,
orderDir
)
break
default
:
queryBuilder
.
orderBy
(
'pages.id'
,
orderDir
)
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
)
...
...
server/graph/schemas/page.graphql
View file @
b6fd070b
...
@@ -31,7 +31,9 @@ type PageQuery {
...
@@ -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 {
...
@@ -177,6 +179,7 @@ type PageListItem {
privateNS
:
String
privateNS
:
String
createdAt
:
Date
!
createdAt
:
Date
!
updatedAt
:
Date
!
updatedAt
:
Date
!
tags
:
[
String
]
}
}
enum
PageOrderBy
{
enum
PageOrderBy
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment