ソースを参照

add: 移动页面

wzl 1 年間 前
コミット
3658c51b94

+ 9 - 0
packages/core/src/index.js

@@ -2939,6 +2939,15 @@ class ComPDFKitViewer {
       }
       return
     }
+    if (op.type === 'move') {
+      const res = await this.messageHandler.sendWithPromise('MovePage', {
+        doc,
+        index: op.index,
+        targetIndex: op.targetIndex
+      })
+      if(!res) console.warn('move', res)
+    }
+
     this.docEditorCopy.pages = await this.getPages(doc)
   }
 }

+ 10 - 0
packages/core/src/worker/compdfkit_worker.js

@@ -1181,6 +1181,16 @@ class CPDFWorker {
         return saveDocument(newDoc, 2)
       }
     })
+
+    messageHandler.on('MovePage', (data) => {
+      const { doc, index, targetIndex } = data
+
+      return Module._MovePage(
+        doc,
+        index,
+        targetIndex
+      )
+    })
   }
 }
 

+ 3 - 7
packages/webview/locales/en.json

@@ -155,8 +155,9 @@
 
   "prevent": {
     "title": "Not Available in Server-Backed",
+    "standalone": "Standalone",
     "description": "Content Editor is only available on Standalone. Change to Standalone to edit PDF content.",
-    "standalone": "Standalone"
+    "description1": "Document Editor is only available on Standalone. Change to Standalone to edit PDF pages."
   },
 
   "passwordDialog": {
@@ -314,12 +315,7 @@
     "eachPage": "Each page in a separate file",
     "deleteAfter": "Delete page after extraction",
 
-    "moveTo": "Move to",
+    "moveTo": "Move after page:",
     "inputPageTip": "Input 0 to maximum page number"
-  },
-
-  "serverBackedDialog": {
-    "notAvailable": "Not Available in Server-Backed",
-    "changeMode": "Document Editor is only available on Standalone. Change to Standalone to edit PDF pages."
   }
 }

+ 2 - 6
packages/webview/locales/zh-CN.json

@@ -155,8 +155,9 @@
 
   "prevent": {
     "title": "不可用",
+    "standalone": "离线版",
     "description": "在线版不支持内容编辑,请切换到离线版(Standalone)模式使用内容编辑。",
-    "standalone": "离线版"
+    "description1": "在线版不支持文档编辑,请切换到离线版(Standalone)模式使用文档编辑。"
   },
 
   "passwordDialog": {
@@ -316,10 +317,5 @@
 
     "moveTo": "移动到",
     "inputPageTip": "输入0~文档最大页码数"
-  },
-
-  "serverBackedDialog": {
-    "notAvailable": "不可用",
-    "changeMode": "在线版不支持文档编辑,请切换到离线版(Standalone)模式使用文档编辑。"
   }
 }

BIN
packages/webview/public/example/Number Pages.pdf


+ 1 - 0
packages/webview/src/components/Dialogs/Dialog.vue

@@ -46,6 +46,7 @@ const closeDialog = () => {
 .dialog-container {
   background-color: var(--c-header-bg);
   padding: 20px;
+  margin: 0 10px;
   border-radius: 4px;
   .close {
     margin-top: -6px;

+ 1 - 1
packages/webview/src/components/Dialogs/ExtractPageSettingDialog.vue

@@ -45,7 +45,7 @@
 import { ref, computed, watch } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 
-const props = defineProps([ 'selectedPageList', 'totalPages' ])
+const props = defineProps(['selectedPageList', 'totalPages'])
 const emits = defineEmits(['extractPage'])
 
 const useViewer = useViewerStore()

+ 1 - 2
packages/webview/src/components/Dialogs/InsertPageSettingDialog.vue

@@ -88,7 +88,7 @@
 import { ref, computed, watch, h } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 
-const props = defineProps([ 'selectedPageIndex', 'totalPages' ])
+const props = defineProps(['selectedPageIndex', 'totalPages'])
 const emits = defineEmits(['insertPage'])
 
 const useViewer = useViewerStore()
@@ -123,7 +123,6 @@ const closeDialog = () => {
   inputFile.value = null
 
   place.value = 'first'
-  targetIndex.value = 1
   targetPlace.value = 0
 }
 

+ 131 - 0
packages/webview/src/components/Dialogs/MovePageSettingDialog.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="move-page-popup" v-if="show">
+    <Dialog :show="show" :dialogName="dialogName" :close="true">
+      <template #header>
+        <p>{{ $t('documentEditor.moveTo') }}</p>
+      </template>
+
+      <input v-model.number="targetIndex"
+        type="number"
+        pattern="\d*"
+        onkeypress="return (/[\d]/.test(String.fromCharCode(event.keyCode)))"
+        @input="validateInput"
+        :placeholder="$t('documentEditor.inputPageTip')"
+      >
+
+      <template #footer>
+        <div class="rect-button blue" :class="{ 'disabled': invalidMove }" @click="handleMove">{{ $t('ok') }}</div>
+      </template>
+    </Dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { useViewerStore } from '@/stores/modules/viewer'
+
+const props = defineProps(['totalPages', 'selectedPageList'])
+const emits = defineEmits(['movePage'])
+
+const useViewer = useViewerStore()
+
+const dialogName = 'movePageSettingDialog'
+const show = computed(() => useViewer.isElementOpen(dialogName))
+
+const targetIndex = ref('')
+
+const invalidMove = computed(() => {
+  if (typeof targetIndex.value !== 'number') return true
+  const selected = props.selectedPageList
+  if (selected.length === 1 && (selected[0] === targetIndex.value || selected[0] === targetIndex.value - 1)) return true
+  if (selected.length > 1 && isConsecutive(selected) && (selected.includes(targetIndex.value) || selected.includes(targetIndex.value - 1))) return true
+  if (selected.length > 1 && !isConsecutive(selected) && selected.includes(targetIndex.value - 1)) return true
+  return false
+})
+
+// 是否是连续的数字
+const isConsecutive = (arr) => {
+  arr.sort(function(a, b) {
+    return a - b
+  })
+
+  for (var i = 0; i < arr.length - 1; i++) {
+    if (arr[i] + 1 !== arr[i + 1]) {
+      return false
+    }
+  }
+
+  return true
+}
+
+const closeDialog = () => {
+  useViewer.closeElement(dialogName)
+  targetIndex.value = ''
+}
+
+const handleMove = async () => {
+  emits('movePage', targetIndex.value)
+  closeDialog()
+}
+
+const validateInput = (e) => {
+  const value = e.target.valueAsNumber
+
+  if (value > props.totalPages) {
+    targetIndex.value = props.totalPages
+  }
+  if (value === 0) {
+    e.target.value = value.toFixed(0)
+  }
+}
+</script>
+
+<style lang="scss">
+.move-page-popup {
+
+  .dialog-container {
+    width: 289px;
+
+    .close {
+      float: right;
+      margin-top: 3px;
+    }
+
+    main {
+      margin-top: 20px;
+    }
+
+    footer {
+      .rect-button {
+        width: 100%;
+      }
+    }
+  }
+
+  input[type="number"] {
+    padding: 0 20px 0 8px;
+    width: 100%;
+    height: 24px;
+    background: var(--c-right-side-content-fillbox-bg);
+    border: 1px solid var(--c-right-side-content-fillbox-border);
+    border-radius: 1px;
+    font-size: 14px;
+    line-height: 16px;
+
+    &:focus {
+      border-color: #0078D7;
+    }
+
+    &::-webkit-inner-spin-button,
+    &::-webkit-outer-spin-button {
+      -webkit-appearance: none;
+      appearance: none;
+    }
+
+    &::placeholder {
+      font-size: 12px;
+      color: #999;
+    }
+  }
+}
+</style>

+ 1 - 1
packages/webview/src/components/Dialogs/PreventDialog.vue

@@ -4,7 +4,7 @@
       <div class="close"><Close @click="handleClose" /></div>
       <div class="warning"><Warning /></div>
       <div class="title">{{ $t('prevent.title') }}</div>
-      <div class="des">{{ $t('prevent.description') }}</div>
+      <div class="des">{{ mode === 'editor' ? $t('prevent.description') : $t('prevent.description1') }}</div>
       <div class="button" @click="handleClose">{{ $t('ok') }}</div>
     </div>
   </div>

+ 65 - 2
packages/webview/src/components/DocumentEditorContainer/DocumentEditorContainer.vue

@@ -30,7 +30,7 @@
         <ReplacePage />
         <span>{{ $t('documentEditor.replace') }}</span>
       </Button>
-      <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="">
+      <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="openMovePageDialog">
         <MovePage />
         <span>{{ $t('documentEditor.move') }}</span>
       </Button>
@@ -59,6 +59,7 @@
   <InsertPageSettingDialog @insertPage="handleInsertPage" :selectedPageIndex="selectedPageList.length === 1 ? selectedPageList[0] + 1 : -1" :totalPages="pageList.length" />
   <DeletePageDialog @deletePage="handleDeletePage" />
   <ExtractPageSettingDialog @extractPage="handleExtractPage" :totalPages="pageList.length" :selectedPageList="selectedPageList" />
+  <MovePageSettingDialog @movePage="handleMovePage" :totalPages="pageList.length" :selectedPageList="selectedPageList" />
 </template>
 
 <script setup>
@@ -66,7 +67,6 @@ import { computed, ref, watch, onMounted, reactive, onUnmounted } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 import { useDocumentStore } from '@/stores/modules/document'
 import core from '@/core'
-import { saveAs } from 'file-saver'
 
 const useViewer = useViewerStore()
 const useDocument = useDocumentStore()
@@ -155,6 +155,7 @@ const selectPage = (index) => {
 // 全选
 const selectAll = () => {
   if (selectedPageList.length < pageList.length) {
+    selectedPageList.length = 0
     for (let i = 0; i < pageList.length; i++) {
       selectedPageList.push(i)
     }
@@ -296,6 +297,68 @@ const handleExtractPage = async (data) => {
     selectedPageList.length = 0
   }
 }
+
+// 打开移动页面弹窗
+const openMovePageDialog = () => {
+  useViewer.openElement('movePageSettingDialog')
+}
+
+// 移动页面
+const handleMovePage = (targetIndex) => {
+  if (selectedPageList.length === 1) { // 移动一页
+    const index = selectedPageList[0]
+    const tIndex = index > targetIndex ? targetIndex : targetIndex - 1
+    const [page] = pageList.splice(index, 1)
+    pageList.splice(tIndex, 0, page)
+
+    const operation = {
+      type: 'move',
+      index,
+      targetIndex: tIndex
+    }
+    core.saveDocumentEdit(operation)
+    useDocument.setDocEditorOperationList(operation)
+    selectedPageList[0] = tIndex
+
+  } else { // 移动多页
+    selectedPageList.sort((a, b) => a - b)
+    
+    for (let i = 0; i < selectedPageList.length; i++) {
+      let index, tIndex
+      if (selectedPageList[i] === targetIndex) {
+        index = selectedPageList[i]
+        tIndex = targetIndex
+      } else if (selectedPageList[i] > targetIndex) {
+        index = selectedPageList[i]
+        if (selectedPageList.includes(targetIndex)) {
+          tIndex = targetIndex
+          for (let j = 0; j < selectedPageList.length; j++) {
+            if (selectedPageList[j] === tIndex) tIndex++
+          }
+        } else {
+          tIndex = targetIndex
+        }
+      } else {
+        index = selectedPageList[i] - i
+        tIndex = targetIndex - 1
+      }
+
+      if (selectedPageList.includes(index) && selectedPageList.includes(tIndex)) continue
+
+      const [page] = pageList.splice(index, 1)
+      pageList.splice(tIndex, 0, page)
+
+      const operation = {
+        type: 'move',
+        index,
+        targetIndex: tIndex
+      }
+      core.saveDocumentEdit(operation)
+      useDocument.setDocEditorOperationList(operation)
+      selectedPageList[i] = selectedPageList[i] > targetIndex ? tIndex : tIndex - i
+    }
+  }
+}
 </script>
 
 <style lang="scss">

+ 1 - 1
packages/webview/src/components/HeaderItems/HeaderItems.vue

@@ -127,7 +127,7 @@ const changeToolMode = (mode) => {
     alert('Invalid license')
     return
   }
-  if (webviewerMode.value !== 'Standalone' && mode === 'editor') {
+  if (webviewerMode.value !== 'Standalone' && (mode === 'editor' || mode === 'document')) {
     useViewer.openElement('preventDialog')
     return
   }

+ 4 - 2
packages/webview/src/stores/modules/viewer.js

@@ -37,7 +37,8 @@ export const useViewerStore = defineStore({
       preventDialog: false,
       insertPageSettingDialog: false,
       deletePageDialog: false,
-      extractPageSettingDialog: false
+      extractPageSettingDialog: false,
+      movePageSettingDialog: false
     },
     activeElementsTab: {
       leftPanelTab: 'THUMBS',
@@ -360,7 +361,8 @@ export const useViewerStore = defineStore({
         editTextPanel: false,
         insertPageSettingDialog: false,
         deletePageDialog: false,
-        extractPageSettingDialog: false
+        extractPageSettingDialog: false,
+        movePageSettingDialog: false
       },
       this.activeElementsTab = {
         leftPanelTab: 'THUMBS',