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
1c80faa9
Commit
1c80faa9
authored
Apr 12, 2020
by
NGPixel
Committed by
Nicolas Giard
Apr 18, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: browse nav + pageTree ancestors
parent
3ca72ccc
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
210 additions
and
62 deletions
+210
-62
admin-navigation.vue
client/components/admin/admin-navigation.vue
+32
-14
nav-sidebar.vue
client/themes/default/components/nav-sidebar.vue
+74
-10
page.vue
client/themes/default/components/page.vue
+12
-12
data.yml
server/app/data.yml
+2
-0
2.3.23.js
server/db/migrations-sqlite/2.3.23.js
+8
-0
2.3.23.js
server/db/migrations/2.3.23.js
+8
-0
navigation.js
server/graph/resolvers/navigation.js
+21
-4
page.js
server/graph/resolvers/page.js
+34
-20
navigation.graphql
server/graph/schemas/navigation.graphql
+14
-0
page.graphql
server/graph/schemas/page.graphql
+1
-1
rebuild-tree.js
server/jobs/rebuild-tree.js
+4
-1
No files found.
client/components/admin/admin-navigation.vue
View file @
1c80faa9
...
...
@@ -20,35 +20,35 @@
v-toolbar(color='teal', dark, dense, flat, height='56')
v-toolbar-title.subtitle-1
{{
$t
(
'admin:navigation.mode'
)
}}
v-list(nav, two-line)
v-list-item-group(v-model='
navM
ode', mandatory, :color='$vuetify.theme.dark ? `teal lighten-3` : `teal`')
v-list-item(value='
classic
')
v-list-item-group(v-model='
config.m
ode', mandatory, :color='$vuetify.theme.dark ? `teal lighten-3` : `teal`')
v-list-item(value='
TREE
')
v-list-item-avatar
img(src='/svg/icon-tree-structure-dotted.svg', alt='Site Tree')
v-list-item-content
v-list-item-title
{{
$t
(
'admin:navigation.modeSiteTree.title'
)
}}
v-list-item-subtitle
{{
$t
(
'admin:navigation.modeSiteTree.description'
)
}}
v-list-item-avatar
v-icon(v-if='$vuetify.theme.dark', :color='
navMode === `classic
` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
v-icon(v-else, :color='
navMode === `classic
` ? `teal` : `grey lighten-3`') mdi-check-circle
v-list-item(value='
custom
')
v-icon(v-if='$vuetify.theme.dark', :color='
config.mode === `TREE
` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
v-icon(v-else, :color='
config.mode === `TREE
` ? `teal` : `grey lighten-3`') mdi-check-circle
v-list-item(value='
MIXED
')
v-list-item-avatar
img(src='/svg/icon-user-menu-male-dotted.svg', alt='Custom Navigation')
v-list-item-content
v-list-item-title
{{
$t
(
'admin:navigation.modeCustom.title'
)
}}
v-list-item-subtitle
{{
$t
(
'admin:navigation.modeCustom.description'
)
}}
v-list-item-avatar
v-icon(v-if='$vuetify.theme.dark', :color='
navMode === `custom
` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
v-icon(v-else, :color='
navMode === `custom
` ? `teal` : `grey lighten-3`') mdi-check-circle
v-list-item(value='
none
')
v-icon(v-if='$vuetify.theme.dark', :color='
config.mode === `MIXED
` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
v-icon(v-else, :color='
config.mode === `MIXED
` ? `teal` : `grey lighten-3`') mdi-check-circle
v-list-item(value='
NONE
')
v-list-item-avatar
img(src='/svg/icon-cancel-dotted.svg', alt='None')
v-list-item-content
v-list-item-title
{{
$t
(
'admin:navigation.modeNone.title'
)
}}
v-list-item-subtitle
{{
$t
(
'admin:navigation.modeNone.description'
)
}}
v-list-item-avatar
v-icon(v-if='$vuetify.theme.dark', :color='
navM
ode === `none` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
v-icon(v-else, :color='
navM
ode === `none` ? `teal` : `grey lighten-3`') mdi-check-circle
v-col(cols='9', v-if='
navMode === `custom
`')
v-icon(v-if='$vuetify.theme.dark', :color='
config.m
ode === `none` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
v-icon(v-else, :color='
config.m
ode === `none` ? `teal` : `grey lighten-3`') mdi-check-circle
v-col(cols='9', v-if='
config.mode === `MIXED
`')
v-card.animated.fadeInUp.wait-p2s
v-row(no-gutters, align='stretch')
v-col(style='flex: 0 0 350px;')
...
...
@@ -232,7 +232,7 @@
<
script
>
import
_
from
'lodash'
import
gql
from
'graphql-tag'
import
uuid
from
'uuid/v4
'
import
{
v4
as
uuid
}
from
'uuid
'
import
groupsQuery
from
'gql/admin/users/users-query-groups.gql'
...
...
@@ -247,11 +247,13 @@ export default {
data
()
{
return
{
selectPageModal
:
false
,
navMode
:
'custom'
,
navTree
:
[],
current
:
{
}
,
currentLang
:
'en'
,
groups
:
[]
groups
:
[],
config
:
{
mode
:
'NONE'
}
}
}
,
computed
:
{
...
...
@@ -355,6 +357,22 @@ export default {
this
.
currentLang
=
siteConfig
.
lang
}
,
apollo
:
{
config
:
{
query
:
gql
`
{
navigation {
config {
mode
}
}
}
`
,
fetchPolicy
:
'network-only'
,
update
:
(
data
)
=>
_
.
cloneDeep
(
data
.
navigation
.
config
),
watchLoading
(
isLoading
)
{
this
.
$store
.
commit
(
`loading${isLoading ? 'Start' : 'Stop'
}
`
,
'admin-navigation-config'
)
}
}
,
navTree
:
{
query
:
gql
`
{
...
...
client/themes/default/components/nav-sidebar.vue
View file @
1c80faa9
...
...
@@ -38,7 +38,7 @@
v-list-item-title
{{
item
.
title
}}
v-list-item(v-else, :href='`/` + item.path', :key='`childpage-` + item.id', :input-value='path === item.path')
v-list-item-avatar(size='24')
v-icon mdi-
file-documen
t-box
v-icon mdi-
tex
t-box
v-list-item-title
{{
item
.
title
}}
</
template
>
...
...
@@ -74,7 +74,8 @@ export default {
id
:
0
,
title
:
'/ (root)'
},
all
:
[]
parents
:
[],
loadedCache
:
[]
}
},
computed
:
{
...
...
@@ -84,25 +85,40 @@ export default {
methods
:
{
switchMode
(
mode
)
{
this
.
currentMode
=
mode
if
(
mode
===
`browse`
)
{
this
.
fetchBrowseItems
()
if
(
mode
===
`browse`
&&
this
.
loadedCache
.
length
<
1
)
{
this
.
loadFromCurrentPath
()
}
},
async
fetchBrowseItems
(
item
)
{
this
.
$store
.
commit
(
`loadingStart`
,
'browse-load'
)
if
(
!
item
)
{
item
=
this
.
currentParent
}
if
(
this
.
loadedCache
.
indexOf
(
item
.
id
)
<
0
)
{
this
.
currentItems
=
[]
}
if
(
item
.
id
===
0
)
{
this
.
parents
=
[]
}
else
{
if
(
!
_
.
some
(
this
.
parents
,
[
'id'
,
item
.
id
]))
{
const
flushRightIndex
=
_
.
findIndex
(
this
.
parents
,
[
'id'
,
item
.
id
])
if
(
flushRightIndex
>=
0
)
{
this
.
parents
=
_
.
take
(
this
.
parents
,
flushRightIndex
)
}
if
(
this
.
parents
.
length
<
1
)
{
this
.
parents
.
push
(
this
.
currentParent
)
}
this
.
currentParent
=
item
this
.
parents
.
push
(
item
)
}
this
.
currentParent
=
item
const
resp
=
await
this
.
$apollo
.
query
({
query
:
gql
`
query ($parent: Int
!
, $locale: String!) {
query ($parent: Int, $locale: String!) {
pages {
tree(parent: $parent, mode: ALL, locale: $locale
, includeParents: true
) {
tree(parent: $parent, mode: ALL, locale: $locale) {
id
path
title
...
...
@@ -119,15 +135,63 @@ export default {
locale
:
this
.
locale
}
})
this
.
loadedCache
=
_
.
union
(
this
.
loadedCache
,
[
item
.
id
])
this
.
currentItems
=
_
.
get
(
resp
,
'data.pages.tree'
,
[])
this
.
all
.
push
(...
this
.
currentItems
)
this
.
$store
.
commit
(
`loadingStop`
,
'browse-load'
)
},
async
loadFromCurrentPath
()
{
this
.
$store
.
commit
(
`loadingStart`
,
'browse-load'
)
const
resp
=
await
this
.
$apollo
.
query
({
query
:
gql
`
query ($path: String, $locale: String!) {
pages {
tree(path: $path, mode: ALL, locale: $locale, includeAncestors: true) {
id
path
title
isFolder
pageId
parent
}
}
}
`
,
fetchPolicy
:
'cache-first'
,
variables
:
{
path
:
this
.
path
,
locale
:
this
.
locale
}
})
const
items
=
_
.
get
(
resp
,
'data.pages.tree'
,
[])
const
curPage
=
_
.
find
(
items
,
[
'pageId'
,
this
.
$store
.
get
(
'page/id'
)])
if
(
!
curPage
)
{
console
.
warn
(
'Could not find current page in page tree listing!'
)
return
}
let
curParentId
=
curPage
.
parent
let
invertedAncestors
=
[]
while
(
curParentId
)
{
const
curParent
=
_
.
find
(
items
,
[
'id'
,
curParentId
])
if
(
!
curParent
)
{
break
}
invertedAncestors
.
push
(
curParent
)
curParentId
=
curParent
.
parent
}
this
.
parents
=
[
this
.
currentParent
,
...
invertedAncestors
.
reverse
()]
this
.
currentParent
=
_
.
last
(
this
.
parents
)
this
.
loadedCache
=
[
curPage
.
parent
]
this
.
currentItems
=
_
.
filter
(
items
,
[
'parent'
,
curPage
.
parent
])
this
.
$store
.
commit
(
`loadingStop`
,
'browse-load'
)
}
},
mounted
()
{
this
.
currentMode
=
this
.
mode
if
(
this
.
mode
===
'browse'
)
{
this
.
fetchBrowseItems
()
this
.
loadFromCurrentPath
()
}
}
}
...
...
client/themes/default/components/page.vue
View file @
1c80faa9
...
...
@@ -409,19 +409,19 @@ export default {
}
},
created
()
{
this
.
$store
.
commit
(
'page/SET_AUTHOR_ID
'
,
this
.
authorId
)
this
.
$store
.
commit
(
'page/SET_AUTHOR_NAME
'
,
this
.
authorName
)
this
.
$store
.
commit
(
'page/SET_CREATED_AT
'
,
this
.
createdAt
)
this
.
$store
.
commit
(
'page/SET_DESCRIPTION
'
,
this
.
description
)
this
.
$store
.
commit
(
'page/SET_IS_PUBLISHED
'
,
this
.
isPublished
)
this
.
$store
.
commit
(
'page/SET_ID
'
,
this
.
pageId
)
this
.
$store
.
commit
(
'page/SET_LOCALE
'
,
this
.
locale
)
this
.
$store
.
commit
(
'page/SET_PATH
'
,
this
.
path
)
this
.
$store
.
commit
(
'page/SET_TAGS
'
,
this
.
tags
)
this
.
$store
.
commit
(
'page/SET_TITLE
'
,
this
.
title
)
this
.
$store
.
commit
(
'page/SET_UPDATED_AT
'
,
this
.
updatedAt
)
this
.
$store
.
set
(
'page/authorId
'
,
this
.
authorId
)
this
.
$store
.
set
(
'page/authorName
'
,
this
.
authorName
)
this
.
$store
.
set
(
'page/createdAt
'
,
this
.
createdAt
)
this
.
$store
.
set
(
'page/description
'
,
this
.
description
)
this
.
$store
.
set
(
'page/isPublished
'
,
this
.
isPublished
)
this
.
$store
.
set
(
'page/id
'
,
this
.
pageId
)
this
.
$store
.
set
(
'page/locale
'
,
this
.
locale
)
this
.
$store
.
set
(
'page/path
'
,
this
.
path
)
this
.
$store
.
set
(
'page/tags
'
,
this
.
tags
)
this
.
$store
.
set
(
'page/title
'
,
this
.
title
)
this
.
$store
.
set
(
'page/updatedAt
'
,
this
.
updatedAt
)
this
.
$store
.
commit
(
'page/SET_MODE
'
,
'view'
)
this
.
$store
.
set
(
'page/mode
'
,
'view'
)
},
mounted
()
{
// -> Check side navigation visibility
...
...
server/app/data.yml
View file @
1c80faa9
...
...
@@ -45,6 +45,8 @@ defaults:
company
:
'
'
contentLicense
:
'
'
logoUrl
:
https://static.requarks.io/logo/wikijs-butterfly.svg
nav
:
mode
:
'
MIXED'
theming
:
theme
:
'
default'
iconset
:
'
md'
...
...
server/db/migrations-sqlite/2.3.23.js
0 → 100644
View file @
1c80faa9
exports
.
up
=
knex
=>
{
return
knex
.
schema
.
alterTable
(
'pageTree'
,
table
=>
{
table
.
json
(
'ancestors'
)
})
}
exports
.
down
=
knex
=>
{
}
server/db/migrations/2.3.23.js
0 → 100644
View file @
1c80faa9
exports
.
up
=
knex
=>
{
return
knex
.
schema
.
alterTable
(
'pageTree'
,
table
=>
{
table
.
json
(
'ancestors'
)
})
}
exports
.
down
=
knex
=>
{
}
server/graph/resolvers/navigation.js
View file @
1c80faa9
...
...
@@ -4,18 +4,21 @@ const graphHelper = require('../../helpers/graph')
module
.
exports
=
{
Query
:
{
async
navigation
()
{
return
{}
}
async
navigation
()
{
return
{}
}
},
Mutation
:
{
async
navigation
()
{
return
{}
}
async
navigation
()
{
return
{}
}
},
NavigationQuery
:
{
async
tree
(
obj
,
args
,
context
,
info
)
{
async
tree
(
obj
,
args
,
context
,
info
)
{
return
WIKI
.
models
.
navigation
.
getTree
({
cache
:
false
,
locale
:
'all'
})
},
config
(
obj
,
args
,
context
,
info
)
{
return
WIKI
.
config
.
nav
}
},
NavigationMutation
:
{
async
updateTree
(
obj
,
args
,
context
)
{
async
updateTree
(
obj
,
args
,
context
)
{
try
{
await
WIKI
.
models
.
navigation
.
query
().
patch
({
config
:
args
.
tree
...
...
@@ -28,6 +31,20 @@ module.exports = {
}
catch
(
err
)
{
return
graphHelper
.
generateError
(
err
)
}
},
async
updateConfig
(
obj
,
args
,
context
)
{
try
{
WIKI
.
config
.
nav
=
{
mode
:
args
.
mode
}
await
WIKI
.
configSvc
.
saveToDb
([
'nav'
])
return
{
responseResult
:
graphHelper
.
generateSuccess
(
'Navigation config updated successfully'
)
}
}
catch
(
err
)
{
return
graphHelper
.
generateError
(
err
)
}
}
}
}
server/graph/resolvers/page.js
View file @
1c80faa9
...
...
@@ -196,27 +196,41 @@ module.exports = {
* FETCH PAGE TREE
*/
async
tree
(
obj
,
args
,
context
,
info
)
{
let
results
=
[]
let
conds
=
{
localeCode
:
args
.
locale
}
if
(
args
.
parent
)
{
conds
.
parent
=
(
args
.
parent
<
1
)
?
null
:
args
.
parent
}
else
if
(
args
.
path
)
{
// conds.parent = (args.parent < 1) ? null : args.parent
}
switch
(
args
.
mode
)
{
case
'FOLDERS'
:
conds
.
isFolder
=
true
results
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
conds
)
break
case
'PAGES'
:
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
conds
).
andWhereNotNull
(
'pageId'
)
break
default
:
results
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
conds
)
break
let
curPage
=
null
if
(
!
args
.
locale
)
{
args
.
locale
=
WIKI
.
config
.
lang
.
code
}
if
(
args
.
path
&&
!
args
.
parent
)
{
curPage
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
first
(
'parent'
,
'ancestors'
).
where
({
path
:
args
.
path
,
localeCode
:
args
.
locale
})
if
(
curPage
)
{
args
.
parent
=
curPage
.
parent
||
0
}
else
{
return
[]
}
}
const
results
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
builder
=>
{
builder
.
where
(
'localeCode'
,
args
.
locale
)
switch
(
args
.
mode
)
{
case
'FOLDERS'
:
builder
.
andWhere
(
'isFolder'
,
true
)
break
case
'PAGES'
:
builder
.
andWhereNotNull
(
'pageId'
)
break
}
if
(
!
args
.
parent
||
args
.
parent
<
1
)
{
builder
.
whereNull
(
'parent'
)
}
else
{
builder
.
where
(
'parent'
,
args
.
parent
)
if
(
args
.
includeAncestors
&&
curPage
&&
curPage
.
ancestors
.
length
>
0
)
{
builder
.
orWhereIn
(
'id'
,
curPage
.
ancestors
)
}
}
}).
orderBy
([{
column
:
'isFolder'
,
order
:
'desc'
},
'title'
])
return
results
.
filter
(
r
=>
{
return
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'read:pages'
],
{
path
:
r
.
path
,
...
...
server/graph/schemas/navigation.graphql
View file @
1c80faa9
...
...
@@ -16,6 +16,7 @@ extend type Mutation {
type
NavigationQuery
{
tree
:
[
NavigationTree
]!
config
:
NavigationConfig
!
}
# -----------------------------------------------
...
...
@@ -26,6 +27,9 @@ type NavigationMutation {
updateTree
(
tree
:
[
NavigationTreeInput
]!
):
DefaultResponse
@
auth
(
requires
:
[
"
manage
:
navigation
"
,
"
manage
:
system
"
])
updateConfig
(
mode
:
NavigationMode
!
):
DefaultResponse
@
auth
(
requires
:
[
"
manage
:
navigation
"
,
"
manage
:
system
"
])
}
# -----------------------------------------------
...
...
@@ -59,3 +63,13 @@ input NavigationItemInput {
targetType
:
String
target
:
String
}
type
NavigationConfig
{
mode
:
NavigationMode
!
}
enum
NavigationMode
{
NONE
TREE
MIXED
}
server/graph/schemas/page.graphql
View file @
1c80faa9
...
...
@@ -57,7 +57,7 @@ type PageQuery {
parent
:
Int
mode
:
PageTreeMode
!
locale
:
String
!
include
Parent
s
:
Boolean
include
Ancestor
s
:
Boolean
):
[
PageTreeItem
]
@
auth
(
requires
:
[
"
manage
:
system
"
,
"
read
:
pages
"
])
links
(
...
...
server/jobs/rebuild-tree.js
View file @
1c80faa9
...
...
@@ -19,6 +19,7 @@ module.exports = async (pageId) => {
let
currentPath
=
''
let
depth
=
0
let
parentId
=
null
let
ancestors
=
[]
for
(
const
part
of
pagePaths
)
{
depth
++
const
isFolder
=
(
depth
<
pagePaths
.
length
)
...
...
@@ -39,7 +40,8 @@ module.exports = async (pageId) => {
isPrivate
:
!
isFolder
&&
page
.
isPrivate
,
privateNS
:
!
isFolder
?
page
.
privateNS
:
null
,
parent
:
parentId
,
pageId
:
isFolder
?
null
:
page
.
id
pageId
:
isFolder
?
null
:
page
.
id
,
ancestors
:
JSON
.
stringify
(
ancestors
)
})
parentId
=
pik
}
else
if
(
isFolder
&&
!
found
.
isFolder
)
{
...
...
@@ -48,6 +50,7 @@ module.exports = async (pageId) => {
}
else
{
parentId
=
found
.
id
}
ancestors
.
push
(
parentId
)
}
}
...
...
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