소스 검색

feat: rename / move page + code styling fixes

NGPixel 5 달 전
부모
커밋
d9523fe393

+ 12 - 12
server/graph/resolvers/page.mjs

@@ -86,7 +86,7 @@ export default {
 
         // -> Add Highlighting if enabled
         if (WIKI.config.search.termHighlighting && hasQuery) {
-          searchCols.push(WIKI.db.knex.raw(`ts_headline(?, "searchContent", query, 'MaxWords=5, MinWords=3, MaxFragments=5') AS highlight`, [dictName]))
+          searchCols.push(WIKI.db.knex.raw('ts_headline(?, "searchContent", query, \'MaxWords=5, MinWords=3, MaxFragments=5\') AS highlight', [dictName]))
         }
 
         const results = await WIKI.db.knex
@@ -408,7 +408,7 @@ export default {
      * CHECK FOR EDITING CONFLICT
      */
     async checkConflicts (obj, args, context, info) {
-      let page = await WIKI.db.pages.query().select('path', 'locale', 'updatedAt').findById(args.id)
+      const page = await WIKI.db.pages.query().select('path', 'locale', 'updatedAt').findById(args.id)
       if (page) {
         if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
           path: page.path,
@@ -426,7 +426,7 @@ export default {
      * FETCH LATEST VERSION FOR CONFLICT COMPARISON
      */
     async checkConflictsLatest (obj, args, context, info) {
-      let page = await WIKI.db.pages.getPageFromDb(args.id)
+      const page = await WIKI.db.pages.getPageFromDb(args.id)
       if (page) {
         if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
           path: page.path,
@@ -449,7 +449,7 @@ export default {
     /**
      * CREATE PAGE
      */
-    async createPage(obj, args, context) {
+    async createPage (obj, args, context) {
       try {
         const page = await WIKI.db.pages.createPage({
           ...args,
@@ -466,7 +466,7 @@ export default {
     /**
      * UPDATE PAGE
      */
-    async updatePage(obj, args, context) {
+    async updatePage (obj, args, context) {
       try {
         const page = await WIKI.db.pages.updatePage({
           ...args,
@@ -483,7 +483,7 @@ export default {
     /**
      * CONVERT PAGE
      */
-    async convertPage(obj, args, context) {
+    async convertPage (obj, args, context) {
       try {
         await WIKI.db.pages.convertPage({
           ...args,
@@ -497,9 +497,9 @@ export default {
       }
     },
     /**
-     * RENAME PAGE
+     * MOVE PAGE
      */
-    async renamePage(obj, args, context) {
+    async movePage (obj, args, context) {
       try {
         await WIKI.db.pages.movePage({
           ...args,
@@ -515,7 +515,7 @@ export default {
     /**
      * DELETE PAGE
      */
-    async deletePage(obj, args, context) {
+    async deletePage (obj, args, context) {
       try {
         await WIKI.db.pages.deletePage({
           ...args,
@@ -571,7 +571,7 @@ export default {
     /**
      * FLUSH PAGE CACHE
      */
-    async flushCache(obj, args, context) {
+    async flushCache (obj, args, context) {
       try {
         await WIKI.db.pages.flushCache()
         WIKI.events.outbound.emit('flushCache')
@@ -585,7 +585,7 @@ export default {
     /**
      * MIGRATE ALL PAGES FROM SOURCE LOCALE TO TARGET LOCALE
      */
-    async migrateToLocale(obj, args, context) {
+    async migrateToLocale (obj, args, context) {
       try {
         const count = await WIKI.db.pages.migrateToLocale(args)
         return {
@@ -599,7 +599,7 @@ export default {
     /**
      * REBUILD TREE
      */
-    async rebuildPageTree(obj, args, context) {
+    async rebuildPageTree (obj, args, context) {
       try {
         await WIKI.db.pages.rebuildTree()
         return {

+ 4 - 3
server/graph/schemas/page.graphql

@@ -162,10 +162,11 @@ extend type Mutation {
     editor: String!
   ): DefaultResponse
 
-  renamePage(
-    id: Int!
-    destinationPath: String!
+  movePage(
+    id: UUID!
     destinationLocale: String!
+    destinationPath: String!
+    title: String
   ): DefaultResponse
 
   deletePage(

+ 66 - 51
server/models/pages.mjs

@@ -1,5 +1,5 @@
 import { Model } from 'objection'
-import { find, get, has, initial, isEmpty, isString, last, pick } from 'lodash-es'
+import { cloneDeep, find, get, has, initial, isEmpty, isString, last, pick } from 'lodash-es'
 import { Type as JSBinType } from 'js-binary'
 import { getDictNameFromLocale } from '../helpers/common.mjs'
 import { generateHash, getFileExtension, injectPageMetadata } from '../helpers/page.mjs'
@@ -888,15 +888,10 @@ export class Page extends Model {
    * @returns {Promise} Promise with no value
    */
   static async movePage (opts) {
-    let page
-    if (has(opts, 'id')) {
-      page = await WIKI.db.pages.query().findById(opts.id)
-    } else {
-      page = await WIKI.db.pages.query().findOne({
-        path: opts.path,
-        locale: opts.locale
-      })
+    if (!has(opts, 'id')) {
+      throw new Error('Missing page ID')
     }
+    const page = await WIKI.db.pages.query().findById(opts.id)
     if (!page) {
       throw new WIKI.Error.PageNotFound()
     }
@@ -947,63 +942,83 @@ export class Page extends Model {
       versionDate: page.updatedAt
     })
 
-    const destinationHash = generateHash({ path: opts.destinationPath, locale: opts.destinationLocale })
+    // -> Update page object
+    const updatedPage = cloneDeep(page)
+    updatedPage.path = opts.destinationPath
+    updatedPage.locale = opts.destinationLocale
+    updatedPage.title = opts.title ?? page.title
+    updatedPage.hash = generateHash({ path: opts.destinationPath, locale: opts.destinationLocale })
+    updatedPage.authorId = opts.user.id
 
     // -> Move page
-    const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title)
     await WIKI.db.pages.query().patch({
-      path: opts.destinationPath,
-      locale: opts.destinationLocale,
-      title: destinationTitle,
-      hash: destinationHash
+      path: updatedPage.path,
+      locale: updatedPage.locale,
+      title: updatedPage.title,
+      hash: updatedPage.hash,
+      authorId: updatedPage.authorId
     }).findById(page.id)
     await WIKI.db.pages.deletePageFromCache(page.hash)
     WIKI.events.outbound.emit('deletePageFromCache', page.hash)
 
-    // -> Rebuild page tree
-    await WIKI.db.pages.rebuildTree()
+    // -> Replace tree node
+    const pathParts = updatedPage.path.split('/')
+    await WIKI.db.knex('tree').where('id', page.id).del()
+    await WIKI.db.tree.addPage({
+      id: page.id,
+      parentPath: initial(pathParts).join('/'),
+      fileName: last(pathParts),
+      locale: updatedPage.locale,
+      title: updatedPage.title,
+      tags: updatedPage.tags,
+      meta: {
+        authorId: updatedPage.authorId,
+        contentType: updatedPage.contentType,
+        creatorId: updatedPage.creatorId,
+        description: updatedPage.description,
+        isBrowsable: updatedPage.isBrowsable,
+        ownerId: updatedPage.ownerId,
+        publishState: updatedPage.publishState,
+        publishEndDate: updatedPage.publishEndDate,
+        publishStartDate: updatedPage.publishStartDate
+      },
+      siteId: updatedPage.siteId
+    })
 
     // -> Rename in Search Index
-    const pageContents = await WIKI.db.pages.query().findById(page.id).select('render')
-    page.safeContent = WIKI.db.pages.cleanHTML(pageContents.render)
-    await WIKI.data.searchEngine.renamed({
-      ...page,
-      destinationPath: opts.destinationPath,
-      destinationLocale: opts.destinationLocale,
-      destinationHash
-    })
+    WIKI.db.pages.updatePageSearchVector({ id: page.id })
 
     // -> Rename in Storage
     if (!opts.skipStorage) {
-      await WIKI.db.storage.pageEvent({
-        event: 'renamed',
-        page: {
-          ...page,
-          destinationPath: opts.destinationPath,
-          destinationLocale: opts.destinationLocale,
-          destinationHash,
-          moveAuthorId: opts.user.id,
-          moveAuthorName: opts.user.name,
-          moveAuthorEmail: opts.user.email
-        }
-      })
+      // await WIKI.db.storage.pageEvent({
+      //   event: 'renamed',
+      //   page: {
+      //     ...page,
+      //     destinationPath: updatedPage.path,
+      //     destinationLocale: updatedPage.locale,
+      //     destinationHash: updatedPage.hash,
+      //     moveAuthorId: opts.user.id,
+      //     moveAuthorName: opts.user.name,
+      //     moveAuthorEmail: opts.user.email
+      //   }
+      // })
     }
 
-    // -> Reconnect Links : Changing old links to the new path
-    await WIKI.db.pages.reconnectLinks({
-      sourceLocale: page.locale,
-      sourcePath: page.path,
-      locale: opts.destinationLocale,
-      path: opts.destinationPath,
-      mode: 'move'
-    })
+    // // -> Reconnect Links : Changing old links to the new path
+    // await WIKI.db.pages.reconnectLinks({
+    //   sourceLocale: page.locale,
+    //   sourcePath: page.path,
+    //   locale: opts.destinationLocale,
+    //   path: opts.destinationPath,
+    //   mode: 'move'
+    // })
 
-    // -> Reconnect Links : Validate invalid links to the new path
-    await WIKI.db.pages.reconnectLinks({
-      locale: opts.destinationLocale,
-      path: opts.destinationPath,
-      mode: 'create'
-    })
+    // // -> Reconnect Links : Validate invalid links to the new path
+    // await WIKI.db.pages.reconnectLinks({
+    //   locale: opts.destinationLocale,
+    //   path: opts.destinationPath,
+    //   mode: 'create'
+    // })
   }
 
   /**

+ 51 - 46
server/models/tree.mjs

@@ -17,7 +17,7 @@ const reTitle = /^[^<>"]+$/
  * Tree model
  */
 export class Tree extends Model {
-  static get tableName() { return 'tree' }
+  static get tableName () { return 'tree' }
 
   static get jsonSchema () {
     return {
@@ -25,22 +25,22 @@ export class Tree extends Model {
       required: ['fileName'],
 
       properties: {
-        id: {type: 'string'},
-        folderPath: {type: 'string'},
-        fileName: {type: 'string'},
-        type: {type: 'string'},
-        title: {type: 'string'},
-        createdAt: {type: 'string'},
-        updatedAt: {type: 'string'}
+        id: { type: 'string' },
+        folderPath: { type: 'string' },
+        fileName: { type: 'string' },
+        type: { type: 'string' },
+        title: { type: 'string' },
+        createdAt: { type: 'string' },
+        updatedAt: { type: 'string' }
       }
     }
   }
 
-  static get jsonAttributes() {
+  static get jsonAttributes () {
     return ['meta']
   }
 
-  static get relationMappings() {
+  static get relationMappings () {
     return {
       site: {
         relation: Model.BelongsToOneRelation,
@@ -53,10 +53,11 @@ export class Tree extends Model {
     }
   }
 
-  $beforeUpdate() {
+  $beforeUpdate () {
     this.updatedAt = new Date().toISOString()
   }
-  $beforeInsert() {
+
+  $beforeInsert () {
     this.createdAt = new Date().toISOString()
     this.updatedAt = new Date().toISOString()
   }
@@ -90,7 +91,7 @@ export class Tree extends Model {
       const parent = await WIKI.db.knex('tree').where({
         ...parentFilter,
         type: 'folder',
-        locale: locale,
+        locale,
         siteId
       }).first()
       if (parent) {
@@ -123,16 +124,18 @@ export class Tree extends Model {
    * @param {Object} [args.meta] - Extra metadata
    */
   static async addPage ({ id, parentId, parentPath, fileName, title, locale, siteId, tags = [], meta = {} }) {
-    const folder = (parentId || parentPath) ? await WIKI.db.tree.getFolder({
-      id: parentId,
-      path: parentPath,
-      locale,
-      siteId,
-      createIfMissing: true
-    }) : {
-      folderPath: '',
-      fileName: ''
-    }
+    const folder = (parentId || parentPath)
+      ? await WIKI.db.tree.getFolder({
+        id: parentId,
+        path: parentPath,
+        locale,
+        siteId,
+        createIfMissing: true
+      })
+      : {
+          folderPath: '',
+          fileName: ''
+        }
     const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
     const fullPath = folderPath ? `${folderPath}/${fileName}` : fileName
 
@@ -143,9 +146,9 @@ export class Tree extends Model {
       folderPath: encodeFolderPath(folderPath),
       fileName,
       type: 'page',
-      title: title,
+      title,
       hash: generateHash(fullPath),
-      locale: locale,
+      locale,
       siteId,
       tags,
       meta,
@@ -169,16 +172,18 @@ export class Tree extends Model {
    * @param {Object} [args.meta] - Extra metadata
    */
   static async addAsset ({ id, parentId, parentPath, fileName, title, locale, siteId, tags = [], meta = {} }) {
-    const folder = (parentId || parentPath) ? await WIKI.db.tree.getFolder({
-      id: parentId,
-      path: parentPath,
-      locale,
-      siteId,
-      createIfMissing: true
-    }) : {
-      folderPath: '',
-      fileName: ''
-    }
+    const folder = (parentId || parentPath)
+      ? await WIKI.db.tree.getFolder({
+        id: parentId,
+        path: parentPath,
+        locale,
+        siteId,
+        createIfMissing: true
+      })
+      : {
+          folderPath: '',
+          fileName: ''
+        }
     const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
     const fullPath = folderPath ? `${folderPath}/${fileName}` : fileName
 
@@ -189,9 +194,9 @@ export class Tree extends Model {
       folderPath: encodeFolderPath(folderPath),
       fileName,
       type: 'asset',
-      title: title,
+      title,
       hash: generateHash(fullPath),
-      locale: locale,
+      locale,
       siteId,
       tags,
       meta
@@ -246,8 +251,8 @@ export class Tree extends Model {
 
     // Check for collision
     const existingFolder = await WIKI.db.knex('tree').select('id').where({
-      siteId: siteId,
-      locale: locale,
+      siteId,
+      locale,
       folderPath: encodeFolderPath(parentPath),
       fileName: pathName,
       type: 'folder'
@@ -281,8 +286,8 @@ export class Tree extends Model {
           type: 'folder',
           title: ancestor.fileName,
           hash: generateHash(newAncestorFullPath),
-          locale: locale,
-          siteId: siteId,
+          locale,
+          siteId,
           meta: {
             children: 1
           }
@@ -301,10 +306,10 @@ export class Tree extends Model {
       folderPath: encodeFolderPath(parentPath),
       fileName: pathName,
       type: 'folder',
-      title: title,
+      title,
       hash: generateHash(fullPath),
-      locale: locale,
-      siteId: siteId,
+      locale,
+      siteId,
       meta: {
         children: 0
       }
@@ -383,13 +388,13 @@ export class Tree extends Model {
       const fullPath = folder.folderPath ? `${decodeFolderPath(folder.folderPath)}/${pathName}` : pathName
       await WIKI.db.knex('tree').where('id', folder.id).update({
         fileName: pathName,
-        title: title,
+        title,
         hash: generateHash(fullPath)
       })
     } else {
       // Update the folder title only
       await WIKI.db.knex('tree').where('id', folder.id).update({
-        title: title
+        title
       })
     }
 

+ 21 - 2
ux/src/components/PageActionsCol.vue

@@ -244,8 +244,27 @@ function renamePage () {
       itemTitle: pageStore.title,
       itemFileName: pageStore.path
     }
-  }).onOk(() => {
-    // TODO: change route to new location
+  }).onOk(async (renamedPageOpts) => {
+    try {
+      if (renamedPageOpts.path === pageStore.path) {
+        await pageStore.pageRename({ id: pageStore.id, title: renamedPageOpts.title })
+        $q.notify({
+          type: 'positive',
+          message: 'Page renamed successfully.'
+        })
+      } else {
+        await pageStore.pageMove({ id: pageStore.id, path: renamedPageOpts.path, title: renamedPageOpts.title })
+        $q.notify({
+          type: 'positive',
+          message: 'Page moved successfully.'
+        })
+      }
+    } catch (err) {
+      $q.notify({
+        type: 'negative',
+        message: err.message
+      })
+    }
   })
 }
 

+ 13 - 2
ux/src/components/TreeBrowserDialog.vue

@@ -64,7 +64,9 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
             dense
             outlined
             @focus='state.pathDirty = true; state.currentFileId = null'
-          )
+            )
+            //- template(#append)
+            //-   q-badge(outline, color='grey', label='valid')
     q-card-actions.card-actions.q-px-md
       q-btn.acrylic-btn(
         icon='las la-ellipsis-h'
@@ -408,8 +410,17 @@ onMounted(() => {
     fName = last(fParts)
   }
   switch (props.mode) {
-    case 'pageSave': {
+    case 'savePage': {
+      state.typesToFetch = ['folder', 'page']
+      break
+    }
+    case 'duplicatePage': {
+      state.typesToFetch = ['folder', 'page']
+      break
+    }
+    case 'renamePage': {
       state.typesToFetch = ['folder', 'page']
+      state.pathDirty = true
       break
     }
   }

+ 77 - 0
ux/src/stores/page.js

@@ -452,6 +452,83 @@ export const usePageStore = defineStore('page', {
         editor: this.editor
       })
     },
+    /**
+     * PAGE - MOVE
+     */
+    async pageMove ({ id, title, path } = {}) {
+      const resp = await APOLLO_CLIENT.mutate({
+        mutation: gql`
+          mutation movePage (
+            $id: UUID!
+            $destinationLocale: String!
+            $destinationPath: String!
+            $title: String
+          ) {
+            movePage (
+              id: $id
+              destinationLocale: $destinationLocale
+              destinationPath: $destinationPath
+              title: $title
+            ) {
+              operation {
+                succeeded
+                message
+              }
+            }
+          }
+          `,
+        variables: {
+          id,
+          destinationLocale: this.locale,
+          destinationPath: path,
+          title
+        }
+      })
+      const result = resp?.data?.movePage?.operation ?? {}
+      if (!result.succeeded) {
+        throw new Error(result.message)
+      } else {
+        this.router.replace(`/${path}`)
+      }
+    },
+    /**
+     * PAGE - Rename
+     */
+    async pageRename ({ id, title } = {}) {
+      const resp = await APOLLO_CLIENT.mutate({
+        mutation: gql`
+          mutation renamePage (
+            $id: UUID!
+            $patch: PageUpdateInput!
+          ) {
+            updatePage (
+              id: $id
+              patch: $patch
+            ) {
+              operation {
+                succeeded
+                message
+              }
+            }
+          }
+          `,
+        variables: {
+          id: id,
+          patch: {
+            title
+          }
+        }
+      })
+      const result = resp?.data?.updatePage?.operation ?? {}
+      if (!result.succeeded) {
+        throw new Error(result.message)
+      }
+
+      // Update page store
+      if (id === this.id) {
+        this.$patch({ title })
+      }
+    },
     /**
      * PAGE SAVE
      */