feat: double click the selected text through the segmenter #510
This commit is contained in:
@@ -1,38 +1,63 @@
|
||||
import { ZERO } from '../../../dataset/constant/Common'
|
||||
import { TEXTLIKE_ELEMENT_TYPE } from '../../../dataset/constant/Element'
|
||||
import { NUMBER_LIKE_REG } from '../../../dataset/constant/Regular'
|
||||
import { ControlComponent } from '../../../dataset/enum/Control'
|
||||
import { IRange } from '../../../interface/Range'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
function dblclick(host: CanvasEvent, evt: MouseEvent) {
|
||||
// 通过分词器获取单词所在选区
|
||||
function getWordRangeBySegmenter(host: CanvasEvent): IRange | null {
|
||||
if (!Intl.Segmenter) return null
|
||||
const draw = host.getDraw()
|
||||
const LETTER_REG = draw.getLetterReg()
|
||||
const position = draw.getPosition()
|
||||
const positionContext = position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
// 图片预览
|
||||
if (positionContext.isImage && positionContext.isDirectHit) {
|
||||
draw.getPreviewer().render()
|
||||
return
|
||||
}
|
||||
// 切换区域
|
||||
if (draw.getIsPagingMode()) {
|
||||
if (!~positionContext.index && positionContext.zone) {
|
||||
draw.getZone().setZone(positionContext.zone)
|
||||
draw.clearSideEffect()
|
||||
position.setPositionContext({
|
||||
isTable: false
|
||||
})
|
||||
return
|
||||
const cursorPosition = draw.getPosition().getCursorPosition()
|
||||
if (!cursorPosition) return null
|
||||
const rangeManager = draw.getRange()
|
||||
const paragraphInfo = rangeManager.getRangeParagraphInfo()
|
||||
if (!paragraphInfo) return null
|
||||
// 组装段落文本
|
||||
const paragraphText =
|
||||
paragraphInfo?.elementList
|
||||
?.map(e =>
|
||||
!e.type ||
|
||||
(TEXTLIKE_ELEMENT_TYPE.includes(e.type) &&
|
||||
e.controlComponent !== ControlComponent.CHECKBOX)
|
||||
? e.value
|
||||
: ZERO
|
||||
)
|
||||
.join('') || ''
|
||||
if (!paragraphText) return null
|
||||
// 光标所在位置
|
||||
const cursorStartIndex = cursorPosition.index
|
||||
// 段落首字符相对文档起始位置
|
||||
const offset = paragraphInfo.startIndex
|
||||
const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' })
|
||||
const segments = segmenter.segment(paragraphText)
|
||||
// 新的光标位置
|
||||
let startIndex = -1
|
||||
let endIndex = -1
|
||||
for (const { segment, index, isWordLike } of segments) {
|
||||
const realSegmentStartIndex = index + offset
|
||||
if (
|
||||
isWordLike &&
|
||||
cursorStartIndex >= realSegmentStartIndex &&
|
||||
cursorStartIndex < realSegmentStartIndex + segment.length
|
||||
) {
|
||||
startIndex = realSegmentStartIndex - 1
|
||||
endIndex = startIndex + segment.length
|
||||
break
|
||||
}
|
||||
}
|
||||
// 复选框双击时是切换选择状态,禁用扩选
|
||||
if (positionContext.isCheckbox && positionContext.isDirectHit) return
|
||||
// 自动扩选文字
|
||||
const cursorPosition = position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
return ~startIndex && ~endIndex ? { startIndex, endIndex } : null
|
||||
}
|
||||
|
||||
// 通过光标位置获取单词所在选区
|
||||
function getWordRangeByCursor(host: CanvasEvent): IRange | null {
|
||||
const draw = host.getDraw()
|
||||
const cursorPosition = draw.getPosition().getCursorPosition()
|
||||
if (!cursorPosition) return null
|
||||
const { value, index } = cursorPosition
|
||||
// 判断是否是数字或英文
|
||||
const LETTER_REG = draw.getLetterReg()
|
||||
let upCount = 0
|
||||
let downCount = 0
|
||||
const isNumber = NUMBER_LIKE_REG.test(value)
|
||||
@@ -67,11 +92,46 @@ function dblclick(host: CanvasEvent, evt: MouseEvent) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 设置选中区域
|
||||
// 新的光标位置
|
||||
const startIndex = index - upCount - 1
|
||||
if (startIndex < 0) return
|
||||
if (startIndex < 0) return null
|
||||
return {
|
||||
startIndex,
|
||||
endIndex: index + downCount
|
||||
}
|
||||
}
|
||||
|
||||
function dblclick(host: CanvasEvent, evt: MouseEvent) {
|
||||
const draw = host.getDraw()
|
||||
const position = draw.getPosition()
|
||||
const positionContext = position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
// 图片预览
|
||||
if (positionContext.isImage && positionContext.isDirectHit) {
|
||||
draw.getPreviewer().render()
|
||||
return
|
||||
}
|
||||
// 切换区域
|
||||
if (draw.getIsPagingMode()) {
|
||||
if (!~positionContext.index && positionContext.zone) {
|
||||
draw.getZone().setZone(positionContext.zone)
|
||||
draw.clearSideEffect()
|
||||
position.setPositionContext({
|
||||
isTable: false
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
// 复选框双击时是切换选择状态,禁用扩选
|
||||
if (positionContext.isCheckbox && positionContext.isDirectHit) return
|
||||
// 自动扩选文字-分词处理,优先使用分词器否则降级使用光标所在位置
|
||||
const rangeManager = draw.getRange()
|
||||
rangeManager.setRange(startIndex, index + downCount)
|
||||
const segmenterRange =
|
||||
getWordRangeBySegmenter(host) || getWordRangeByCursor(host)
|
||||
if (!segmenterRange) return
|
||||
rangeManager.setRange(segmenterRange.startIndex, segmenterRange.endIndex)
|
||||
// 刷新文档
|
||||
draw.render({
|
||||
isSubmitHistory: false,
|
||||
|
||||
@@ -8,7 +8,12 @@ import { IEditorOption } from '../../interface/Editor'
|
||||
import { IElement } from '../../interface/Element'
|
||||
import { EventBusMap } from '../../interface/EventBus'
|
||||
import { IRangeStyle } from '../../interface/Listener'
|
||||
import { IRange, RangeRowArray, RangeRowMap } from '../../interface/Range'
|
||||
import {
|
||||
IRange,
|
||||
IRangeParagraphInfo,
|
||||
RangeRowArray,
|
||||
RangeRowMap
|
||||
} from '../../interface/Range'
|
||||
import { getAnchorElement } from '../../utils/element'
|
||||
import { Draw } from '../draw/Draw'
|
||||
import { EventBus } from '../event/eventbus/EventBus'
|
||||
@@ -208,10 +213,12 @@ export class RangeManager {
|
||||
return rangeRow
|
||||
}
|
||||
|
||||
// 获取选区段落元素列表
|
||||
public getRangeParagraphElementList(): IElement[] | null {
|
||||
// 获取选区段落信息
|
||||
public getRangeParagraphInfo(): IRangeParagraphInfo | null {
|
||||
const { startIndex, endIndex } = this.range
|
||||
if (!~startIndex && !~endIndex) return null
|
||||
/// 起始元素位置
|
||||
let startPositionIndex = -1
|
||||
// 需要改变的元素列表
|
||||
const rangeElementList: IElement[] = []
|
||||
// 选区行信息
|
||||
@@ -224,10 +231,22 @@ export class RangeManager {
|
||||
const rowArray = rangeRow.get(position.pageNo)
|
||||
if (!rowArray) continue
|
||||
if (rowArray.includes(position.rowNo)) {
|
||||
if (!~startPositionIndex) {
|
||||
startPositionIndex = position.index
|
||||
}
|
||||
rangeElementList.push(elementList[p])
|
||||
}
|
||||
}
|
||||
return rangeElementList
|
||||
if (!rangeElementList.length) return null
|
||||
return {
|
||||
elementList: rangeElementList,
|
||||
startIndex: startPositionIndex
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选区段落元素列表
|
||||
public getRangeParagraphElementList(): IElement[] | null {
|
||||
return this.getRangeParagraphInfo()?.elementList || null
|
||||
}
|
||||
|
||||
public getIsSelectAll() {
|
||||
|
||||
@@ -29,3 +29,8 @@ export type RangeContext = {
|
||||
zone: EditorZone
|
||||
isTable: boolean
|
||||
}
|
||||
|
||||
export interface IRangeParagraphInfo {
|
||||
elementList: IElement[]
|
||||
startIndex: number
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user