feat:add search and replace

pr675
黄云飞 4 years ago
parent c2628dd75f
commit e3f0f04a2b

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

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

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

@ -24,7 +24,6 @@ import { TextParticle } from "./particle/TextParticle"
import { PageNumber } from "./frame/PageNumber" import { PageNumber } from "./frame/PageNumber"
import { GlobalObserver } from "../observer/GlobalObserver" import { GlobalObserver } from "../observer/GlobalObserver"
import { TableParticle } from "./particle/table/TableParticle" import { TableParticle } from "./particle/table/TableParticle"
import { ISearchResult } from "../../interface/Search"
import { TableTool } from "./particle/table/TableTool" import { TableTool } from "./particle/table/TableTool"
import { HyperlinkParticle } from "./particle/HyperlinkParticle" import { HyperlinkParticle } from "./particle/HyperlinkParticle"
import { Header } from "./frame/Header" import { Header } from "./frame/Header"
@ -73,7 +72,7 @@ export class Draw {
private rowList: IRow[] private rowList: IRow[]
private painterStyle: IElementStyle | null private painterStyle: IElementStyle | null
private searchMatchList: ISearchResult[] | null private searchKeyword: string | null
private visiblePageNoList: number[] private visiblePageNoList: number[]
private intersectionPageNo: number private intersectionPageNo: number
@ -127,7 +126,7 @@ export class Draw {
this.rowList = [] this.rowList = []
this.painterStyle = null this.painterStyle = null
this.searchMatchList = null this.searchKeyword = null
this.visiblePageNoList = [] this.visiblePageNoList = []
this.intersectionPageNo = 0 this.intersectionPageNo = 0
@ -247,6 +246,10 @@ export class Draw {
return this.options return this.options
} }
public getSearch(): Search {
return this.search
}
public getHistoryManager(): HistoryManager { public getHistoryManager(): HistoryManager {
return this.historyManager return this.historyManager
} }
@ -315,12 +318,12 @@ export class Draw {
} }
} }
public getSearchMatch(): ISearchResult[] | null { public getSearchKeyword(): string | null {
return this.searchMatchList return this.searchKeyword
} }
public setSearchMatch(payload: ISearchResult[] | null) { public setSearchKeyword(payload: string | null) {
this.searchMatchList = payload this.searchKeyword = payload
} }
public setDefaultRange() { public setDefaultRange() {
@ -781,7 +784,7 @@ export class Draw {
// 绘制页码 // 绘制页码
this.pageNumber.render(ctx, pageNo) this.pageNumber.render(ctx, pageNo)
// 搜索匹配绘制 // 搜索匹配绘制
if (this.searchMatchList) { if (this.searchKeyword) {
this.search.render(ctx, pageNo) this.search.render(ctx, pageNo)
} }
// 绘制水印 // 绘制水印
@ -802,6 +805,9 @@ export class Draw {
// 计算行信息 // 计算行信息
if (isComputeRowList) { if (isComputeRowList) {
this.rowList = this._computeRowList(innerWidth, this.elementList) this.rowList = this._computeRowList(innerWidth, this.elementList)
if (this.searchKeyword) {
this.search.compute(this.searchKeyword)
}
} }
// 清除光标等副作用 // 清除光标等副作用
this.cursor.recoveryCursor() 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 { EditorContext } from "../../../dataset/enum/Editor"
import { ElementType } from "../../../dataset/enum/Element"
import { IEditorOption } from "../../../interface/Editor" 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 { Position } from "../../position/Position"
import { Draw } from "../Draw" import { Draw } from "../Draw"
@ -9,24 +14,118 @@ export class Search {
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
private position: Position private position: Position
private searchMatchList: ISearchResult[]
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw this.draw = draw
this.options = draw.getOptions() this.options = draw.getOptions()
this.position = draw.getPosition() 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) { public render(ctx: CanvasRenderingContext2D, pageIndex: number) {
const searchMatchList = this.draw.getSearchMatch() if (!this.searchMatchList || !this.searchMatchList.length) return
if (!searchMatchList || !searchMatchList.length) return
const { searchMatchAlpha, searchMatchColor } = this.options const { searchMatchAlpha, searchMatchColor } = this.options
const positionList = this.position.getOriginalPositionList() const positionList = this.position.getOriginalPositionList()
const elementList = this.draw.getOriginalElementList() const elementList = this.draw.getOriginalElementList()
ctx.save() ctx.save()
ctx.globalAlpha = searchMatchAlpha ctx.globalAlpha = searchMatchAlpha
ctx.fillStyle = searchMatchColor ctx.fillStyle = searchMatchColor
for (let s = 0; s < searchMatchList.length; s++) { for (let s = 0; s < this.searchMatchList.length; s++) {
const searchMatch = searchMatchList[s] const searchMatch = this.searchMatchList[s]
let position: IElementPosition | null = null let position: IElementPosition | null = null
if (searchMatch.type === EditorContext.TABLE) { if (searchMatch.type === EditorContext.TABLE) {
const { tableIndex, trIndex, tdIndex, index } = searchMatch const { tableIndex, trIndex, tdIndex, index } = searchMatch

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

@ -402,24 +402,33 @@ window.onload = function () {
}) })
} }
// 5. | 搜索 | 打印 | // 5. | 搜索&替换 | 打印 |
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse') const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')!
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input') 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 () { document.querySelector<HTMLDivElement>('.menu-item__search')!.onclick = function () {
console.log('search') console.log('search')
searchCollapseDom!.style.display = 'block' searchCollapseDom.style.display = 'block'
} }
document.querySelector<HTMLDivElement>('.menu-item__search__collapse span')!.onclick = function () { searchCollapseDom.querySelector<HTMLSpanElement>('span')!.onclick = function () {
searchCollapseDom!.style.display = 'none' searchCollapseDom.style.display = 'none'
searchInputDom!.value = '' searchInputDom.value = ''
replaceInputDom.value = ''
instance.command.executeSearch(null) instance.command.executeSearch(null)
} }
searchInputDom!.oninput = function () { searchInputDom.oninput = function () {
instance.command.executeSearch(searchInputDom?.value || null) instance.command.executeSearch(searchInputDom.value || null)
} }
searchInputDom!.onkeydown = function (evt) { searchInputDom.onkeydown = function (evt) {
if (evt.key === 'Enter') { 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 { .menu-item .menu-item__search__collapse {
width: 215px; width: 235px;
height: 36px; height: 72px;
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
display: none; display: none;
z-index: 99; z-index: 99;
top: 25px; top: 25px;
left: 0; left: 0;
background: #ffffff;
box-shadow: 0px 5px 5px #e3dfdf; box-shadow: 0px 5px 5px #e3dfdf;
} }
.menu-item .menu-item__search__collapse__search { .menu-item .menu-item__search__collapse:hover {
width: 205px; background: #ffffff;
}
.menu-item .menu-item__search__collapse>div {
width: 225px;
height: 36px; height: 36px;
padding: 0 5px; padding: 0 5px;
line-height: 36px; line-height: 36px;
background: #ffffff;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
border-radius: 4px; border-radius: 4px;
} }
.menu-item .menu-item__search__collapse__search input { .menu-item .menu-item__search__collapse>div input {
height: 27px; height: 27px;
appearance: none; appearance: none;
background-color: #fff; background-color: #fff;
@ -477,12 +481,30 @@ ul {
padding: 0 5px; padding: 0 5px;
} }
.menu-item .menu-item__search__collapse__search span { .menu-item .menu-item__search__collapse>div span {
height: 100%; height: 100%;
color: #dcdfe6; color: #dcdfe6;
font-size: 25px; font-size: 25px;
display: inline-block; display: inline-block;
border: 0; 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 { .menu-item__print i {

Loading…
Cancel
Save