Merge pull request #73 from Hufe921/feature/search-navigate

Feature/search navigate
pr675
Hufe 4 years ago committed by GitHub
commit 176ea96119
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -194,6 +194,12 @@
<div class="menu-item__search__collapse" data-menu="search"> <div class="menu-item__search__collapse" data-menu="search">
<div class="menu-item__search__collapse__search"> <div class="menu-item__search__collapse__search">
<input type="text" /> <input type="text" />
<div class="arrow-left">
<i></i>
</div>
<div class="arrow-right">
<i></i>
</div>
<span>×</span> <span>×</span>
</div> </div>
<div class="menu-item__search__collapse__replace"> <div class="menu-item__search__collapse__replace">

@ -0,0 +1 @@
<svg width="4" height="7" xmlns="http://www.w3.org/2000/svg"><path fill="#6F6F6F" d="M0 3.5L4 0v7z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 127 B

@ -0,0 +1 @@
<svg width="4" height="7" xmlns="http://www.w3.org/2000/svg"><path fill="#6F6F6F" d="M4 3.5L0 0v7z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 127 B

@ -56,6 +56,8 @@ export class Command {
private static addWatermark: CommandAdapt['addWatermark'] private static addWatermark: CommandAdapt['addWatermark']
private static deleteWatermark: CommandAdapt['deleteWatermark'] private static deleteWatermark: CommandAdapt['deleteWatermark']
private static search: CommandAdapt['search'] private static search: CommandAdapt['search']
private static searchNavigatePre: CommandAdapt['searchNavigatePre']
private static searchNavigateNext: CommandAdapt['searchNavigateNext']
private static replace: CommandAdapt['replace'] private static replace: CommandAdapt['replace']
private static print: CommandAdapt['print'] private static print: CommandAdapt['print']
private static replaceImageElement: CommandAdapt['replaceImageElement'] private static replaceImageElement: CommandAdapt['replaceImageElement']
@ -122,6 +124,8 @@ 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.searchNavigatePre = adapt.searchNavigatePre.bind(adapt)
Command.searchNavigateNext = adapt.searchNavigateNext.bind(adapt)
Command.replace = adapt.replace.bind(adapt) Command.replace = adapt.replace.bind(adapt)
Command.print = adapt.print.bind(adapt) Command.print = adapt.print.bind(adapt)
Command.replaceImageElement = adapt.replaceImageElement.bind(adapt) Command.replaceImageElement = adapt.replaceImageElement.bind(adapt)
@ -333,6 +337,14 @@ export class Command {
return Command.search(payload) return Command.search(payload)
} }
public executeSearchNavigatePre() {
return Command.searchNavigatePre()
}
public executeSearchNavigateNext() {
return Command.searchNavigateNext()
}
public executeReplace(payload: string) { public executeReplace(payload: string) {
return Command.replace(payload) return Command.replace(payload)
} }

@ -19,6 +19,7 @@ import { formatElementList } from '../../utils/element'
import { printImageBase64 } from '../../utils/print' import { printImageBase64 } from '../../utils/print'
import { Control } from '../draw/control/Control' import { Control } from '../draw/control/Control'
import { Draw } from '../draw/Draw' import { Draw } from '../draw/Draw'
import { Search } from '../draw/interactive/Search'
import { TableTool } from '../draw/particle/table/TableTool' import { TableTool } from '../draw/particle/table/TableTool'
import { CanvasEvent } from '../event/CanvasEvent' import { CanvasEvent } from '../event/CanvasEvent'
import { HistoryManager } from '../history/HistoryManager' import { HistoryManager } from '../history/HistoryManager'
@ -40,6 +41,7 @@ export class CommandAdapt {
private options: Required<IEditorOption> private options: Required<IEditorOption>
private control: Control private control: Control
private workerManager: WorkerManager private workerManager: WorkerManager
private searchManager: Search
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw this.draw = draw
@ -51,6 +53,7 @@ export class CommandAdapt {
this.options = draw.getOptions() this.options = draw.getOptions()
this.control = draw.getControl() this.control = draw.getControl()
this.workerManager = draw.getWorkerManager() this.workerManager = draw.getWorkerManager()
this.searchManager = draw.getSearch()
} }
public mode(payload: EditorMode) { public mode(payload: EditorMode) {
@ -1211,7 +1214,25 @@ export class CommandAdapt {
} }
public search(payload: string | null) { public search(payload: string | null) {
this.draw.setSearchKeyword(payload) this.searchManager.setSearchKeyword(payload)
this.draw.render({
isSetCursor: false,
isSubmitHistory: false
})
}
public searchNavigatePre() {
const index = this.searchManager.searchNavigatePre()
if (index === null) return
this.draw.render({
isSetCursor: false,
isSubmitHistory: false
})
}
public searchNavigateNext() {
const index = this.searchManager.searchNavigateNext()
if (index === null) return
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false

@ -92,7 +92,6 @@ export class Draw {
private rowList: IRow[] private rowList: IRow[]
private painterStyle: IElementStyle | null private painterStyle: IElementStyle | null
private painterOptions: IPainterOptions | null private painterOptions: IPainterOptions | null
private searchKeyword: string | null
private visiblePageNoList: number[] private visiblePageNoList: number[]
private intersectionPageNo: number private intersectionPageNo: number
@ -155,7 +154,6 @@ export class Draw {
this.rowList = [] this.rowList = []
this.painterStyle = null this.painterStyle = null
this.painterOptions = null this.painterOptions = null
this.searchKeyword = null
this.visiblePageNoList = [] this.visiblePageNoList = []
this.intersectionPageNo = 0 this.intersectionPageNo = 0
@ -417,14 +415,6 @@ export class Draw {
} }
} }
public getSearchKeyword(): string | null {
return this.searchKeyword
}
public setSearchKeyword(payload: string | null) {
this.searchKeyword = payload
}
public setDefaultRange() { public setDefaultRange() {
if (!this.elementList.length) return if (!this.elementList.length) return
setTimeout(() => { setTimeout(() => {
@ -995,7 +985,7 @@ export class Draw {
// 绘制页码 // 绘制页码
this.pageNumber.render(ctx, pageNo) this.pageNumber.render(ctx, pageNo)
// 搜索匹配绘制 // 搜索匹配绘制
if (this.searchKeyword) { if (this.search.getSearchKeyword()) {
this.search.render(ctx, pageNo) this.search.render(ctx, pageNo)
} }
// 绘制水印 // 绘制水印
@ -1017,8 +1007,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) { const searchKeyword = this.search.getSearchKeyword()
this.search.compute(this.searchKeyword) if (searchKeyword) {
this.search.compute(searchKeyword)
} }
} }
// 清除光标等副作用 // 清除光标等副作用

@ -15,15 +15,107 @@ export class Search {
private draw: Draw private draw: Draw
private options: Required<IEditorOption> private options: Required<IEditorOption>
private position: Position private position: Position
private searchKeyword: string | null
private searchNavigateIndex: number | null
private searchMatchList: ISearchResult[] 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.searchNavigateIndex = null
this.searchKeyword = null
this.searchMatchList = [] this.searchMatchList = []
} }
public getSearchKeyword(): string | null {
return this.searchKeyword
}
public setSearchKeyword(payload: string | null) {
this.searchKeyword = payload
this.searchNavigateIndex = null
}
public searchNavigatePre(): number | null {
if (!this.searchMatchList.length || !this.searchKeyword) return null
if (this.searchNavigateIndex === null) {
this.searchNavigateIndex = 0
} else {
let index = this.searchNavigateIndex - 1
let isExistPre = false
const searchNavigateId = this.searchMatchList[this.searchNavigateIndex].groupId
while (index >= 0) {
const match = this.searchMatchList[index]
if (searchNavigateId !== match.groupId) {
isExistPre = true
this.searchNavigateIndex = index - (this.searchKeyword.length - 1)
break
}
index--
}
if (!isExistPre) {
const lastSearchMatch = this.searchMatchList[this.searchMatchList.length - 1]
if (lastSearchMatch.groupId === searchNavigateId) return null
this.searchNavigateIndex = this.searchMatchList.length - 1 - (this.searchKeyword.length - 1)
}
}
return this.searchNavigateIndex
}
public searchNavigateNext(): number | null {
if (!this.searchMatchList.length || !this.searchKeyword) return null
if (this.searchNavigateIndex === null) {
this.searchNavigateIndex = 0
} else {
let index = this.searchNavigateIndex + 1
let isExistNext = false
const searchNavigateId = this.searchMatchList[this.searchNavigateIndex].groupId
while (index < this.searchMatchList.length) {
const match = this.searchMatchList[index]
if (searchNavigateId !== match.groupId) {
isExistNext = true
this.searchNavigateIndex = index
break
}
index++
}
if (!isExistNext) {
const firstSearchMatch = this.searchMatchList[0]
if (firstSearchMatch.groupId === searchNavigateId) return null
this.searchNavigateIndex = 0
}
}
return this.searchNavigateIndex
}
public searchNavigateScrollIntoView(position: IElementPosition) {
const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
const preY = pageNo * (height + pageGap)
// 创建定位锚点
const anchor = document.createElement('div')
anchor.style.position = 'absolute'
// 扩大搜索词尺寸,使可视范围更广
const ANCHOR_OVERFLOW_SIZE = 50
anchor.style.width = `${rightTop[0] - leftTop[0] + ANCHOR_OVERFLOW_SIZE}px`
anchor.style.height = `${leftBottom[1] - leftTop[1] + ANCHOR_OVERFLOW_SIZE}px`
anchor.style.left = `${leftTop[0]}px`
anchor.style.top = `${leftTop[1] + preY}px`
this.draw.getContainer().append(anchor)
// 移动到可视范围
anchor.scrollIntoView(false)
anchor.remove()
}
public getSearchNavigateIndexList() {
if (this.searchNavigateIndex === null || !this.searchKeyword) return []
return new Array(this.searchKeyword.length)
.fill(this.searchNavigateIndex)
.map((navigate, index) => navigate + index)
}
public getSearchMatchList(): ISearchResult[] { public getSearchMatchList(): ISearchResult[] {
return this.searchMatchList return this.searchMatchList
} }
@ -121,13 +213,12 @@ export class Search {
} }
public render(ctx: CanvasRenderingContext2D, pageIndex: number) { public render(ctx: CanvasRenderingContext2D, pageIndex: number) {
if (!this.searchMatchList || !this.searchMatchList.length) return if (!this.searchMatchList || !this.searchMatchList.length || !this.searchKeyword) return
const { searchMatchAlpha, searchMatchColor } = this.options const { searchMatchAlpha, searchMatchColor, searchNavigateMatchColor } = 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
for (let s = 0; s < this.searchMatchList.length; s++) { for (let s = 0; s < this.searchMatchList.length; s++) {
const searchMatch = this.searchMatchList[s] const searchMatch = this.searchMatchList[s]
let position: IElementPosition | null = null let position: IElementPosition | null = null
@ -140,6 +231,14 @@ export class Search {
if (!position) continue if (!position) continue
const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position
if (pageNo !== pageIndex) continue if (pageNo !== pageIndex) continue
// 高亮并定位当前搜索词
const searchMatchIndexList = this.getSearchNavigateIndexList()
if (searchMatchIndexList.includes(s)) {
ctx.fillStyle = searchNavigateMatchColor
this.searchNavigateScrollIntoView(position)
} else {
ctx.fillStyle = searchMatchColor
}
const x = leftTop[0] const x = leftTop[0]
const y = leftTop[1] const y = leftTop[1]
const width = rightTop[0] - leftTop[0] const width = rightTop[0] - leftTop[0]

@ -70,6 +70,7 @@ export default class Editor {
rangeMinWidth: 5, rangeMinWidth: 5,
searchMatchAlpha: 0.6, searchMatchAlpha: 0.6,
searchMatchColor: '#FFFF00', searchMatchColor: '#FFFF00',
searchNavigateMatchColor: '#AAD280',
highlightAlpha: 0.6, highlightAlpha: 0.6,
resizerColor: '#4182D9', resizerColor: '#4182D9',
resizerSize: 5, resizerSize: 5,

@ -27,6 +27,7 @@ export interface IEditorOption {
rangeAlpha?: number; rangeAlpha?: number;
rangeMinWidth?: number; rangeMinWidth?: number;
searchMatchColor?: string; searchMatchColor?: string;
searchNavigateMatchColor?: string;
searchMatchAlpha?: number; searchMatchAlpha?: number;
highlightAlpha?: number; highlightAlpha?: number;
resizerColor?: string; resizerColor?: string;

@ -645,6 +645,12 @@ window.onload = function () {
instance.command.executeReplace(replaceValue) instance.command.executeReplace(replaceValue)
} }
} }
searchCollapseDom.querySelector<HTMLDivElement>('.arrow-left')!.onclick = function () {
instance.command.executeSearchNavigatePre()
}
searchCollapseDom.querySelector<HTMLDivElement>('.arrow-right')!.onclick = function () {
instance.command.executeSearchNavigateNext()
}
document.querySelector<HTMLDivElement>('.menu-item__print')!.onclick = function () { document.querySelector<HTMLDivElement>('.menu-item__print')!.onclick = function () {
console.log('print') console.log('print')

@ -540,6 +540,52 @@ ul {
background: rgba(25, 55, 88, .04); background: rgba(25, 55, 88, .04);
} }
.menu-item .menu-item__search__collapse__search {
position: relative;
}
.menu-item .menu-item__search__collapse__search>input {
width: 181px;
padding: 5px 60px 5px 5px !important;
}
.menu-item .menu-item__search__collapse__search>div {
width: 28px;
height: 27px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
border-left: 1px solid #e2e6ed;
transition: all .5s;
}
.menu-item .menu-item__search__collapse__search>div:hover {
background-color: rgba(25, 55, 88, .04);
}
.menu-item .menu-item__search__collapse__search i {
width: 6px;
height: 8px;
transform: translateY(1px);
}
.menu-item .menu-item__search__collapse__search .arrow-left {
right: 76px;
}
.menu-item .menu-item__search__collapse__search .arrow-left i {
background: url(./assets/images/arrow-left.svg) no-repeat;
}
.menu-item .menu-item__search__collapse__search .arrow-right {
right: 48px;
}
.menu-item .menu-item__search__collapse__search .arrow-right i {
background: url(./assets/images/arrow-right.svg) no-repeat;
}
.menu-item__print i { .menu-item__print i {
background-image: url('./assets/images/print.svg'); background-image: url('./assets/images/print.svg');
} }

Loading…
Cancel
Save