diff --git a/index.html b/index.html index dd4fde4..166f7a8 100644 --- a/index.html +++ b/index.html @@ -59,6 +59,12 @@ + diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index 2892726..0db473f 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -7,15 +7,17 @@ export class Command { private static painter: Function private static format: Function private static bold: Function + private static search: Function private static print: Function constructor(adapt: CommandAdapt) { Command.undo = adapt.undo.bind(adapt) Command.redo = adapt.redo.bind(adapt) Command.painter = adapt.painter.bind(adapt) - Command.print = adapt.print.bind(adapt) Command.format = adapt.format.bind(adapt) Command.bold = adapt.bold.bind(adapt) + Command.search = adapt.search.bind(adapt) + Command.print = adapt.print.bind(adapt) } // 撤销、重做、格式刷、清除格式 @@ -41,6 +43,10 @@ export class Command { } // 搜索、打印 + public executeSearch(payload: string | null) { + return Command.search(payload) + } + public executePrint() { return Command.print() } diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index e58d641..8bf0275 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -64,6 +64,27 @@ export class CommandAdapt { this.draw.render({ isSetCursor: false }) } + public search(payload: string | null) { + if (payload) { + const elementList = this.draw.getElementList() + const text = elementList.map(e => !e.type || e.type === 'TEXT' ? e.value : null) + .filter(Boolean) + .join('') + const matchStartIndexList = [] + let index = text.indexOf(payload) + while (index !== -1) { + matchStartIndexList.push(index) + index = text.indexOf(payload, index + 1) + } + const searchMatch: number[][] = matchStartIndexList + .map(i => Array(payload.length).fill(i).map((_, j) => i + j)) + this.draw.setSearchMatch(searchMatch.length ? searchMatch : null) + } else { + this.draw.setSearchMatch(null) + } + this.draw.render({ isSetCursor: false, isSubmitHistory: false }) + } + public print() { return printImageBase64(this.draw.getDataURL()) } diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 4661ff0..45cf0de 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -12,6 +12,7 @@ import { Position } from "../position/Position" import { RangeManager } from "../range/RangeManager" import { Background } from "./Background" import { Margin } from "./Margin" +import { Search } from "./Search" export class Draw { @@ -25,10 +26,12 @@ export class Draw { private range: RangeManager private margin: Margin private background: Background + private search: Search private historyManager: HistoryManager private rowCount: number private painterStyle: IElementStyle | null + private searchMatchList: number[][] | null constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, options: Required, elementList: IElement[]) { this.canvas = canvas @@ -41,6 +44,7 @@ export class Draw { this.range = new RangeManager(ctx, elementList, options) this.margin = new Margin(ctx, options) this.background = new Background(ctx) + this.search = new Search(ctx, options, this) const canvasEvent = new CanvasEvent(canvas, this) this.cursor = new Cursor(canvas, this, canvasEvent) @@ -50,6 +54,7 @@ export class Draw { this.rowCount = 0 this.painterStyle = null + this.searchMatchList = null } public getHistoryManager(): HistoryManager { @@ -91,6 +96,14 @@ export class Draw { } } + public getSearchMathch(): number[][] | null { + return this.searchMatchList + } + + public setSearchMatch(payload: number[][] | null) { + this.searchMatchList = payload + } + public render(payload?: IDrawOption) { let { curIndex, isSubmitHistory = true, isSetCursor = true } = payload || {} // 清除光标 @@ -189,6 +202,10 @@ export class Draw { x = leftTopPoint[0] y += curRow.height } + // 搜索匹配绘制 + if (this.searchMatchList) { + this.search.render() + } // 光标重绘 if (curIndex === undefined) { curIndex = positionList.length - 1 diff --git a/src/editor/core/draw/Search.ts b/src/editor/core/draw/Search.ts new file mode 100644 index 0000000..33af0f7 --- /dev/null +++ b/src/editor/core/draw/Search.ts @@ -0,0 +1,39 @@ +import { IEditorOption } from "../../interface/Editor" +import { Position } from "../position/Position" +import { Draw } from "./Draw" + +export class Search { + + private ctx: CanvasRenderingContext2D + private options: Required + private draw: Draw + private position: Position + + constructor(ctx: CanvasRenderingContext2D, options: Required, draw: Draw) { + this.ctx = ctx + this.options = options + this.draw = draw + this.position = draw.getPosition() + } + + render() { + const searchMatch = this.draw.getSearchMathch() + if (!searchMatch || !searchMatch.length) return + const searchMatchList = searchMatch.flat() + const positionList = this.position.getPositionList() + this.ctx.save() + this.ctx.globalAlpha = this.options.searchMatchAlpha + this.ctx.fillStyle = this.options.searchMatchColor + searchMatchList.forEach(s => { + const position = positionList[s] + const { leftTop, leftBottom, rightTop } = position.coordinate + const x = leftTop[0] + const y = leftTop[1] + const width = rightTop[0] - leftTop[0] + const height = leftBottom[1] - leftTop[1] + this.ctx.fillRect(x, y, width, height) + }) + this.ctx.restore() + } + +} \ No newline at end of file diff --git a/src/editor/core/range/RangeManager.ts b/src/editor/core/range/RangeManager.ts index faac0e5..8c76dee 100644 --- a/src/editor/core/range/RangeManager.ts +++ b/src/editor/core/range/RangeManager.ts @@ -9,7 +9,7 @@ export class RangeManager { private options: Required private range: IRange - constructor(ctx: CanvasRenderingContext2D, elementList: IElement[], options: Required,) { + constructor(ctx: CanvasRenderingContext2D, elementList: IElement[], options: Required) { this.ctx = ctx this.elementList = elementList this.options = options diff --git a/src/editor/index.ts b/src/editor/index.ts index 73e8a2e..5bd51d6 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -17,6 +17,8 @@ export default class Editor { defaultSize: 16, rangeAlpha: 0.6, rangeColor: '#AECBFA', + searchMatchAlpha: 0.6, + searchMatchColor: '#FFFF00', marginIndicatorSize: 35, marginIndicatorColor: '#BABABA', margins: [100, 120, 100, 120], diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index aac9221..d591681 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -4,6 +4,8 @@ export interface IEditorOption { defaultSize?: number; rangeColor?: string; rangeAlpha?: number; + searchMatchColor?: string; + searchMatchAlpha?: number; marginIndicatorSize?: number; marginIndicatorColor?: string, margins?: [top: number, right: number, bootom: number, left: number] diff --git a/src/main.ts b/src/main.ts index 8c4a5bf..024d741 100644 --- a/src/main.ts +++ b/src/main.ts @@ -70,6 +70,25 @@ window.onload = function () { } // 搜索、打印 + const collspanDom = document.querySelector('.menu-item__search__collapse') + const searchInputDom = document.querySelector('.menu-item__search__collapse__search input') + document.querySelector('.menu-item__search')!.onclick = function () { + console.log('search') + collspanDom!.style.display = 'block' + } + document.querySelector('.menu-item__search__collapse span')!.onclick = function () { + collspanDom!.style.display = 'none' + searchInputDom!.value = '' + instance.command.executeSearch(null) + } + searchInputDom!.oninput = function () { + instance.command.executeSearch(searchInputDom?.value || null) + } + searchInputDom!.onkeydown = function (evt) { + if (evt.key === 'Enter') { + instance.command.executeSearch(searchInputDom?.value || null) + } + } document.querySelector('.menu-item__print')!.onclick = function () { console.log('print') instance.command.executePrint() diff --git a/src/style.css b/src/style.css index b36f06a..2ede447 100644 --- a/src/style.css +++ b/src/style.css @@ -32,6 +32,7 @@ body { height: 24px; display: flex; align-items: center; + position: relative; } .menu-item>div { @@ -125,10 +126,59 @@ body { background-color: #ffff00; } +.menu-item__search { + position: relative; +} + .menu-item__search i { background-image: url('./assets/images/search.svg'); } +.menu-item .menu-item__search__collapse { + box-sizing: border-box; + position: absolute; + display: none; + z-index: 99; + top: 25px; + left: 0; + box-shadow: 0px 5px 5px #e3dfdf; +} + +.menu-item .menu-item__search__collapse__search { + width: 205px; + 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 { + height: 27px; + appearance: none; + background-color: #fff; + background-image: none; + border-radius: 4px; + border: 1px solid #ebebeb; + box-sizing: border-box; + color: #606266; + display: inline-block; + line-height: 27px; + outline: none; + padding: 0 5px; +} + +.menu-item .menu-item__search__collapse__search span { + height: 100%; + color: #dcdfe6; + font-size: 25px; + display: inline-block; + border: 0; +} + .menu-item__print i { background-image: url('./assets/images/print.svg'); }