From 9a92789d1358c84dc1303780c7dc3c76bbc0ffa1 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Thu, 6 Apr 2023 20:01:30 +0000 Subject: [PATCH] feat: editor create page mode --- server/controllers/common.js | 704 ++++++++++++------------ server/web.js | 6 +- ux/src/components/HeaderNav.vue | 1 + ux/src/components/PageHeader.vue | 54 +- ux/src/components/TreeBrowserDialog.vue | 5 +- ux/src/components/WelcomeOverlay.vue | 2 +- ux/src/pages/AdminGeneral.vue | 7 +- ux/src/stores/page.js | 228 ++++++-- ux/src/stores/site.js | 6 +- 9 files changed, 594 insertions(+), 419 deletions(-) diff --git a/server/controllers/common.js b/server/controllers/common.js index 597b278f..c5cd057c 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -106,20 +106,6 @@ router.get('/_thumb/:id.webp', async (req, res, next) => { } }) -/** - * New v3 vue app - */ -router.get([ - '/_admin', - '/_admin/*', - '/_profile', - '/_profile/*', - '/_error', - '/_error/*', - '/_welcome' -], (req, res, next) => { - res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html')) -}) // router.get(['/_admin', '/_admin/*'], (req, res, next) => { // if (!WIKI.auth.checkAccess(req.user, [ // 'manage:system', @@ -140,297 +126,297 @@ router.get([ // }) -/** - * Download Page / Version - */ -router.get(['/d', '/d/*'], async (req, res, next) => { - const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) - - const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0 - - const page = await WIKI.db.pages.getPageFromDb({ - path: pageArgs.path, - locale: pageArgs.locale, - userId: req.user.id, - isPrivate: false - }) - - pageArgs.tags = _.get(page, 'tags', []) - - if (versionId > 0) { - if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'downloadVersion' }) - } - } else { - if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'download' }) - } - } - - if (page) { - const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType) - res.attachment(fileName) - if (versionId > 0) { - const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId }) - res.send(pageHelper.injectPageMetadata(pageVersion)) - } else { - res.send(pageHelper.injectPageMetadata(page)) - } - } else { - res.status(404).end() - } -}) - -/** - * Create/Edit document - */ -router.get(['/_edit', '/_edit/*'], async (req, res, next) => { - 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 === '') { - return res.redirect(`/_edit/home`) - } - - // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { - // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`) - // } +// /** +// * Download Page / Version +// */ +// router.get(['/d', '/d/*'], async (req, res, next) => { +// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) + +// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0 + +// const page = await WIKI.db.pages.getPageFromDb({ +// path: pageArgs.path, +// locale: pageArgs.locale, +// userId: req.user.id, +// isPrivate: false +// }) + +// pageArgs.tags = _.get(page, 'tags', []) + +// if (versionId > 0) { +// if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'downloadVersion' }) +// } +// } else { +// if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'download' }) +// } +// } - // req.i18n.changeLanguage(pageArgs.locale) +// if (page) { +// const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType) +// res.attachment(fileName) +// if (versionId > 0) { +// const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId }) +// res.send(pageHelper.injectPageMetadata(pageVersion)) +// } else { +// res.send(pageHelper.injectPageMetadata(page)) +// } +// } else { +// res.status(404).end() +// } +// }) - // -> Set Editor Lang - _.set(res, 'locals.siteConfig.lang', pageArgs.locale) - // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') +// /** +// * Create/Edit document +// */ +// router.get(['/_edit', '/_edit/*'], async (req, res, next) => { +// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) +// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname }) - // -> Check for reserved path - if (pageHelper.isReservedPath(pageArgs.path)) { - return next(new Error('Cannot create this page because it starts with a system reserved path.')) - } +// if (!site) { +// throw new Error('INVALID_SITE') +// } - // -> Get page data from DB - let page = await WIKI.db.pages.getPageFromDb({ - siteId: site.id, - path: pageArgs.path, - locale: pageArgs.locale, - userId: req.user.id - }) +// if (pageArgs.path === '') { +// return res.redirect(`/_edit/home`) +// } - pageArgs.tags = _.get(page, 'tags', []) +// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { +// // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`) +// // } - // -> Effective Permissions - const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) +// // req.i18n.changeLanguage(pageArgs.locale) - const injectCode = { - css: '', // WIKI.config.theming.injectCSS, - head: '', // WIKI.config.theming.injectHead, - body: '' // WIKI.config.theming.injectBody - } +// // -> Set Editor Lang +// _.set(res, 'locals.siteConfig.lang', pageArgs.locale) +// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') - if (page) { - // -> EDIT MODE - if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'edit' }) - } +// // -> Check for reserved path +// if (pageHelper.isReservedPath(pageArgs.path)) { +// return next(new Error('Cannot create this page because it starts with a system reserved path.')) +// } - // -> Get page tags - await page.$relatedQuery('tags') - page.tags = _.map(page.tags, 'tag') +// // -> Get page data from DB +// let page = await WIKI.db.pages.getPageFromDb({ +// siteId: site.id, +// path: pageArgs.path, +// locale: pageArgs.locale, +// userId: req.user.id +// }) - // Handle missing extra field - page.extra = page.extra || { css: '', js: '' } +// pageArgs.tags = _.get(page, 'tags', []) - // -> Beautify Script CSS - if (!_.isEmpty(page.extra.css)) { - page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles - } +// // -> Effective Permissions +// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) - _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`) - _.set(res.locals, 'pageMeta.description', page.description) - page.mode = 'update' - page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false' - page.content = Buffer.from(page.content).toString('base64') - } else { - // -> CREATE MODE - if (!effectivePermissions.pages.write) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'create' }) - } - - _.set(res.locals, 'pageMeta.title', `New Page`) - page = { - path: pageArgs.path, - localeCode: pageArgs.locale, - editorKey: null, - mode: 'create', - content: null, - title: null, - description: null, - updatedAt: new Date().toISOString(), - extra: { - css: '', - js: '' - } - } - } +// const injectCode = { +// css: '', // WIKI.config.theming.injectCSS, +// head: '', // WIKI.config.theming.injectHead, +// body: '' // WIKI.config.theming.injectBody +// } - res.render('editor', { page, injectCode, effectivePermissions }) -}) +// if (page) { +// // -> EDIT MODE +// if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'edit' }) +// } + +// // -> Get page tags +// await page.$relatedQuery('tags') +// page.tags = _.map(page.tags, 'tag') + +// // Handle missing extra field +// page.extra = page.extra || { css: '', js: '' } + +// // -> Beautify Script CSS +// if (!_.isEmpty(page.extra.css)) { +// page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles +// } + +// _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`) +// _.set(res.locals, 'pageMeta.description', page.description) +// page.mode = 'update' +// page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false' +// page.content = Buffer.from(page.content).toString('base64') +// } else { +// // -> CREATE MODE +// if (!effectivePermissions.pages.write) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'create' }) +// } + +// _.set(res.locals, 'pageMeta.title', `New Page`) +// page = { +// path: pageArgs.path, +// localeCode: pageArgs.locale, +// editorKey: null, +// mode: 'create', +// content: null, +// title: null, +// description: null, +// updatedAt: new Date().toISOString(), +// extra: { +// css: '', +// js: '' +// } +// } +// } -/** - * History - */ -router.get(['/h', '/h/*'], async (req, res, next) => { - const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) +// res.render('editor', { page, injectCode, effectivePermissions }) +// }) - if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { - return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`) - } +// /** +// * History +// */ +// router.get(['/h', '/h/*'], async (req, res, next) => { +// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) - req.i18n.changeLanguage(pageArgs.locale) +// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { +// return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`) +// } - _.set(res, 'locals.siteConfig.lang', pageArgs.locale) - _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') +// req.i18n.changeLanguage(pageArgs.locale) - const page = await WIKI.db.pages.getPageFromDb({ - path: pageArgs.path, - locale: pageArgs.locale, - userId: req.user.id, - isPrivate: false - }) +// _.set(res, 'locals.siteConfig.lang', pageArgs.locale) +// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') - if (!page) { - _.set(res.locals, 'pageMeta.title', 'Page Not Found') - return res.status(404).render('notfound', { action: 'history' }) - } +// const page = await WIKI.db.pages.getPageFromDb({ +// path: pageArgs.path, +// locale: pageArgs.locale, +// userId: req.user.id, +// isPrivate: false +// }) - pageArgs.tags = _.get(page, 'tags', []) +// if (!page) { +// _.set(res.locals, 'pageMeta.title', 'Page Not Found') +// return res.status(404).render('notfound', { action: 'history' }) +// } - const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) +// pageArgs.tags = _.get(page, 'tags', []) - if (!effectivePermissions.history.read) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'history' }) - } +// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) - if (page) { - _.set(res.locals, 'pageMeta.title', page.title) - _.set(res.locals, 'pageMeta.description', page.description) +// if (!effectivePermissions.history.read) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'history' }) +// } - res.render('history', { page, effectivePermissions }) - } else { - res.redirect(`/${pageArgs.path}`) - } -}) +// if (page) { +// _.set(res.locals, 'pageMeta.title', page.title) +// _.set(res.locals, 'pageMeta.description', page.description) -/** - * Page ID redirection - */ -router.get(['/i', '/i/:id'], async (req, res, next) => { - const pageId = _.toSafeInteger(req.params.id) - if (pageId <= 0) { - return res.redirect('/') - } +// res.render('history', { page, effectivePermissions }) +// } else { +// res.redirect(`/${pageArgs.path}`) +// } +// }) - const page = await WIKI.db.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId) - if (!page) { - _.set(res.locals, 'pageMeta.title', 'Page Not Found') - return res.status(404).render('notfound', { action: 'view' }) - } +// /** +// * Page ID redirection +// */ +// router.get(['/i', '/i/:id'], async (req, res, next) => { +// const pageId = _.toSafeInteger(req.params.id) +// if (pageId <= 0) { +// return res.redirect('/') +// } - if (!WIKI.auth.checkAccess(req.user, ['read:pages'], { - locale: page.localeCode, - path: page.path, - private: page.isPrivate, - privateNS: page.privateNS, - explicitLocale: false, - tags: page.tags - })) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'view' }) - } +// const page = await WIKI.db.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId) +// if (!page) { +// _.set(res.locals, 'pageMeta.title', 'Page Not Found') +// return res.status(404).render('notfound', { action: 'view' }) +// } - if (WIKI.config.lang.namespacing) { - return res.redirect(`/${page.localeCode}/${page.path}`) - } else { - return res.redirect(`/${page.path}`) - } -}) +// if (!WIKI.auth.checkAccess(req.user, ['read:pages'], { +// locale: page.localeCode, +// path: page.path, +// private: page.isPrivate, +// privateNS: page.privateNS, +// explicitLocale: false, +// tags: page.tags +// })) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'view' }) +// } -/** - * Source - */ -router.get(['/s', '/s/*'], async (req, res, next) => { - const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) - const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0 - - const page = await WIKI.db.pages.getPageFromDb({ - path: pageArgs.path, - locale: pageArgs.locale, - userId: req.user.id, - isPrivate: false - }) +// if (WIKI.config.lang.namespacing) { +// return res.redirect(`/${page.localeCode}/${page.path}`) +// } else { +// return res.redirect(`/${page.path}`) +// } +// }) - pageArgs.tags = _.get(page, 'tags', []) +// /** +// * Source +// */ +// router.get(['/s', '/s/*'], async (req, res, next) => { +// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) +// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0 - if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { - return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`) - } +// const page = await WIKI.db.pages.getPageFromDb({ +// path: pageArgs.path, +// locale: pageArgs.locale, +// userId: req.user.id, +// isPrivate: false +// }) - // -> Effective Permissions - const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) +// pageArgs.tags = _.get(page, 'tags', []) - _.set(res, 'locals.siteConfig.lang', pageArgs.locale) - _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') +// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { +// return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`) +// } - if (versionId > 0) { - if (!effectivePermissions.history.read) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'sourceVersion' }) - } - } else { - if (!effectivePermissions.source.read) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.render('unauthorized', { action: 'source' }) - } - } +// // -> Effective Permissions +// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) + +// _.set(res, 'locals.siteConfig.lang', pageArgs.locale) +// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') + +// if (versionId > 0) { +// if (!effectivePermissions.history.read) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'sourceVersion' }) +// } +// } else { +// if (!effectivePermissions.source.read) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.render('unauthorized', { action: 'source' }) +// } +// } - if (page) { - if (versionId > 0) { - const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId }) - _.set(res.locals, 'pageMeta.title', pageVersion.title) - _.set(res.locals, 'pageMeta.description', pageVersion.description) - res.render('source', { - page: { - ...page, - ...pageVersion - }, - effectivePermissions - }) - } else { - _.set(res.locals, 'pageMeta.title', page.title) - _.set(res.locals, 'pageMeta.description', page.description) - - res.render('source', { page, effectivePermissions }) - } - } else { - res.redirect(`/${pageArgs.path}`) - } -}) +// if (page) { +// if (versionId > 0) { +// const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId }) +// _.set(res.locals, 'pageMeta.title', pageVersion.title) +// _.set(res.locals, 'pageMeta.description', pageVersion.description) +// res.render('source', { +// page: { +// ...page, +// ...pageVersion +// }, +// effectivePermissions +// }) +// } else { +// _.set(res.locals, 'pageMeta.title', page.title) +// _.set(res.locals, 'pageMeta.description', page.description) + +// res.render('source', { page, effectivePermissions }) +// } +// } else { +// res.redirect(`/${pageArgs.path}`) +// } +// }) -/** - * Tags - */ -router.get(['/t', '/t/*'], (req, res, next) => { - _.set(res.locals, 'pageMeta.title', 'Tags') - res.render('tags') -}) +// /** +// * Tags +// */ +// router.get(['/t', '/t/*'], (req, res, next) => { +// _.set(res.locals, 'pageMeta.title', 'Tags') +// res.render('tags') +// }) /** * User Avatar @@ -448,96 +434,100 @@ router.get('/_user/:uid/avatar', async (req, res, next) => { return res.sendStatus(404) }) -/** - * View document / asset - */ -router.get('/*', async (req, res, next) => { - const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`)) - const pageArgs = pageHelper.parsePath(req.path, { stripExt }) - 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 (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { - // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`) - // } - - // req.i18n.changeLanguage(pageArgs.locale) - - try { - // -> Get Page from cache - const page = await WIKI.db.pages.getPage({ - siteId: site.id, - path: pageArgs.path, - locale: pageArgs.locale, - userId: req.user.id - }) - pageArgs.tags = _.get(page, 'tags', []) - - // -> Effective Permissions - const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) - - // -> Check User Access - if (!effectivePermissions.pages.read) { - if (req.user.id === WIKI.auth.guest.id) { - res.cookie('loginRedirect', req.path, { - maxAge: 15 * 60 * 1000 - }) - } - if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) { - return res.redirect('/login') - } - return res.redirect(`/_error/unauthorized?from=${req.path}`) - } +// /** +// * View document / asset +// */ +// router.get('/*', async (req, res, next) => { +// const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`)) +// const pageArgs = pageHelper.parsePath(req.path, { stripExt }) +// const isPage = (stripExt || pageArgs.path.indexOf('.') === -1) +// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname }) + +// if (!site) { +// throw new Error('INVALID_SITE') +// } - _.set(res, 'locals.siteConfig.lang', pageArgs.locale) - // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') - - if (page) { - _.set(res.locals, 'pageMeta.title', page.title) - _.set(res.locals, 'pageMeta.description', page.description) - - // -> Check Publishing State - let pageIsPublished = page.isPublished - if (pageIsPublished && !_.isEmpty(page.publishStartDate)) { - pageIsPublished = moment(page.publishStartDate).isSameOrBefore() - } - if (pageIsPublished && !_.isEmpty(page.publishEndDate)) { - pageIsPublished = moment(page.publishEndDate).isSameOrAfter() - } - if (!pageIsPublished && !effectivePermissions.pages.write) { - _.set(res.locals, 'pageMeta.title', 'Unauthorized') - return res.status(403).render('unauthorized', { - action: 'view' - }) - } - - // -> Render view - res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html')) - } else if (pageArgs.path === 'home') { - res.redirect('/_welcome') - } else { - _.set(res.locals, 'pageMeta.title', 'Page Not Found') - if (effectivePermissions.pages.write) { - res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale }) - } else { - res.status(404).render('notfound', { action: 'view' }) - } - } - } catch (err) { - next(err) - } - } else { - if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) { - return res.sendStatus(403) - } +// if (isPage) { +// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { +// // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`) +// // } + +// // req.i18n.changeLanguage(pageArgs.locale) + +// try { +// // -> Get Page from cache +// const page = await WIKI.db.pages.getPage({ +// siteId: site.id, +// path: pageArgs.path, +// locale: pageArgs.locale, +// userId: req.user.id +// }) +// pageArgs.tags = _.get(page, 'tags', []) + +// // -> Effective Permissions +// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) + +// // -> Check User Access +// if (!effectivePermissions.pages.read) { +// if (req.user.id === WIKI.auth.guest.id) { +// res.cookie('loginRedirect', req.path, { +// maxAge: 15 * 60 * 1000 +// }) +// } +// if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) { +// return res.redirect('/login') +// } +// return res.redirect(`/_error/unauthorized?from=${req.path}`) +// } + +// _.set(res, 'locals.siteConfig.lang', pageArgs.locale) +// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') + +// if (page) { +// _.set(res.locals, 'pageMeta.title', page.title) +// _.set(res.locals, 'pageMeta.description', page.description) + +// // -> Check Publishing State +// let pageIsPublished = page.isPublished +// if (pageIsPublished && !_.isEmpty(page.publishStartDate)) { +// pageIsPublished = moment(page.publishStartDate).isSameOrBefore() +// } +// if (pageIsPublished && !_.isEmpty(page.publishEndDate)) { +// pageIsPublished = moment(page.publishEndDate).isSameOrAfter() +// } +// if (!pageIsPublished && !effectivePermissions.pages.write) { +// _.set(res.locals, 'pageMeta.title', 'Unauthorized') +// return res.status(403).render('unauthorized', { +// action: 'view' +// }) +// } + +// // -> Render view +// res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html')) +// } else if (pageArgs.path === 'home') { +// res.redirect('/_welcome') +// } else { +// _.set(res.locals, 'pageMeta.title', 'Page Not Found') +// if (effectivePermissions.pages.write) { +// res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale }) +// } else { +// res.status(404).render('notfound', { action: 'view' }) +// } +// } +// } catch (err) { +// next(err) +// } +// } else { +// if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) { +// return res.sendStatus(403) +// } + +// await WIKI.db.assets.getAsset(pageArgs.path, res) +// } +// }) - await WIKI.db.assets.getAsset(pageArgs.path, res) - } +router.get('/*', (req, res, next) => { + res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html')) }) module.exports = router diff --git a/server/web.js b/server/web.js index 2ddf5448..92b62922 100644 --- a/server/web.js +++ b/server/web.js @@ -74,17 +74,13 @@ module.exports = async () => { index: false, maxAge: '7d' })) - app.use('/_assets-legacy/svg/twemoji', async (req, res, next) => { + app.use('/_assets/svg/twemoji', async (req, res, next) => { try { WIKI.asar.serve('twemoji', req, res, next) } catch (err) { res.sendStatus(404) } }) - app.use('/_assets-legacy', express.static(path.join(WIKI.ROOTPATH, 'assets-legacy'), { - index: false, - maxAge: '7d' - })) // ---------------------------------------- // SSL Handlers diff --git a/ux/src/components/HeaderNav.vue b/ux/src/components/HeaderNav.vue index 2e1a2dfe..0523ad2d 100644 --- a/ux/src/components/HeaderNav.vue +++ b/ux/src/components/HeaderNav.vue @@ -27,6 +27,7 @@ q-header.bg-header.text-white.site-header( q-toolbar.gt-sm( style='height: 64px;' dark + v-if='siteStore.features.search' ) q-input( dark diff --git a/ux/src/components/PageHeader.vue b/ux/src/components/PageHeader.vue index 83e714f7..7a09a63a 100644 --- a/ux/src/components/PageHeader.vue +++ b/ux/src/components/PageHeader.vue @@ -126,6 +126,17 @@ @click='discardChanges' ) q-btn.acrylic-btn( + v-if='editorStore.mode === `create`' + flat + icon='las la-check' + color='positive' + label='Create Page' + aria-label='Create Page' + no-caps + @click='createPage' + ) + q-btn.acrylic-btn( + v-else flat icon='las la-check' color='positive' @@ -246,10 +257,49 @@ async function saveChanges () { $q.loading.hide() } -function editPage () { +async function createPage () { + $q.dialog({ + component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')), + componentProps: { + mode: 'createPage', + folderPath: '', + itemTitle: pageStore.title, + itemFileName: pageStore.path + } + }).onOk(async ({ path, title }) => { + $q.loading.show() + try { + pageStore.$patch({ + title, + path + }) + await pageStore.pageSave() + $q.notify({ + type: 'positive', + message: 'Page created successfully.' + }) + editorStore.$patch({ + isActive: false + }) + } catch (err) { + $q.notify({ + type: 'negative', + message: 'Failed to create page.', + caption: err.message + }) + } + $q.loading.hide() + }) +} + +async function editPage () { + $q.loading.show() + await pageStore.pageLoad({ id: pageStore.id, withContent: true }) editorStore.$patch({ isActive: true, - editor: 'markdown' + mode: 'edit', + editor: pageStore.editor }) + $q.loading.hide() } diff --git a/ux/src/components/TreeBrowserDialog.vue b/ux/src/components/TreeBrowserDialog.vue index b8b128e1..7ce6a687 100644 --- a/ux/src/components/TreeBrowserDialog.vue +++ b/ux/src/components/TreeBrowserDialog.vue @@ -230,7 +230,10 @@ const files = computed(() => { // METHODS async function save () { - onDialogOK() + onDialogOK({ + title: state.title, + path: state.path + }) } async function treeLazyLoad (nodeId, { done, fail }) { diff --git a/ux/src/components/WelcomeOverlay.vue b/ux/src/components/WelcomeOverlay.vue index 65b089d9..b89fdd72 100644 --- a/ux/src/components/WelcomeOverlay.vue +++ b/ux/src/components/WelcomeOverlay.vue @@ -93,7 +93,7 @@ function createHomePage (editor) { pageStore.pageCreate({ editor, locale: 'en', - path: '', + path: 'home', title: t('welcome.homeDefault.title'), description: t('welcome.homeDefault.description'), content: t('welcome.homeDefault.content') diff --git a/ux/src/pages/AdminGeneral.vue b/ux/src/pages/AdminGeneral.vue index 751fdc30..598d1c5a 100644 --- a/ux/src/pages/AdminGeneral.vue +++ b/ux/src/pages/AdminGeneral.vue @@ -752,12 +752,7 @@ async function save () { }) await adminStore.fetchSites() if (adminStore.currentSiteId === siteStore.id) { - siteStore.$patch({ - title: state.config.title, - description: state.config.description, - company: state.config.company, - contentLicense: state.config.contentLicense - }) + siteStore.loadSite(window.location.hostname) } } catch (err) { $q.notify({ diff --git a/ux/src/stores/page.js b/ux/src/stores/page.js index 86fab68e..a573c307 100644 --- a/ux/src/stores/page.js +++ b/ux/src/stores/page.js @@ -78,6 +78,34 @@ const gqlQueries = { } } ${pagePropsFragment} + `, + pageByIdWithContent: gql` + query loadPageWithContent ( + $id: UUID! + ) { + pageById( + id: $id + ) { + ...PageRead, + content + } + } + ${pagePropsFragment} + `, + pageByPathWithContent: gql` + query loadPageWithContent ( + $siteId: UUID! + $path: String! + ) { + pageByPath( + siteId: $siteId + path: $path + ) { + ...PageRead, + content + } + } + ${pagePropsFragment} ` } @@ -92,6 +120,7 @@ export const usePageStore = defineStore('page', { content: '', createdAt: '', description: '', + editor: '', icon: 'las la-file-alt', id: '', isBrowsable: true, @@ -140,12 +169,18 @@ export const usePageStore = defineStore('page', { /** * PAGE - LOAD */ - async pageLoad ({ path, id }) { + async pageLoad ({ path, id, withContent = false }) { const editorStore = useEditorStore() const siteStore = useSiteStore() try { + let query + if (withContent) { + query = id ? gqlQueries.pageByIdWithContent : gqlQueries.pageByPathWithContent + } else { + query = id ? gqlQueries.pageById : gqlQueries.pageByPath + } const resp = await APOLLO_CLIENT.query({ - query: id ? gqlQueries.pageById : gqlQueries.pageByPath, + query, variables: id ? { id } : { siteId: siteStore.id, path }, fetchPolicy: 'network-only' }) @@ -221,56 +256,157 @@ export const usePageStore = defineStore('page', { */ async pageSave () { const editorStore = useEditorStore() + const siteStore = useSiteStore() try { - const resp = await APOLLO_CLIENT.mutate({ - mutation: gql` - mutation savePage ( - $id: UUID! - $patch: PageUpdateInput! - ) { - updatePage ( - id: $id - patch: $patch + if (editorStore.mode === 'create') { + const resp = await APOLLO_CLIENT.mutate({ + mutation: gql` + mutation createPage ( + $allowComments: Boolean + $allowContributions: Boolean + $allowRatings: Boolean + $content: String! + $description: String! + $editor: String! + $icon: String + $isBrowsable: Boolean + $locale: String! + $path: String! + $publishState: PagePublishState! + $publishEndDate: Date + $publishStartDate: Date + $relations: [PageRelationInput!] + $scriptCss: String + $scriptJsLoad: String + $scriptJsUnload: String + $showSidebar: Boolean + $showTags: Boolean + $showToc: Boolean + $siteId: UUID! + $tags: [String!] + $title: String! + $tocDepth: PageTocDepthInput ) { - operation { - succeeded - message + createPage ( + allowComments: $allowComments + allowContributions: $allowContributions + allowRatings: $allowRatings + content: $content + description: $description + editor: $editor + icon: $icon + isBrowsable: $isBrowsable + locale: $locale + path: $path + publishState: $publishState + publishEndDate: $publishEndDate + publishStartDate: $publishStartDate + relations: $relations + scriptCss: $scriptCss + scriptJsLoad: $scriptJsLoad + scriptJsUnload: $scriptJsUnload + showSidebar: $showSidebar + showTags: $showTags + showToc: $showToc + siteId: $siteId + tags: $tags + title: $title + tocDepth: $tocDepth + ) { + operation { + succeeded + message + } } } + `, + variables: { + ...pick(this, [ + 'allowComments', + 'allowContributions', + 'allowRatings', + 'content', + 'description', + 'icon', + 'isBrowsable', + 'locale', + 'password', + 'path', + 'publishEndDate', + 'publishStartDate', + 'publishState', + 'relations', + 'scriptJsLoad', + 'scriptJsUnload', + 'scriptCss', + 'showSidebar', + 'showTags', + 'showToc', + 'tags', + 'title', + 'tocDepth' + ]), + editor: editorStore.editor, + siteId: siteStore.id } - `, - variables: { - id: this.id, - patch: pick(this, [ - 'allowComments', - 'allowContributions', - 'allowRatings', - // 'content', - 'description', - 'icon', - 'isBrowsable', - 'locale', - 'password', - 'path', - 'publishEndDate', - 'publishStartDate', - 'publishState', - 'relations', - 'scriptJsLoad', - 'scriptJsUnload', - 'scriptCss', - 'showSidebar', - 'showTags', - 'showToc', - 'tags', - 'title', - 'tocDepth' - ]) + }) + const result = resp?.data?.createPage?.operation ?? {} + if (!result.succeeded) { + throw new Error(result.message) + } + this.id = resp.data.createPage.page.id + this.editor = editorStore.editor + } else { + const resp = await APOLLO_CLIENT.mutate({ + mutation: gql` + mutation savePage ( + $id: UUID! + $patch: PageUpdateInput! + ) { + updatePage ( + id: $id + patch: $patch + ) { + operation { + succeeded + message + } + } + } + `, + variables: { + id: this.id, + patch: pick(this, [ + 'allowComments', + 'allowContributions', + 'allowRatings', + 'content', + 'description', + 'icon', + 'isBrowsable', + 'locale', + 'password', + 'path', + 'publishEndDate', + 'publishStartDate', + 'publishState', + 'relations', + 'scriptJsLoad', + 'scriptJsUnload', + 'scriptCss', + 'showSidebar', + 'showTags', + 'showToc', + 'tags', + 'title', + 'tocDepth' + ]) + } + }) + const result = resp?.data?.updatePage?.operation ?? {} + if (!result.succeeded) { + throw new Error(result.message) } - }) - const result = resp?.data?.updatePage?.operation ?? {} - if (!result.succeeded) { - throw new Error(result.message) } // Update editor state timestamps const curDate = DateTime.utc() diff --git a/ux/src/stores/site.js b/ux/src/stores/site.js index 59eeabff..25b62b9e 100644 --- a/ux/src/stores/site.js +++ b/ux/src/stores/site.js @@ -26,7 +26,9 @@ export const useSiteStore = defineStore('site', { showSidebar: true, overlay: null, features: { - ratingsMode: 'off' + ratingsMode: 'off', + reasonForChange: 'required', + search: false }, editors: { asciidoc: false, @@ -87,6 +89,8 @@ export const useSiteStore = defineStore('site', { footerExtra features { ratingsMode + reasonForChange + search } editors { asciidoc {