Browse Source

feat: reconnect links after page update

pull/1066/head
Nick 5 years ago
parent
commit
26842ab62b
3 changed files with 175 additions and 3 deletions
  1. 5
      client/components/admin/admin-storage.vue
  2. 2
      client/components/common/nav-header.vue
  3. 171
      server/models/pages.js

5
client/components/admin/admin-storage.vue

@ -66,8 +66,9 @@
v-list-item-subtitle.red--text.caption {{$t('admin:storage.lastSyncAttempt', { time: $options.filters.moment(tgt.lastAttempt, 'from') })}} v-list-item-subtitle.red--text.caption {{$t('admin:storage.lastSyncAttempt', { time: $options.filters.moment(tgt.lastAttempt, 'from') })}}
v-list-item-action v-list-item-action
v-menu v-menu
v-btn(slot='activator', icon)
v-icon(color='red') mdi-information
template(v-slot:activator='{ on }')
v-btn(icon, v-on='on')
v-icon(color='red') mdi-information
v-card(width='450') v-card(width='450')
v-toolbar(flat, color='red', dark, dense) {{$t('admin:storage.errorMsg')}} v-toolbar(flat, color='red', dark, dense) {{$t('admin:storage.errorMsg')}}
v-card-text {{tgt.message}} v-card-text {{tgt.message}}

2
client/components/common/nav-header.vue

@ -107,7 +107,7 @@
//- PAGE ACTIONS //- PAGE ACTIONS
template(v-if='isAuthenticated && path')
template(v-if='isAuthenticated && path && mode !== `edit`')
v-menu(offset-y, bottom, nudge-bottom='30', transition='slide-y-transition') v-menu(offset-y, bottom, nudge-bottom='30', transition='slide-y-transition')
template(v-slot:activator='{ on: menu }') template(v-slot:activator='{ on: menu }')
v-tooltip(bottom) v-tooltip(bottom)

171
server/models/pages.js

@ -114,6 +114,9 @@ module.exports = class Page extends Model {
this.updatedAt = new Date().toISOString() this.updatedAt = new Date().toISOString()
} }
/**
* Cache Schema
*/
static get cacheSchema() { static get cacheSchema() {
return new JSBinType({ return new JSBinType({
id: 'uint', id: 'uint',
@ -142,6 +145,8 @@ module.exports = class Page extends Model {
/** /**
* Inject page metadata into contents * Inject page metadata into contents
*
* @returns {string} Page Contents with Injected Metadata
*/ */
injectMetadata () { injectMetadata () {
return pageHelper.injectPageMetadata(this) return pageHelper.injectPageMetadata(this)
@ -149,6 +154,8 @@ module.exports = class Page extends Model {
/** /**
* Get the page's file extension based on content type * Get the page's file extension based on content type
*
* @returns {string} File Extension
*/ */
getFileExtension() { getFileExtension() {
switch (this.contentType) { switch (this.contentType) {
@ -166,6 +173,7 @@ module.exports = class Page extends Model {
* *
* @param {String} raw Raw file contents * @param {String} raw Raw file contents
* @param {String} contentType Content Type * @param {String} contentType Content Type
* @returns {Object} Parsed Page Metadata with Raw Content
*/ */
static parseMetadata (raw, contentType) { static parseMetadata (raw, contentType) {
let result let result
@ -204,6 +212,12 @@ module.exports = class Page extends Model {
} }
} }
/**
* Create a New Page
*
* @param {Object} opts Page Properties
* @returns {Promise} Promise of the Page Model Instance
*/
static async createPage(opts) { static async createPage(opts) {
const dupCheck = await WIKI.models.pages.query().select('id').where('localeCode', opts.locale).where('path', opts.path).first() const dupCheck = await WIKI.models.pages.query().select('id').where('localeCode', opts.locale).where('path', opts.path).first()
if (dupCheck) { if (dupCheck) {
@ -259,9 +273,22 @@ module.exports = class Page extends Model {
}) })
} }
// -> Reconnect Links
await WIKI.models.pages.reconnectLinks({
locale: page.localeCode,
path: page.path,
mode: 'create'
})
return page return page
} }
/**
* Update an Existing Page
*
* @param {Object} opts Page Properties
* @returns {Promise} Promise of the Page Model Instance
*/
static async updatePage(opts) { static async updatePage(opts) {
const ogPage = await WIKI.models.pages.query().findById(opts.id) const ogPage = await WIKI.models.pages.query().findById(opts.id)
if (!ogPage) { if (!ogPage) {
@ -314,6 +341,12 @@ module.exports = class Page extends Model {
return page return page
} }
/**
* Delete an Existing Page
*
* @param {Object} opts Page Properties
* @returns {Promise} Promise with no value
*/
static async deletePage(opts) { static async deletePage(opts) {
let page let page
if (_.has(opts, 'id')) { if (_.has(opts, 'id')) {
@ -344,8 +377,98 @@ module.exports = class Page extends Model {
page page
}) })
} }
// -> Reconnect Links
await WIKI.models.pages.reconnectLinks({
locale: page.localeCode,
path: page.path,
mode: 'delete'
})
}
/**
* Reconnect links to new/updated/deleted page
*
* @param {Object} opts - Page parameters
* @param {string} opts.path - Page Path
* @param {string} opts.locale - Page Locale Code
* @param {string} [opts.sourcePath] - Previous Page Path (move only)
* @param {string} [opts.sourceLocale] - Previous Page Locale Code (move only)
* @param {string} opts.mode - Page Update mode (new, move, delete)
* @returns {Promise} Promise with no value
*/
static async reconnectLinks (opts) {
const pageHref = `/${opts.locale}/${opts.path}`
let replaceArgs = {
from: '',
to: ''
}
switch (opts.mode) {
case 'create':
replaceArgs.from = `<a href="${pageHref}" class="is-internal-link is-invalid-page">`
replaceArgs.to = `<a href="${pageHref}" class="is-internal-link is-valid-page">`
break
case 'move':
const prevPageHref = `/${opts.sourceLocale}/${opts.sourcePath}`
replaceArgs.from = `<a href="${prevPageHref}" class="is-internal-link is-invalid-page">`
replaceArgs.to = `<a href="${pageHref}" class="is-internal-link is-valid-page">`
break
case 'delete':
replaceArgs.from = `<a href="${pageHref}" class="is-internal-link is-valid-page">`
replaceArgs.to = `<a href="${pageHref}" class="is-internal-link is-invalid-page">`
break
default:
return false
}
let affectedHashes = []
// -> Perform replace and return affected page hashes (POSTGRES, MSSQL only)
if (WIKI.config.db.type === 'postgres' || WIKI.config.db.type === 'mssql') {
affectedHashes = await WIKI.models.pages.query()
.returning('hash')
.patch({
render: WIKI.models.knex.raw('REPLACE(??, ?, ?)', ['render', replaceArgs.from, replaceArgs.to])
})
.whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale
})
})
.pluck('hash')
} else {
// -> Perform replace, then query affected page hashes (MYSQL, MARIADB, SQLITE only)
await WIKI.models.pages.query()
.patch({
render: WIKI.models.knex.raw('REPLACE(??, ?, ?)', ['render', replaceArgs.from, replaceArgs.to])
})
.whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale
})
})
affectedHashes = await WIKI.models.pages.query()
.column('hash')
.whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale
})
})
.pluck('hash')
}
for (const hash of affectedHashes) {
await WIKI.models.pages.deletePageFromCache({ hash })
}
} }
/**
* Trigger the rendering of a page
*
* @param {Object} page Page Model Instance
* @returns {Promise} Promise with no value
*/
static async renderPage(page) { static async renderPage(page) {
const renderJob = await WIKI.scheduler.registerJob({ const renderJob = await WIKI.scheduler.registerJob({
name: 'render-page', name: 'render-page',
@ -355,6 +478,12 @@ module.exports = class Page extends Model {
return renderJob.finished return renderJob.finished
} }
/**
* Fetch an Existing Page from Cache if possible, from DB otherwise and save render to Cache
*
* @param {Object} opts Page Properties
* @returns {Promise} Promise of the Page Model Instance
*/
static async getPage(opts) { static async getPage(opts) {
// -> Get from cache first // -> Get from cache first
let page = await WIKI.models.pages.getPageFromCache(opts) let page = await WIKI.models.pages.getPageFromCache(opts)
@ -375,6 +504,12 @@ module.exports = class Page extends Model {
return page return page
} }
/**
* Fetch an Existing Page from the Database
*
* @param {Object} opts Page Properties
* @returns {Promise} Promise of the Page Model Instance
*/
static async getPageFromDb(opts) { static async getPageFromDb(opts) {
const queryModeID = _.isNumber(opts) const queryModeID = _.isNumber(opts)
try { try {
@ -426,6 +561,12 @@ module.exports = class Page extends Model {
} }
} }
/**
* Save a Page Model Instance to Cache
*
* @param {Object} page Page Model Instance
* @returns {Promise} Promise with no value
*/
static async savePageToCache(page) { static async savePageToCache(page) {
const cachePath = path.join(process.cwd(), `data/cache/${page.hash}.bin`) const cachePath = path.join(process.cwd(), `data/cache/${page.hash}.bin`)
await fs.outputFile(cachePath, WIKI.models.pages.cacheSchema.encode({ await fs.outputFile(cachePath, WIKI.models.pages.cacheSchema.encode({
@ -448,6 +589,12 @@ module.exports = class Page extends Model {
})) }))
} }
/**
* Fetch an Existing Page from Cache
*
* @param {Object} opts Page Properties
* @returns {Promise} Promise of the Page Model Instance
*/
static async getPageFromCache(opts) { static async getPageFromCache(opts) {
const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' }) const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' })
const cachePath = path.join(process.cwd(), `data/cache/${pageHash}.bin`) const cachePath = path.join(process.cwd(), `data/cache/${pageHash}.bin`)
@ -470,14 +617,32 @@ module.exports = class Page extends Model {
} }
} }
/**
* Delete an Existing Page from Cache
*
* @param {Object} page Page Model Instance
* @param {string} page.hash Hash of the Page
* @returns {Promise} Promise with no value
*/
static async deletePageFromCache(page) { static async deletePageFromCache(page) {
return fs.remove(path.join(process.cwd(), `data/cache/${page.hash}.bin`)) return fs.remove(path.join(process.cwd(), `data/cache/${page.hash}.bin`))
} }
/**
* Flush the contents of the Cache
*/
static async flushCache() { static async flushCache() {
return fs.emptyDir(path.join(process.cwd(), `data/cache`)) return fs.emptyDir(path.join(process.cwd(), `data/cache`))
} }
/**
* Migrate all pages from a source locale to the target locale
*
* @param {Object} opts Migration properties
* @param {string} opts.sourceLocale Source Locale Code
* @param {string} opts.targetLocale Target Locale Code
* @returns {Promise} Promise with no value
*/
static async migrateToLocale({ sourceLocale, targetLocale }) { static async migrateToLocale({ sourceLocale, targetLocale }) {
return WIKI.models.pages.query() return WIKI.models.pages.query()
.patch({ .patch({
@ -491,6 +656,12 @@ module.exports = class Page extends Model {
}) })
} }
/**
* Clean raw HTML from content for use in search engines
*
* @param {string} rawHTML Raw HTML
* @returns {string} Cleaned Content Text
*/
static cleanHTML(rawHTML = '') { static cleanHTML(rawHTML = '') {
return striptags(rawHTML || '') return striptags(rawHTML || '')
.replace(emojiRegex(), '') .replace(emojiRegex(), '')

Loading…
Cancel
Save