feat: Configurable ToC Heading Levels (#5101)

Co-authored-by: Regev Brody <regevbr@gmail.com>
feat-toc
Timo Kruth 3 years ago committed by NGPixel
parent e3d94f7177
commit d9f4e90e2c
No known key found for this signature in database
GPG Key ID: 8FDA2F1757F60D63

@ -1,5 +1,5 @@
<template lang='pug'> <template lang='pug'>
v-container(fluid, grid-list-lg) v-container(fluid, grid-list-lg)
v-layout(row wrap) v-layout(row wrap)
v-flex(xs12) v-flex(xs12)
.admin-header .admin-header
@ -51,7 +51,6 @@
persistent-hint persistent-hint
:hint='$t(`admin:theme.darkModeHint`)' :hint='$t(`admin:theme.darkModeHint`)'
) )
v-card.mt-3.animated.fadeInUp.wait-p1s v-card.mt-3.animated.fadeInUp.wait-p1s
v-toolbar(color='primary', dark, dense, flat) v-toolbar(color='primary', dark, dense, flat)
v-toolbar-title.subtitle-1 {{$t(`admin:theme.options`)}} v-toolbar-title.subtitle-1 {{$t(`admin:theme.options`)}}
@ -68,7 +67,15 @@
hint='Select whether the table of contents is shown on the left, right or not at all.' hint='Select whether the table of contents is shown on the left, right or not at all.'
disabled 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.'
v-model='tocRange'
:min='1'
:max='6'
:tick-labels='["H1", "H2", "H3", "H4", "H5", "H6"]'
)
v-flex(lg6 xs12) v-flex(lg6 xs12)
//- v-card.animated.fadeInUp.wait-p2s //- v-card.animated.fadeInUp.wait-p2s
//- v-toolbar(color='teal', dark, dense, flat) //- v-toolbar(color='teal', dark, dense, flat)
@ -154,6 +161,9 @@ export default {
config: { config: {
theme: 'default', theme: 'default',
darkMode: false, darkMode: false,
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
iconset: '', iconset: '',
injectCSS: '', injectCSS: '',
injectHead: '', injectHead: '',
@ -163,6 +173,17 @@ export default {
} }
}, },
computed: { computed: {
tocRange: {
get() {
var range = [this.config.minTocLevel, this.config.tocLevel]
return range
},
set(value) {
this.config.minTocLevel = value[0]
this.config.tocLevel = value[1]
this.config.tocCollapseLevel = value[1]
}
},
darkMode: sync('site/dark'), darkMode: sync('site/dark'),
headers() { headers() {
return [ return [
@ -209,6 +230,9 @@ export default {
theme: this.config.theme, theme: this.config.theme,
iconset: this.config.iconset, iconset: this.config.iconset,
darkMode: this.darkMode, darkMode: this.darkMode,
minTocLevel: parseInt(this.config.minTocLevel, 10),
tocLevel: parseInt(this.config.tocLevel, 10),
tocCollapseLevel: parseInt(this.config.tocCollapseLevel, 10),
injectCSS: this.config.injectCSS, injectCSS: this.config.injectCSS,
injectHead: this.config.injectHead, injectHead: this.config.injectHead,
injectBody: this.config.injectBody injectBody: this.config.injectBody

@ -144,6 +144,22 @@ export default {
type: Number, type: Number,
default: 0 default: 0
}, },
minTocLevel: {
type: Number,
default: 0
},
tocLevel: {
type: Number,
default: 1
},
tocCollapseLevel: {
type: Number,
default: 0
},
doUseTocDefault: {
type: Boolean,
default: true
},
checkoutDate: { checkoutDate: {
type: String, type: String,
default: new Date().toISOString() default: new Date().toISOString()
@ -190,6 +206,10 @@ export default {
this.path !== this.$store.get('page/path'), this.path !== this.$store.get('page/path'),
this.savedState.title !== this.$store.get('page/title'), this.savedState.title !== this.$store.get('page/title'),
this.savedState.description !== this.$store.get('page/description'), 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.tags !== this.$store.get('page/tags'), this.savedState.tags !== this.$store.get('page/tags'),
this.savedState.isPublished !== this.$store.get('page/isPublished'), this.savedState.isPublished !== this.$store.get('page/isPublished'),
this.savedState.publishStartDate !== this.$store.get('page/publishStartDate'), this.savedState.publishStartDate !== this.$store.get('page/publishStartDate'),
@ -223,7 +243,10 @@ export default {
this.$store.set('page/title', this.title) this.$store.set('page/title', this.title)
this.$store.set('page/scriptCss', this.scriptCss) this.$store.set('page/scriptCss', this.scriptCss)
this.$store.set('page/scriptJs', this.scriptJs) 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') this.$store.set('page/mode', 'edit')
this.setCurrentSavedState() this.setCurrentSavedState()
@ -303,6 +326,10 @@ export default {
$publishStartDate: Date $publishStartDate: Date
$scriptCss: String $scriptCss: String
$scriptJs: String $scriptJs: String
$minTocLevel: Int!
$tocLevel: Int!
$tocCollapseLevel: Int!
$doUseTocDefault: Boolean!
$tags: [String]! $tags: [String]!
$title: String! $title: String!
) { ) {
@ -319,6 +346,10 @@ export default {
publishStartDate: $publishStartDate publishStartDate: $publishStartDate
scriptCss: $scriptCss scriptCss: $scriptCss
scriptJs: $scriptJs scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tags: $tags tags: $tags
title: $title title: $title
) { ) {
@ -348,6 +379,10 @@ export default {
publishStartDate: this.$store.get('page/publishStartDate') || '', publishStartDate: this.$store.get('page/publishStartDate') || '',
scriptCss: this.$store.get('page/scriptCss'), scriptCss: this.$store.get('page/scriptCss'),
scriptJs: this.$store.get('page/scriptJs'), 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'),
tags: this.$store.get('page/tags'), tags: this.$store.get('page/tags'),
title: this.$store.get('page/title') title: this.$store.get('page/title')
} }
@ -391,7 +426,6 @@ export default {
this.$root.$emit('saveConflict') this.$root.$emit('saveConflict')
throw new Error(this.$t('editor:conflict.warning')) throw new Error(this.$t('editor:conflict.warning'))
} }
let resp = await this.$apollo.mutate({ let resp = await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation ( mutation (
@ -407,6 +441,10 @@ export default {
$publishStartDate: Date $publishStartDate: Date
$scriptCss: String $scriptCss: String
$scriptJs: String $scriptJs: String
$minTocLevel: Int
$tocLevel: Int
$tocCollapseLevel: Int
$doUseTocDefault: Boolean
$tags: [String] $tags: [String]
$title: String $title: String
) { ) {
@ -424,6 +462,10 @@ export default {
publishStartDate: $publishStartDate publishStartDate: $publishStartDate
scriptCss: $scriptCss scriptCss: $scriptCss
scriptJs: $scriptJs scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tags: $tags tags: $tags
title: $title title: $title
) { ) {
@ -453,6 +495,10 @@ export default {
publishStartDate: this.$store.get('page/publishStartDate') || '', publishStartDate: this.$store.get('page/publishStartDate') || '',
scriptCss: this.$store.get('page/scriptCss'), scriptCss: this.$store.get('page/scriptCss'),
scriptJs: this.$store.get('page/scriptJs'), 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'),
tags: this.$store.get('page/tags'), tags: this.$store.get('page/tags'),
title: this.$store.get('page/title') title: this.$store.get('page/title')
} }
@ -535,7 +581,11 @@ export default {
tags: this.$store.get('page/tags'), tags: this.$store.get('page/tags'),
title: this.$store.get('page/title'), title: this.$store.get('page/title'),
css: this.$store.get('page/scriptCss'), css: this.$store.get('page/scriptCss'),
js: this.$store.get('page/scriptJs') 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')
} }
}, },
injectCustomCss: _.debounce(css => { injectCustomCss: _.debounce(css => {

@ -1,5 +1,5 @@
<template lang='pug'> <template lang='pug'>
v-dialog( v-dialog(
v-model='isShown' v-model='isShown'
persistent persistent
width='1000' width='1000'
@ -67,6 +67,23 @@
:rules='[rules.required, rules.path]' :rules='[rules.required, rules.path]'
) )
v-divider 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`') v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-4`')
.overline.pb-5 {{$t('editor:props.categorization')}} .overline.pb-5 {{$t('editor:props.categorization')}}
v-chip-group.radius-5.mb-5(column, v-if='tags && tags.length > 0') v-chip-group.radius-5.mb-5(column, v-if='tags && tags.length > 0')
@ -255,6 +272,7 @@ import 'codemirror/mode/htmlmixed/htmlmixed.js'
import 'codemirror/mode/css/css.js' import 'codemirror/mode/css/css.js'
/* global siteLangs, siteConfig */ /* global siteLangs, siteConfig */
// eslint-disable-next-line no-useless-escape
const filenamePattern = /^(?![\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s])(?!.*[\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]$)[^\#\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]*$/ const filenamePattern = /^(?![\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s])(?!.*[\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]$)[^\#\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]*$/
export default { export default {
@ -297,6 +315,19 @@ export default {
isPublished: sync('page/isPublished'), isPublished: sync('page/isPublished'),
publishStartDate: sync('page/publishStartDate'), publishStartDate: sync('page/publishStartDate'),
publishEndDate: sync('page/publishEndDate'), publishEndDate: sync('page/publishEndDate'),
tocRange: {
get() {
var range = [this.$store.get('page/minTocLevel'), this.$store.get('page/tocLevel')]
return range
// return [get('page/minTocLevel'), get('page/tocLevel')]
},
set(value) {
this.$store.set('page/minTocLevel', value[0])
this.$store.set('page/tocLevel', value[1])
this.$store.set('page/tocCollapseLevel', value[1])
}
},
doUseTocDefault: sync('page/doUseTocDefault'),
scriptJs: sync('page/scriptJs'), scriptJs: sync('page/scriptJs'),
scriptCss: sync('page/scriptCss'), scriptCss: sync('page/scriptCss'),
hasScriptPermission: get('page/effectivePermissions@pages.script'), hasScriptPermission: get('page/effectivePermissions@pages.script'),

@ -1,6 +1,6 @@
mutation($theme: String!, $iconset: String!, $darkMode: Boolean!, $injectCSS: String, $injectHead: String, $injectBody: String) { mutation($theme: String!, $iconset: String!, $darkMode: Boolean!, $minTocLevel: Int!, $tocLevel: Int!, $tocCollapseLevel: Int!, $injectCSS: String, $injectHead: String, $injectBody: String) {
theming { theming {
setConfig(theme: $theme, iconset: $iconset, darkMode: $darkMode, injectCSS: $injectCSS, injectHead: $injectHead, injectBody: $injectBody) { setConfig(theme: $theme, iconset: $iconset, darkMode: $darkMode, minTocLevel: $minTocLevel, tocLevel: $tocLevel, tocCollapseLevel: $tocCollapseLevel, injectCSS: $injectCSS, injectHead: $injectHead, injectBody: $injectBody) {
responseResult { responseResult {
succeeded succeeded
errorCode errorCode

@ -4,6 +4,9 @@ query {
theme theme
iconset iconset
darkMode darkMode
minTocLevel
tocLevel
tocCollapseLevel
injectCSS injectCSS
injectHead injectHead
injectBody injectBody

@ -17,6 +17,10 @@ const state = {
editor: '', editor: '',
mode: '', mode: '',
scriptJs: '', scriptJs: '',
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
doUseTocDefault: true,
scriptCss: '', scriptCss: '',
effectivePermissions: { effectivePermissions: {
comments: { comments: {

@ -0,0 +1,81 @@
<template lang="pug">
div
template(v-if='level >= minTocLevel')
v-list-item(@click='click(item.anchor)', v-if='(item.children.length === 0 && tocCollapseLevel > level) || tocCollapseLevel > 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}}
v-list-group(sub-group, v-else, v-bind:class='{"pl-3": isNestedLevel}')
template(v-slot:activator)
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')
template(v-else, v-for='subItem in item.children')
page-toc-item(:item='subItem', :level='level + 1', :tocLevel='tocLevel', :minTocLevel='minTocLevel', :tocCollapseLevel='tocCollapseLevel')
</template>
<script>
export default {
name: 'PageTocItem',
props: {
item: {
type: Object,
default: () => {}
},
minTocLevel: {
type: Number,
default: 0
},
tocLevel: {
type: Number,
default: 2
},
tocCollapseLevel: {
type: Number,
default: 2
},
level: {
type: Number,
default: 1
}
},
data() {
return {
scrollOpts: {
duration: 1500,
offset: 0,
easing: 'easeInOutCubic'
}
}
},
computed: {
isNestedLevel() {
return this.level > this.minTocLevel
},
titleClasses() {
return {
'caption': this.isNestedLevel,
'grey--text': this.isNestedLevel,
'text--lighten-1': this.$vuetify.theme.dark && this.isNestedLevel,
'text--darken-1': !this.$vuetify.theme.dark && this.isNestedLevel
}
}
},
methods: {
click (anchor) {
this.$vuetify.goTo(anchor, this.scrollOpts)
}
}
}
</script>
<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 {
transform: rotate(0deg)!important;
}
</style>

@ -59,18 +59,9 @@
v-flex.page-col-sd(lg3, xl2, v-if='$vuetify.breakpoint.lgAndUp') v-flex.page-col-sd(lg3, xl2, v-if='$vuetify.breakpoint.lgAndUp')
v-card.mb-5(v-if='tocDecoded.length') 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')}} .overline.pa-5.pb-0(:class='$vuetify.theme.dark ? `blue--text text--lighten-2` : `primary--text`') {{$t('common:page.toc')}}
v-list.pb-3(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``') v-list.py-0(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``')
template(v-for='(tocItem, tocIdx) in tocDecoded') template(v-for='item in tocDecoded')
v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)') page-toc-item(:item='item', :tocLevel='tocLevel', :minTocLevel='minTocLevel', :tocCollapseLevel='tocCollapseLevel')
v-icon(color='grey', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
v-list-item-title.px-3 {{tocItem.title}}
//- v-divider(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
template(v-for='tocSubItem in tocItem.children')
v-list-item(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)')
v-icon.px-3(color='grey lighten-1', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
v-list-item-title.px-3.caption.grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-1`') {{tocSubItem.title}}
//- v-divider(inset, v-if='tocIdx < toc.length - 1')
v-card.mb-5(v-if='tags.length > 0') v-card.mb-5(v-if='tags.length > 0')
.pa-5 .pa-5
.overline.teal--text.pb-2(:class='$vuetify.theme.dark ? `text--lighten-3` : ``') {{$t('common:page.tags')}} .overline.teal--text.pb-2(:class='$vuetify.theme.dark ? `text--lighten-3` : ``') {{$t('common:page.tags')}}
@ -319,6 +310,7 @@
import { StatusIndicator } from 'vue-status-indicator' import { StatusIndicator } from 'vue-status-indicator'
import Tabset from './tabset.vue' import Tabset from './tabset.vue'
import NavSidebar from './nav-sidebar.vue' import NavSidebar from './nav-sidebar.vue'
import PageTocItem from './page-toc-item.vue'
import Prism from 'prismjs' import Prism from 'prismjs'
import mermaid from 'mermaid' import mermaid from 'mermaid'
import { get, sync } from 'vuex-pathify' import { get, sync } from 'vuex-pathify'
@ -366,6 +358,7 @@ Prism.plugins.toolbar.registerButton('copy-to-clipboard', (env) => {
export default { export default {
components: { components: {
NavSidebar, NavSidebar,
PageTocItem,
StatusIndicator StatusIndicator
}, },
props: { props: {
@ -440,6 +433,22 @@ export default {
commentsExternal: { commentsExternal: {
type: Boolean, type: Boolean,
default: false default: false
},
minTocLevel: {
type: Number,
default: 0
},
tocLevel: {
type: Number,
default: 2
},
tocCollapseLevel: {
type: Number,
default: 2
},
doUseTocDefault: {
type: Boolean,
default: true
} }
}, },
data() { data() {
@ -515,6 +524,7 @@ export default {
hasDeletePagesPermission: get('page/effectivePermissions@pages.delete'), hasDeletePagesPermission: get('page/effectivePermissions@pages.delete'),
hasReadSourcePermission: get('page/effectivePermissions@source.read'), hasReadSourcePermission: get('page/effectivePermissions@source.read'),
hasReadHistoryPermission: get('page/effectivePermissions@history.read'), hasReadHistoryPermission: get('page/effectivePermissions@history.read'),
hasAnyPagePermissions () { hasAnyPagePermissions () {
return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission || return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission ||
this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission

@ -5,7 +5,7 @@ FROM node:14
LABEL maintainer "requarks.io" LABEL maintainer "requarks.io"
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y bash curl git python make g++ nano openssh-server gnupg && \ apt-get install -y bash curl git python make g++ nano openssh-server gnupg cmake && \
mkdir -p /wiki mkdir -p /wiki
WORKDIR /wiki WORKDIR /wiki

@ -55,6 +55,10 @@ defaults:
theme: 'default' theme: 'default'
iconset: 'md' iconset: 'md'
darkMode: false darkMode: false
minTocLevel: 0
tocLevel: 2
tocCollapseLevel: 2
doUseTocDefault: true
auth: auth:
autoLogin: false autoLogin: false
enforce2FA: false enforce2FA: false

@ -504,6 +504,19 @@ router.get('/*', async (req, res, next) => {
if (!_.isEmpty(page.extra.js)) { if (!_.isEmpty(page.extra.js)) {
injectCode.body = `${injectCode.body}\n${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
}
if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) { if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
// -> Convert page TOC // -> Convert page TOC
@ -515,6 +528,10 @@ router.get('/*', async (req, res, next) => {
res.render('legacy/page', { res.render('legacy/page', {
page, page,
sidebar, sidebar,
minTocLevel,
tocLevel,
tocCollapseLevel,
doUseTocDefault,
injectCode, injectCode,
isAuthenticated: req.user && req.user.id !== 2 isAuthenticated: req.user && req.user.id !== 2
}) })
@ -546,6 +563,10 @@ router.get('/*', async (req, res, next) => {
res.render('page', { res.render('page', {
page, page,
sidebar, sidebar,
minTocLevel,
tocLevel,
tocCollapseLevel,
doUseTocDefault,
injectCode, injectCode,
comments: commentTmpl, comments: commentTmpl,
effectivePermissions effectivePermissions

@ -0,0 +1,11 @@
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)
})
}
exports.down = knex => { }

@ -0,0 +1,11 @@
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)
})
}
exports.down = knex => { }

@ -24,6 +24,9 @@ module.exports = {
theme: WIKI.config.theming.theme, theme: WIKI.config.theming.theme,
iconset: WIKI.config.theming.iconset, iconset: WIKI.config.theming.iconset,
darkMode: WIKI.config.theming.darkMode, darkMode: WIKI.config.theming.darkMode,
minTocLevel: WIKI.config.theming.minTocLevel,
tocLevel: WIKI.config.theming.tocLevel,
tocCollapseLevel: WIKI.config.theming.tocCollapseLevel,
injectCSS: new CleanCSS({ format: 'beautify' }).minify(WIKI.config.theming.injectCSS).styles, injectCSS: new CleanCSS({ format: 'beautify' }).minify(WIKI.config.theming.injectCSS).styles,
injectHead: WIKI.config.theming.injectHead, injectHead: WIKI.config.theming.injectHead,
injectBody: WIKI.config.theming.injectBody injectBody: WIKI.config.theming.injectBody
@ -44,6 +47,9 @@ module.exports = {
theme: args.theme, theme: args.theme,
iconset: args.iconset, iconset: args.iconset,
darkMode: args.darkMode, darkMode: args.darkMode,
minTocLevel: args.minTocLevel,
tocLevel: args.tocLevel,
tocCollapseLevel: args.tocCollapseLevel,
injectCSS: args.injectCSS || '', injectCSS: args.injectCSS || '',
injectHead: args.injectHead || '', injectHead: args.injectHead || '',
injectBody: args.injectBody || '' injectBody: args.injectBody || ''

@ -93,6 +93,10 @@ type PageMutation {
scriptJs: String scriptJs: String
tags: [String]! tags: [String]!
title: String! title: String!
minTocLevel: Int
tocLevel: Int
tocCollapseLevel: Int
doUseTocDefault: Boolean
): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"]) ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
update( update(
@ -110,6 +114,10 @@ type PageMutation {
scriptJs: String scriptJs: String
tags: [String] tags: [String]
title: String title: String
minTocLevel: Int
tocLevel: Int
tocCollapseLevel: Int
doUseTocDefault: Boolean
): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"]) ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
convert( convert(
@ -189,6 +197,10 @@ type Page {
content: String! @auth(requires: ["read:source", "write:pages", "manage:system"]) content: String! @auth(requires: ["read:source", "write:pages", "manage:system"])
render: String render: String
toc: String toc: String
minTocLevel: Int!
tocLevel: Int!
tocCollapseLevel: Int!
doUseTocDefault: Boolean!
contentType: String! contentType: String!
createdAt: Date! createdAt: Date!
updatedAt: Date! updatedAt: Date!

@ -28,6 +28,9 @@ type ThemingMutation {
theme: String! theme: String!
iconset: String! iconset: String!
darkMode: Boolean! darkMode: Boolean!
minTocLevel: Int!
tocLevel: Int!
tocCollapseLevel: Int!
injectCSS: String injectCSS: String
injectHead: String injectHead: String
injectBody: String injectBody: String
@ -42,6 +45,9 @@ type ThemingConfig {
theme: String! theme: String!
iconset: String! iconset: String!
darkMode: Boolean! darkMode: Boolean!
minTocLevel: Int!
tocLevel: Int!
tocCollapseLevel: Int!
injectCSS: String injectCSS: String
injectHead: String injectHead: String
injectBody: String injectBody: String

@ -81,8 +81,18 @@ module.exports = {
['date', page.updatedAt], ['date', page.updatedAt],
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''], ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
['editor', page.editorKey], ['editor', page.editorKey],
['dateCreated', page.createdAt] ['dateCreated', page.createdAt],
['doUseTocDefault', page.doUseTocDefault]
] ]
if (page.minTocLevel) {
meta.push(['minTocLevel', page.minTocLevel])
}
if (page.tocLevel) {
meta.push(['tocLevel', page.tocLevel])
}
if (page.tocCollapseLevel) {
meta.push(['tocCollapseLevel', page.tocCollapseLevel])
}
switch (page.contentType) { switch (page.contentType) {
case 'markdown': case 'markdown':
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content

@ -47,7 +47,10 @@ module.exports = class Page extends Model {
publishEndDate: {type: 'string'}, publishEndDate: {type: 'string'},
content: {type: 'string'}, content: {type: 'string'},
contentType: {type: 'string'}, contentType: {type: 'string'},
minTocLevel: {type: 'integer'},
tocLevel: {type: 'integer'},
tocCollapseLevel: {type: 'integer'},
doUseTocDefault: {type: 'boolean'},
createdAt: {type: 'string'}, createdAt: {type: 'string'},
updatedAt: {type: 'string'} updatedAt: {type: 'string'}
} }
@ -161,6 +164,10 @@ module.exports = class Page extends Model {
}, },
title: 'string', title: 'string',
toc: 'string', toc: 'string',
minTocLevel: 'uint',
tocLevel: 'uint',
tocCollapseLevel: 'uint',
doUseTocDefault: 'boolean',
updatedAt: 'string' updatedAt: 'string'
}) })
} }
@ -311,6 +318,10 @@ module.exports = class Page extends Model {
publishStartDate: opts.publishStartDate || '', publishStartDate: opts.publishStartDate || '',
title: opts.title, title: opts.title,
toc: '[]', toc: '[]',
minTocLevel: opts.minTocLevel || 0,
tocLevel: opts.tocLevel || 1,
tocCollapseLevel: opts.tocCollapseLevel || 0,
doUseTocDefault: opts.doUseTocDefault || true,
extra: JSON.stringify({ extra: JSON.stringify({
js: scriptJs, js: scriptJs,
css: scriptCss css: scriptCss
@ -430,6 +441,10 @@ module.exports = class Page extends Model {
publishEndDate: opts.publishEndDate || '', publishEndDate: opts.publishEndDate || '',
publishStartDate: opts.publishStartDate || '', publishStartDate: opts.publishStartDate || '',
title: opts.title, title: opts.title,
minTocLevel: opts.minTocLevel || 0,
tocLevel: opts.tocLevel || 1,
tocCollapseLevel: opts.tocCollapseLevel || 0,
doUseTocDefault: opts.doUseTocDefault === true || opts.doUseTocDefault === 1,
extra: JSON.stringify({ extra: JSON.stringify({
...ogPage.extra, ...ogPage.extra,
js: scriptJs, js: scriptJs,
@ -991,6 +1006,10 @@ module.exports = class Page extends Model {
'pages.content', 'pages.content',
'pages.render', 'pages.render',
'pages.toc', 'pages.toc',
'pages.minTocLevel',
'pages.tocLevel',
'pages.tocCollapseLevel',
'pages.doUseTocDefault',
'pages.contentType', 'pages.contentType',
'pages.createdAt', 'pages.createdAt',
'pages.updatedAt', 'pages.updatedAt',
@ -1071,6 +1090,10 @@ module.exports = class Page extends Model {
tags: page.tags.map(t => _.pick(t, ['tag', 'title'])), tags: page.tags.map(t => _.pick(t, ['tag', 'title'])),
title: page.title, title: page.title,
toc: _.isString(page.toc) ? page.toc : JSON.stringify(page.toc), toc: _.isString(page.toc) ? page.toc : JSON.stringify(page.toc),
minTocLevel: page.minTocLevel,
tocLevel: page.tocLevel,
tocCollapseLevel: page.tocCollapseLevel,
doUseTocDefault: page.doUseTocDefault,
updatedAt: page.updatedAt updatedAt: page.updatedAt
})) }))
} }

@ -92,6 +92,10 @@ module.exports = {
isPublished: _.get(pageData, 'isPublished', currentPage.isPublished), isPublished: _.get(pageData, 'isPublished', currentPage.isPublished),
isPrivate: false, isPrivate: false,
content: pageData.content, content: pageData.content,
minTocLevel: pageData.minTocLevel,
tocLevel: pageData.tocLevel,
tocCollapseLevel: pageData.tocCollapseLevel,
doUseTocDefault: pageData.doUseTocDefault,
user: user, user: user,
skipStorage: true skipStorage: true
}) })
@ -110,7 +114,10 @@ module.exports = {
content: pageData.content, content: pageData.content,
user: user, user: user,
editor: pageEditor, editor: pageEditor,
skipStorage: true skipStorage: true,
tocLevel: pageData.tocLevel,
tocCollapseLevel: pageData.tocCollapseLevel,
doUseTocDefault: pageData.doUseTocDefault
}) })
} }
}, },

@ -126,6 +126,10 @@ module.exports = () => {
_.set(WIKI.config, 'theming', { _.set(WIKI.config, 'theming', {
theme: 'default', theme: 'default',
darkMode: false, darkMode: false,
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
doUseTocDefault: true,
iconset: 'mdi', iconset: 'mdi',
injectCSS: '', injectCSS: '',
injectHead: '', injectHead: '',

@ -19,6 +19,10 @@ block body
script-css=page.extra.css script-css=page.extra.css
script-js=page.extra.js script-js=page.extra.js
init-mode=page.mode 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()
init-editor=page.editorKey init-editor=page.editorKey
init-content=page.content init-content=page.content
checkout-date=page.updatedAt checkout-date=page.updatedAt

@ -29,6 +29,10 @@ block body
comments-enabled=config.features.featurePageComments comments-enabled=config.features.featurePageComments
effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64')
comments-external=comments.codeTemplate comments-external=comments.codeTemplate
:min-toc-level=minTocLevel
:toc-level=tocLevel
:toc-collapse-level=tocCollapseLevel
:do-use-toc-default=doUseTocDefault.toString()
) )
template(slot='contents') template(slot='contents')
div!= page.render div!= page.render

Loading…
Cancel
Save