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
You need to sign in or sign up before continuing.
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
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
202 additions
and
54 deletions
+202
-54
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
+26
-12
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
=
{
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
[]
}
if
(
args
.
parent
)
{
conds
.
parent
=
(
args
.
parent
<
1
)
?
null
:
args
.
parent
}
else
if
(
args
.
path
)
{
// conds.parent = (args.parent < 1) ? null : args.parent
}
const
results
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
builder
=>
{
builder
.
where
(
'localeCode'
,
args
.
locale
)
switch
(
args
.
mode
)
{
case
'FOLDERS'
:
conds
.
isFolder
=
true
results
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
conds
)
builder
.
andWhere
(
'isFolder'
,
true
)
break
case
'PAGES'
:
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
conds
).
andWhereNotNull
(
'pageId'
)
break
default
:
results
=
await
WIKI
.
models
.
knex
(
'pageTree'
).
where
(
conds
)
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