feat: page TOC + refactor PageDataDialog to composition API

parent 85a74aa3
...@@ -153,6 +153,11 @@ router.get(['/d', '/d/*'], async (req, res, next) => { ...@@ -153,6 +153,11 @@ router.get(['/d', '/d/*'], async (req, res, next) => {
*/ */
router.get(['/_edit', '/_edit/*'], async (req, res, next) => { router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
if (!site) {
throw new Error('INVALID_SITE')
}
if (pageArgs.path === '') { if (pageArgs.path === '') {
return res.redirect(`/_edit/home`) return res.redirect(`/_edit/home`)
...@@ -175,10 +180,10 @@ router.get(['/_edit', '/_edit/*'], async (req, res, next) => { ...@@ -175,10 +180,10 @@ router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
// -> Get page data from DB // -> Get page data from DB
let page = await WIKI.db.pages.getPageFromDb({ let page = await WIKI.db.pages.getPageFromDb({
siteId: site.id,
path: pageArgs.path, path: pageArgs.path,
locale: pageArgs.locale, locale: pageArgs.locale,
userId: req.user.id, userId: req.user.id
isPrivate: false
}) })
pageArgs.tags = _.get(page, 'tags', []) pageArgs.tags = _.get(page, 'tags', [])
...@@ -415,6 +420,11 @@ router.get('/*', async (req, res, next) => { ...@@ -415,6 +420,11 @@ router.get('/*', async (req, res, next) => {
const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`)) const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
const pageArgs = pageHelper.parsePath(req.path, { stripExt }) const pageArgs = pageHelper.parsePath(req.path, { stripExt })
const isPage = (stripExt || pageArgs.path.indexOf('.') === -1) const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
if (!site) {
throw new Error('INVALID_SITE')
}
if (isPage) { if (isPage) {
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
...@@ -426,6 +436,7 @@ router.get('/*', async (req, res, next) => { ...@@ -426,6 +436,7 @@ router.get('/*', async (req, res, next) => {
try { try {
// -> Get Page from cache // -> Get Page from cache
const page = await WIKI.db.pages.getPage({ const page = await WIKI.db.pages.getPage({
siteId: site.id,
path: pageArgs.path, path: pageArgs.path,
locale: pageArgs.locale, locale: pageArgs.locale,
userId: req.user.id userId: req.user.id
...@@ -470,67 +481,8 @@ router.get('/*', async (req, res, next) => { ...@@ -470,67 +481,8 @@ router.get('/*', async (req, res, next) => {
}) })
} }
// -> Build sidebar navigation
let sdi = 1
const sidebar = (await WIKI.db.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({
i: `sdi-${sdi++}`,
k: n.kind,
l: n.label,
c: n.icon,
y: n.targetType,
t: n.target
}))
// -> Build theme code injection
const injectCode = {
css: '', // WIKI.config.theming.injectCSS,
head: '', // WIKI.config.theming.injectHead,
body: '' // WIKI.config.theming.injectBody
}
// Handle missing extra field
page.extra = page.extra || { css: '', js: '' }
if (!_.isEmpty(page.extra.css)) {
injectCode.css = `${injectCode.css}\n${page.extra.css}`
}
if (!_.isEmpty(page.extra.js)) {
injectCode.body = `${injectCode.body}\n${page.extra.js}`
}
// -> Convert page TOC
if (!_.isString(page.toc)) {
page.toc = JSON.stringify(page.toc)
}
// -> Inject comments variables
const commentTmpl = {
codeTemplate: '', // WIKI.data.commentProvider.codeTemplate,
head: '', // WIKI.data.commentProvider.head,
body: '', // WIKI.data.commentProvider.body,
main: '' // WIKI.data.commentProvider.main
}
if (false && WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
[
{ key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
{ key: 'pageId', value: page.id }
].forEach((cfg) => {
commentTmpl.head = _.replace(commentTmpl.head, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
commentTmpl.body = _.replace(commentTmpl.body, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
commentTmpl.main = _.replace(commentTmpl.main, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
})
}
// -> Render view // -> Render view
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html')) res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
// res.render('page', {
// page,
// sidebar,
// injectCode,
// comments: commentTmpl,
// effectivePermissions
// })
} else if (pageArgs.path === 'home') { } else if (pageArgs.path === 'home') {
res.redirect('/_welcome') res.redirect('/_welcome')
} else { } else {
......
...@@ -166,7 +166,10 @@ module.exports = { ...@@ -166,7 +166,10 @@ module.exports = {
*/ */
async pageByPath (obj, args, context, info) { async pageByPath (obj, args, context, info) {
const pageArgs = pageHelper.parsePath(args.path) const pageArgs = pageHelper.parsePath(args.path)
let page = await WIKI.db.pages.getPageFromDb(pageArgs) let page = await WIKI.db.pages.getPageFromDb({
...pageArgs,
siteId: args.siteId
})
if (page) { if (page) {
return { return {
...page, ...page,
......
...@@ -35,6 +35,7 @@ extend type Query { ...@@ -35,6 +35,7 @@ extend type Query {
): Page ): Page
pageByPath( pageByPath(
siteId: UUID!
path: String! path: String!
): Page ): Page
...@@ -173,7 +174,7 @@ type Page { ...@@ -173,7 +174,7 @@ type Page {
tags: [PageTag] tags: [PageTag]
content: String content: String
render: String render: String
toc: String toc: [JSON]
contentType: String contentType: String
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
......
...@@ -1010,6 +1010,7 @@ module.exports = class Page extends Model { ...@@ -1010,6 +1010,7 @@ module.exports = class Page extends Model {
.where(queryModeID ? { .where(queryModeID ? {
'pages.id': opts 'pages.id': opts
} : { } : {
'pages.siteId': opts.siteId,
'pages.path': opts.path, 'pages.path': opts.path,
'pages.localeCode': opts.locale 'pages.localeCode': opts.locale
}) })
......
...@@ -62,8 +62,8 @@ module.exports = async ({ payload }) => { ...@@ -62,8 +62,8 @@ module.exports = async ({ payload }) => {
$('.toc-anchor', el).remove() $('.toc-anchor', el).remove()
_.get(toc, leafPath).push({ _.get(toc, leafPath).push({
title: _.trim($(el).text()), label: _.trim($(el).text()),
anchor: leafSlug, key: leafSlug.substring(1),
children: [] children: []
}) })
}) })
......
<template lang="pug"> <template lang="pug">
q-card.page-data-dialog(style='width: 750px;') q-card.page-data-dialog(style='width: 750px;')
q-toolbar.bg-primary.text-white.flex q-toolbar.bg-primary.text-white.flex
.text-subtitle2 {{$t('editor.pageData.title')}} .text-subtitle2 {{t('editor.pageData.title')}}
q-space q-space
q-btn( q-btn(
icon='las la-times' icon='las la-times'
...@@ -10,13 +10,13 @@ q-card.page-data-dialog(style='width: 750px;') ...@@ -10,13 +10,13 @@ q-card.page-data-dialog(style='width: 750px;')
v-close-popup v-close-popup
) )
q-card-section.page-data-dialog-selector q-card-section.page-data-dialog-selector
//- .text-overline.text-white {{$t('editor.pageData.template')}} //- .text-overline.text-white {{t('editor.pageData.template')}}
.flex.q-gutter-sm .flex.q-gutter-sm
q-select( q-select(
dark dark
v-model='templateId' v-model='state.templateId'
:label='$t(`editor.pageData.template`)' :label='t(`editor.pageData.template`)'
:aria-label='$t(`editor.pageData.template`)' :aria-label='t(`editor.pageData.template`)'
:options='templates' :options='templates'
option-value='id' option-value='id'
map-options map-options
...@@ -28,14 +28,14 @@ q-card.page-data-dialog(style='width: 750px;') ...@@ -28,14 +28,14 @@ q-card.page-data-dialog(style='width: 750px;')
q-btn.acrylic-btn( q-btn.acrylic-btn(
dark dark
icon='las la-pen' icon='las la-pen'
:label='$t(`common.actions.manage`)' :label='t(`common.actions.manage`)'
unelevated unelevated
no-caps no-caps
color='deep-orange-9' color='deep-orange-9'
@click='editTemplates' @click='editTemplates'
) )
q-tabs.alt-card( q-tabs.alt-card(
v-model='mode' v-model='state.mode'
inline-label inline-label
no-caps no-caps
) )
...@@ -48,11 +48,11 @@ q-card.page-data-dialog(style='width: 750px;') ...@@ -48,11 +48,11 @@ q-card.page-data-dialog(style='width: 750px;')
label='YAML' label='YAML'
) )
q-scroll-area( q-scroll-area(
:thumb-style='thumbStyle' :thumb-style='siteStore.thumbStyle'
:bar-style='barStyle' :bar-style='siteStore.barStyle'
style='height: calc(100% - 50px - 75px - 48px);' style='height: calc(100% - 50px - 75px - 48px);'
) )
q-card-section(v-if='mode === `visual`') q-card-section(v-if='state.mode === `visual`')
.q-gutter-sm .q-gutter-sm
q-input( q-input(
label='Attribute Text' label='Attribute Text'
...@@ -76,60 +76,69 @@ q-card.page-data-dialog(style='width: 750px;') ...@@ -76,60 +76,69 @@ q-card.page-data-dialog(style='width: 750px;')
dense dense
size='lg' size='lg'
) )
q-no-ssr(v-else, :placeholder='$t(`common.loading`)') q-no-ssr(v-else, :placeholder='t(`common.loading`)')
codemirror.admin-theme-cm( codemirror.admin-theme-cm(
ref='cmData' ref='cmData'
v-model='content' v-model='state.content'
:options='{ mode: `text/yaml` }' :options='{ mode: `text/yaml` }'
) )
q-dialog( q-dialog(
v-model='showDataTemplateDialog' v-model='state.showDataTemplateDialog'
) )
page-data-template-dialog page-data-template-dialog
</template> </template>
<script> <script setup>
import { get } from 'vuex-pathify' import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar'
import { nextTick, onMounted, reactive, ref, watch } from 'vue'
import PageDataTemplateDialog from './PageDataTemplateDialog.vue' import PageDataTemplateDialog from './PageDataTemplateDialog.vue'
export default { import { usePageStore } from 'src/stores/page'
components: { import { useSiteStore } from 'src/stores/site'
PageDataTemplateDialog
}, // QUASAR
data () {
return { const $q = useQuasar()
showDataTemplateDialog: false,
templateId: '', // STORES
content: '',
mode: 'visual' const pageStore = usePageStore()
} const siteStore = useSiteStore()
},
computed: { // I18N
thumbStyle: get('site/thumbStyle', false),
barStyle: get('site/barStyle', false), const { t } = useI18n()
templates () {
return [ // DATA
{
id: '', const state = reactive({
label: 'None', showDataTemplateDialog: false,
data: [] templateId: '',
}, content: '',
...this.$store.get('site/pageDataTemplates'), mode: 'visual'
{ })
id: 'basic',
label: 'Basic', const templates = [
data: [] {
} id: '',
] label: 'None',
} data: []
}, },
methods: { ...siteStore.pageDataTemplates,
editTemplates () { {
this.showDataTemplateDialog = !this.showDataTemplateDialog id: 'basic',
} label: 'Basic',
data: []
} }
]
// METHODS
function editTemplates () {
state.showDataTemplateDialog = !state.showDataTemplateDialog
} }
</script> </script>
......
...@@ -270,8 +270,6 @@ q-card.page-properties-dialog ...@@ -270,8 +270,6 @@ q-card.page-properties-dialog
</template> </template>
<script setup> <script setup>
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import { nextTick, onMounted, reactive, ref, watch } from 'vue' import { nextTick, onMounted, reactive, ref, watch } from 'vue'
...@@ -280,6 +278,9 @@ import PageRelationDialog from './PageRelationDialog.vue' ...@@ -280,6 +278,9 @@ import PageRelationDialog from './PageRelationDialog.vue'
import PageScriptsDialog from './PageScriptsDialog.vue' import PageScriptsDialog from './PageScriptsDialog.vue'
import PageTags from './PageTags.vue' import PageTags from './PageTags.vue'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
// QUASAR // QUASAR
const $q = useQuasar() const $q = useQuasar()
......
...@@ -141,9 +141,11 @@ q-page.column ...@@ -141,9 +141,11 @@ q-page.column
q-icon.q-mr-sm(name='las la-stream', color='grey') q-icon.q-mr-sm(name='las la-stream', color='grey')
.text-caption.text-grey-7 Contents .text-caption.text-grey-7 Contents
.q-px-md.q-pb-sm .q-px-md.q-pb-sm
q-tree( q-tree.page-toc(
:nodes='state.toc' :nodes='pageStore.toc'
icon='las la-caret-right'
node-key='key' node-key='key'
dense
v-model:expanded='state.tocExpanded' v-model:expanded='state.tocExpanded'
v-model:selected='state.tocSelected' v-model:selected='state.tocSelected'
) )
...@@ -287,6 +289,7 @@ q-page.column ...@@ -287,6 +289,7 @@ q-page.column
transition-show='jump-left' transition-show='jump-left'
transition-hide='jump-right' transition-hide='jump-right'
class='floating-sidepanel' class='floating-sidepanel'
no-shake
) )
component(:is='sideDialogs[state.sideDialogComponent]') component(:is='sideDialogs[state.sideDialogComponent]')
...@@ -354,54 +357,6 @@ const state = reactive({ ...@@ -354,54 +357,6 @@ const state = reactive({
globalDialogComponent: null, globalDialogComponent: null,
showTagsEditBtn: false, showTagsEditBtn: false,
tagEditMode: false, tagEditMode: false,
toc: [
{
key: 'h1-0',
label: 'Introduction'
},
{
key: 'h1-1',
label: 'Planets',
children: [
{
key: 'h2-0',
label: 'Earth',
children: [
{
key: 'h3-0',
label: 'Countries',
children: [
{
key: 'h4-0',
label: 'Cities',
children: [
{
key: 'h5-0',
label: 'Montreal',
children: [
{
key: 'h6-0',
label: 'Districts'
}
]
}
]
}
]
}
]
},
{
key: 'h2-1',
label: 'Mars'
},
{
key: 'h2-2',
label: 'Jupiter'
}
]
}
],
tocExpanded: ['h1-0', 'h1-1'], tocExpanded: ['h1-0', 'h1-1'],
tocSelected: [], tocSelected: [],
currentRating: 3 currentRating: 3
...@@ -472,8 +427,8 @@ watch(() => route.path, async (newValue) => { ...@@ -472,8 +427,8 @@ watch(() => route.path, async (newValue) => {
} }
}, { immediate: true }) }, { immediate: true })
watch(() => state.toc, refreshTocExpanded) watch(() => pageStore.toc, () => { refreshTocExpanded() }, { immediate: true })
watch(() => pageStore.tocDepth, refreshTocExpanded) watch(() => pageStore.tocDepth, () => { refreshTocExpanded() })
// METHODS // METHODS
...@@ -492,20 +447,22 @@ function savePage () { ...@@ -492,20 +447,22 @@ function savePage () {
state.showGlobalDialog = true state.showGlobalDialog = true
} }
function refreshTocExpanded (baseToc) { function refreshTocExpanded (baseToc, lvl) {
console.info(pageStore.tocDepth.min, lvl, pageStore.tocDepth.max)
const toExpand = [] const toExpand = []
let isRootNode = false let isRootNode = false
if (!baseToc) { if (!baseToc) {
baseToc = state.toc baseToc = pageStore.toc
isRootNode = true isRootNode = true
lvl = 1
} }
if (baseToc.length > 0) { if (baseToc.length > 0) {
for (const node of baseToc) { for (const node of baseToc) {
if (node.key >= `h${pageStore.tocDepth.min}` && node.key <= `h${pageStore.tocDepth.max}`) { if (lvl >= pageStore.tocDepth.min && lvl < pageStore.tocDepth.max) {
toExpand.push(node.key) toExpand.push(node.key)
} }
if (node.children?.length && node.key < `h${pageStore.tocDepth.max}`) { if (node.children?.length && lvl < pageStore.tocDepth.max - 1) {
toExpand.push(...refreshTocExpanded(node.children)) toExpand.push(...refreshTocExpanded(node.children, lvl + 1))
} }
} }
} }
...@@ -515,12 +472,6 @@ function refreshTocExpanded (baseToc) { ...@@ -515,12 +472,6 @@ function refreshTocExpanded (baseToc) {
return toExpand return toExpand
} }
} }
// MOUNTED
onMounted(() => {
refreshTocExpanded()
})
</script> </script>
<style lang="scss"> <style lang="scss">
...@@ -691,4 +642,10 @@ onMounted(() => { ...@@ -691,4 +642,10 @@ onMounted(() => {
background-color: $dark-3; background-color: $dark-3;
} }
} }
.page-toc {
&.q-tree--dense .q-tree__node {
padding-bottom: 5px;
}
}
</style> </style>
...@@ -80,7 +80,8 @@ export const usePageStore = defineStore('page', { ...@@ -80,7 +80,8 @@ export const usePageStore = defineStore('page', {
}, },
commentsCount: 0, commentsCount: 0,
content: '', content: '',
render: '' render: '',
toc: []
}), }),
getters: {}, getters: {},
actions: { actions: {
...@@ -93,9 +94,11 @@ export const usePageStore = defineStore('page', { ...@@ -93,9 +94,11 @@ export const usePageStore = defineStore('page', {
const resp = await APOLLO_CLIENT.query({ const resp = await APOLLO_CLIENT.query({
query: gql` query: gql`
query loadPage ( query loadPage (
$siteId: UUID!
$path: String! $path: String!
) { ) {
pageByPath( pageByPath(
siteId: $siteId
path: $path path: $path
) { ) {
id id
...@@ -105,10 +108,12 @@ export const usePageStore = defineStore('page', { ...@@ -105,10 +108,12 @@ export const usePageStore = defineStore('page', {
locale locale
updatedAt updatedAt
render render
toc
} }
} }
`, `,
variables: { variables: {
siteId: siteStore.id,
path path
}, },
fetchPolicy: 'network-only' fetchPolicy: 'network-only'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment