feat: reconnect links after page update
This commit is contained in:
		| @@ -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) |                       template(v-slot:activator='{ on }') | ||||||
|                         v-icon(color='red') mdi-information |                         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}} | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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(), '') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user