feat: edit page properties + update dependencies

This commit is contained in:
Nicolas Giard
2022-11-08 06:47:17 +00:00
parent 274f3f4a0a
commit f05a73dccb
16 changed files with 1072 additions and 782 deletions
+214 -85
View File
@@ -13,6 +13,8 @@ const TurndownService = require('turndown')
const turndownPluginGfm = require('@joplin/turndown-plugin-gfm').gfm
const cheerio = require('cheerio')
const pageRegex = /^[a-zA0-90-9-_/]*$/
const frontmatterRegex = {
html: /^(<!-{2}(?:\n|\r)([\w\W]+?)(?:\n|\r)-{2}>)?(?:\n|\r)*([\w\W]*)*/,
legacy: /^(<!-- TITLE: ?([\w\W]+?) ?-{2}>)?(?:\n|\r)?(<!-- SUBTITLE: ?([\w\W]+?) ?-{2}>)?(?:\n|\r)*([\w\W]*)*/i,
@@ -52,7 +54,7 @@ module.exports = class Page extends Model {
}
static get jsonAttributes() {
return ['extra']
return ['config', 'historyData', 'relations', 'scripts', 'toc']
}
static get relationMappings() {
@@ -231,11 +233,6 @@ module.exports = class Page extends Model {
throw new WIKI.Error.Custom('InvalidSiteId', 'Site ID is invalid.')
}
// -> Validate path
if (opts.path.includes('.') || opts.path.includes(' ') || opts.path.includes('\\') || opts.path.includes('//')) {
throw new WIKI.Error.PageIllegalPath()
}
// -> Remove trailing slash
if (opts.path.endsWith('/')) {
opts.path = opts.path.slice(0, -1)
@@ -246,6 +243,14 @@ module.exports = class Page extends Model {
opts.path = opts.path.slice(1)
}
// -> Validate path
if (!pageRegex.test(opts.path)) {
throw new Error('ERR_INVALID_PATH')
}
opts.path = opts.path.toLowerCase()
const dotPath = opts.path.replaceAll('/', '.').replaceAll('-', '_')
// -> Check for page access
if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], {
locale: opts.locale,
@@ -279,41 +284,52 @@ module.exports = class Page extends Model {
}
// -> Format JS Scripts
let scriptJs = ''
let scriptJsLoad = ''
let scriptJsUnload = ''
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: opts.locale,
path: opts.path
})) {
scriptJs = opts.scriptJs || ''
scriptJsLoad = opts.scriptJsLoad || ''
scriptJsUnload = opts.scriptJsUnload || ''
}
// -> Create page
await WIKI.db.pages.query().insert({
const page = await WIKI.db.pages.query().insert({
authorId: opts.user.id,
content: opts.content,
creatorId: opts.user.id,
contentType: _.get(WIKI.data.editors[opts.editor], 'contentType', 'text'),
config: {
allowComments: opts.allowComments ?? true,
allowContributions: opts.allowContributions ?? true,
allowRatings: opts.allowRatings ?? true,
showSidebar: opts.showSidebar ?? true,
showTags: opts.showTags ?? true,
showToc: opts.showToc ?? true,
tocDepth: opts.tocDepth ?? WIKI.sites[opts.siteId].config?.defaults.tocDepth
},
contentType: WIKI.data.editors[opts.editor]?.contentType ?? 'text',
description: opts.description,
dotPath: dotPath,
editor: opts.editor,
hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale }),
publishState: opts.publishState,
icon: opts.icon,
isBrowsable: opts.isBrowsable ?? true,
localeCode: opts.locale,
path: opts.path,
publishState: opts.publishState,
publishEndDate: opts.publishEndDate?.toISO(),
publishStartDate: opts.publishStartDate?.toISO(),
relations: opts.relations ?? [],
siteId: opts.siteId,
title: opts.title,
toc: '[]',
extra: JSON.stringify({
js: scriptJs,
scripts: JSON.stringify({
jsLoad: scriptJsLoad,
jsUnload: scriptJsUnload,
css: scriptCss
})
})
const page = await WIKI.db.pages.getPageFromDb({
path: opts.path,
locale: opts.locale,
userId: opts.user.id
})
}).returning('*')
// -> Save Tags
if (opts.tags && opts.tags.length > 0) {
@@ -365,7 +381,7 @@ module.exports = class Page extends Model {
// -> Fetch original page
const ogPage = await WIKI.db.pages.query().findById(opts.id)
if (!ogPage) {
throw new Error('Invalid Page Id')
throw new Error('ERR_PAGE_NOT_FOUND')
}
// -> Check for page access
@@ -373,70 +389,205 @@ module.exports = class Page extends Model {
locale: ogPage.localeCode,
path: ogPage.path
})) {
throw new WIKI.Error.PageUpdateForbidden()
throw new Error('ERR_PAGE_UPDATE_FORBIDDEN')
}
// -> Check for empty content
if (!opts.content || _.trim(opts.content).length < 1) {
throw new WIKI.Error.PageEmptyContent()
const patch = {}
const historyData = {
action: 'updated',
affectedFields: []
}
// -> Create version snapshot
await WIKI.db.pageHistory.addVersion({
...ogPage,
action: opts.action ? opts.action : 'updated',
versionDate: ogPage.updatedAt
})
await WIKI.db.pageHistory.addVersion(ogPage)
// -> Format Extra Properties
if (!_.isPlainObject(ogPage.extra)) {
ogPage.extra = {}
// -> Basic fields
if ('title' in opts.patch) {
patch.title = opts.patch.title.trim()
historyData.affectedFields.push('title')
if (patch.title.length < 1) {
throw new Error('ERR_PAGE_TITLE_MISSING')
}
}
if ('description' in opts.patch) {
patch.description = opts.patch.description.trim()
historyData.affectedFields.push('description')
}
if ('icon' in opts.patch) {
patch.icon = opts.patch.icon.trim()
historyData.affectedFields.push('icon')
}
if ('content' in opts.patch) {
patch.content = opts.patch.content
historyData.affectedFields.push('content')
}
// -> Publish State
if (opts.patch.publishState) {
patch.publishState = opts.patch.publishState
historyData.affectedFields.push('publishState')
if (patch.publishState === 'scheduled' && (!opts.patch.publishStartDate || !opts.patch.publishEndDate)) {
throw new Error('ERR_PAGE_MISSING_SCHEDULED_DATES')
}
}
if (opts.patch.publishStartDate) {
patch.publishStartDate = opts.patch.publishStartDate
historyData.affectedFields.push('publishStartDate')
}
if (opts.patch.publishEndDate) {
patch.publishEndDate = opts.patch.publishEndDate
historyData.affectedFields.push('publishEndDate')
}
// -> Page Config
if ('isBrowsable' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
isBrowsable: opts.patch.isBrowsable
}
historyData.affectedFields.push('isBrowsable')
}
if ('allowComments' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
allowComments: opts.patch.allowComments
}
historyData.affectedFields.push('allowComments')
}
if ('allowContributions' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
allowContributions: opts.patch.allowContributions
}
historyData.affectedFields.push('allowContributions')
}
if ('allowRatings' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
allowRatings: opts.patch.allowRatings
}
historyData.affectedFields.push('allowRatings')
}
if ('showSidebar' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
showSidebar: opts.patch.showSidebar
}
historyData.affectedFields.push('showSidebar')
}
if ('showTags' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
showTags: opts.patch.showTags
}
historyData.affectedFields.push('showTags')
}
if ('showToc' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
showToc: opts.patch.showToc
}
historyData.affectedFields.push('showToc')
}
if ('tocDepth' in opts.patch) {
patch.config = {
...patch.config ?? ogPage.config ?? {},
tocDepth: opts.patch.tocDepth
}
historyData.affectedFields.push('tocDepth')
if (patch.config.tocDepth?.min < 1 || patch.config.tocDepth?.min > 6) {
throw new Error('ERR_PAGE_INVALID_TOC_DEPTH')
}
if (patch.config.tocDepth?.max < 1 || patch.config.tocDepth?.max > 6) {
throw new Error('ERR_PAGE_INVALID_TOC_DEPTH')
}
}
// -> Relations
if ('relations' in opts.patch) {
patch.relations = opts.patch.relations.map(r => {
if (r.label.length < 1) {
throw new Error('ERR_PAGE_RELATION_LABEL_MISSING')
} else if (r.label.length > 255) {
throw new Error('ERR_PAGE_RELATION_LABEL_TOOLONG')
} else if (r.icon.length > 255) {
throw new Error('ERR_PAGE_RELATION_ICON_INVALID')
} else if (r.target.length > 1024) {
throw new Error('ERR_PAGE_RELATION_TARGET_INVALID')
}
return r
})
historyData.affectedFields.push('relations')
}
// -> Format CSS Scripts
let scriptCss = _.get(ogPage, 'extra.css', '')
if (WIKI.auth.checkAccess(opts.user, ['write:styles'], {
locale: opts.locale,
path: opts.path
})) {
if (!_.isEmpty(opts.scriptCss)) {
scriptCss = new CleanCSS({ inline: false }).minify(opts.scriptCss).styles
} else {
scriptCss = ''
if (opts.patch.scriptCss) {
if (WIKI.auth.checkAccess(opts.user, ['write:styles'], {
locale: ogPage.localeCode,
path: ogPage.path
})) {
patch.scripts = {
...patch.scripts ?? ogPage.scripts ?? {},
css: !_.isEmpty(opts.patch.scriptCss) ? new CleanCSS({ inline: false }).minify(opts.patch.scriptCss).styles : ''
}
historyData.affectedFields.push('scripts.css')
}
}
// -> Format JS Scripts
let scriptJs = _.get(ogPage, 'extra.js', '')
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: opts.locale,
path: opts.path
})) {
scriptJs = opts.scriptJs || ''
if (opts.patch.scriptJsLoad) {
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: ogPage.localeCode,
path: ogPage.path
})) {
patch.scripts = {
...patch.scripts ?? ogPage.scripts ?? {},
jsLoad: opts.patch.scriptJsLoad ?? ''
}
historyData.affectedFields.push('scripts.jsLoad')
}
}
if (opts.patch.scriptJsUnload) {
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: ogPage.localeCode,
path: ogPage.path
})) {
patch.scripts = {
...patch.scripts ?? ogPage.scripts ?? {},
jsUnload: opts.patch.scriptJsUnload ?? ''
}
historyData.affectedFields.push('scripts.jsUnload')
}
}
// -> Tags
if ('tags' in opts.patch) {
historyData.affectedFields.push('tags')
}
// -> Update page
await WIKI.db.pages.query().patch({
...patch,
authorId: opts.user.id,
content: opts.content,
description: opts.description,
publishState: opts.publishState,
publishEndDate: opts.publishEndDate?.toISO(),
publishStartDate: opts.publishStartDate?.toISO(),
title: opts.title,
extra: JSON.stringify({
...ogPage.extra,
js: scriptJs,
css: scriptCss
})
historyData
}).where('id', ogPage.id)
let page = await WIKI.db.pages.getPageFromDb(ogPage.id)
// -> Save Tags
await WIKI.db.tags.associateTags({ tags: opts.tags, page })
if (opts.patch.tags) {
await WIKI.db.tags.associateTags({ tags: opts.patch.tags, page })
}
// -> Render page to HTML
await WIKI.db.pages.renderPage(page)
if (opts.patch.content) {
await WIKI.db.pages.renderPage(page)
}
WIKI.events.outbound.emit('deletePageFromCache', page.hash)
// // -> Update Search Index
@@ -468,11 +619,6 @@ module.exports = class Page extends Model {
destinationPath: opts.path,
user: opts.user
})
} else {
// -> Update title of page tree entry
await WIKI.db.knex.table('pageTree').where({
pageId: page.id
}).update('title', page.title)
}
// -> Get latest updatedAt
@@ -944,6 +1090,8 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise of the Page Model Instance
*/
static async getPage(opts) {
return WIKI.db.pages.getPageFromDb(opts)
// -> Get from cache first
let page = await WIKI.db.pages.getPageFromCache(opts)
if (!page) {
@@ -974,26 +1122,7 @@ module.exports = class Page extends Model {
try {
return WIKI.db.pages.query()
.column([
'pages.id',
'pages.path',
'pages.hash',
'pages.title',
'pages.description',
'pages.publishState',
'pages.publishStartDate',
'pages.publishEndDate',
'pages.content',
'pages.render',
'pages.toc',
'pages.contentType',
'pages.createdAt',
'pages.updatedAt',
'pages.editor',
'pages.localeCode',
'pages.authorId',
'pages.creatorId',
'pages.siteId',
'pages.extra',
'pages.*',
{
authorName: 'author.name',
authorEmail: 'author.email',