UNPKG

substance

Version:

Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing systems.

253 lines (223 loc) 7.36 kB
import { ToggleTool } from '../../ui' import { debounce } from '../../util' class FindAndReplaceTool extends ToggleTool { didMount() { this.context.editorSession.onPosition(this._onPosition, this) } dispose() { this.context.editorSession.off(this) } _onPosition() { let findAndReplaceManager = this.context.editorSession.getManager('find-and-replace') // After each operation, such as start find or find next, // findAndReplaceManager leaves a flag `_requestLookupMatch` to indicate we // should scroll to the desired position if (findAndReplaceManager._requestLookupMatch) { this._scrollToSelectedMatch() findAndReplaceManager._requestLookupMatch = false } if (findAndReplaceManager._requestFocusSearchString) { setTimeout(() => { this.refs.findString.el.focus() this.refs.findString.el.select() findAndReplaceManager._requestFocusSearchString = false },0) } } render($$) { let commandState = this.props.commandState let el = $$('div').addClass('sc-find-and-replace-tool') el.append( this._renderStatusDescription($$), // TODO: enable status options (case-insensitive search / regexp etc.) // $$('div').addClass('se-status-options').append( // $$('span').addClass('sm-light').append( // 'Finding with Options: ' // ), // 'Case Insensitive' // ), // $$('button').append('X'), // $$('button').append('Y'), // $$('button').append('Z'), // $$('button').append('A') $$('div').addClass('se-section').append( $$('div') .addClass('se-section-item se-find-input') .addClass('sm-flex') .append( $$('input') .ref('findString') .attr('type', 'text') .attr('placeholder', 'Find in body') .attr('tabindex', 500) .val(commandState.findString) .on('input', debounce(this._startFind, 300)) .on('keypress', this._triggerFindNext) .on('focus', this._onFocus) .on('blur', this._onBlur), this._renderStatusCounter($$) ), $$('div') .addClass('se-section-item se-replace-input') .append( $$('button') .append('Find') .attr('tabindex', 502) .on('click', this._findNext) ), $$('div') .addClass('se-section-item') // .append( // $$('button').append('Find All') // ) ), $$('div').addClass('se-section').append( $$('div') .addClass('se-section-item') .addClass('sm-flex') .append( $$('input') .ref('replaceString') .val(commandState.replaceString) .attr('type', 'text') .attr('tabindex', 501) .attr('placeholder', 'Replace in body') .on('focus', this._onFocus) .on('blur', this._onBlur) .on('input', this._setReplaceString) .on('keypress', this._triggerReplace) ), $$('div') .addClass('se-section-item') .append( $$('button') .append('Replace') .attr('tabindex', 503) .on('click', this._replaceNext) ), $$('div') .addClass('se-section-item') .append( $$('button') .append('Replace All') .attr('tabindex', 504) .on('click', this._replaceAll) ) ) ) return el } _renderStatusDescription($$) { let commandState = this.props.commandState let statusDescriptionEl = $$('div').addClass('se-status').append( $$('div').addClass('se-status-title').append( this.getLabel('find-and-replace-title') ) ) if (commandState.totalMatches > 0) { statusDescriptionEl.append( $$('div').addClass('se-status-description').append( commandState.totalMatches, ' results found for ', '"'+ commandState.findString +'"' ) ) } else if (commandState.findString !== '') { statusDescriptionEl.append( $$('div').addClass('se-status-description').append( 'No results found for ', '"'+ commandState.findString +'"' ) ) } else { statusDescriptionEl.append( $$('div').addClass('se-status-description').append( 'Close this panel with ESC key' ) ) } return statusDescriptionEl } /* We disable selection rendering in the surface, while our tool has the focus */ _onFocus() { let editorSession = this.context.editorSession editorSession.setBlurred(true) // HACK: we need to manually start a flow, otherwise tools would not update visually editorSession.startFlow() } /* Re-enable selection rendering in surface */ _onBlur() { let editorSession = this.context.editorSession editorSession.setBlurred(false) } _renderStatusCounter($$) { let commandState = this.props.commandState let statusCounterEl if (commandState.totalMatches > 0) { statusCounterEl = $$('span').addClass('se-status-counter').append( [commandState.selectedMatch, commandState.totalMatches].join(' of ') ) } return statusCounterEl } _findNext() { let findAndReplaceManager = this.context.editorSession.getManager('find-and-replace') findAndReplaceManager.findNext() } _replaceNext() { let findAndReplaceManager = this.context.editorSession.getManager('find-and-replace') findAndReplaceManager.replaceNext() } _replaceAll() { let findAndReplaceManager = this.context.editorSession.getManager('find-and-replace') findAndReplaceManager.replaceAll() } /* Returns true if keyboard event is a text input */ findStringHasChanged() { let findString = this.refs.findString.val() let previousFindString = this._previousFindString this._previousFindString = findString return findString !== previousFindString } /* Starts a new find operations. All matches are highlighted in the document and the first one is selected. */ _startFind() { let editorSession = this.context.editorSession let findString = this.refs.findString.val() let findAndReplaceManager = editorSession.getManager('find-and-replace') findAndReplaceManager.startFind(findString) } _scrollToSelectedMatch() { let editorSession = this.context.editorSession let surface = editorSession.getFocusedSurface() surface.context.scrollPane.scrollTo('.sc-selected-match', 'onlyIfNotVisible') } _triggerFindNext(e) { if (e.keyCode === 13) { this._findNext() } } _triggerReplace(e) { if (e.keyCode === 13) { this._replaceNext() } } /* Just sets the replace string in findAndReplaceManager state NOTE: no flow is triggered here */ _setReplaceString() { let replaceString = this.refs.replaceString.val() let findAndReplaceManager = this.context.editorSession.getManager('find-and-replace') findAndReplaceManager.setReplaceString(replaceString) } } export default FindAndReplaceTool