Bläddra i källkod

add: 新增标记类

wzl 4 månader sedan
förälder
incheckning
8cd7136397

+ 73 - 506
packages/core/src/annotation/layer.js

@@ -24,6 +24,8 @@ import PaintLink from './paint/link'
 import Link from './link.js'
 import AddSignatureFields from '../form/add_signature_fields.js'
 import SignatureFields from '../form/signature_fields.js'
+import PaintRedaction from './paint/redaction'
+import Redaction from './redaction.js'
 
 // 注释的画布层
 class ComPDFAnnotationLayer {
@@ -60,6 +62,7 @@ class ComPDFAnnotationLayer {
     this.selectedElementName = null
     this.addMode = false
     this.toolMode = pageViewer.toolMode || ''
+    this.managerArray = ['freetextManager', 'textManager', 'textFieldManager', 'checkBoxManager', 'radioButtonManager', 'pushButtonManager', 'listBoxManager', 'comboBoxManager', 'annotateManager', 'linkManager', 'signFieldManager', 'redactionManager']
 
     this.annotationStore = annotationStore
 
@@ -90,121 +93,26 @@ class ComPDFAnnotationLayer {
     if (!tool || markup.includes(tool)) {
       document.querySelector('.document').classList.remove('annotation-edit')
     }
+
     if (markup.includes(tool) || tool === 'cropPage' || tool === 'selectText') {
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers()
     }
+
     const shapes = ['line', 'arrow', 'circle', 'square', 'ink']
     if (markup.includes(tool) && color) {
       this.svgElement.classList.add('point-none')
     }
-    if (this.annotateManager) {
-      this.annotateManager.tool = tool
-      this.annotateManager.color = color
-    }
-    if (this.freetextManager) {
-      this.freetextManager.tool = tool
-      this.freetextManager.color = color
-    }
-    if (this.textManager) {
-      this.textManager.tool = tool
-      this.textManager.color = color
-    }
-    if (this.textFieldManager) {
-      this.textFieldManager.tool = tool
-      this.textFieldManager.color = color
-    }
-    if (this.checkBoxManager) {
-      this.checkBoxManager.tool = tool
-      this.checkBoxManager.color = color
-    }
-    if (this.radioButtonManager) {
-      this.radioButtonManager.tool = tool
-      this.radioButtonManager.color = color
-    }
-    if (this.pushButtonManager) {
-      this.pushButtonManager.tool = tool
-      this.pushButtonManager.color = color
-    }
-    if (this.listBoxManager) {
-      this.listBoxManager.tool = tool
-      this.listBoxManager.color = color
-    }
-    if (this.comboBoxManager) {
-      this.comboBoxManager.tool = tool
-      this.comboBoxManager.color = color
-    }
-    if (this.linkManager) {
-      this.linkManager.tool = tool
-      this.linkManager.color = color
-    }
-    if (this.signFieldManager) {
-      this.signFieldManager.tool = tool
-      this.signFieldManager.color = color
-    }
+
+    this.managerArray.forEach(manager => {
+      if (this[manager]) {
+        this[manager].tool = this.tool
+        this[manager].color = this.color
+      }
+    })
+
     if (shapes.includes(tool) && color) {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('annotateManager')
       if (this.annotateManager) {
         this.annotateManager.init()
         return
@@ -223,36 +131,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'freetext') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('freetextManager')
       if (this.freetextManager) {
         this.freetextManager.init()
         return
@@ -271,36 +150,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'text') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('textManager')
       if (this.textManager) {
         this.textManager.init()
         return
@@ -319,36 +169,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'textfield') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('textFieldManager')
       if (this.textFieldManager) {
         this.textFieldManager.init()
         return
@@ -367,36 +188,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'checkbox') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('checkBoxManager')
       if (this.checkBoxManager) {
         this.checkBoxManager.init()
         return
@@ -415,36 +207,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'radiobutton') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('radioButtonManager')
       if (this.radioButtonManager) {
         this.radioButtonManager.init()
         return
@@ -463,36 +226,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'pushbutton') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('pushButtonManager')
       if (this.pushButtonManager) {
         this.pushButtonManager.init()
         return
@@ -511,36 +245,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'combobox') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('comboBoxManager')
       if (this.comboBoxManager) {
         this.comboBoxManager.init()
         return
@@ -559,36 +264,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'listbox') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('listBoxManager')
       if (this.listBoxManager) {
         this.listBoxManager.init()
         return
@@ -606,71 +282,10 @@ class ComPDFAnnotationLayer {
         selectedElementName: this.selectedElementName
       })
     } else if (tool === 'stamp') {
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers()
     } else if (tool === 'link') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.reset()
-      }
+      this.resetManagers('linkManager')
       if (this.linkManager) {
         this.linkManager.init()
         return
@@ -689,36 +304,7 @@ class ComPDFAnnotationLayer {
       })
     } else if (tool === 'signatureFields') {
       document.querySelector('.document').classList.add('annotation-edit')
-      if (this.annotateManager) {
-        this.annotateManager.reset()
-      }
-      if (this.freetextManager) {
-        this.freetextManager.reset()
-      }
-      if (this.textManager) {
-        this.textManager.reset()
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.reset()
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.reset()
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.reset()
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.reset()
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.reset()
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.reset()
-      }
-      if (this.linkManager) {
-        this.linkManager.reset()
-      }
+      this.resetManagers('signFieldManager')
       if (this.signFieldManager) {
         this.signFieldManager.init()
         return
@@ -735,9 +321,36 @@ class ComPDFAnnotationLayer {
         eventBus: this.eventBus,
         selectedElementName: this.selectedElementName
       })
+    } else if (tool === 'redaction') {
+      document.querySelector('.document').classList.add('annotation-edit')
+      this.resetManagers('redactionManager')
+      if (this.redactionManager) {
+        this.redactionManager.init()
+        return
+      }
+      this.redactionManager = new PaintRedaction({
+        tool,
+        color,
+        container: this.pageDiv,
+        svgElement: this.svgElement,
+        layer: this,
+        viewport: this.viewport,
+        scale: this.scale,
+        page: this.page,
+        eventBus: this.eventBus,
+        selectedElementName: this.selectedElementName
+      })
     }
   }
 
+  resetManagers(ignoreManagers = []) {
+    if (!Array.isArray(ignoreManagers)) ignoreManagers = [ignoreManagers]
+    const managers = this.managerArray.filter(item => !ignoreManagers.includes(item))
+    managers.forEach(manager => {
+      this[manager] && this[manager].reset()
+    })
+  }
+
   // 渲染注释
   render(viewport) {
     if (this._cancelled) {
@@ -754,39 +367,11 @@ class ComPDFAnnotationLayer {
       this.viewport = clonedViewport;
       this.scale = clonedViewport.scale
 
-      if (this.annotateManager) {
-        this.annotateManager.viewport = clonedViewport
-      }
-      if (this.freetextManager) {
-        this.freetextManager.viewport = clonedViewport
-      }
-      if (this.textManager) {
-        this.textManager.viewport = clonedViewport
-      }
-      if (this.textFieldManager) {
-        this.textFieldManager.viewport = clonedViewport
-      }
-      if (this.checkBoxManager) {
-        this.checkBoxManager.viewport = clonedViewport
-      }
-      if (this.radioButtonManager) {
-        this.radioButtonManager.viewport = clonedViewport
-      }
-      if (this.pushButtonManager) {
-        this.pushButtonManager.viewport = clonedViewport
-      }
-      if (this.listBoxManager) {
-        this.listBoxManager.viewport = clonedViewport
-      }
-      if (this.comboBoxManager) {
-        this.comboBoxManager.viewport = clonedViewport
-      }
-      if (this.linkManager) {
-        this.linkManager.viewport = clonedViewport
-      }
-      if (this.signFieldManager) {
-        this.signFieldManager.viewport = clonedViewport
-      }
+      this.managerArray.forEach(manager => {
+        if (this[manager]) {
+          this[manager].viewport = clonedViewport
+        }
+      })
     }
     if (!this.div) {
       this.renderContainer()
@@ -1047,39 +632,8 @@ class ComPDFAnnotationLayer {
     }
     this.div?.remove()
     this.div = null
-    if (this.freetextManager) {
-      this.freetextManager.reset()
-    }
-    if (this.textManager) {
-      this.textManager.reset()
-    }
-    if (this.textFieldManager) {
-      this.textFieldManager.reset()
-    }
-    if (this.checkBoxManager) {
-      this.checkBoxManager.reset()
-    }
-    if (this.radioButtonManager) {
-      this.radioButtonManager.reset()
-    }
-    if (this.pushButtonManager) {
-      this.pushButtonManager.reset()
-    }
-    if (this.listBoxManager) {
-      this.listBoxManager.reset()
-    }
-    if (this.comboBoxManager) {
-      this.comboBoxManager.reset()
-    }
-    if (this.annotateManager) {
-      this.annotateManager.reset()
-    }
-    if (this.linkManager) {
-      this.linkManager.reset()
-    }
-    if (this.signFieldManager) {
-      this.signFieldManager.reset()
-    }
+    this.resetManagers()
+
     this.eventBus._off('toolChanged', this.onHandleTool)
     this.eventBus._off('toolModeChanged', this.onHandleToolMode)
     this.eventBus._off('setDefaultSelect', this.onHandleDefaultSelect)
@@ -1304,6 +858,19 @@ class ComPDFAnnotationLayer {
         messageHandler: this.messageHandler
       })
       this.annotationsArray.push(signatureFields)
+    } else if (annotation.type === 'redaction' && !annotation.isDelete) {
+      const redaction = new Redaction({
+        container: this.div,
+        annotation,
+        page: this.page,
+        viewport: this.viewport,
+        scale: this.scale,
+        eventBus: this.eventBus,
+        layer: this,
+        show,
+        highlight: this.annotationStore.highlightLink
+      })
+      this.annotationsArray.push(redaction)
     }
   }
 

+ 214 - 0
packages/core/src/annotation/paint/redaction.js

@@ -0,0 +1,214 @@
+import {
+  getAbsoluteCoordinate,
+  createElement,
+  getInitialPoint
+} from '../utils';
+
+export default class PaintRedaction {
+  constructor ({
+    tool,
+    color,
+    svgElement,
+    layer,
+    container,
+    viewport,
+    page,
+    eventBus
+  }) {
+    this.layer = layer
+    this._tool = tool
+    this.color = color
+    this.svgElement = svgElement
+    this.container = container
+    this.viewport = viewport
+    this.page = page
+    this.eventBus = eventBus
+    this.path = []
+
+    this.initStartPoint = null
+    this.initEndPoint = null
+    this.linkEle = null
+
+    this.onMousedown = this.handleMouseDown.bind(this)
+    this.onMouseup = this.handleMouseUp.bind(this)
+    this.onMousemove = this.handleMouseMove.bind(this)
+
+    this.init()
+  }
+
+  init () {
+    this.container.removeEventListener('mousedown', this.onMousedown)
+    this.container.removeEventListener('touchstart', this.onMousedown)
+
+    this.container.addEventListener('mousedown', this.onMousedown)
+    this.container.addEventListener('touchstart', this.onMousedown)
+
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+  }
+
+  get tool () {
+    return this._tool
+  }
+
+  set tool (toolType) {
+    if (toolType === this._tool) return
+    if (!toolType) {
+      this.reset()
+      this.container.removeEventListener('mousedown', this.onMousedown)
+      this.container.removeEventListener('touchstart', this.onMousedown)
+    }
+    this._tool = toolType
+  }
+
+  reset () {
+    this.initStartPoint = null
+    this.initEndPoint = null
+    if (this.linkEle) this.linkEle.remove()
+    this.linkEle = null
+    this.svgElement.innerHTML = ''
+    this.path = []
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+
+    this.container.removeEventListener('mousedown', this.onMousedown)
+    this.container.removeEventListener('touchstart', this.onMousedown)
+  }
+
+  handleMouseDown (event) {
+    if (this.layer.annotationStore.selectedElementName) return
+    const { x, y } = getAbsoluteCoordinate(this.container, event)
+    this.initStartPoint = { x, y }
+
+    document.addEventListener('mousemove', this.onMousemove)
+    document.addEventListener('mouseup', this.onMouseup)
+    
+    document.addEventListener('touchmove', this.onMousemove)
+    document.addEventListener('touchend', this.onMouseup)
+  }
+
+  handleMouseUp () {
+    let initStartPoint = this.initStartPoint
+    let initEndPoint = this.initEndPoint
+    if (initEndPoint && initStartPoint.x !== initEndPoint.x && initStartPoint.y !== initEndPoint.y) {
+      let start = getInitialPoint(initStartPoint, this.viewport, this.viewport.scale)
+      let end = getInitialPoint(initEndPoint, this.viewport, this.viewport.scale)
+
+      const annotationData = {
+        operate: "add-annot",
+        type: 'redaction',
+        pageIndex: this.page,
+        date: new Date()
+      }
+
+      const newStart = {
+        x: Math.min(start.x, end.x),
+        y: Math.min(start.y, end.y)
+      }
+      const newEnd = {
+        x: Math.max(start.x, end.x),
+        y: Math.max(start.y, end.y)
+      }
+      const rect = {
+        left: newStart.x,
+        top: newStart.y,
+        right: newEnd.x,
+        bottom: newEnd.y
+      }
+      annotationData.rect = rect
+
+      this.eventBus.dispatch('annotationChange', {
+        type: 'add',
+        show: true,
+        annotation: annotationData
+      })
+    }
+    // this.reset()
+    
+    this.initStartPoint = null
+    this.initEndPoint = null
+    if (this.linkEle) this.linkEle.remove()
+    this.linkEle = null
+    this.svgElement.innerHTML = ''
+    this.path = []
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+  }
+
+  handleMouseMove (event) {
+    let { x, y } = getAbsoluteCoordinate(this.container, event)
+    const { width, height } = this.viewport
+    const scale = this.viewport.scale
+    x = Math.min(width, x)
+    x = Math.max(0, x)
+    y = Math.min(height, y)
+    y = Math.max(0, y)
+    this.initEndPoint = { x, y }
+    if (this.initStartPoint.x !== this.initEndPoint.x && this.initStartPoint.y !== this.initEndPoint.y) {
+      if (!this.linkEle) {
+        const linkEle = createElement('div', {
+          'display': 'block',
+          'backgroundColor': '#93B9FD',
+          'opacity': '0.3',
+          'position': 'absolute'
+        })
+        this.linkEle = linkEle
+        this.scene = this.container.querySelector('.annotationLayer')
+        this.scene.append(this.linkEle)
+      } else {
+        // if (event.shiftKey) {
+        //   const { initStartPoint, initEndPoint } = this
+        //   const distanceX = Math.abs(initEndPoint.x - initStartPoint.x)
+        //   const distanceY = Math.abs(initEndPoint.y - initStartPoint.y)
+        //   const distance = Math.min(distanceX, distanceY)
+        //   if (initStartPoint.x < initEndPoint.x) {
+        //     initEndPoint.x = initStartPoint.x + distance
+        //   } else {
+        //     initEndPoint.x = initStartPoint.x - distance
+        //   }
+        //   if (initStartPoint.y < initEndPoint.y) {
+        //     initEndPoint.y = initStartPoint.y + distance
+        //   } else {
+        //     initEndPoint.y = initStartPoint.y - distance
+        //   }
+        // }
+        const rect = this.calculate(this.initStartPoint, this.initEndPoint)
+
+        const linkEle = this.linkEle
+        linkEle.style.top = `${rect.top}px`;
+        linkEle.style.left = `${rect.left}px`;
+        linkEle.style.width = `${Math.abs(rect.width - 1)}px`;
+        linkEle.style.height = `${Math.abs(rect.height - 1)}px`;
+      }
+    }
+  }
+  
+  rectCalc ({ start, end }) {
+    return {
+      top: start.y < end.y ? start.y : end.y,
+      left: start.x < end.x ? start.x : end.x,
+      width: Math.abs(end.x - start.x),
+      height: Math.abs(end.y - start.y),
+    };
+  };
+
+  calculate (start, end) {
+    const initRect = this.rectCalc({ start, end });
+    const actualbdwidth = 1 * this.viewport.scale
+
+    return {
+      top: initRect.top - actualbdwidth,
+      left: initRect.left - actualbdwidth,
+      width: initRect.width + actualbdwidth * 2,
+      height: initRect.height + actualbdwidth * 2,
+    }
+  }
+}

+ 678 - 0
packages/core/src/annotation/redaction.js

@@ -0,0 +1,678 @@
+import Base from './base';
+import { getActualPoint, getClickPoint, createSvg, createElement } from './utils';
+import { onClickOutside } from '../ui_utils'
+
+export default class Redaction extends Base {
+  
+  constructor ({
+    container,
+    annotation,
+    page,
+    viewport,
+    scale,
+    eventBus,
+    layer,
+    show = false,
+    highlight
+  }) {
+    super({
+      container,
+      annotation,
+      page,
+      viewport,
+      scale,
+      eventBus,
+      show,
+      highlight
+    })
+
+    this.layer = layer
+    this.hidden = true
+    this.initial = false
+    this.outline = null
+
+    this.start = null
+    this.end = null
+
+    this.newStart = null
+    this.newEnd = null
+
+    this.startCircle = null
+    this.endCircle = null
+
+    this.show = show
+    this.highlight = highlight
+
+    this.onMousedown = this.handleMouseDown.bind(this)
+    this.onMouseup = this.handleMouseUp.bind(this)
+    this.onMousemove = this.handleMouseMove.bind(this)
+    this.onDelete = this.handleDelete.bind(this)
+    this.render()
+  }
+
+  render () {
+    const annotation = this.annotation
+
+    const { start, end } = this.getActualRect(
+      this.viewport,
+      this.scale
+    )
+    this.start = start
+    this.end = end
+
+    const rect = this.calculate(start, end)
+    
+    const annotationContainer = document.createElement('div')
+    annotationContainer.id = annotation.name
+    annotationContainer.className = 'annotation'
+    annotationContainer.style.top = rect.top + 'px'
+    annotationContainer.style.left = rect.left + 'px'
+    annotationContainer.style.width = rect.width + 'px'
+    annotationContainer.style.height = rect.height + 'px'
+    annotationContainer.style.backgroundColor = this.highlight ? '#93B9FD' : ''
+    annotationContainer.style.opacity = 0.3
+    this.annotationContainer = annotationContainer
+    this.annotationContainer.addEventListener('mousedown', this.handleClick.bind(this))
+
+    let shapeElement
+    shapeElement = createElement(
+      'div',
+      {
+        left: 0,
+        top: 0,
+        width: `${Math.abs(rect.width)}px`,
+        height: `${Math.abs(rect.height)}px`,
+        position: 'relative'
+      }
+    )
+
+    this.shapeElement = shapeElement
+
+    this.annotationContainer.append(this.shapeElement)
+
+    this.container.append(this.annotationContainer)
+
+    this.buttonContainer = createElement(
+      'a',
+      {
+        display: 'block',
+        width: '100%',
+        height: '100%',
+        outline: 'none'
+      }
+    )
+    this.shapeElement.append(this.buttonContainer)
+    this.buttonContainer.addEventListener('blur', this.onBlur)
+    if (this.annotation.url) {
+      this.buttonContainer.setAttribute('href', this.annotation.url)
+      this.buttonContainer.setAttribute('target', '_blank')
+    }
+
+    this.outerLineContainer = document.createElement('div')
+    this.outerLineContainer.className = 'outline-container'
+    this.deletetButton = createSvg(
+      "svg",
+      {
+        width: "30",
+        height: "30",
+        viewBox: "0 0 30 30",
+        fill: "none",
+        class: 'delete-button'
+      },
+      {
+        width: '30px',
+        height: '30px'
+      }
+    );
+    this.deletetButton.addEventListener('click', this.onDelete)
+    this.deletetButton.innerHTML = this.deleteSvgStr
+
+    const left = (rect.left + rect.width - 30) + 'px'
+    const top = (rect.top + rect.height + 8) + 'px'
+    this.deletetButton.style.left = left
+    this.deletetButton.style.top = top
+    
+    const outerLine = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+
+    outerLine.style.position = 'absolute'
+    outerLine.style.zIndex = 1
+    outerLine.style.left = `${rect.left - 8}px`
+    outerLine.style.top = `${rect.top - 8}px`
+    outerLine.style.width = `${rect.width + 8 * 2}px`
+    outerLine.style.height = `${rect.height + 8 * 2}px`
+    this.outerLine = outerLine
+    
+    this.moveRect = createSvg(
+      "rect",
+      {
+        class: "move",
+        'data-id': "move",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        'fill-opacity': 0,
+        width: rect.width + 8,
+        height: rect.height + 8,
+        x: 4,
+        y: 4
+      }
+    );
+    
+    this.topLeftRect = createSvg(
+      "rect",
+      {
+        class: "nw-resize",
+        'data-id': "nw-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: 1,
+        y: 1
+      }
+    );
+
+    this.LeftRect = createSvg(
+      "rect",
+      {
+        class: "w-resize",
+        'data-id': "w-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: 1,
+        y: rect.height / 2 + 5
+      }
+    );
+
+    this.bottomLeftRect = createSvg(
+      "rect",
+      {
+        class: "sw-resize",
+        'data-id': "sw-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: 1,
+        y: rect.height + 9
+      }
+    );
+
+    this.bottomRect = createSvg(
+      "rect",
+      {
+        class: "s-resize",
+        'data-id': "s-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: rect.width / 2 + 5,
+        y: rect.height + 9
+      }
+    );
+    
+    this.bottomRightRect = createSvg(
+      "rect",
+      {
+        class: "se-resize",
+        'data-id': "se-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: rect.width + 9,
+        y: rect.height + 9
+      }
+    );
+
+    this.rightRect = createSvg(
+      "rect",
+      {
+        class: "e-resize",
+        'data-id': "e-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: rect.width + 9,
+        y: rect.height / 2 + 5
+      }
+    );
+
+    this.topRightRect = createSvg(
+      "rect",
+      {
+        class: "ne-resize",
+        'data-id': "ne-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: rect.width + 9,
+        y: 1
+      }
+    );
+
+    this.topRect = createSvg(
+      "rect",
+      {
+        class: "n-resize",
+        'data-id': "n-resize",
+        fill: "#FFFFFF",
+        stroke: "#4982E6",
+        'stroke-width': 1,
+        width: 6,
+        height: 6,
+        x: rect.width / 2 + 5,
+        y: 1
+      }
+    );
+
+    this.outerLine.append(this.moveRect)
+    this.outerLine.append(this.topLeftRect)
+    this.outerLine.append(this.LeftRect)
+    this.outerLine.append(this.bottomLeftRect)
+    this.outerLine.append(this.bottomRect)
+    this.outerLine.append(this.bottomRightRect)
+    this.outerLine.append(this.rightRect)
+    this.outerLine.append(this.topRightRect)
+    this.outerLine.append(this.topRect)
+    this.outerLineContainer.append(this.outerLine)
+    this.outerLineContainer.append(this.deletetButton)
+    this.initial = true
+  }
+
+  getActualRect (viewport, s,) {
+    const annotation = this.annotation
+    const { left: x1, top: y1, right: x2, bottom: y2 } = annotation.rect
+    
+    const start = getActualPoint(
+      {
+        x: x1,
+        y: y1
+      },
+      viewport,
+      s
+    )
+    const end = getActualPoint(
+      {
+        x: x2,
+        y: y2
+      },
+      viewport,
+      s
+    )
+    return {
+      start,
+      end
+    }
+  }
+
+  rectCalc ({ start, end }) {
+    return {
+      top: start.y < end.y ? start.y : end.y,
+      left: start.x < end.x ? start.x : end.x,
+      width: Math.abs(end.x - start.x),
+      height: Math.abs(end.y - start.y),
+    };
+  };
+
+  calculate (start, end) {
+    const initRect = this.rectCalc({ start, end });
+    const actualbdwidth = (this.annotation.borderWidth || 2) * this.scale
+
+    return {
+      top: initRect.top - actualbdwidth,
+      left: initRect.left - actualbdwidth,
+      width: initRect.width + actualbdwidth * 2,
+      height: initRect.height + actualbdwidth * 2,
+    }
+  }
+
+  getInitialPoint () {
+    const s = this.scale
+    const startX = this.start.x
+    const startY = this.start.y
+    const endX = this.end.x
+    const endY = this.end.y
+
+    let x1, y1, x2, y2;
+    x1 = startX / s;
+    y1 = startY / s; 
+    x2 = endX / s;
+    y2 = endY / s;
+    return {
+      start: {
+        x: x1,
+        y: y1,
+      },
+      end: {
+        x: x2,
+        y: y2,
+      },
+    };
+  }
+
+  updateTool () {
+    if (!this.initial) return
+    if (this.hidden) {
+      if (this.layer.annotationStore.selectedElementName === this.annotation.name) {
+        this.layer.annotationStore.selectedElementName = null
+      }
+      this.outerLineContainer.remove()
+    } else {
+      if (this.layer.annotationStore.selectedElementName !== this.annotation.name) {
+        this.layer.annotationStore.selectedElementName = this.annotation.name
+      }
+      this.container.append(this.outerLineContainer)
+      this.outerLine.addEventListener('mousedown', this.onMousedown)
+      this.outerLine.addEventListener('touchstart', this.onMousedown)
+    }
+  }
+
+  handleClick () {
+    if (!this.hidden || document.fullscreenElement || this.layer.annotationStore.creating || this.layer.toolMode === 'editor') return
+    if (this.layer.toolMode === 'security' && this.layer.tool === 'redaction') {
+      this.hidden = false
+      this.updateTool()
+
+      onClickOutside([this.annotationContainer, this.outerLine, this.deletetButton], this.handleOutside.bind(this))
+    }
+  }
+
+  handleOutside () {
+    if (!this.hidden && this.layer.annotationStore.selectedElementName === this.annotation.name) {
+      this.layer.annotationStore.selectedElementName = null
+    }
+
+    this.hidden = true
+
+    if (this.layer.annotationStore.selectedElementName === this.annotation.name) {
+      this.layer.annotationStore.selectedElementName = null
+    }
+    this.outerLine.removeEventListener('mousedown', this.onMousedown)
+    this.outerLine.removeEventListener('touchstart', this.onMousedown)
+    this.outerLineContainer.remove()
+  }
+
+  handleMouseDown (event) {
+    if (event.button !== 0 && event.type === 'mousedown') return
+    if (this.layer.tool) {
+      event.stopPropagation()
+    }
+    const operatorId = event.target.getAttribute('data-id')
+  
+    const { pageX, pageY } = getClickPoint(event)
+    this.startState = {
+      operator: operatorId,
+      clickX: pageX,
+      clickY: pageY,
+    }
+
+    document.addEventListener('mousemove', this.onMousemove)
+    document.addEventListener('mouseup', this.onMouseup)
+    document.addEventListener('touchmove', this.onMousemove)
+    document.addEventListener('touchend', this.onMouseup)
+
+    this.handleClick()
+  };
+
+  handleMouseMove (event) {
+    if (event.button !== 0 && event.type === 'mousemove') return
+    if (event.type === 'touchmove') {
+      document.querySelector('.document-container').style.overflow = 'hidden'
+    }
+    this.moving = true
+    const { pageX, pageY } = getClickPoint(event)
+    const start = this.start
+    const end = this.end
+    const startState = this.startState
+
+    const { width, height } = this.viewport
+    if (startState.operator === 'nw-resize') {
+      let x1 = pageX - (startState.clickX - this.start.x);
+      let y1 = pageY - (startState.clickY - this.start.y);
+      x1 = Math.min(end.x - 5, x1)
+      x1 = Math.max(0, x1)
+      y1 = Math.min(end.y - 5, y1)
+      y1 = Math.max(0, y1)
+
+      this.update({
+        start: { x: x1, y: y1 },
+        end,
+      });
+    } else if (startState.operator === 'w-resize') {
+      let x1 = pageX - (startState.clickX - this.start.x);
+      x1 = Math.min(end.x - 5, x1)
+      x1 = Math.max(0, x1)
+
+      this.update({
+        start: { x: x1, y: start.y },
+        end,
+      });
+    } else if (startState.operator === 'sw-resize') {
+      let x1 = pageX - (startState.clickX - this.start.x);
+      let y1 = pageY - (startState.clickY - this.end.y);
+      x1 = Math.min(end.x - 5, x1)
+      x1 = Math.max(0, x1)
+      y1 = Math.min(height, y1)
+      y1 = Math.max(start.y + 5, y1)
+
+      this.update({
+        start: { x: x1, y: start.y },
+        end: { x: end.x, y: y1 },
+      });
+    } else if (startState.operator === 's-resize') {
+      let y1 = pageY - (startState.clickY - this.end.y);
+      y1 = Math.min(height, y1)
+      y1 = Math.max(start.y + 5, y1)
+      this.update({
+        start,
+        end: { x: end.x, y: y1 },
+      });
+    } else if (startState.operator === 'se-resize') {
+      let x = pageX - (startState.clickX - this.end.x);
+      let y = pageY - (startState.clickY - this.end.y);
+      x = Math.min(width, x)
+      x = Math.max(start.x + 5, x)
+      y = Math.min(height, y)
+      y = Math.max(start.y + 5, y)
+      this.update({
+        start,
+        end: { x, y },
+      });
+    } else if (startState.operator === 'e-resize') {
+      let x = pageX - (startState.clickX - this.end.x);
+      x = Math.min(width, x)
+      x = Math.max(start.x + 5, x)
+      this.update({
+        start,
+        end: { x, y: end.y },
+      });
+    } else if (startState.operator === 'ne-resize') {
+      let x = pageX - (startState.clickX - this.end.x);
+      let y = pageY - (startState.clickY - this.start.y);
+      x = Math.min(width, x)
+      x = Math.max(start.x + 5, x)
+      y = Math.min(end.y - 5, y)
+      y = Math.max(0, y)
+      this.update({
+        start: { x: start.x, y },
+        end: { x, y: end.y },
+      });
+    } else if (startState.operator === 'n-resize') {
+      let y = pageY - (startState.clickY - this.start.y);
+      y = Math.min(end.y - 5, y)
+      y = Math.max(0, y)
+      this.update({
+        start: { x: start.x, y },
+        end,
+      });
+    } else if (startState.operator === 'move') {
+      let x1 = pageX - (startState.clickX - this.start.x);
+      let y1 = pageY - (startState.clickY - this.start.y);
+      let x2 = pageX - (startState.clickX - this.end.x);
+      let y2 = pageY - (startState.clickY - this.end.y);
+
+      const rect = {
+        width: Math.abs(start.x -end.x),
+        height: Math.abs(start.y -end.y)
+      }
+      if (x1 < x2) {
+        x1 = Math.max(0, x1)
+        x1 = Math.min(width - rect.width, x1)
+        x2 = Math.max(rect.width, x2)
+        x2 = Math.min(width, x2)
+      } else {
+        x2 = Math.max(0, x2)
+        x2 = Math.min(width - rect.width, x2)
+        x1 = Math.max(rect.width, x1)
+        x1 = Math.min(width, x1)
+      }
+
+      if (y1 < y2) {
+        y1 = Math.max(0, y1)
+        y1 = Math.min(height - rect.height, y1)
+        y2 = Math.max(rect.height, y2)
+        y2 = Math.min(height, y2)
+      } else {
+        y2 = Math.max(0, y2)
+        y2 = Math.min(height - rect.height, y2)
+        y1 = Math.max(rect.height, y1)
+        y1 = Math.min(height, y1)
+      }
+
+      this.update({
+        start: { x: x1, y: y1 },
+        end: { x: x2, y: y2 },
+      });
+    }
+  }
+
+  handleDelete (event) {
+    if (!this.annotationContainer) return
+    if (this.layer.tool && event) {
+      event.stopPropagation()
+    }
+    if (event) this.handleOutside()
+    this.annotationContainer.remove()
+
+    this.annotation.isDelete = true
+    const annotationData = {
+      type: 'delete',
+      annotation: {
+        operate: "del-annot",
+        name: this.annotation.name,
+        pageIndex: this.page
+      }
+    }
+    this.annotation.annotPtr && (annotationData.annotation.annotPtr = this.annotation.annotPtr)
+    if (!event) {
+      annotationData.type = 'empty'
+    }
+    this.eventBus.dispatch('annotationChange', annotationData)
+  }
+
+  handleMouseUp (event) {
+    if (event.button !== 0 && event.type === 'mouseup') return
+    if (this.layer.tool) {
+      event.stopPropagation()
+    }
+    if (event.type === 'touchend') {
+      document.querySelector('.document-container').style.overflow = 'auto'
+    }
+    this.moving = false
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+    
+    const { pageX, pageY } = getClickPoint(event)
+    if (pageX === this.startState.clickX && pageY === this.startState.clickY) return
+
+    this.start = this.newStart
+    this.end = this.newEnd
+
+    const annotation = this.annotation
+    const { start, end } = this.getInitialPoint()
+
+    const rect = {
+      left: start.x,
+      top: start.y,
+      right: end.x,
+      bottom: end.y
+    }
+
+    this.eventBus.dispatch('annotationChange', {
+      type: 'modify',
+      annotation: {
+        operate: "mod-annot",
+        name: annotation.name,
+        pageIndex: this.page,
+        pagePtr: annotation.pagePtr,
+        annotPtr: annotation.annotPtr,
+        rect
+      }
+    })
+  }
+
+  update ({ start, end }) {
+    const rect = this.calculate(start, end)
+
+    this.newStart = start
+    this.newEnd = end
+
+    this.annotationContainer.style.top = rect.top + 'px'
+    this.annotationContainer.style.left = rect.left + 'px'
+    this.annotationContainer.style.width = rect.width + 'px'
+    this.annotationContainer.style.height = rect.height + 'px'
+    
+    this.annotationContainer.style.width = `${rect.width}px`;
+    this.annotationContainer.style.height = `${rect.height}px`;
+
+    this.shapeElement.style.width = `${rect.width}px`;
+    this.shapeElement.style.height = `${rect.height}px`;
+
+    this.outerLine.style.left = `${rect.left - 8}px`
+    this.outerLine.style.top = `${rect.top - 8}px`
+    this.outerLine.style.width = `${rect.width + 8 * 2}px`
+    this.outerLine.style.height = `${rect.height + 8 * 2}px`
+
+    const left = (rect.left + rect.width - 30) + 'px'
+    const top = (rect.top + rect.height + 8) + 'px'
+    this.deletetButton.style.left = left
+    this.deletetButton.style.top = top
+    
+    this.moveRect.setAttribute('width',  rect.width + 9)
+    this.moveRect.setAttribute('height',  rect.height + 9)
+    
+    this.LeftRect.setAttribute("y", rect.height / 2 + 5)
+
+    this.bottomLeftRect.setAttribute("y", rect.height + 9)
+
+    this.bottomRect.setAttribute("x", rect.width / 2 + 5)
+    this.bottomRect.setAttribute("y", rect.height + 9)
+
+
+    this.bottomRightRect.setAttribute("x", rect.width + 9)
+    this.bottomRightRect.setAttribute("y", rect.height + 9)
+
+
+    this.rightRect.setAttribute("x", rect.width + 9)
+    this.rightRect.setAttribute("y", rect.height / 2 + 5)
+
+    
+    this.topRightRect.setAttribute("x", rect.width + 9)
+
+    this.topRect.setAttribute("x", rect.width / 2 + 5)
+  }
+}

+ 2 - 2
packages/webview/src/components/SecurityToolbar/SecurityToolbar.vue

@@ -7,7 +7,7 @@
       :show-arrow="false"
       to="#outerContainer"
       :raw="true"
-      :z-index="3"
+      :z-index="4"
       class="security-popover"
     >
       <template #trigger>
@@ -29,7 +29,7 @@
       :show-arrow="false"
       to="#outerContainer"
       :raw="true"
-      :z-index="3"
+      :z-index="4"
       class="security-popover"
     >
       <template #trigger>