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
fb015250
Unverified
Commit
fb015250
authored
Jun 05, 2022
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: change toc headings to match v3 behavior
parent
d9f4e90e
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
221 additions
and
338 deletions
+221
-338
admin-theme.vue
client/components/admin/admin-theme.vue
+14
-51
editor.vue
client/components/editor.vue
+29
-52
editor-modal-properties.vue
client/components/editor/editor-modal-properties.vue
+28
-64
theme-mutation-save.gql
client/graph/admin/theme/theme-mutation-save.gql
+18
-2
theme-query-config.gql
client/graph/admin/theme/theme-query-config.gql
+4
-3
page.js
client/store/page.js
+5
-4
page-toc-item.vue
client/themes/default/components/page-toc-item.vue
+12
-16
page.vue
client/themes/default/components/page.vue
+13
-17
Dockerfile
dev/containers/Dockerfile
+1
-1
data.yml
server/app/data.yml
+3
-4
common.js
server/controllers/common.js
+12
-21
2.5.284.js
server/db/migrations-sqlite/2.5.284.js
+5
-4
2.5.284.js
server/db/migrations/2.5.284.js
+5
-4
theming.js
server/graph/resolvers/theming.js
+2
-6
common.graphql
server/graph/schemas/common.graphql
+11
-0
page.graphql
server/graph/schemas/page.graphql
+28
-34
theming.graphql
server/graph/schemas/theming.graphql
+5
-9
pages.js
server/models/pages.js
+19
-26
common.js
server/modules/storage/disk/common.js
+1
-8
setup.js
server/setup.js
+4
-4
editor.pug
server/views/editor.pug
+1
-4
page.pug
server/views/page.pug
+1
-4
No files found.
client/components/admin/admin-theme.vue
View file @
fb015250
<
template
lang=
'pug'
>
v-container(fluid, grid-list-lg)
v-container(fluid, grid-list-lg)
v-layout(row wrap)
v-flex(xs12)
.admin-header
...
...
@@ -54,54 +54,17 @@ v-container(fluid, grid-list-lg)
v-card.mt-3.animated.fadeInUp.wait-p1s
v-toolbar(color='primary', dark, dense, flat)
v-toolbar-title.subtitle-1
{{
$t
(
`admin:theme.options`
)
}}
v-spacer
v-chip(label, color='white', small).primary--text coming soon
v-card-text
v-select(
:items='[]'
outlined
prepend-icon='mdi-border-vertical'
v-model='config.iconset'
label='Table of Contents Position'
persistent-hint
hint='Select whether the table of contents is shown on the left, right or not at all.'
disabled
)
v-range-slider(
prepend-icon='mdi-serial-port'
label='Heading Levels in ToC'
hint='The table of contents will show headings from and up to the selected levels.'
prepend-icon='mdi-menu-open'
:label='$t(`admin:theme.tocHeadingLevels`)'
v-model='tocRange'
:min='1'
:max='6'
:tick-labels='["H1", "H2", "H3", "H4", "H5", "H6"]'
)
.text-caption
{{
$t
(
'admin:theme.tocHeadingLevelsHint'
)
}}
v-flex(lg6 xs12)
//- v-card.animated.fadeInUp.wait-p2s
//- v-toolbar(color='teal', dark, dense, flat)
//- v-toolbar-title.subtitle-1
{{
$t
(
'admin:theme.downloadThemes'
)
}}
//- v-spacer
//- v-chip(label, color='white', small).teal--text coming soon
//- v-data-table(
//- :headers='headers',
//- :items='themes',
//- hide-default-footer,
//- item-key='value',
//- :items-per-page='1000'
//- )
//- template(v-slot:item='thm')
//- td
//- strong
{{
thm
.
item
.
text
}}
//- td
//- span
{{
thm
.
item
.
author
}}
//- td.text-xs-center
//- v-progress-circular(v-if='thm.item.isDownloading', indeterminate, color='blue', size='20', :width='2')
//- v-btn(v-else-if='thm.item.isInstalled && thm.item.installDate < thm.item.updatedAt', icon)
//- v-icon.blue--text mdi-cached
//- v-btn(v-else-if='thm.item.isInstalled', icon)
//- v-icon.green--text mdi-check-bold
//- v-btn(v-else, icon)
//- v-icon.grey--text mdi-cloud-download
v-card.animated.fadeInUp.wait-p2s
v-toolbar(color='primary', dark, dense, flat)
...
...
@@ -161,9 +124,10 @@ export default {
config
:
{
theme
:
'default'
,
darkMode
:
false
,
minTocLevel
:
0
,
tocLevel
:
2
,
tocCollapseLevel
:
2
,
tocDepth
:
{
min
:
1
,
max
:
2
},
iconset
:
''
,
injectCSS
:
''
,
injectHead
:
''
,
...
...
@@ -175,13 +139,14 @@ export default {
computed
:
{
tocRange
:
{
get
()
{
var
range
=
[
this
.
config
.
minTocLevel
,
this
.
config
.
tocLevel
]
var
range
=
[
this
.
config
.
tocDepth
.
min
,
this
.
config
.
tocDepth
.
max
]
return
range
},
set
(
value
)
{
this
.
config
.
minTocLevel
=
value
[
0
]
this
.
config
.
tocLevel
=
value
[
1
]
this
.
config
.
tocCollapseLevel
=
value
[
1
]
this
.
config
.
tocDepth
=
{
min
:
parseInt
(
value
[
0
]),
max
:
parseInt
(
value
[
1
])
}
}
},
darkMode
:
sync
(
'site/dark'
),
...
...
@@ -230,9 +195,7 @@ export default {
theme
:
this
.
config
.
theme
,
iconset
:
this
.
config
.
iconset
,
darkMode
:
this
.
darkMode
,
minTocLevel
:
parseInt
(
this
.
config
.
minTocLevel
,
10
),
tocLevel
:
parseInt
(
this
.
config
.
tocLevel
,
10
),
tocCollapseLevel
:
parseInt
(
this
.
config
.
tocCollapseLevel
,
10
),
tocDepth
:
this
.
config
.
tocDepth
,
injectCSS
:
this
.
config
.
injectCSS
,
injectHead
:
this
.
config
.
injectHead
,
injectBody
:
this
.
config
.
injectBody
...
...
client/components/editor.vue
View file @
fb015250
...
...
@@ -144,21 +144,9 @@ export default {
type
:
Number
,
default
:
0
},
minTocLevel
:
{
type
:
Number
,
default
:
0
},
tocLevel
:
{
type
:
Number
,
default
:
1
},
tocCollapseLevel
:
{
type
:
Number
,
default
:
0
},
doUseTocDefault
:
{
type
:
Boolean
,
default
:
true
tocOptions
:
{
type
:
String
,
default
:
''
},
checkoutDate
:
{
type
:
String
,
...
...
@@ -187,7 +175,9 @@ export default {
tags
:
''
,
title
:
''
,
css
:
''
,
js
:
''
js
:
''
,
tocDepth
:
0
,
useDefaultTocDepth
:
false
}
}
},
...
...
@@ -206,10 +196,8 @@ export default {
this
.
path
!==
this
.
$store
.
get
(
'page/path'
),
this
.
savedState
.
title
!==
this
.
$store
.
get
(
'page/title'
),
this
.
savedState
.
description
!==
this
.
$store
.
get
(
'page/description'
),
this
.
savedState
.
minTocLevel
!==
this
.
$store
.
get
(
'page/minTocLevel'
),
this
.
savedState
.
tocLevel
!==
this
.
$store
.
get
(
'page/tocLevel'
),
this
.
savedState
.
tocCollapseLevel
!==
this
.
$store
.
get
(
'page/tocCollapseLevel'
),
this
.
savedState
.
doUseTocDefault
!==
this
.
$store
.
get
(
'page/doUseTocDefault'
),
this
.
savedState
.
tocDepth
!==
this
.
$store
.
get
(
'page/tocDepth@min'
)
+
(
this
.
$store
.
get
(
'page/tocDepth@max'
)
*
10
),
this
.
savedState
.
useDefaultTocDepth
!==
this
.
$store
.
get
(
'page/useDefaultTocDepth'
),
this
.
savedState
.
tags
!==
this
.
$store
.
get
(
'page/tags'
),
this
.
savedState
.
isPublished
!==
this
.
$store
.
get
(
'page/isPublished'
),
this
.
savedState
.
publishStartDate
!==
this
.
$store
.
get
(
'page/publishStartDate'
),
...
...
@@ -243,12 +231,15 @@ export default {
this
.
$store
.
set
(
'page/title'
,
this
.
title
)
this
.
$store
.
set
(
'page/scriptCss'
,
this
.
scriptCss
)
this
.
$store
.
set
(
'page/scriptJs'
,
this
.
scriptJs
)
this
.
$store
.
set
(
'page/minTocLevel'
,
this
.
minTocLevel
)
this
.
$store
.
set
(
'page/tocLevel'
,
this
.
tocLevel
)
this
.
$store
.
set
(
'page/tocCollapseLevel'
,
this
.
tocCollapseLevel
)
this
.
$store
.
set
(
'page/doUseTocDefault'
,
this
.
doUseTocDefault
)
this
.
$store
.
set
(
'page/mode'
,
'edit'
)
const
tocOptions
=
JSON
.
parse
(
Buffer
.
from
(
this
.
tocOptions
,
'base64'
).
toString
())
this
.
$store
.
set
(
'page/tocDepth'
,
{
min
:
tocOptions
.
min
,
max
:
tocOptions
.
max
})
this
.
$store
.
set
(
'page/useDefaultTocDepth'
,
tocOptions
.
useDefault
)
this
.
setCurrentSavedState
()
this
.
checkoutDateActive
=
this
.
checkoutDate
...
...
@@ -326,10 +317,8 @@ export default {
$publishStartDate: Date
$scriptCss: String
$scriptJs: String
$minTocLevel: Int!
$tocLevel: Int!
$tocCollapseLevel: Int!
$doUseTocDefault: Boolean!
$tocDepth: RangeInput
$useDefaultTocDepth: Boolean
$tags: [String]!
$title: String!
) {
...
...
@@ -346,10 +335,8 @@ export default {
publishStartDate: $publishStartDate
scriptCss: $scriptCss
scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tocDepth: $tocDepth
useDefaultTocDepth: $useDefaultTocDepth
tags: $tags
title: $title
) {
...
...
@@ -379,10 +366,8 @@ export default {
publishStartDate
:
this
.
$store
.
get
(
'page/publishStartDate'
)
||
''
,
scriptCss
:
this
.
$store
.
get
(
'page/scriptCss'
),
scriptJs
:
this
.
$store
.
get
(
'page/scriptJs'
),
minTocLevel
:
this
.
$store
.
get
(
'page/minTocLevel'
),
tocLevel
:
this
.
$store
.
get
(
'page/tocLevel'
),
tocCollapseLevel
:
this
.
$store
.
get
(
'page/tocCollapseLevel'
),
doUseTocDefault
:
this
.
$store
.
get
(
'page/doUseTocDefault'
),
tocDepth
:
this
.
$store
.
get
(
'page/tocDepth'
),
useDefaultTocDepth
:
this
.
$store
.
get
(
'page/useDefaultTocDepth'
),
tags
:
this
.
$store
.
get
(
'page/tags'
),
title
:
this
.
$store
.
get
(
'page/title'
)
}
...
...
@@ -441,10 +426,8 @@ export default {
$publishStartDate: Date
$scriptCss: String
$scriptJs: String
$minTocLevel: Int
$tocLevel: Int
$tocCollapseLevel: Int
$doUseTocDefault: Boolean
$tocDepth: RangeInput
$useDefaultTocDepth: Boolean
$tags: [String]
$title: String
) {
...
...
@@ -462,10 +445,8 @@ export default {
publishStartDate: $publishStartDate
scriptCss: $scriptCss
scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tocDepth: $tocDepth
useDefaultTocDepth: $useDefaultTocDepth
tags: $tags
title: $title
) {
...
...
@@ -495,10 +476,8 @@ export default {
publishStartDate
:
this
.
$store
.
get
(
'page/publishStartDate'
)
||
''
,
scriptCss
:
this
.
$store
.
get
(
'page/scriptCss'
),
scriptJs
:
this
.
$store
.
get
(
'page/scriptJs'
),
minTocLevel
:
this
.
$store
.
get
(
'page/minTocLevel'
),
tocLevel
:
this
.
$store
.
get
(
'page/tocLevel'
),
tocCollapseLevel
:
this
.
$store
.
get
(
'page/tocCollapseLevel'
),
doUseTocDefault
:
this
.
$store
.
get
(
'page/doUseTocDefault'
),
tocDepth
:
this
.
$store
.
get
(
'page/tocDepth'
),
useDefaultTocDepth
:
this
.
$store
.
get
(
'page/useDefaultTocDepth'
),
tags
:
this
.
$store
.
get
(
'page/tags'
),
title
:
this
.
$store
.
get
(
'page/title'
)
}
...
...
@@ -582,10 +561,8 @@ export default {
title
:
this
.
$store
.
get
(
'page/title'
),
css
:
this
.
$store
.
get
(
'page/scriptCss'
),
js
:
this
.
$store
.
get
(
'page/scriptJs'
),
minTocLevel
:
this
.
$store
.
get
(
'page/minTocLevel'
),
tocLevel
:
this
.
$store
.
get
(
'page/tocLevel'
),
tocCollapseLevel
:
this
.
$store
.
get
(
'page/tocCollapseLevel'
),
doUseTocDefault
:
this
.
$store
.
get
(
'page/doUseTocDefault'
)
tocDepth
:
this
.
$store
.
get
(
'page/tocDepth@min'
)
+
(
this
.
$store
.
get
(
'page/tocDepth@max'
)
*
10
),
useDefaultTocDepth
:
this
.
$store
.
get
(
'page/useDefaultTocDepth'
)
}
},
injectCustomCss
:
_
.
debounce
(
css
=>
{
...
...
client/components/editor/editor-modal-properties.vue
View file @
fb015250
...
...
@@ -19,9 +19,9 @@ v-dialog(
v-card(tile)
v-tabs(color='white', background-color='blue darken-1', dark, centered, v-model='currentTab')
v-tab
{{
$t
(
'editor:props.info'
)
}}
v-tab
{{
$t
(
'editor:props.toc'
)
}}
v-tab
{{
$t
(
'editor:props.scheduling'
)
}}
v-tab(:disabled='!hasScriptPermission')
{{
$t
(
'editor:props.scripts'
)
}}
v-tab(disabled)
{{
$t
(
'editor:props.social'
)
}}
v-tab(:disabled='!hasStylePermission')
{{
$t
(
'editor:props.styles'
)
}}
v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text.pt-5
...
...
@@ -67,23 +67,6 @@ v-dialog(
:rules='[rules.required, rules.path]'
)
v-divider
v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d3` : `lighten-5`')
.overline.pb-5 Theme Options
v-switch(
label='Use Site Defaults'
v-model='doUseTocDefault'
)
v-range-slider(
:disabled='doUseTocDefault'
prepend-icon='mdi-serial-port'
label='Heading Levels in ToC'
hint='The table of contents will show headings from and up to the selected levels.'
v-model='tocRange'
:min='1'
:max='6'
:tick-labels='["H1", "H2", "H3", "H4", "H5", "H6"]'
)
v-divider
v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-4`')
.overline.pb-5
{{
$t
(
'editor:props.categorization'
)
}}
v-chip-group.radius-5.mb-5(column, v-if='tags && tags.length > 0')
...
...
@@ -109,6 +92,24 @@ v-dialog(
)
v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text
.overline
{{
$t
(
'editor:props.tocTitle'
)
}}
v-switch(
:label='$t(`editor:props.tocUseDefault`)'
v-model='useDefaultTocDepth'
)
v-range-slider(
:disabled='useDefaultTocDepth'
prepend-icon='mdi-menu-open'
:label='$t(`editor:props.tocHeadingLevels`)'
v-model='tocDepth'
:min='1'
:max='6'
:tick-labels='["H1", "H2", "H3", "H4", "H5", "H6"]'
)
.text-caption.pl-8.grey--text
{{
$t
(
'editor:props.tocHeadingLevelsHint'
)
}}
v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text
.overline
{{
$t
(
'editor:props.publishState'
)
}}
v-switch(
:label='$t(`editor:props.publishToggle`)'
...
...
@@ -213,43 +214,6 @@ v-dialog(
.editor-props-codeeditor-hint
.caption
{{
$t
(
'editor:props.htmlHint'
)
}}
v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text
.overline
{{
$t
(
'editor:props.socialFeatures'
)
}}
v-switch(
:label='$t(`editor:props.allowComments`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.allowCommentsHint`)'
persistent-hint
inset
)
v-switch(
:label='$t(`editor:props.allowRatings`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.allowRatingsHint`)'
persistent-hint
disabled
inset
)
v-switch(
:label='$t(`editor:props.displayAuthor`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.displayAuthorHint`)'
persistent-hint
inset
)
v-switch(
:label='$t(`editor:props.displaySharingBar`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.displaySharingBarHint`)'
persistent-hint
inset
)
v-tab-item(:transition='false', :reverse-transition='false')
.editor-props-codeeditor-title
.overline
{{
$t
(
'editor:props.css'
)
}}
...
...
@@ -315,19 +279,19 @@ export default {
isPublished
:
sync
(
'page/isPublished'
),
publishStartDate
:
sync
(
'page/publishStartDate'
),
publishEndDate
:
sync
(
'page/publishEndDate'
),
toc
Range
:
{
toc
Depth
:
{
get
()
{
var
range
=
[
this
.
$store
.
get
(
'page/minTocLevel'
),
this
.
$store
.
get
(
'page/tocLevel'
)]
return
range
// return [get('page/minTocLevel'), get('page/tocLevel')]
const
tocDepth
=
this
.
$store
.
get
(
'page/tocDepth'
)
return
[
tocDepth
.
min
,
tocDepth
.
max
]
},
set
(
value
)
{
this
.
$store
.
set
(
'page/minTocLevel'
,
value
[
0
])
this
.
$store
.
set
(
'page/tocLevel'
,
value
[
1
])
this
.
$store
.
set
(
'page/tocCollapseLevel'
,
value
[
1
])
this
.
$store
.
set
(
'page/tocDepth'
,
{
min
:
parseInt
(
value
[
0
]),
max
:
parseInt
(
value
[
1
])
})
}
},
doUseTocDefault
:
sync
(
'page/doUseTocDefault
'
),
useDefaultTocDepth
:
sync
(
'page/useDefaultTocDepth
'
),
scriptJs
:
sync
(
'page/scriptJs'
),
scriptCss
:
sync
(
'page/scriptCss'
),
hasScriptPermission
:
get
(
'page/effectivePermissions@pages.script'
),
...
...
@@ -359,7 +323,7 @@ export default {
if
(
this
.
cm
)
{
this
.
cm
.
toTextArea
()
}
if
(
newValue
===
2
)
{
if
(
newValue
===
3
)
{
this
.
$nextTick
(()
=>
{
setTimeout
(()
=>
{
this
.
loadEditor
(
this
.
$refs
.
codejs
,
'html'
)
...
...
client/graph/admin/theme/theme-mutation-save.gql
View file @
fb015250
mutation
(
$theme
:
String
!,
$iconset
:
String
!,
$darkMode
:
Boolean
!,
$minTocLevel
:
Int
!,
$tocLevel
:
Int
!,
$tocCollapseLevel
:
Int
!,
$injectCSS
:
String
,
$injectHead
:
String
,
$injectBody
:
String
)
{
mutation
(
$theme
:
String
!
$iconset
:
String
!
$darkMode
:
Boolean
!
$tocDepth
:
RangeInput
!
$injectCSS
:
String
$injectHead
:
String
$injectBody
:
String
)
{
theming
{
setConfig
(
theme
:
$theme
,
iconset
:
$iconset
,
darkMode
:
$darkMode
,
minTocLevel
:
$minTocLevel
,
tocLevel
:
$tocLevel
,
tocCollapseLevel
:
$tocCollapseLevel
,
injectCSS
:
$injectCSS
,
injectHead
:
$injectHead
,
injectBody
:
$injectBody
)
{
setConfig
(
theme
:
$theme
iconset
:
$iconset
darkMode
:
$darkMode
tocDepth
:
$tocDepth
injectCSS
:
$injectCSS
injectHead
:
$injectHead
injectBody
:
$injectBody
)
{
responseResult
{
succeeded
errorCode
...
...
client/graph/admin/theme/theme-query-config.gql
View file @
fb015250
...
...
@@ -4,9 +4,10 @@ query {
theme
iconset
darkMode
minTocLevel
tocLevel
tocCollapseLevel
tocDepth
{
min
max
}
injectCSS
injectHead
injectBody
...
...
client/store/page.js
View file @
fb015250
...
...
@@ -17,10 +17,11 @@ const state = {
editor
:
''
,
mode
:
''
,
scriptJs
:
''
,
minTocLevel
:
0
,
tocLevel
:
2
,
tocCollapseLevel
:
2
,
doUseTocDefault
:
true
,
tocDepth
:
{
min
:
1
,
max
:
2
},
useDefaultTocDepth
:
true
,
scriptCss
:
''
,
effectivePermissions
:
{
comments
:
{
...
...
client/themes/default/components/page-toc-item.vue
View file @
fb015250
<
template
lang=
"pug"
>
div
template(v-if='level >= min
TocLevel
')
v-list-item(@click='click(item.anchor)', v-if='(item.children.length === 0 &&
tocCollapseLevel > level) || tocCollapseLevel
> level',
.page-toc-item
template(v-if='level >= min')
v-list-item(@click='click(item.anchor)', v-if='(item.children.length === 0 &&
max > level) || max
> level',
:key='item.anchor', :class='isNestedLevel ? `pl-9` : `pl-6`')
v-icon.pl-0(small, color='grey lighten-1')
{{
$vuetify
.
rtl
?
`mdi-chevron-left`
:
`mdi-chevron-right`
}}
v-list-item-title.pl-4(v-bind:class='titleClasses')
{{
item
.
title
}}
...
...
@@ -10,11 +10,11 @@
v-list-item.pl-0(@click='click(item.anchor)', :key='item.anchor')
v-list-item-title(v-bind:class='titleClasses')
{{
item
.
title
}}
template(v-if='item.children.length !== 0', v-for='subItem in item.children')
page-toc-item(:item='subItem', :level='level + 1', :
tocLevel='tocLevel', :minTocLevel='minTocLevel', :tocCollapseLevel='tocCollapseLevel
')
template(v-if='
tocCollapseLevel
> level', v-for='subItem in item.children')
page-toc-item(:item='subItem', :level='level + 1', :
tocLevel='tocLevel', :minTocLevel='minTocLevel', :tocCollapseLevel='tocCollapseLevel
')
page-toc-item(:item='subItem', :level='level + 1', :
min='min', :max='max
')
template(v-if='
max
> level', v-for='subItem in item.children')
page-toc-item(:item='subItem', :level='level + 1', :
min='min', :max='max
')
template(v-else, v-for='subItem in item.children')
page-toc-item(:item='subItem', :level='level + 1', :
tocLevel='tocLevel', :minTocLevel='minTocLevel', :tocCollapseLevel='tocCollapseLevel
')
page-toc-item(:item='subItem', :level='level + 1', :
min='min', :max='max
')
</
template
>
<
script
>
...
...
@@ -26,15 +26,11 @@ export default {
type
:
Object
,
default
:
()
=>
{}
},
min
TocLevel
:
{
min
:
{
type
:
Number
,
default
:
0
},
tocLevel
:
{
type
:
Number
,
default
:
2
default
:
1
},
tocCollapseLevel
:
{
max
:
{
type
:
Number
,
default
:
2
},
...
...
@@ -54,7 +50,7 @@ export default {
},
computed
:
{
isNestedLevel
()
{
return
this
.
level
>
this
.
min
TocLevel
return
this
.
level
>
this
.
min
},
titleClasses
()
{
return
{
...
...
@@ -75,7 +71,7 @@ export default {
<
style
lang=
'scss'
>
// Hack to fix animations of multi level nesting v-list-group
.v-list-group--sub-group.v-list-group--active
.v-list-item
:not
(
.v-list-item--active
)
.v-list-item__icon.v-list-group__header__prepend-icon
.v-icon
{
.
page-toc-item
.
v-list-group--sub-group.v-list-group--active
.v-list-item
:not
(
.v-list-item--active
)
.v-list-item__icon.v-list-group__header__prepend-icon
.v-icon
{
transform
:
rotate
(
0deg
)
!
important
;
}
</
style
>
client/themes/default/components/page.vue
View file @
fb015250
...
...
@@ -60,8 +60,13 @@
v-card.mb-5(v-if='tocDecoded.length')
.overline.pa-5.pb-0(:class='$vuetify.theme.dark ? `blue--text text--lighten-2` : `primary--text`')
{{
$t
(
'common:page.toc'
)
}}
v-list.py-0(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``')
template(v-for='item in tocDecoded')
page-toc-item(:item='item', :tocLevel='tocLevel', :minTocLevel='minTocLevel', :tocCollapseLevel='tocCollapseLevel')
page-toc-item(
v-for='(item, idx) in tocDecoded'
:key='`tocitem-` + idx'
:item='item'
:min='tocOptionsDecoded.min'
:max='tocOptionsDecoded.max'
)
v-card.mb-5(v-if='tags.length > 0')
.pa-5
.overline.teal--text.pb-2(:class='$vuetify.theme.dark ? `text--lighten-3` : ``')
{{
$t
(
'common:page.tags'
)
}}
...
...
@@ -434,21 +439,9 @@ export default {
type
:
Boolean
,
default
:
false
},
minTocLevel
:
{
type
:
Number
,
default
:
0
},
tocLevel
:
{
type
:
Number
,
default
:
2
},
tocCollapseLevel
:
{
type
:
Number
,
default
:
2
},
doUseTocDefault
:
{
type
:
Boolean
,
default
:
true
tocOptions
:
{
type
:
String
,
default
:
''
}
},
data
()
{
...
...
@@ -518,6 +511,9 @@ export default {
tocDecoded
()
{
return
JSON
.
parse
(
Buffer
.
from
(
this
.
toc
,
'base64'
).
toString
())
},
tocOptionsDecoded
()
{
return
JSON
.
parse
(
Buffer
.
from
(
this
.
tocOptions
,
'base64'
).
toString
())
},
hasAdminPermission
:
get
(
'page/effectivePermissions@system.manage'
),
hasWritePagesPermission
:
get
(
'page/effectivePermissions@pages.write'
),
hasManagePagesPermission
:
get
(
'page/effectivePermissions@pages.manage'
),
...
...
dev/containers/Dockerfile
View file @
fb015250
...
...
@@ -5,7 +5,7 @@ FROM node:14
LABEL
maintainer "requarks.io"
RUN
apt-get update
&&
\
apt-get
install
-y
bash curl git python make g++ nano openssh-server gnupg
cmake
&&
\
apt-get
install
-y
bash curl git python make g++ nano openssh-server gnupg
&&
\
mkdir
-p
/wiki
WORKDIR
/wiki
...
...
server/app/data.yml
View file @
fb015250
...
...
@@ -55,10 +55,9 @@ defaults:
theme
:
'
default'
iconset
:
'
md'
darkMode
:
false
minTocLevel
:
0
tocLevel
:
2
tocCollapseLevel
:
2
doUseTocDefault
:
true
tocDepth
:
min
:
1
max
:
2
auth
:
autoLogin
:
false
enforce2FA
:
false
...
...
server/controllers/common.js
View file @
fb015250
...
...
@@ -178,6 +178,11 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
title
:
null
,
description
:
null
,
updatedAt
:
new
Date
().
toISOString
(),
tocOptions
:
{
min
:
1
,
max
:
2
,
useDefault
:
true
},
extra
:
{
css
:
''
,
js
:
''
...
...
@@ -504,19 +509,12 @@ router.get('/*', async (req, res, next) => {
if
(
!
_
.
isEmpty
(
page
.
extra
.
js
))
{
injectCode
.
body
=
`
${
injectCode
.
body
}
\n
${
page
.
extra
.
js
}
`
}
const
doUseTocDefault
=
page
.
doUseTocDefault
===
true
||
page
.
doUseTocDefault
===
1
var
tocLevel
var
tocCollapseLevel
var
minTocLevel
if
(
doUseTocDefault
)
{
minTocLevel
=
WIKI
.
config
.
theming
.
minTocLevel
tocLevel
=
WIKI
.
config
.
theming
.
tocLevel
tocCollapseLevel
=
WIKI
.
config
.
theming
.
tocCollapseLevel
}
else
{
minTocLevel
=
page
.
minTocLevel
||
WIKI
.
config
.
theming
.
minTocLevel
tocLevel
=
page
.
tocLevel
||
WIKI
.
config
.
theming
.
tocLevel
tocCollapseLevel
=
page
.
tocCollapseLevel
||
WIKI
.
config
.
theming
.
tocCollapseLevel
}
// -> Set TOC display options
const
tocOptions
=
_
.
get
(
page
,
'tocOptions.useDefault'
,
true
)
?
{
min
:
page
.
tocOptions
.
min
,
max
:
page
.
tocOptions
.
max
}
:
WIKI
.
config
.
theming
.
tocDepth
if
(
req
.
query
.
legacy
||
req
.
get
(
'user-agent'
).
indexOf
(
'Trident'
)
>=
0
)
{
// -> Convert page TOC
...
...
@@ -528,10 +526,6 @@ router.get('/*', async (req, res, next) => {
res
.
render
(
'legacy/page'
,
{
page
,
sidebar
,
minTocLevel
,
tocLevel
,
tocCollapseLevel
,
doUseTocDefault
,
injectCode
,
isAuthenticated
:
req
.
user
&&
req
.
user
.
id
!==
2
})
...
...
@@ -563,10 +557,7 @@ router.get('/*', async (req, res, next) => {
res
.
render
(
'page'
,
{
page
,
sidebar
,
minTocLevel
,
tocLevel
,
tocCollapseLevel
,
doUseTocDefault
,
tocOptions
,
injectCode
,
comments
:
commentTmpl
,
effectivePermissions
...
...
server/db/migrations-sqlite/2.5.
13
.js
→
server/db/migrations-sqlite/2.5.
284
.js
View file @
fb015250
exports
.
up
=
async
knex
=>
{
await
knex
.
schema
.
alterTable
(
'pages'
,
table
=>
{
table
.
integer
(
'minTocLevel'
).
notNullable
().
defaultTo
(
0
)
table
.
integer
(
'tocLevel'
).
notNullable
().
defaultTo
(
0
)
table
.
integer
(
'tocCollapseLevel'
).
notNullable
().
defaultTo
(
0
)
table
.
boolean
(
'doUseTocDefault'
).
notNullable
().
defaultTo
(
true
)
table
.
json
(
'tocOptions'
).
notNullable
().
defaultTo
(
JSON
.
stringify
({
min
:
1
,
max
:
2
,
useDefault
:
true
}))
})
}
...
...
server/db/migrations/2.5.
13
.js
→
server/db/migrations/2.5.
284
.js
View file @
fb015250
exports
.
up
=
async
knex
=>
{
await
knex
.
schema
.
alterTable
(
'pages'
,
table
=>
{
table
.
integer
(
'minTocLevel'
).
notNullable
().
defaultTo
(
0
)
table
.
integer
(
'tocLevel'
).
notNullable
().
defaultTo
(
0
)
table
.
integer
(
'tocCollapseLevel'
).
notNullable
().
defaultTo
(
0
)
table
.
boolean
(
'doUseTocDefault'
).
notNullable
().
defaultTo
(
true
)
table
.
json
(
'tocOptions'
).
notNullable
().
defaultTo
(
JSON
.
stringify
({
min
:
1
,
max
:
2
,
useDefault
:
true
}))
})
}
...
...
server/graph/resolvers/theming.js
View file @
fb015250
...
...
@@ -24,9 +24,7 @@ module.exports = {
theme
:
WIKI
.
config
.
theming
.
theme
,
iconset
:
WIKI
.
config
.
theming
.
iconset
,
darkMode
:
WIKI
.
config
.
theming
.
darkMode
,
minTocLevel
:
WIKI
.
config
.
theming
.
minTocLevel
,
tocLevel
:
WIKI
.
config
.
theming
.
tocLevel
,
tocCollapseLevel
:
WIKI
.
config
.
theming
.
tocCollapseLevel
,
tocDepth
:
WIKI
.
config
.
theming
.
tocDepth
,
injectCSS
:
new
CleanCSS
({
format
:
'beautify'
}).
minify
(
WIKI
.
config
.
theming
.
injectCSS
).
styles
,
injectHead
:
WIKI
.
config
.
theming
.
injectHead
,
injectBody
:
WIKI
.
config
.
theming
.
injectBody
...
...
@@ -47,9 +45,7 @@ module.exports = {
theme
:
args
.
theme
,
iconset
:
args
.
iconset
,
darkMode
:
args
.
darkMode
,
minTocLevel
:
args
.
minTocLevel
,
tocLevel
:
args
.
tocLevel
,
tocCollapseLevel
:
args
.
tocCollapseLevel
,
tocDepth
:
args
.
tocDepth
,
injectCSS
:
args
.
injectCSS
||
''
,
injectHead
:
args
.
injectHead
||
''
,
injectBody
:
args
.
injectBody
||
''
...
...
server/graph/schemas/common.graphql
View file @
fb015250
...
...
@@ -21,6 +21,17 @@ input KeyValuePairInput {
value
:
String
!
}
# Generic Range Value
type
Range
{
min
:
Int
max
:
Int
}
input
RangeInput
{
min
:
Int
!
max
:
Int
!
}
# Generic Mutation Response
type
DefaultResponse
{
responseResult
:
ResponseStatus
...
...
server/graph/schemas/page.graphql
View file @
fb015250
...
...
@@ -93,10 +93,8 @@ type PageMutation {
scriptJs
:
String
tags
:
[
String
]!
title
:
String
!
minTocLevel
:
Int
tocLevel
:
Int
tocCollapseLevel
:
Int
doUseTocDefault
:
Boolean
tocDepth
:
RangeInput
useDefaultTocDepth
:
Boolean
):
PageResponse
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
pages
"
,
"
manage
:
system
"
])
update
(
...
...
@@ -114,10 +112,8 @@ type PageMutation {
scriptJs
:
String
tags
:
[
String
]
title
:
String
minTocLevel
:
Int
tocLevel
:
Int
tocCollapseLevel
:
Int
doUseTocDefault
:
Boolean
tocDepth
:
RangeInput
useDefaultTocDepth
:
Boolean
):
PageResponse
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
pages
"
,
"
manage
:
system
"
])
convert
(
...
...
@@ -183,37 +179,35 @@ type PageMigrationResponse {
}
type
Page
{
id
:
Int
!
path
:
String
!
hash
:
String
!
title
:
String
!
description
:
String
!
isPrivate
:
Boolean
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
isPublished
:
Boolean
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
id
:
Int
path
:
String
hash
:
String
title
:
String
description
:
String
isPrivate
:
Boolean
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
isPublished
:
Boolean
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
privateNS
:
String
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
publishStartDate
:
Date
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
publishEndDate
:
Date
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
tags
:
[
PageTag
]
!
content
:
String
!
@
auth
(
requires
:
[
"
read
:
source
"
,
"
write
:
pages
"
,
"
manage
:
system
"
])
publishStartDate
:
Date
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
publishEndDate
:
Date
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
tags
:
[
PageTag
]
content
:
String
@
auth
(
requires
:
[
"
read
:
source
"
,
"
write
:
pages
"
,
"
manage
:
system
"
])
render
:
String
toc
:
String
minTocLevel
:
Int
!
tocLevel
:
Int
!
tocCollapseLevel
:
Int
!
doUseTocDefault
:
Boolean
!
contentType
:
String
!
createdAt
:
Date
!
updatedAt
:
Date
!
editor
:
String
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
locale
:
String
!
tocDepth
:
Range
useDefaultTocDepth
:
Boolean
contentType
:
String
createdAt
:
Date
updatedAt
:
Date
editor
:
String
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
locale
:
String
scriptCss
:
String
scriptJs
:
String
authorId
:
Int
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
authorName
:
String
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
authorEmail
:
String
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
creatorId
:
Int
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
creatorName
:
String
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
creatorEmail
:
String
!
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
authorId
:
Int
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
authorName
:
String
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
authorEmail
:
String
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
creatorId
:
Int
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
creatorName
:
String
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
creatorEmail
:
String
@
auth
(
requires
:
[
"
write
:
pages
"
,
"
manage
:
system
"
])
}
type
PageTag
{
...
...
server/graph/schemas/theming.graphql
View file @
fb015250
...
...
@@ -28,9 +28,7 @@ type ThemingMutation {
theme
:
String
!
iconset
:
String
!
darkMode
:
Boolean
!
minTocLevel
:
Int
!
tocLevel
:
Int
!
tocCollapseLevel
:
Int
!
tocDepth
:
RangeInput
!
injectCSS
:
String
injectHead
:
String
injectBody
:
String
...
...
@@ -42,12 +40,10 @@ type ThemingMutation {
# -----------------------------------------------
type
ThemingConfig
{
theme
:
String
!
iconset
:
String
!
darkMode
:
Boolean
!
minTocLevel
:
Int
!
tocLevel
:
Int
!
tocCollapseLevel
:
Int
!
theme
:
String
iconset
:
String
darkMode
:
Boolean
tocDepth
:
Range
injectCSS
:
String
injectHead
:
String
injectBody
:
String
...
...
server/models/pages.js
View file @
fb015250
...
...
@@ -47,10 +47,6 @@ module.exports = class Page extends Model {
publishEndDate
:
{
type
:
'string'
},
content
:
{
type
:
'string'
},
contentType
:
{
type
:
'string'
},
minTocLevel
:
{
type
:
'integer'
},
tocLevel
:
{
type
:
'integer'
},
tocCollapseLevel
:
{
type
:
'integer'
},
doUseTocDefault
:
{
type
:
'boolean'
},
createdAt
:
{
type
:
'string'
},
updatedAt
:
{
type
:
'string'
}
}
...
...
@@ -58,7 +54,7 @@ module.exports = class Page extends Model {
}
static
get
jsonAttributes
()
{
return
[
'extra'
]
return
[
'extra'
,
'tocOptions'
]
}
static
get
relationMappings
()
{
...
...
@@ -164,10 +160,11 @@ module.exports = class Page extends Model {
},
title
:
'string'
,
toc
:
'string'
,
minTocLevel
:
'uint'
,
tocLevel
:
'uint'
,
tocCollapseLevel
:
'uint'
,
doUseTocDefault
:
'boolean'
,
tocOptions
:
{
min
:
'uint'
,
max
:
'uint'
,
useDefault
:
'boolean'
},
updatedAt
:
'string'
})
}
...
...
@@ -318,10 +315,11 @@ module.exports = class Page extends Model {
publishStartDate
:
opts
.
publishStartDate
||
''
,
title
:
opts
.
title
,
toc
:
'[]'
,
minTocLevel
:
opts
.
minTocLevel
||
0
,
tocLevel
:
opts
.
tocLevel
||
1
,
tocCollapseLevel
:
opts
.
tocCollapseLevel
||
0
,
doUseTocDefault
:
opts
.
doUseTocDefault
||
true
,
tocOptions
:
JSON
.
stringify
({
min
:
_
.
get
(
opts
,
'tocDepth.min'
,
1
),
max
:
_
.
get
(
opts
,
'tocDepth.max'
,
2
),
useDefault
:
opts
.
useDefaultTocDepth
!==
false
}),
extra
:
JSON
.
stringify
({
js
:
scriptJs
,
css
:
scriptCss
...
...
@@ -441,10 +439,11 @@ module.exports = class Page extends Model {
publishEndDate
:
opts
.
publishEndDate
||
''
,
publishStartDate
:
opts
.
publishStartDate
||
''
,
title
:
opts
.
title
,
minTocLevel
:
opts
.
minTocLevel
||
0
,
tocLevel
:
opts
.
tocLevel
||
1
,
tocCollapseLevel
:
opts
.
tocCollapseLevel
||
0
,
doUseTocDefault
:
opts
.
doUseTocDefault
===
true
||
opts
.
doUseTocDefault
===
1
,
tocOptions
:
JSON
.
stringify
({
min
:
_
.
get
(
opts
,
'tocDepth.min'
,
ogPage
.
tocOptions
.
min
||
1
),
max
:
_
.
get
(
opts
,
'tocDepth.max'
,
ogPage
.
tocOptions
.
max
||
2
),
useDefault
:
_
.
get
(
opts
,
'useDefaultTocDepth'
,
ogPage
.
tocOptions
.
useDefault
!==
false
)
}),
extra
:
JSON
.
stringify
({
...
ogPage
.
extra
,
js
:
scriptJs
,
...
...
@@ -802,7 +801,7 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value
*/
static
async
deletePage
(
opts
)
{
const
page
=
await
WIKI
.
models
.
pages
.
getPageFromDb
(
_
.
has
(
opts
,
'id'
)
?
opts
.
id
:
opts
)
;
const
page
=
await
WIKI
.
models
.
pages
.
getPageFromDb
(
_
.
has
(
opts
,
'id'
)
?
opts
.
id
:
opts
)
if
(
!
page
)
{
throw
new
WIKI
.
Error
.
PageNotFound
()
}
...
...
@@ -1006,10 +1005,7 @@ module.exports = class Page extends Model {
'pages.content'
,
'pages.render'
,
'pages.toc'
,
'pages.minTocLevel'
,
'pages.tocLevel'
,
'pages.tocCollapseLevel'
,
'pages.doUseTocDefault'
,
'pages.tocOptions'
,
'pages.contentType'
,
'pages.createdAt'
,
'pages.updatedAt'
,
...
...
@@ -1090,10 +1086,7 @@ module.exports = class Page extends Model {
tags
:
page
.
tags
.
map
(
t
=>
_
.
pick
(
t
,
[
'tag'
,
'title'
])),
title
:
page
.
title
,
toc
:
_
.
isString
(
page
.
toc
)
?
page
.
toc
:
JSON
.
stringify
(
page
.
toc
),
minTocLevel
:
page
.
minTocLevel
,
tocLevel
:
page
.
tocLevel
,
tocCollapseLevel
:
page
.
tocCollapseLevel
,
doUseTocDefault
:
page
.
doUseTocDefault
,
tocOptions
:
page
.
tocOptions
,
updatedAt
:
page
.
updatedAt
}))
}
...
...
server/modules/storage/disk/common.js
View file @
fb015250
...
...
@@ -92,10 +92,6 @@ module.exports = {
isPublished
:
_
.
get
(
pageData
,
'isPublished'
,
currentPage
.
isPublished
),
isPrivate
:
false
,
content
:
pageData
.
content
,
minTocLevel
:
pageData
.
minTocLevel
,
tocLevel
:
pageData
.
tocLevel
,
tocCollapseLevel
:
pageData
.
tocCollapseLevel
,
doUseTocDefault
:
pageData
.
doUseTocDefault
,
user
:
user
,
skipStorage
:
true
})
...
...
@@ -114,10 +110,7 @@ module.exports = {
content
:
pageData
.
content
,
user
:
user
,
editor
:
pageEditor
,
skipStorage
:
true
,
tocLevel
:
pageData
.
tocLevel
,
tocCollapseLevel
:
pageData
.
tocCollapseLevel
,
doUseTocDefault
:
pageData
.
doUseTocDefault
skipStorage
:
true
})
}
},
...
...
server/setup.js
View file @
fb015250
...
...
@@ -126,11 +126,11 @@ module.exports = () => {
_
.
set
(
WIKI
.
config
,
'theming'
,
{
theme
:
'default'
,
darkMode
:
false
,
minTocLevel
:
0
,
tocLevel
:
2
,
tocCollapseLevel
:
2
,
doUseTocDefault
:
true
,
iconset
:
'mdi'
,
tocDepth
:
{
min
:
1
,
max
:
2
},
injectCSS
:
''
,
injectHead
:
''
,
injectBody
:
''
...
...
server/views/editor.pug
View file @
fb015250
...
...
@@ -19,10 +19,7 @@ block body
script-css=page.extra.css
script-js=page.extra.js
init-mode=page.mode
:min-toc-level=page.minTocLevel
:toc-level=page.tocLevel
:toc-collapse-level=page.tocCollapseLevel
:do-use-toc-default=page.doUseTocDefault.toString()
toc-options=Buffer.from(JSON.stringify(page.tocOptions)).toString('base64')
init-editor=page.editorKey
init-content=page.content
checkout-date=page.updatedAt
...
...
server/views/page.pug
View file @
fb015250
...
...
@@ -25,14 +25,11 @@ block body
toc=Buffer.from(page.toc).toString('base64')
:page-id=page.id
sidebar=Buffer.from(JSON.stringify(sidebar)).toString('base64')
toc-options=Buffer.from(JSON.stringify(tocOptions)).toString('base64')
nav-mode=config.nav.mode
comments-enabled=config.features.featurePageComments
effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64')
comments-external=comments.codeTemplate
:min-toc-level=minTocLevel
:toc-level=tocLevel
:toc-collapse-level=tocCollapseLevel
:do-use-toc-default=doUseTocDefault.toString()
)
template(slot='contents')
div!= page.render
...
...
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