UNPKG

electron-find

Version:

Find all matches for the text in electron app

476 lines (429 loc) 17.6 kB
const Find = require('./find.js') const { print, on, off, move } = require('./utils.js') const INPUT_INTERVAL_THRESHOLD = 360 const findBox = Symbol('findBox') const findInput = Symbol('findInput') const findMatches = Symbol('findMatches') const findCase = Symbol('findCase') const findBack = Symbol('findBack') const findForward = Symbol('findForward') const findClose = Symbol('findClose') const hasOpened = Symbol('hasOpened') const matchCase = Symbol('matchCase') const documentKeydown = Symbol('documentKeydown') const inputFocus = Symbol('inputFocus') const inputBlur = Symbol('inputBlur') const inputEvent = Symbol('inputEvent') const compositionstart = Symbol('compositionstart') const compositionend = Symbol('compositionend') const caseMouseenter = Symbol('caseMouseenter') const caseMouseleave = Symbol('caseMouseleave') const caseClick = Symbol('caseClick') const backMouseenter = Symbol('backMouseenter') const backMouseleave = Symbol('backMouseleave') const backClick = Symbol('backClick') const forwardMouseenter = Symbol('forwardMouseenter') const forwardMouseleave = Symbol('forwardMouseleave') const forwardClick = Symbol('forwardClick') const closeMouseenter = Symbol('closeMouseenter') const closeMouseleave = Symbol('closeMouseleave') const closeClick = Symbol('closeClick') const events = Symbol('events') const inComposition = Symbol('inComposition') const action = Symbol('action') const lastText = Symbol('lastText') const inputCnt = Symbol('inputCnt') const initialized = Symbol('initialized') const config = Symbol('config') class FindInPage extends Find{ constructor (webContents, options = {}) { super(webContents) this[findBox] = null this[findInput] = null this[findMatches] = null this[findCase] = null this[findBack] = null this[findForward] = null this[findClose] = null this[hasOpened] = false this[matchCase] = false this[inComposition] = false this[action] = '' this[lastText] = '' this[inputCnt] = 0 this[initialized] = false this[config] = {} this[events] = [] this.parentElement = options.parentElement ? options.parentElement : document.body this.duration = (typeof options.duration === 'number' && options.duration > 0) ? options.duration : 300 this.options = options this.options.preload ? this.initialize() : '' } initialize () { if (this[initialized]) { print('[FindInPage] Has initialize.') return true } if (!this.initFind()) { print('[FindInPage] Failed to initialize.') return false } this[findBox] = creatElement('find-box') this[findInput] = creatElement('find-input', 'input') this[findMatches] = creatElement('find-matches') this[findCase] = creatElement('find-case') this[findBack] = creatElement('find-back') this[findForward] = creatElement('find-forward') this[findClose] = creatElement('find-close') getUserConfig.call(this, this.options) setBoxStyle.call(this) setInputStyle.call(this) setMatchesStyle.call(this) setCaseStyle.call(this) setBackStyle.call(this) setForwardStyle.call(this) setCloseStyle.call(this) lockNext.call(this) creatEventHandler.call(this) bindEvents.call(this) appendElement.call(this) onResult.call(this) move(this[findBox], (0 - this[findBox].offsetHeight - 10), this.duration) return this[initialized] = true } openFindWindow () { if (this[hasOpened]) { focusInput.call(this) return false } if (!this.initialize()) return false setTimeout(() => { this[findBox].style['visibility'] = 'visible' lockNext.call(this) focusInput.call(this) }, 10) move(this[findBox], parseInt(this[config].offsetTop), this.duration) .then(() => {}) .catch(err => { throw err }) return this[hasOpened] = true } closeFindWindow () { if (!this[hasOpened]) return false this[findInput].value = '' this[action] = '' this[lastText] = '' this[findMatches].innerText = '0/0' this[hasOpened] = false lockNext.call(this) move(this[findBox], (0 - this[findBox].offsetHeight - 10), this.duration) .then(() => { this[findBox].style['visibility'] = 'hidden' }) .catch(err => { throw err }) return true } update () { if (this[hasOpened] && this.isFinding()) { onForwardClick.call(this) } } destroy () { this.destroyFind() unbindEvents.call(this) this.closeFindWindow() removeElement.call(this) } } function creatElement (className = '', tag = 'div') { const ele = document.createElement(tag) ele.classList.add(className) return ele } function getUserConfig (options) { this[config].offsetTop = typeof options.offsetTop === 'number' ? `${options.offsetTop}px` : '5px' this[config].offsetRight = typeof options.offsetRight === 'number' ? `${options.offsetRight}px` : '5px' this[config].boxBgColor = typeof options.boxBgColor === 'string' ? options.boxBgColor : '#fff' this[config].boxShadowColor = typeof options.boxShadowColor === 'string' ? options.boxShadowColor : '#909399' this[config].inputColor = typeof options.inputColor === 'string' ? options.inputColor : '#606266' this[config].inputBgColor = typeof options.inputBgColor === 'string' ? options.inputBgColor : '#f0f0f0' this[config].inputFocusColor = typeof options.inputFocusColor === 'string' ? options.inputFocusColor : '#c5ade0' this[config].textColor = typeof options.textColor === 'string' ? options.textColor : '#606266' this[config].textHoverBgColor = typeof options.textHoverBgColor === 'string' ? options.textHoverBgColor : '#eaeaea' this[config].caseSelectedColor = typeof options.caseSelectedColor === 'string' ? options.caseSelectedColor : '#c5ade0' } function setBoxStyle () { this[findBox].style.cssText = `position:fixed; top:-110%; z-index: 3001; max-height:48px; min-height:30px; right:${this[config].offsetRight}; display:flex; align-items:center; box-sizing:border-box !important; padding:6px; visibility: hidden; background:${this[config].boxBgColor}; box-shadow: 1px 1px 2px 0.5px ${this[config].boxShadowColor};` } function setInputStyle () { this[findInput].style.cssText = `width:168px; outline:0; border:1px solid ${this[config].inputBgColor}; background:${this[config].inputBgColor}; margin-right:6px; border-radius:2px; color:${this[config].inputColor}` } function setMatchesStyle () { this[findMatches].innerText = '0/0' this[findMatches].style.cssText = `color:${this[config].textColor}; font-size:14px; display:flex; align-items:center; justify-content:center; min-width:40px; max-width:64px; overflow:hidden; margin-right:4px;` } function setCaseStyle () { this[findCase].innerText = 'Aa' this[findCase].style.cssText = `font-size:14px; font-weight:700; cursor:pointer; -webkit-user-select:none; color:${this[config].textColor}; padding:0px 2px; border-radius:2px; border:1px solid transparent; margin-right:4px; display:flex; align-items:center;` } function setBackStyle () { this[findBack].style.cssText = `cursor:pointer; -webkit-user-select:none; position: relative; height: 20px; width: 20px; border-radius:2px; overflow: hidden; display: inline-block; background:${this[config].boxBgColor}; border:0px solid ${this[config].boxBgColor};` let backLine = creatElement('find-back-line') backLine.style.cssText = `width:0; height:0; border:7px solid transparent; border-right-color:${this[config].textColor}; position: absolute; top:3px; left:-1px;` this[findBack].appendChild(backLine) let backCover = creatElement('find-back-cover') backCover.style.cssText = `width:0; height:0; border:7px solid transparent; border-right-color:inherit; position: absolute; top:3px; left:2px; z-index:1001;` this[findBack].appendChild(backCover) } function setForwardStyle () { this[findForward].style.cssText = `cursor:pointer; -webkit-user-select:none; position: relative; height: 20px; width: 20px; border-radius:2px; overflow: hidden; display: inline-block; background:${this[config].boxBgColor}; border:0px solid ${this[config].boxBgColor};` let forwardLine = creatElement('find-forward-line') forwardLine.style.cssText = `width:0; height:0; border:7px solid transparent; border-left-color:${this[config].textColor}; position: absolute; top:3px; left:6px;` this[findForward].appendChild(forwardLine) let forwardCover = creatElement('find-forward-cover') forwardCover.style.cssText = `width:0; height:0; border:7px solid transparent; border-left-color:inherit; position: absolute; top:3px; left:3px; z-index:1001;` this[findForward].appendChild(forwardCover) } function setCloseStyle () { this[findClose].style.cssText = `cursor:pointer; -webkit-user-select:none; position: relative; height: 20px; width: 20px; overflow: hidden; display: inline-block; background:${this[config].boxBgColor}; border-radius:2px;` let closeInner1 = creatElement('find-close-inner1') closeInner1.style.cssText = `width:14px; height:2px; background:${this[config].textColor}; transform:rotate(45deg); position: absolute; top:9px; left:3px;` this[findClose].appendChild(closeInner1) let closeInner2 = creatElement('find-close-inner2') closeInner2.style.cssText = `width:14px; height:2px; background:${this[config].textColor}; transform:rotate(-45deg); position: absolute; top:9px; left:3px;` this[findClose].appendChild(closeInner2) } function appendElement () { [this[findInput], this[findMatches], this[findCase], this[findBack], this[findForward], this[findClose]].forEach((item) => { this[findBox].appendChild(item) }) this.parentElement.appendChild(this[findBox]) } function removeElement () { this.parentElement.removeChild(this[findBox]) } function creatEventHandler () { this[documentKeydown] = (function (e) { if (!this[hasOpened]) return onKeydown.call(this, e) }).bind(this) this[events].push({ ele: document, name: 'keydown', fn: this[documentKeydown] }) this[inputFocus] = (function () { this[findInput].style.border = `1px solid ${this[config].inputFocusColor}` }).bind(this) this[events].push({ ele: this[findInput], name: 'focus', fn: this[inputFocus] }) this[inputBlur] = (function () { this[findInput].style.border = `1px solid ${this[config].inputBgColor}` }).bind(this) this[events].push({ ele: this[findInput], name: 'blur', fn: this[inputBlur] }) this[inputEvent] = (function () { updateCnt.call(this) isInputing.call(this) .then(res => { res ? '' : onInput.call(this) }) }).bind(this) this[events].push({ ele: this[findInput], name: 'input', fn: this[inputEvent] }) this[compositionstart] = (function () { print('compositionstart') this[inComposition] = true }).bind(this) this[events].push({ ele: this[findInput], name: 'compositionstart', fn: this[compositionstart] }) this[compositionend] = (function () { print('compositionend') this[inComposition] = false }).bind(this) this[events].push({ ele: this[findInput], name: 'compositionend', fn: this[compositionend] }) this[caseMouseenter] = (function () { this[findCase].style['background'] = this[config].textHoverBgColor }).bind(this) this[events].push({ ele: this[findCase], name: 'mouseenter', fn: this[caseMouseenter] }) this[caseMouseleave] = (function () { this[findCase].style['background'] = this[config].boxBgColor }).bind(this) this[events].push({ ele: this[findCase], name: 'mouseleave', fn: this[caseMouseleave] }) this[caseClick] = (function () { onCaseClick.call(this) }).bind(this) this[events].push({ ele: this[findCase], name: 'click', fn: this[caseClick] }) this[backMouseenter] = (function () { this[findBack].style['background'] = this[config].textHoverBgColor this[findBack].style['border'] = `0px solid ${this[config].textHoverBgColor}` }).bind(this) this[events].push({ ele: this[findBack], name: 'mouseenter', fn: this[backMouseenter] }) this[backMouseleave] = (function () { this[findBack].style['background'] = this[config].boxBgColor this[findBack].style['border'] = `0px solid ${this[config].boxBgColor}` }).bind(this) this[events].push({ ele: this[findBack], name: 'mouseleave', fn: this[backMouseleave] }) this[backClick] = (function () { onBackClick.call(this) }).bind(this) this[events].push({ ele: this[findBack], name: 'click', fn: this[backClick] }) this[forwardMouseenter] = (function () { this[findForward].style['background'] = this[config].textHoverBgColor this[findForward].style['border'] = `0px solid ${this[config].textHoverBgColor}` }).bind(this) this[events].push({ ele: this[findForward], name: 'mouseenter', fn: this[forwardMouseenter] }) this[forwardMouseleave] = (function () { this[findForward].style['background'] = this[config].boxBgColor this[findForward].style['border'] = `0px solid ${this[config].boxBgColor}` }).bind(this) this[events].push({ ele: this[findForward], name: 'mouseleave', fn: this[forwardMouseleave] }) this[forwardClick] = (function () { onForwardClick.call(this) }).bind(this) this[events].push({ ele: this[findForward], name: 'click', fn: this[forwardClick] }) this[closeMouseenter] = (function () { this[findClose].style['background'] = this[config].textHoverBgColor }).bind(this) this[events].push({ ele: this[findClose], name: 'mouseenter', fn: this[closeMouseenter] }) this[closeMouseleave] = (function () { this[findClose].style['background'] = this[config].boxBgColor }).bind(this) this[events].push({ ele: this[findClose], name: 'mouseleave', fn: this[closeMouseleave] }) this[closeClick] = (function () { onCloseClick.call(this) }).bind(this) this[events].push({ ele: this[findClose], name: 'click', fn: this[closeClick] }) } function bindEvents () { this[events].forEach((item) => { on(item.ele, item.name, item.fn) }) } function unbindEvents () { this[events].forEach((item) => { off(item.ele, item.name, item.fn) }) } function updateCnt () { if (this[inputCnt] >= 0xFFFFFFFE) { this[inputCnt] = 0 } this[inputCnt]++ } function isInputing () { return new Promise((resolve, reject) => { let currCnt = this[inputCnt] setTimeout(() => { currCnt !== this[inputCnt] ? resolve(true) : resolve(false) }, INPUT_INTERVAL_THRESHOLD) }) } function focusInput (doBlur = false) { setTimeout(() => { doBlur ? this[findInput].blur() : '' this[findInput].focus() }, 50) } function wrapInput (inputEle, caseEle, timeout = 50) { inputEle.type = 'password' caseEle.style['visibility'] = 'hidden' setTimeout(() => { if (inputEle.type !== 'text') { print('[FindInPage] wrapInput timeout..') unwrapInput(inputEle, caseEle) } }, timeout) } function unwrapInput (inputEle, caseEle) { inputEle.type = 'text' caseEle.style['visibility'] = 'visible' } function onInput () { setTimeout(() => { if (this[inComposition]) return this[action] = 'input' let text = this[findInput].value if (text && text !== this[lastText]) { this[lastText] = text wrapInput(this[findInput], this[findCase], 100) this.startFind(text, true, this[matchCase]) } else if (this[lastText] && text === '') { this.stopFind() this[findMatches].innerText = '0/0' lockNext.call(this) focusInput.call(this, true) } }, 50) } function onKeydown (e) { if (this[inComposition] || !e) return switch (e.code) { case 'Enter': case 'NumpadEnter': let text = this[findInput].value if (!text) return e.shiftKey ? findKeep.call(this, false) : findKeep.call(this, true) break case 'Escape': onCloseClick.call(this) break default: break } } function findKeep (forward) { if (!this.isFinding()) return forward ? onForwardClick.call(this) : onBackClick.call(this) } function onCaseClick () { if (!this[matchCase]) { this[matchCase] = true this[findCase].style['border-color'] = this[config].caseSelectedColor wrapInput(this[findInput], this[findCase], 100) this.startFind(this[findInput].value, true, this[matchCase]) } else { this[matchCase] = false this[findCase].style['border-color'] = 'transparent' wrapInput(this[findInput], this[findCase], 100) this.startFind(this[findInput].value, true, this[matchCase]) } } function onBackClick () { this[action] = 'back' wrapInput(this[findInput], this[findCase], 100) this.findNext(false, this[matchCase]) } function onForwardClick () { this[action] = 'forward' wrapInput(this[findInput], this[findCase], 100) this.findNext(true, this[matchCase]) } function onCloseClick () { this.closeFindWindow() ? this.stopFind() : '' } function onResult () { this.on('result', (activeMatch, matches) => { unwrapInput(this[findInput], this[findCase]) this[findMatches].innerText = `${activeMatch}/${matches}` matches > 0 ? unlockNext.call(this) : lockNext.call(this) this[action] === 'input' ? focusInput.call(this) : '' }) } function lockNext () { this[findBack].style['opacity'] = 0.6 this[findBack].style['pointer-events'] = 'none' this[findForward].style['opacity'] = 0.6 this[findForward].style['pointer-events'] = 'none' } function unlockNext () { this[findBack].style['opacity'] = 1 this[findBack].style['pointer-events'] = 'auto' this[findForward].style['opacity'] = 1 this[findForward].style['pointer-events'] = 'auto' } module.exports = FindInPage