Browse Source

refactor: change toc headings to match v3 behavior

feat-toc
NGPixel 2 years ago
parent
commit
fb01525042
No known key found for this signature in database GPG Key ID: 8FDA2F1757F60D63
24 changed files with 235 additions and 352 deletions
  1. 65
      client/components/admin/admin-theme.vue
  2. 81
      client/components/editor.vue
  3. 92
      client/components/editor/editor-modal-properties.vue
  4. 20
      client/graph/admin/theme/theme-mutation-save.gql
  5. 7
      client/graph/admin/theme/theme-query-config.gql
  6. 9
      client/store/page.js
  7. 28
      client/themes/default/components/page-toc-item.vue
  8. 30
      client/themes/default/components/page.vue
  9. 2
      dev/containers/Dockerfile
  10. 7
      server/app/data.yml
  11. 33
      server/controllers/common.js
  12. 11
      server/db/migrations-sqlite/2.5.13.js
  13. 12
      server/db/migrations-sqlite/2.5.284.js
  14. 11
      server/db/migrations/2.5.13.js
  15. 12
      server/db/migrations/2.5.284.js
  16. 8
      server/graph/resolvers/theming.js
  17. 11
      server/graph/schemas/common.graphql
  18. 62
      server/graph/schemas/page.graphql
  19. 14
      server/graph/schemas/theming.graphql
  20. 45
      server/models/pages.js
  21. 9
      server/modules/storage/disk/common.js
  22. 8
      server/setup.js
  23. 5
      server/views/editor.pug
  24. 5
      server/views/page.pug

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

@ -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
@ -54,54 +54,17 @@ v-container(fluid, grid-list-lg)
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`)}}
v-spacer
v-chip(label, color='white', small).primary--text coming soon
v-card-text 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( 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' v-model='tocRange'
:min='1' :min='1'
:max='6' :max='6'
:tick-labels='["H1", "H2", "H3", "H4", "H5", "H6"]' :tick-labels='["H1", "H2", "H3", "H4", "H5", "H6"]'
) )
.text-caption {{$t('admin:theme.tocHeadingLevelsHint')}}
v-flex(lg6 xs12) 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-card.animated.fadeInUp.wait-p2s
v-toolbar(color='primary', dark, dense, flat) v-toolbar(color='primary', dark, dense, flat)
@ -161,9 +124,10 @@ export default {
config: { config: {
theme: 'default', theme: 'default',
darkMode: false, darkMode: false,
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
tocDepth: {
min: 1,
max: 2
},
iconset: '', iconset: '',
injectCSS: '', injectCSS: '',
injectHead: '', injectHead: '',
@ -175,13 +139,14 @@ export default {
computed: { computed: {
tocRange: { tocRange: {
get() { get() {
var range = [this.config.minTocLevel, this.config.tocLevel]
var range = [this.config.tocDepth.min, this.config.tocDepth.max]
return range return range
}, },
set(value) { 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'), darkMode: sync('site/dark'),
@ -230,9 +195,7 @@ 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),
tocDepth: this.config.tocDepth,
injectCSS: this.config.injectCSS, injectCSS: this.config.injectCSS,
injectHead: this.config.injectHead, injectHead: this.config.injectHead,
injectBody: this.config.injectBody injectBody: this.config.injectBody

81
client/components/editor.vue

@ -144,21 +144,9 @@ 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
tocOptions: {
type: String,
default: ''
}, },
checkoutDate: { checkoutDate: {
type: String, type: String,
@ -187,7 +175,9 @@ export default {
tags: '', tags: '',
title: '', title: '',
css: '', css: '',
js: ''
js: '',
tocDepth: 0,
useDefaultTocDepth: false
} }
} }
}, },
@ -206,10 +196,8 @@ 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.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.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'),
@ -243,12 +231,15 @@ 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')
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.setCurrentSavedState()
this.checkoutDateActive = this.checkoutDate this.checkoutDateActive = this.checkoutDate
@ -326,10 +317,8 @@ export default {
$publishStartDate: Date $publishStartDate: Date
$scriptCss: String $scriptCss: String
$scriptJs: String $scriptJs: String
$minTocLevel: Int!
$tocLevel: Int!
$tocCollapseLevel: Int!
$doUseTocDefault: Boolean!
$tocDepth: RangeInput
$useDefaultTocDepth: Boolean
$tags: [String]! $tags: [String]!
$title: String! $title: String!
) { ) {
@ -346,10 +335,8 @@ export default {
publishStartDate: $publishStartDate publishStartDate: $publishStartDate
scriptCss: $scriptCss scriptCss: $scriptCss
scriptJs: $scriptJs scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tocDepth: $tocDepth
useDefaultTocDepth: $useDefaultTocDepth
tags: $tags tags: $tags
title: $title title: $title
) { ) {
@ -379,10 +366,8 @@ 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'),
tocDepth: this.$store.get('page/tocDepth'),
useDefaultTocDepth: this.$store.get('page/useDefaultTocDepth'),
tags: this.$store.get('page/tags'), tags: this.$store.get('page/tags'),
title: this.$store.get('page/title') title: this.$store.get('page/title')
} }
@ -441,10 +426,8 @@ export default {
$publishStartDate: Date $publishStartDate: Date
$scriptCss: String $scriptCss: String
$scriptJs: String $scriptJs: String
$minTocLevel: Int
$tocLevel: Int
$tocCollapseLevel: Int
$doUseTocDefault: Boolean
$tocDepth: RangeInput
$useDefaultTocDepth: Boolean
$tags: [String] $tags: [String]
$title: String $title: String
) { ) {
@ -462,10 +445,8 @@ export default {
publishStartDate: $publishStartDate publishStartDate: $publishStartDate
scriptCss: $scriptCss scriptCss: $scriptCss
scriptJs: $scriptJs scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tocDepth: $tocDepth
useDefaultTocDepth: $useDefaultTocDepth
tags: $tags tags: $tags
title: $title title: $title
) { ) {
@ -495,10 +476,8 @@ 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'),
tocDepth: this.$store.get('page/tocDepth'),
useDefaultTocDepth: this.$store.get('page/useDefaultTocDepth'),
tags: this.$store.get('page/tags'), tags: this.$store.get('page/tags'),
title: this.$store.get('page/title') title: this.$store.get('page/title')
} }
@ -582,10 +561,8 @@ export default {
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')
tocDepth: this.$store.get('page/tocDepth@min') + (this.$store.get('page/tocDepth@max') * 10),
useDefaultTocDepth: this.$store.get('page/useDefaultTocDepth')
} }
}, },
injectCustomCss: _.debounce(css => { injectCustomCss: _.debounce(css => {

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

@ -19,9 +19,9 @@ v-dialog(
v-card(tile) v-card(tile)
v-tabs(color='white', background-color='blue darken-1', dark, centered, v-model='currentTab') 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.info')}}
v-tab {{$t('editor:props.toc')}}
v-tab {{$t('editor:props.scheduling')}} v-tab {{$t('editor:props.scheduling')}}
v-tab(:disabled='!hasScriptPermission') {{$t('editor:props.scripts')}} 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(:disabled='!hasStylePermission') {{$t('editor:props.styles')}}
v-tab-item(transition='fade-transition', reverse-transition='fade-transition') v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text.pt-5 v-card-text.pt-5
@ -67,23 +67,6 @@ v-dialog(
: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')
@ -107,6 +90,24 @@ v-dialog(
hide-no-data hide-no-data
:search-input.sync='newTagSearch' :search-input.sync='newTagSearch'
) )
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-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text v-card-text
.overline {{$t('editor:props.publishState')}} .overline {{$t('editor:props.publishState')}}
@ -213,43 +214,6 @@ v-dialog(
.editor-props-codeeditor-hint .editor-props-codeeditor-hint
.caption {{$t('editor:props.htmlHint')}} .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') v-tab-item(:transition='false', :reverse-transition='false')
.editor-props-codeeditor-title .editor-props-codeeditor-title
.overline {{$t('editor:props.css')}} .overline {{$t('editor:props.css')}}
@ -315,19 +279,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: {
tocDepth: {
get() { 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) { 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'), scriptJs: sync('page/scriptJs'),
scriptCss: sync('page/scriptCss'), scriptCss: sync('page/scriptCss'),
hasScriptPermission: get('page/effectivePermissions@pages.script'), hasScriptPermission: get('page/effectivePermissions@pages.script'),
@ -359,7 +323,7 @@ export default {
if (this.cm) { if (this.cm) {
this.cm.toTextArea() this.cm.toTextArea()
} }
if (newValue === 2) {
if (newValue === 3) {
this.$nextTick(() => { this.$nextTick(() => {
setTimeout(() => { setTimeout(() => {
this.loadEditor(this.$refs.codejs, 'html') this.loadEditor(this.$refs.codejs, 'html')

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

@ -1,6 +1,22 @@
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 { 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 { responseResult {
succeeded succeeded
errorCode errorCode

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

@ -4,9 +4,10 @@ query {
theme theme
iconset iconset
darkMode darkMode
minTocLevel
tocLevel
tocCollapseLevel
tocDepth {
min
max
}
injectCSS injectCSS
injectHead injectHead
injectBody injectBody

9
client/store/page.js

@ -17,10 +17,11 @@ const state = {
editor: '', editor: '',
mode: '', mode: '',
scriptJs: '', scriptJs: '',
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
doUseTocDefault: true,
tocDepth: {
min: 1,
max: 2
},
useDefaultTocDepth: true,
scriptCss: '', scriptCss: '',
effectivePermissions: { effectivePermissions: {
comments: { comments: {

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

@ -1,7 +1,7 @@
<template lang="pug"> <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',
.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`') :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-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-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.pl-0(@click='click(item.anchor)', :key='item.anchor')
v-list-item-title(v-bind:class='titleClasses') {{item.title}} v-list-item-title(v-bind:class='titleClasses') {{item.title}}
template(v-if='item.children.length !== 0', v-for='subItem in item.children') 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') 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> </template>
<script> <script>
@ -26,15 +26,11 @@ export default {
type: Object, type: Object,
default: () => {} default: () => {}
}, },
minTocLevel: {
min: {
type: Number, type: Number,
default: 0
},
tocLevel: {
type: Number,
default: 2
default: 1
}, },
tocCollapseLevel: {
max: {
type: Number, type: Number,
default: 2 default: 2
}, },
@ -54,7 +50,7 @@ export default {
}, },
computed: { computed: {
isNestedLevel() { isNestedLevel() {
return this.level > this.minTocLevel
return this.level > this.min
}, },
titleClasses() { titleClasses() {
return { return {
@ -75,7 +71,7 @@ export default {
<style lang='scss'> <style lang='scss'>
// Hack to fix animations of multi level nesting v-list-group // 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; transform: rotate(0deg)!important;
} }
</style> </style>

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

@ -60,8 +60,13 @@
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.py-0(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='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') 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')}}
@ -434,21 +439,9 @@ export default {
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
tocOptions: {
type: String,
default: ''
} }
}, },
data() { data() {
@ -518,6 +511,9 @@ export default {
tocDecoded () { tocDecoded () {
return JSON.parse(Buffer.from(this.toc, 'base64').toString()) 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'), hasAdminPermission: get('page/effectivePermissions@system.manage'),
hasWritePagesPermission: get('page/effectivePermissions@pages.write'), hasWritePagesPermission: get('page/effectivePermissions@pages.write'),
hasManagePagesPermission: get('page/effectivePermissions@pages.manage'), hasManagePagesPermission: get('page/effectivePermissions@pages.manage'),

2
dev/containers/Dockerfile

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

7
server/app/data.yml

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

33
server/controllers/common.js

@ -178,6 +178,11 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
title: null, title: null,
description: null, description: null,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
tocOptions: {
min: 1,
max: 2,
useDefault: true
},
extra: { extra: {
css: '', css: '',
js: '' js: ''
@ -504,19 +509,12 @@ 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
}
// -> 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) { if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
// -> Convert page TOC // -> Convert page TOC
@ -528,10 +526,6 @@ 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
}) })
@ -563,10 +557,7 @@ router.get('/*', async (req, res, next) => {
res.render('page', { res.render('page', {
page, page,
sidebar, sidebar,
minTocLevel,
tocLevel,
tocCollapseLevel,
doUseTocDefault,
tocOptions,
injectCode, injectCode,
comments: commentTmpl, comments: commentTmpl,
effectivePermissions effectivePermissions

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

@ -1,11 +0,0 @@
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 => { }

12
server/db/migrations-sqlite/2.5.284.js

@ -0,0 +1,12 @@
exports.up = async knex => {
await knex.schema
.alterTable('pages', table => {
table.json('tocOptions').notNullable().defaultTo(JSON.stringify({
min: 1,
max: 2,
useDefault: true
}))
})
}
exports.down = knex => { }

11
server/db/migrations/2.5.13.js

@ -1,11 +0,0 @@
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 => { }

12
server/db/migrations/2.5.284.js

@ -0,0 +1,12 @@
exports.up = async knex => {
await knex.schema
.alterTable('pages', table => {
table.json('tocOptions').notNullable().defaultTo(JSON.stringify({
min: 1,
max: 2,
useDefault: true
}))
})
}
exports.down = knex => { }

8
server/graph/resolvers/theming.js

@ -24,9 +24,7 @@ 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,
tocDepth: WIKI.config.theming.tocDepth,
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
@ -47,9 +45,7 @@ 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,
tocDepth: args.tocDepth,
injectCSS: args.injectCSS || '', injectCSS: args.injectCSS || '',
injectHead: args.injectHead || '', injectHead: args.injectHead || '',
injectBody: args.injectBody || '' injectBody: args.injectBody || ''

11
server/graph/schemas/common.graphql

@ -21,6 +21,17 @@ input KeyValuePairInput {
value: String! value: String!
} }
# Generic Range Value
type Range {
min: Int
max: Int
}
input RangeInput {
min: Int!
max: Int!
}
# Generic Mutation Response # Generic Mutation Response
type DefaultResponse { type DefaultResponse {
responseResult: ResponseStatus responseResult: ResponseStatus

62
server/graph/schemas/page.graphql

@ -93,10 +93,8 @@ type PageMutation {
scriptJs: String scriptJs: String
tags: [String]! tags: [String]!
title: String! title: String!
minTocLevel: Int
tocLevel: Int
tocCollapseLevel: Int
doUseTocDefault: Boolean
tocDepth: RangeInput
useDefaultTocDepth: Boolean
): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"]) ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
update( update(
@ -114,10 +112,8 @@ type PageMutation {
scriptJs: String scriptJs: String
tags: [String] tags: [String]
title: String title: String
minTocLevel: Int
tocLevel: Int
tocCollapseLevel: Int
doUseTocDefault: Boolean
tocDepth: RangeInput
useDefaultTocDepth: Boolean
): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"]) ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
convert( convert(
@ -183,37 +179,35 @@ type PageMigrationResponse {
} }
type Page { 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"]) 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 render: String
toc: 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 scriptCss: String
scriptJs: 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 { type PageTag {

14
server/graph/schemas/theming.graphql

@ -28,9 +28,7 @@ type ThemingMutation {
theme: String! theme: String!
iconset: String! iconset: String!
darkMode: Boolean! darkMode: Boolean!
minTocLevel: Int!
tocLevel: Int!
tocCollapseLevel: Int!
tocDepth: RangeInput!
injectCSS: String injectCSS: String
injectHead: String injectHead: String
injectBody: String injectBody: String
@ -42,12 +40,10 @@ type ThemingMutation {
# ----------------------------------------------- # -----------------------------------------------
type ThemingConfig { type ThemingConfig {
theme: String!
iconset: String!
darkMode: Boolean!
minTocLevel: Int!
tocLevel: Int!
tocCollapseLevel: Int!
theme: String
iconset: String
darkMode: Boolean
tocDepth: Range
injectCSS: String injectCSS: String
injectHead: String injectHead: String
injectBody: String injectBody: String

45
server/models/pages.js

@ -47,10 +47,6 @@ 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'}
} }
@ -58,7 +54,7 @@ module.exports = class Page extends Model {
} }
static get jsonAttributes() { static get jsonAttributes() {
return ['extra']
return ['extra', 'tocOptions']
} }
static get relationMappings() { static get relationMappings() {
@ -164,10 +160,11 @@ module.exports = class Page extends Model {
}, },
title: 'string', title: 'string',
toc: 'string', toc: 'string',
minTocLevel: 'uint',
tocLevel: 'uint',
tocCollapseLevel: 'uint',
doUseTocDefault: 'boolean',
tocOptions: {
min: 'uint',
max: 'uint',
useDefault: 'boolean'
},
updatedAt: 'string' updatedAt: 'string'
}) })
} }
@ -318,10 +315,11 @@ 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,
tocOptions: JSON.stringify({
min: _.get(opts, 'tocDepth.min', 1),
max: _.get(opts, 'tocDepth.max', 2),
useDefault: opts.useDefaultTocDepth !== false
}),
extra: JSON.stringify({ extra: JSON.stringify({
js: scriptJs, js: scriptJs,
css: scriptCss css: scriptCss
@ -441,10 +439,11 @@ 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,
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({ extra: JSON.stringify({
...ogPage.extra, ...ogPage.extra,
js: scriptJs, js: scriptJs,
@ -802,7 +801,7 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value * @returns {Promise} Promise with no value
*/ */
static async deletePage(opts) { 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) { if (!page) {
throw new WIKI.Error.PageNotFound() throw new WIKI.Error.PageNotFound()
} }
@ -1006,10 +1005,7 @@ 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.tocOptions',
'pages.contentType', 'pages.contentType',
'pages.createdAt', 'pages.createdAt',
'pages.updatedAt', 'pages.updatedAt',
@ -1090,10 +1086,7 @@ 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,
tocOptions: page.tocOptions,
updatedAt: page.updatedAt updatedAt: page.updatedAt
})) }))
} }

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

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

8
server/setup.js

@ -126,11 +126,11 @@ 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',
tocDepth: {
min: 1,
max: 2
},
injectCSS: '', injectCSS: '',
injectHead: '', injectHead: '',
injectBody: '' injectBody: ''

5
server/views/editor.pug

@ -19,10 +19,7 @@ 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()
toc-options=Buffer.from(JSON.stringify(page.tocOptions)).toString('base64')
init-editor=page.editorKey init-editor=page.editorKey
init-content=page.content init-content=page.content
checkout-date=page.updatedAt checkout-date=page.updatedAt

5
server/views/page.pug

@ -25,14 +25,11 @@ block body
toc=Buffer.from(page.toc).toString('base64') toc=Buffer.from(page.toc).toString('base64')
:page-id=page.id :page-id=page.id
sidebar=Buffer.from(JSON.stringify(sidebar)).toString('base64') sidebar=Buffer.from(JSON.stringify(sidebar)).toString('base64')
toc-options=Buffer.from(JSON.stringify(tocOptions)).toString('base64')
nav-mode=config.nav.mode nav-mode=config.nav.mode
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