Skip to content

Commit

Permalink
feat: Configurable ToC Heading Levels (requarks#5101)
Browse files Browse the repository at this point in the history
Co-authored-by: Regev Brody <[email protected]>
  • Loading branch information
TimoKruth and regevbr authored Apr 4, 2022
1 parent e39ab6c commit 6e0d21f
Show file tree
Hide file tree
Showing 22 changed files with 355 additions and 29 deletions.
30 changes: 27 additions & 3 deletions client/components/admin/admin-theme.vue
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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`)}}
Expand All @@ -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)
Expand Down Expand Up @@ -154,6 +161,9 @@ export default {
config: {
theme: 'default',
darkMode: false,
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
iconset: '',
injectCSS: '',
injectHead: '',
Expand All @@ -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 [
Expand Down Expand Up @@ -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
Expand Down
56 changes: 53 additions & 3 deletions client/components/editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -303,6 +326,10 @@ export default {
$publishStartDate: Date
$scriptCss: String
$scriptJs: String
$minTocLevel: Int!
$tocLevel: Int!
$tocCollapseLevel: Int!
$doUseTocDefault: Boolean!
$tags: [String]!
$title: String!
) {
Expand All @@ -319,6 +346,10 @@ export default {
publishStartDate: $publishStartDate
scriptCss: $scriptCss
scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tags: $tags
title: $title
) {
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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 (
Expand All @@ -407,6 +441,10 @@ export default {
$publishStartDate: Date
$scriptCss: String
$scriptJs: String
$minTocLevel: Int
$tocLevel: Int
$tocCollapseLevel: Int
$doUseTocDefault: Boolean
$tags: [String]
$title: String
) {
Expand All @@ -424,6 +462,10 @@ export default {
publishStartDate: $publishStartDate
scriptCss: $scriptCss
scriptJs: $scriptJs
minTocLevel: $minTocLevel
tocLevel: $tocLevel
tocCollapseLevel: $tocCollapseLevel
doUseTocDefault: $doUseTocDefault
tags: $tags
title: $title
) {
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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 => {
Expand Down
41 changes: 36 additions & 5 deletions client/components/editor/editor-modal-properties.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template lang='pug'>
v-dialog(
v-dialog(
v-model='isShown'
persistent
width='1000'
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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 {
Expand All @@ -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.'
}
}
}
},
Expand All @@ -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/[email protected]'),
Expand Down
4 changes: 2 additions & 2 deletions client/graph/admin/theme/theme-mutation-save.gql
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions client/graph/admin/theme/theme-query-config.gql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ query {
theme
iconset
darkMode
minTocLevel
tocLevel
tocCollapseLevel
injectCSS
injectHead
injectBody
Expand Down
4 changes: 4 additions & 0 deletions client/store/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const state = {
editor: '',
mode: '',
scriptJs: '',
minTocLevel: 0,
tocLevel: 2,
tocCollapseLevel: 2,
doUseTocDefault: true,
scriptCss: '',
effectivePermissions: {
comments: {
Expand Down
81 changes: 81 additions & 0 deletions client/themes/default/components/page-toc-item.vue
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 6e0d21f

Please sign in to comment.