feat:add search and replace

pr675
黄云飞 4 years ago
parent c2628dd75f
commit e3f0f04a2b

@ -171,6 +171,10 @@
<input type="text" />
<span>×</span>
</div>
<div class="menu-item__search__collapse__replace">
<input type="text">
<button>替换</button>
</div>
</div>
<div class="menu-item__print" data-menu="print">
<i></i>

@ -51,6 +51,7 @@ export class Command {
private static addWatermark: Function
private static deleteWatermark: Function
private static search: Function
private static replace: Function
private static print: Function
private static pageScaleRecovery: Function
private static pageScaleMinus: Function
@ -102,6 +103,7 @@ export class Command {
Command.addWatermark = adapt.addWatermark.bind(adapt)
Command.deleteWatermark = adapt.deleteWatermark.bind(adapt)
Command.search = adapt.search.bind(adapt)
Command.replace = adapt.replace.bind(adapt)
Command.print = adapt.print.bind(adapt)
Command.pageScaleRecovery = adapt.pageScaleRecovery.bind(adapt)
Command.pageScaleMinus = adapt.pageScaleMinus.bind(adapt)
@ -289,6 +291,10 @@ export class Command {
return Command.search(payload)
}
public executeReplace(payload: string) {
return Command.replace(payload)
}
public executePrint() {
return Command.print()
}

@ -1,5 +1,5 @@
import { WRAP, ZERO } from "../../dataset/constant/Common"
import { EDITOR_ELEMENT_STYLE_ATTR, TEXTLIKE_ELEMENT_TYPE } from "../../dataset/constant/Element"
import { EDITOR_ELEMENT_STYLE_ATTR } from "../../dataset/constant/Element"
import { defaultWatermarkOption } from "../../dataset/constant/Watermark"
import { EditorContext, EditorMode } from "../../dataset/enum/Editor"
import { ElementType } from "../../dataset/enum/Element"
@ -8,7 +8,6 @@ import { RowFlex } from "../../dataset/enum/Row"
import { IDrawImagePayload } from "../../interface/Draw"
import { IEditorOption } from "../../interface/Editor"
import { IElement, IElementStyle } from "../../interface/Element"
import { ISearchResult, ISearchResultRestArgs } from "../../interface/Search"
import { IColgroup } from "../../interface/table/Colgroup"
import { ITd } from "../../interface/table/Td"
import { ITr } from "../../interface/table/Tr"
@ -1091,94 +1090,83 @@ export class CommandAdapt {
}
public search(payload: string | null) {
if (payload) {
let searchMatchList: ISearchResult[] = []
// 分组
const elementListGroup: { type: EditorContext, elementList: IElement[], index: number }[] = []
const originalElementList = this.draw.getOriginalElementList()
const originalElementListLength = originalElementList.length
// 查找表格所在位置
const tableIndexList = []
for (let e = 0; e < originalElementListLength; e++) {
const element = originalElementList[e]
if (element.type === ElementType.TABLE) {
tableIndexList.push(e)
}
}
let i = 0
let elementIndex = 0
while (elementIndex < originalElementListLength - 1) {
const endIndex = tableIndexList.length ? tableIndexList[i] : originalElementListLength
const pageElement = originalElementList.slice(elementIndex, endIndex)
if (pageElement.length) {
elementListGroup.push({
index: elementIndex,
type: EditorContext.PAGE,
elementList: pageElement
})
}
const tableElement = originalElementList[endIndex]
if (tableElement) {
elementListGroup.push({
index: endIndex,
type: EditorContext.TABLE,
elementList: [tableElement]
})
this.draw.setSearchKeyword(payload)
this.draw.render({
isSetCursor: false,
isSubmitHistory: false
})
}
public replace(payload: string) {
if (!payload || new RegExp(`${ZERO}`, 'g').test(payload)) return
const matchList = this.draw.getSearch().getSearchMatchList()
if (!matchList.length) return
// 匹配index变化的差值
let pageDiffCount = 0
let tableDiffCount = 0
// 匹配element的组标识
let curGroupId = ''
// 表格上下文
let curTdId = ''
// 搜索值 > 替换值:增加元素;搜索值 < 替换值:减少元素
const elementList = this.draw.getOriginalElementList()
for (let m = 0; m < matchList.length; m++) {
const match = matchList[m]
if (match.type === EditorContext.TABLE) {
const { tableIndex, trIndex, tdIndex, index, tdId } = match
if (curTdId && tdId !== curTdId) {
tableDiffCount = 0
}
elementIndex = endIndex + 1
i++
}
// 搜索文本
function searchClosure(payload: string | null, type: EditorContext, elementList: IElement[], restArgs?: ISearchResultRestArgs) {
if (!payload) return
const text = elementList.map(e => !e.type || TEXTLIKE_ELEMENT_TYPE.includes(e.type) ? e.value : ZERO)
.filter(Boolean)
.join('')
const matchStartIndexList = []
let index = text.indexOf(payload)
while (index !== -1) {
matchStartIndexList.push(index)
index = text.indexOf(payload, index + 1)
curTdId = tdId!
const curTableIndex = tableIndex! + pageDiffCount
const tableElementList = elementList[curTableIndex].trList![trIndex!].tdList[tdIndex!].value
// 表格内元素
const curIndex = index + tableDiffCount
const tableElement = tableElementList[curIndex]
if (curGroupId === match.groupId) {
tableElementList.splice(curIndex, 1)
tableDiffCount--
continue
}
for (let m = 0; m < matchStartIndexList.length; m++) {
const startIndex = matchStartIndexList[m]
for (let i = 0; i < payload.length; i++) {
const index = startIndex + i + (restArgs?.startIndex || 0)
searchMatchList.push({
type,
index,
...restArgs
for (let p = 0; p < payload.length; p++) {
const value = payload[p]
if (p === 0) {
tableElement.value = value
} else {
tableElementList.splice(curIndex + p, 0, {
...tableElement,
value
})
tableDiffCount++
}
}
}
for (let e = 0; e < elementListGroup.length; e++) {
const group = elementListGroup[e]
if (group.type === EditorContext.TABLE) {
const tableElement = group.elementList[0]
for (let t = 0; t < tableElement.trList!.length; t++) {
const tr = tableElement.trList![t]
for (let d = 0; d < tr.tdList.length; d++) {
const td = tr.tdList[d]
const restArgs: ISearchResultRestArgs = {
tableIndex: group.index,
trIndex: t,
tdIndex: d
}
searchClosure(payload, group.type, td.value, restArgs)
}
curGroupId = match.groupId
} else {
const curIndex = match.index + pageDiffCount
const element = elementList[curIndex]
if (curGroupId === match.groupId) {
elementList.splice(curIndex, 1)
pageDiffCount--
continue
}
for (let p = 0; p < payload.length; p++) {
const value = payload[p]
if (p === 0) {
element.value = value
} else {
elementList.splice(curIndex + p, 0, {
...element,
value
})
pageDiffCount++
}
} else {
searchClosure(payload, group.type, group.elementList, {
startIndex: group.index
})
}
curGroupId = match.groupId
}
this.draw.setSearchMatch(searchMatchList)
} else {
this.draw.setSearchMatch(null)
}
this.draw.render({ isSetCursor: false, isSubmitHistory: false })
this.draw.render({
isSetCursor: false
})
}
public print() {

@ -24,7 +24,6 @@ import { TextParticle } from "./particle/TextParticle"
import { PageNumber } from "./frame/PageNumber"
import { GlobalObserver } from "../observer/GlobalObserver"
import { TableParticle } from "./particle/table/TableParticle"
import { ISearchResult } from "../../interface/Search"
import { TableTool } from "./particle/table/TableTool"
import { HyperlinkParticle } from "./particle/HyperlinkParticle"
import { Header } from "./frame/Header"
@ -73,7 +72,7 @@ export class Draw {
private rowList: IRow[]
private painterStyle: IElementStyle | null
private searchMatchList: ISearchResult[] | null
private searchKeyword: string | null
private visiblePageNoList: number[]
private intersectionPageNo: number
@ -127,7 +126,7 @@ export class Draw {
this.rowList = []
this.painterStyle = null
this.searchMatchList = null
this.searchKeyword = null
this.visiblePageNoList = []
this.intersectionPageNo = 0
@ -247,6 +246,10 @@ export class Draw {
return this.options
}
public getSearch(): Search {
return this.search
}
public getHistoryManager(): HistoryManager {
return this.historyManager
}
@ -315,12 +318,12 @@ export class Draw {
}
}
public getSearchMatch(): ISearchResult[] | null {
return this.searchMatchList
public getSearchKeyword(): string | null {
return this.searchKeyword
}
public setSearchMatch(payload: ISearchResult[] | null) {
this.searchMatchList = payload
public setSearchKeyword(payload: string | null) {
this.searchKeyword = payload
}
public setDefaultRange() {
@ -781,7 +784,7 @@ export class Draw {
// 绘制页码
this.pageNumber.render(ctx, pageNo)
// 搜索匹配绘制
if (this.searchMatchList) {
if (this.searchKeyword) {
this.search.render(ctx, pageNo)
}
// 绘制水印
@ -802,6 +805,9 @@ export class Draw {
// 计算行信息
if (isComputeRowList) {
this.rowList = this._computeRowList(innerWidth, this.elementList)
if (this.searchKeyword) {
this.search.compute(this.searchKeyword)
}
}
// 清除光标等副作用
this.cursor.recoveryCursor()

@ -1,6 +1,11 @@
import { ZERO } from "../../../dataset/constant/Common"
import { TEXTLIKE_ELEMENT_TYPE } from "../../../dataset/constant/Element"
import { EditorContext } from "../../../dataset/enum/Editor"
import { ElementType } from "../../../dataset/enum/Element"
import { IEditorOption } from "../../../interface/Editor"
import { IElementPosition } from "../../../interface/Element"
import { IElement, IElementPosition } from "../../../interface/Element"
import { ISearchResult, ISearchResultRestArgs } from "../../../interface/Search"
import { getUUID } from "../../../utils"
import { Position } from "../../position/Position"
import { Draw } from "../Draw"
@ -9,24 +14,118 @@ export class Search {
private draw: Draw
private options: Required<IEditorOption>
private position: Position
private searchMatchList: ISearchResult[]
constructor(draw: Draw) {
this.draw = draw
this.options = draw.getOptions()
this.position = draw.getPosition()
this.searchMatchList = []
}
public getSearchMatchList(): ISearchResult[] {
return this.searchMatchList
}
public compute(payload: string) {
let searchMatchList: ISearchResult[] = []
// 分组
const elementListGroup: { type: EditorContext, elementList: IElement[], index: number }[] = []
const originalElementList = this.draw.getOriginalElementList()
const originalElementListLength = originalElementList.length
// 查找表格所在位置
const tableIndexList = []
for (let e = 0; e < originalElementListLength; e++) {
const element = originalElementList[e]
if (element.type === ElementType.TABLE) {
tableIndexList.push(e)
}
}
let i = 0
let elementIndex = 0
while (elementIndex < originalElementListLength - 1) {
const endIndex = tableIndexList.length ? tableIndexList[i] : originalElementListLength
const pageElement = originalElementList.slice(elementIndex, endIndex)
if (pageElement.length) {
elementListGroup.push({
index: elementIndex,
type: EditorContext.PAGE,
elementList: pageElement
})
}
const tableElement = originalElementList[endIndex]
if (tableElement) {
elementListGroup.push({
index: endIndex,
type: EditorContext.TABLE,
elementList: [tableElement]
})
}
elementIndex = endIndex + 1
i++
}
// 搜索文本
function searchClosure(payload: string | null, type: EditorContext, elementList: IElement[], restArgs?: ISearchResultRestArgs) {
if (!payload) return
const text = elementList.map(e => !e.type || TEXTLIKE_ELEMENT_TYPE.includes(e.type) ? e.value : ZERO)
.filter(Boolean)
.join('')
const matchStartIndexList = []
let index = text.indexOf(payload)
while (index !== -1) {
matchStartIndexList.push(index)
index = text.indexOf(payload, index + 1)
}
for (let m = 0; m < matchStartIndexList.length; m++) {
const startIndex = matchStartIndexList[m]
const groupId = getUUID()
for (let i = 0; i < payload.length; i++) {
const index = startIndex + i + (restArgs?.startIndex || 0)
searchMatchList.push({
type,
index,
groupId,
...restArgs
})
}
}
}
for (let e = 0; e < elementListGroup.length; e++) {
const group = elementListGroup[e]
if (group.type === EditorContext.TABLE) {
const tableElement = group.elementList[0]
for (let t = 0; t < tableElement.trList!.length; t++) {
const tr = tableElement.trList![t]
for (let d = 0; d < tr.tdList.length; d++) {
const td = tr.tdList[d]
const restArgs: ISearchResultRestArgs = {
tableIndex: group.index,
trIndex: t,
tdIndex: d,
tdId: td.id
}
searchClosure(payload, group.type, td.value, restArgs)
}
}
} else {
searchClosure(payload, group.type, group.elementList, {
startIndex: group.index
})
}
}
this.searchMatchList = searchMatchList
}
public render(ctx: CanvasRenderingContext2D, pageIndex: number) {
const searchMatchList = this.draw.getSearchMatch()
if (!searchMatchList || !searchMatchList.length) return
if (!this.searchMatchList || !this.searchMatchList.length) return
const { searchMatchAlpha, searchMatchColor } = this.options
const positionList = this.position.getOriginalPositionList()
const elementList = this.draw.getOriginalElementList()
ctx.save()
ctx.globalAlpha = searchMatchAlpha
ctx.fillStyle = searchMatchColor
for (let s = 0; s < searchMatchList.length; s++) {
const searchMatch = searchMatchList[s]
for (let s = 0; s < this.searchMatchList.length; s++) {
const searchMatch = this.searchMatchList[s]
let position: IElementPosition | null = null
if (searchMatch.type === EditorContext.TABLE) {
const { tableIndex, trIndex, tdIndex, index } = searchMatch

@ -3,12 +3,14 @@ import { EditorContext } from "../dataset/enum/Editor"
export interface ISearchResultBasic {
type: EditorContext;
index: number;
groupId: string;
}
export interface ISearchResultRestArgs {
tableIndex?: number;
trIndex?: number;
tdIndex?: number;
tdId?: string;
startIndex?: number;
}

@ -402,24 +402,33 @@ window.onload = function () {
})
}
// 5. | 搜索 | 打印 |
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')
// 5. | 搜索&替换 | 打印 |
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')!
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')!
const replaceInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__replace input')!
document.querySelector<HTMLDivElement>('.menu-item__search')!.onclick = function () {
console.log('search')
searchCollapseDom!.style.display = 'block'
searchCollapseDom.style.display = 'block'
}
document.querySelector<HTMLDivElement>('.menu-item__search__collapse span')!.onclick = function () {
searchCollapseDom!.style.display = 'none'
searchInputDom!.value = ''
searchCollapseDom.querySelector<HTMLSpanElement>('span')!.onclick = function () {
searchCollapseDom.style.display = 'none'
searchInputDom.value = ''
replaceInputDom.value = ''
instance.command.executeSearch(null)
}
searchInputDom!.oninput = function () {
instance.command.executeSearch(searchInputDom?.value || null)
searchInputDom.oninput = function () {
instance.command.executeSearch(searchInputDom.value || null)
}
searchInputDom!.onkeydown = function (evt) {
searchInputDom.onkeydown = function (evt) {
if (evt.key === 'Enter') {
instance.command.executeSearch(searchInputDom?.value || null)
instance.command.executeSearch(searchInputDom.value || null)
}
}
searchCollapseDom.querySelector<HTMLButtonElement>('button')!.onclick = function () {
const searchValue = searchInputDom.value
const replaceValue = replaceInputDom.value
if (searchValue && replaceValue && searchValue !== replaceValue) {
instance.command.executeReplace(replaceValue)
}
}

@ -439,30 +439,34 @@ ul {
}
.menu-item .menu-item__search__collapse {
width: 215px;
height: 36px;
width: 235px;
height: 72px;
box-sizing: border-box;
position: absolute;
display: none;
z-index: 99;
top: 25px;
left: 0;
background: #ffffff;
box-shadow: 0px 5px 5px #e3dfdf;
}
.menu-item .menu-item__search__collapse__search {
width: 205px;
.menu-item .menu-item__search__collapse:hover {
background: #ffffff;
}
.menu-item .menu-item__search__collapse>div {
width: 225px;
height: 36px;
padding: 0 5px;
line-height: 36px;
background: #ffffff;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 4px;
}
.menu-item .menu-item__search__collapse__search input {
.menu-item .menu-item__search__collapse>div input {
height: 27px;
appearance: none;
background-color: #fff;
@ -477,12 +481,30 @@ ul {
padding: 0 5px;
}
.menu-item .menu-item__search__collapse__search span {
.menu-item .menu-item__search__collapse>div span {
height: 100%;
color: #dcdfe6;
font-size: 25px;
display: inline-block;
border: 0;
padding: 0 10px;
}
.menu-item .menu-item__search__collapse__replace button {
display: inline-block;
border: 1px solid #e2e6ed;
border-radius: 2px;
background: #fff;
line-height: 22px;
padding: 0 6px;
white-space: nowrap;
margin-left: 4px;
cursor: pointer;
font-size: 12px;
}
.menu-item .menu-item__search__collapse__replace button:hover {
background: rgba(25, 55, 88, .04);
}
.menu-item__print i {

Loading…
Cancel
Save