Browse Source

feat: Configurable ToC Heading Levels (#5101)

Co-authored-by: Regev Brody <regevbr@gmail.com>
feat-toc
Timo Kruth 2 years ago
committed by NGPixel
parent
commit
d9f4e90e2c
No known key found for this signature in database GPG Key ID: 8FDA2F1757F60D63
22 changed files with 355 additions and 29 deletions
  1. 30
      client/components/admin/admin-theme.vue
  2. 56
      client/components/editor.vue
  3. 41
      client/components/editor/editor-modal-properties.vue
  4. 4
      client/graph/admin/theme/theme-mutation-save.gql
  5. 3
      client/graph/admin/theme/theme-query-config.gql
  6. 4
      client/store/page.js
  7. 81
      client/themes/default/components/page-toc-item.vue
  8. 34
      client/themes/default/components/page.vue
  9. 2
      dev/containers/Dockerfile
  10. 4
      server/app/data.yml
  11. 21
      server/controllers/common.js
  12. 11
      server/db/migrations-sqlite/2.5.13.js
  13. 11
      server/db/migrations/2.5.13.js
  14. 6
      server/graph/resolvers/theming.js
  15. 12
      server/graph/schemas/page.graphql
  16. 6
      server/graph/schemas/theming.graphql
  17. 12
      server/helpers/page.js
  18. 25
      server/models/pages.js
  19. 9
      server/modules/storage/disk/common.js
  20. 4
      server/setup.js
  21. 4
      server/views/editor.pug
  22. 4
      server/views/page.pug

30
client/components/admin/admin-theme.vue

@ -1,5 +1,5 @@
<template lang='pug'>
v-container(fluid, grid-list-lg)
v-container(fluid, grid-list-lg)
v-layout(row wrap)
v-flex(xs12)
.admin-header
@ -51,7 +51,6 @@
persistent-hint
:hint='$t(`admin:theme.darkModeHint`)'
)
v-card.mt-3.animated.fadeInUp.wait-p1s
v-toolbar(color='primary', dark, dense, flat)
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.'
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-card.animated.fadeInUp.wait-p2s
//- v-toolbar(color='teal', dark, dense, flat)
@ -154,6 +161,9 @@ export default {
config: {
theme: 'default',
darkMode: false,
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
iconset: '',
injectCSS: '',
injectHead: '',
@ -163,6 +173,17 @@ export default {
}
},
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'),
headers() {
return [
@ -209,6 +230,9 @@ 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),
injectCSS: this.config.injectCSS,
injectHead: this.config.injectHead,
injectBody: this.config.injectBody

56
client/components/editor.vue

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

41
client/components/editor/editor-modal-properties.vue

@ -1,5 +1,5 @@
<template lang='pug'>
v-dialog(
v-dialog(
v-model='isShown'
persistent
width='1000'
@ -67,6 +67,23 @@
: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')
@ -255,6 +272,7 @@ import 'codemirror/mode/htmlmixed/htmlmixed.js'
import 'codemirror/mode/css/css.js'
/* global siteLangs, siteConfig */
// eslint-disable-next-line no-useless-escape
const filenamePattern = /^(?![\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s])(?!.*[\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]$)[^\#\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]*$/
export default {
@ -276,10 +294,10 @@ export default {
currentTab: 0,
cm: null,
rules: {
required: value => !!value || 'This field is required.',
path: value => {
return filenamePattern.test(value) || 'Invalid path. Please ensure it does not contain special characters, or begin/end in a slash or hashtag string.'
}
required: value => !!value || 'This field is required.',
path: value => {
return filenamePattern.test(value) || 'Invalid path. Please ensure it does not contain special characters, or begin/end in a slash or hashtag string.'
}
}
}
},
@ -297,6 +315,19 @@ export default {
isPublished: sync('page/isPublished'),
publishStartDate: sync('page/publishStartDate'),
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'),
scriptCss: sync('page/scriptCss'),
hasScriptPermission: get('page/effectivePermissions@pages.script'),

4
client/graph/admin/theme/theme-mutation-save.gql

@ -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 {
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 {
succeeded
errorCode

3
client/graph/admin/theme/theme-query-config.gql

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

4
client/store/page.js

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

81
client/themes/default/components/page-toc-item.vue

@ -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>

34
client/themes/default/components/page.vue

@ -59,18 +59,9 @@
v-flex.page-col-sd(lg3, xl2, v-if='$vuetify.breakpoint.lgAndUp')
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.pb-3(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``')
template(v-for='(tocItem, tocIdx) in tocDecoded')
v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)')
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-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')
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')}}
@ -319,6 +310,7 @@
import { StatusIndicator } from 'vue-status-indicator'
import Tabset from './tabset.vue'
import NavSidebar from './nav-sidebar.vue'
import PageTocItem from './page-toc-item.vue'
import Prism from 'prismjs'
import mermaid from 'mermaid'
import { get, sync } from 'vuex-pathify'
@ -366,6 +358,7 @@ Prism.plugins.toolbar.registerButton('copy-to-clipboard', (env) => {
export default {
components: {
NavSidebar,
PageTocItem,
StatusIndicator
},
props: {
@ -440,6 +433,22 @@ export default {
commentsExternal: {
type: Boolean,
default: false
},
minTocLevel: {
type: Number,
default: 0
},
tocLevel: {
type: Number,
default: 2
},
tocCollapseLevel: {
type: Number,
default: 2
},
doUseTocDefault: {
type: Boolean,
default: true
}
},
data() {
@ -515,6 +524,7 @@ export default {
hasDeletePagesPermission: get('page/effectivePermissions@pages.delete'),
hasReadSourcePermission: get('page/effectivePermissions@source.read'),
hasReadHistoryPermission: get('page/effectivePermissions@history.read'),
hasAnyPagePermissions () {
return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission ||
this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission

2
dev/containers/Dockerfile

@ -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 && \
apt-get install -y bash curl git python make g++ nano openssh-server gnupg cmake && \
mkdir -p /wiki
WORKDIR /wiki

4
server/app/data.yml

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

21
server/controllers/common.js

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

11
server/db/migrations-sqlite/2.5.13.js

@ -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 => { }

11
server/db/migrations/2.5.13.js

@ -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 => { }

6
server/graph/resolvers/theming.js

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

12
server/graph/schemas/page.graphql

@ -93,6 +93,10 @@ type PageMutation {
scriptJs: String
tags: [String]!
title: String!
minTocLevel: Int
tocLevel: Int
tocCollapseLevel: Int
doUseTocDefault: Boolean
): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
update(
@ -110,6 +114,10 @@ type PageMutation {
scriptJs: String
tags: [String]
title: String
minTocLevel: Int
tocLevel: Int
tocCollapseLevel: Int
doUseTocDefault: Boolean
): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
convert(
@ -189,6 +197,10 @@ type Page {
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!

6
server/graph/schemas/theming.graphql

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

12
server/helpers/page.js

@ -81,8 +81,18 @@ module.exports = {
['date', page.updatedAt],
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
['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) {
case 'markdown':
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content

25
server/models/pages.js

@ -47,7 +47,10 @@ 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'}
}
@ -161,6 +164,10 @@ module.exports = class Page extends Model {
},
title: 'string',
toc: 'string',
minTocLevel: 'uint',
tocLevel: 'uint',
tocCollapseLevel: 'uint',
doUseTocDefault: 'boolean',
updatedAt: 'string'
})
}
@ -311,6 +318,10 @@ 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,
extra: JSON.stringify({
js: scriptJs,
css: scriptCss
@ -430,6 +441,10 @@ 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,
extra: JSON.stringify({
...ogPage.extra,
js: scriptJs,
@ -991,6 +1006,10 @@ module.exports = class Page extends Model {
'pages.content',
'pages.render',
'pages.toc',
'pages.minTocLevel',
'pages.tocLevel',
'pages.tocCollapseLevel',
'pages.doUseTocDefault',
'pages.contentType',
'pages.createdAt',
'pages.updatedAt',
@ -1071,6 +1090,10 @@ 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,
updatedAt: page.updatedAt
}))
}

9
server/modules/storage/disk/common.js

@ -92,6 +92,10 @@ 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
})
@ -110,7 +114,10 @@ module.exports = {
content: pageData.content,
user: user,
editor: pageEditor,
skipStorage: true
skipStorage: true,
tocLevel: pageData.tocLevel,
tocCollapseLevel: pageData.tocCollapseLevel,
doUseTocDefault: pageData.doUseTocDefault
})
}
},

4
server/setup.js

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

4
server/views/editor.pug

@ -19,6 +19,10 @@ 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()
init-editor=page.editorKey
init-content=page.content
checkout-date=page.updatedAt

4
server/views/page.pug

@ -29,6 +29,10 @@ block body
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

Loading…
Cancel
Save