UNPKG

@syncfusion/ej2-documenteditor

Version:

Feature-rich document editor control with built-in support for context menu, options pane and dialogs.

656 lines (655 loc) 30.4 kB
import { Dictionary } from '../../base/dictionary'; import { isNullOrUndefined } from '@syncfusion/ej2-base'; import { TextPosition } from '../selection/selection-helper'; import { TextElementBox, FieldElementBox, HeaderFooterWidget } from '../viewer/page'; import { SearchWidgetInfo } from './text-search'; import { TextSearch } from '../search/text-search'; import { TextSearchResults } from '../search/text-search-results'; import { SearchResults } from './search-results'; import { searchResultsChangeEvent } from '../../base/index'; /** * Search module */ var Search = /** @class */ (function () { function Search(owner) { /** * @private */ this.searchHighlighters = undefined; this.owner = owner; this.searchHighlighters = new Dictionary(); this.textSearch = new TextSearch(this.owner); this.textSearchResults = new TextSearchResults(this.owner); this.searchResultsInternal = new SearchResults(this); } Object.defineProperty(Search.prototype, "viewer", { get: function () { return this.owner.viewer; }, enumerable: true, configurable: true }); Object.defineProperty(Search.prototype, "searchResults", { /** * Gets the search results object. * * @aspType SearchResults * @returns {SearchResults} - Returns the search results object. */ get: function () { return this.searchResultsInternal; }, enumerable: true, configurable: true }); Object.defineProperty(Search.prototype, "documentHelper", { get: function () { return this.owner.documentHelper; }, enumerable: true, configurable: true }); Search.prototype.getModuleName = function () { return 'Search'; }; //#region Find & Find All /** * Finds the immediate occurrence of specified text from cursor position in the document. * * @param {string} text - Specifies text to find. * @param {FindOption} findOptions - Default value of ‘findOptions’ parameter is 'None'. * @returns {void} */ Search.prototype.find = function (text, findOptions) { if (isNullOrUndefined(findOptions)) { findOptions = 'None'; } var result = this.textSearch.find(text, findOptions); if (!isNullOrUndefined(result)) { this.navigate(result); } }; /** * Finds all occurrence of specified text in the document. * * @param {string} text - Specifies text to find. * @param {FindOption} findOptions - Default value of ‘findOptions’ parameter is 'None'. * @returns {void} */ Search.prototype.findAll = function (text, findOptions) { if (isNullOrUndefined(text || text === '')) { return; } if (isNullOrUndefined(findOptions)) { findOptions = 'None'; } var results = this.textSearch.findAll(text, findOptions); if (!isNullOrUndefined(results) && results.length > 0) { this.navigate(results.innerList[results.currentIndex]); this.highlight(results); } }; //#endregion //#region Replace and Replace All /** * Replace the searched string with specified string * * @private * @param {string} replaceText - Specifies text to replace. * @param {TextSearchResult} result - Specifies the result. * @param {TextSearchResults} results - Specifies the results. * @returns {number} - Returns replaced text count. */ Search.prototype.replace = function (replaceText, result, results) { if (isNullOrUndefined(this.viewer.owner) || this.viewer.owner.isReadOnlyMode || !this.viewer.owner.editorModule.canEditContentControl || isNullOrUndefined(results)) { return 0; } if (!isNullOrUndefined(this.viewer)) { this.clearSearchHighlight(); } this.navigate(result); var endPosition = this.documentHelper.selection.start; if (this.owner.enableTrackChanges && this.documentHelper.selection.start.currentWidget) { var inline = undefined; /* eslint-disable-next-line max-len */ var inlineElement = this.documentHelper.selection.end.currentWidget.getInline(this.owner.selectionModule.start.offset, 0); inline = inlineElement.element; if (inline.revisions.length > 0) { this.isRepalceTracking = true; } } var index = results.indexOf(result); if (index < 0) { return 0; } this.owner.editorModule.insertTextInternal(replaceText, true); var endTextPosition = result.end; var startPosition = new TextPosition(this.viewer.owner); if (endTextPosition.currentWidget.children.length === 0) { var linewidget = endTextPosition.currentWidget.paragraph.childWidgets[0]; startPosition.setPositionParagraph(linewidget, endPosition.offset - replaceText.length); } else { startPosition.setPositionParagraph(endTextPosition.currentWidget, endPosition.offset - replaceText.length); } this.documentHelper.selection.selectRange(endPosition, startPosition); var eventArgs = { source: this.viewer.owner }; this.viewer.owner.trigger(searchResultsChangeEvent, eventArgs); return 1; }; /** * Find the textToFind string in current document and replace the specified string. * * @private * @param {string} textToReplace - Specifies the text to replace. * @param {FindOption} findOptions - Default value of ‘findOptions’ parameter is FindOption.None. * @returns {void} */ Search.prototype.replaceInternal = function (textToReplace, findOptions) { if ((textToReplace === '' || isNullOrUndefined(textToReplace))) { return; } if (isNullOrUndefined(findOptions)) { findOptions = 'None'; } var textToFind = this.textSearchResults.currentSearchResult.text; var pattern = this.viewer.owner.searchModule.textSearch.stringToRegex(textToFind, findOptions); var index = this.owner.selectionModule.end.getHierarchicalIndexInternal(); var result = this.textSearchResults.currentSearchResult; if (isNullOrUndefined(result)) { result = this.viewer.owner.searchModule.textSearch.findNext(pattern, findOptions, index); } if (!isNullOrUndefined(result)) { this.navigate(result); this.textSearchResults.addResult(); this.textSearchResults.innerList[0] = result; this.replace(textToReplace, result, this.textSearchResults); index = this.owner.selectionModule.end.getHierarchicalIndexInternal(); result = this.textSearch.findNext(textToFind, findOptions, index); if (result) { this.textSearchResults.addResult(); this.textSearchResults.innerList[0] = result; this.navigate(result); } } }; /** * Replace all the searched string with specified string * * @private * @param {string} replaceText - Specifies the replace text. * @param {TextSearchResults} results - Specfies the results. * @returns {number} - Returns the replace count. */ Search.prototype.replaceAll = function (replaceText, results) { this.documentHelper.layout.isReplacingAll = true; if (isNullOrUndefined(this.viewer.owner) || this.viewer.owner.isReadOnlyMode || isNullOrUndefined(results)) { return 0; } if (this.owner.editorHistoryModule) { this.owner.editorHistoryModule.initComplexHistory(this.owner.selectionModule, 'ReplaceAll'); } var count = results.length; this.viewer.owner.isLayoutEnabled = false; var text = results.innerList[0].text; for (var i = count - 1; i >= 0; i--) { var result = results.innerList[parseInt(i.toString(), 10)]; if (result.start.currentWidget.children.length === 0) { results = this.textSearch.findAll(text); i = results.length; } else { this.navigate(result); if (this.viewer.owner.isReadOnlyMode || !this.viewer.owner.editorModule.canEditContentControl) { continue; } var allowLayout = true; if (i > 0) { var previousResult = results.innerList[i - 1]; if (previousResult.start.paragraph.equals(result.start.paragraph)) { allowLayout = false; } } this.owner.editorModule.insertTextInternal(replaceText, true, undefined, allowLayout); result.destroy(); } //if (result.isHeader || result.isFooter) { // eslint-disable-next-line max-len //this.documentHelper.layout.updateHeaderFooterToParent(this.documentHelper.selection.start.paragraph.bodyWidget as HeaderFooterWidget); //} } if (this.owner.editorHistoryModule && !isNullOrUndefined(this.owner.editorHistoryModule.currentHistoryInfo)) { this.owner.editorHistoryModule.updateComplexHistory(); } else { this.owner.editorModule.updateComplexWithoutHistory(2); } this.searchResults.clear(); this.documentHelper.layout.isReplacingAll = false; return count; }; /** * Find the textToFind string in current document and replace the specified string. * * @private * @param {string} textToReplace - Specifies the text to replace. * @param {FindOption} findOptions - Default value of ‘findOptions’ parameter is FindOption.None. * @returns {void} */ Search.prototype.replaceAllInternal = function (textToReplace, findOptions) { if (isNullOrUndefined(textToReplace)) { return; } if (isNullOrUndefined(findOptions)) { findOptions = 'None'; } if (this.textSearchResults.length > 0) { this.navigate(this.textSearchResults.innerList[this.textSearchResults.currentIndex]); this.highlight(this.textSearchResults); this.replaceAll(textToReplace, this.textSearchResults); } }; //#endregion //#region Highlight Search Result /** * @private * @param {TextSearchResult} textSearchResult - Specifies the text search results. * @returns {void} */ Search.prototype.navigate = function (textSearchResult) { if (textSearchResult) { var start = textSearchResult.startOffset; var end = textSearchResult.endOffset; if (!isNullOrUndefined(this.owner) && !isNullOrUndefined(this.owner.selectionModule) && !isNullOrUndefined(start) && !isNullOrUndefined(end)) { this.owner.selectionModule.select(start, end); this.documentHelper.updateFocus(); } } }; /** * @private * @param {TextSearchResults} textSearchResults - Specifies the text search results. * @returns {void} */ Search.prototype.highlight = function (textSearchResults) { this.searchHighlighters = new Dictionary(); for (var i = 0; i < textSearchResults.innerList.length; i++) { var result = textSearchResults.innerList[parseInt(i.toString(), 10)]; this.highlightResult(result); } this.viewer.renderVisiblePages(); }; Search.prototype.highlightResult = function (result) { this.highlightSearchResult(result.start.paragraph, result.start, result.end); }; /* eslint-disable */ Search.prototype.highlightSearchResult = function (paragraph, start, end) { var selectionStartIndex = 0; var selectionEndIndex = 0; var startElement = null; var endElement = null; var lineWidget = this.documentHelper.selection.getStartLineWidget(paragraph, start, startElement, selectionStartIndex); selectionStartIndex = lineWidget.index; startElement = lineWidget.element; var startLineWidget = startElement ? startElement.line : paragraph.childWidgets[0]; var endLine = this.documentHelper.selection.getEndLineWidget(end, endElement, selectionEndIndex); selectionEndIndex = endLine.index; endElement = endLine.element; var endLineWidget = endElement ? endElement.line : end.paragraph.childWidgets[end.paragraph.childWidgets.length - 1]; var top = this.documentHelper.selection.getTop(startLineWidget); var left = this.documentHelper.selection.getLeftInternal(startLineWidget, startElement, selectionStartIndex); if (!isNullOrUndefined(startLineWidget) && startLineWidget === endLineWidget) { //find result ends in current line. var right = this.documentHelper.selection.getLeftInternal(endLineWidget, endElement, selectionEndIndex); var isRtlText = false; if (endElement instanceof TextElementBox) { isRtlText = endElement.isRightToLeft; } var width = 0; width = Math.abs(right - left); if (!isRtlText && startElement instanceof TextElementBox) { isRtlText = startElement.isRightToLeft; } // Handled the highlighting approach as genric for normal and rtl text. if (isRtlText || paragraph.bidi) { var elementBox = this.documentHelper.selection.getElementsForward(startLineWidget, startElement, endElement, paragraph.bidi); if (elementBox && elementBox.length > 1) { for (var i = 0; i < elementBox.length; i++) { var element = elementBox[i]; var elementIsRTL = false; var index = element instanceof TextElementBox ? element.length : 1; if (element === startElement) { left = this.documentHelper.selection.getLeftInternal(startLineWidget, element, selectionStartIndex); right = this.documentHelper.selection.getLeftInternal(startLineWidget, element, index); } else if (element === endElement) { left = this.documentHelper.selection.getLeftInternal(startLineWidget, element, 0); right = this.documentHelper.selection.getLeftInternal(startLineWidget, element, selectionEndIndex); } else { left = this.documentHelper.selection.getLeftInternal(startLineWidget, element, 0); right = this.documentHelper.selection.getLeftInternal(startLineWidget, element, index); } if (element instanceof TextElementBox) { elementIsRTL = element.isRightToLeft; } width = Math.abs(right - left); this.createHighlightBorder(startLineWidget, width, elementIsRTL ? right : left, top); } } else { this.createHighlightBorder(startLineWidget, width, isRtlText ? right : left, top); } } else { // Start element and end element will be in reverese for Bidi paragraph highlighting. // So, the right is considered based on Bidi property. this.createHighlightBorder(startLineWidget, width, left, top); } } else { if (!isNullOrUndefined(startLineWidget)) { if (paragraph !== startLineWidget.paragraph) { paragraph = startLineWidget.paragraph; } var width = this.documentHelper.selection.getWidth(startLineWidget, true) - (left - startLineWidget.paragraph.x); // Handled the highlighting approach as genric for normal and rtl text. if (paragraph.bidi || (startElement instanceof TextElementBox && startElement.isRightToLeft)) { var right = 0; var elementCollection = this.documentHelper.selection.getElementsForward(startLineWidget, startElement, endElement, paragraph.bidi); if (elementCollection) { var elementIsRTL = false; for (var i = 0; i < elementCollection.length; i++) { var element = elementCollection[i]; var index = element instanceof TextElementBox ? element.length : 1; right = this.documentHelper.selection.getLeftInternal(startLineWidget, element, index); elementIsRTL = false; if (element === startElement) { left = this.documentHelper.selection.getLeftInternal(startLineWidget, element, selectionStartIndex); } else { left = this.documentHelper.selection.getLeftInternal(startLineWidget, element, 0); } if (element instanceof TextElementBox) { elementIsRTL = element.isRightToLeft; } width = Math.abs(right - left); this.createHighlightBorder(startLineWidget, width, elementIsRTL ? right : left, top); } // Highlight the Paragrph mark for last line. } } else { this.createHighlightBorder(startLineWidget, width, left, top); } var lineIndex = startLineWidget.paragraph.childWidgets.indexOf(startLineWidget); //Iterates to last item of paragraph or search result end. for (var i = 0; i < paragraph.childWidgets.length; i++) { if (paragraph === startLineWidget.paragraph) { lineIndex += 1; } this.highlightSearchResultParaWidget(paragraph, lineIndex, endLineWidget, endElement, selectionEndIndex); if (paragraph === endLineWidget.paragraph) { return; } else { lineIndex = 0; paragraph = endLineWidget.paragraph; i--; } } } } }; Search.prototype.createHighlightBorder = function (lineWidget, width, left, top) { var findHighLight = this.addSearchHighlightBorder(lineWidget); var page = this.viewer.owner.selectionModule.getPage(lineWidget.paragraph); var pageTop = page.boundingRectangle.y; var pageLeft = page.boundingRectangle.x; findHighLight.left = Math.ceil(left); top = Math.ceil(top); findHighLight.width = Math.floor(width); var height = Math.floor(lineWidget.height); var context = this.documentHelper.containerContext; }; Search.prototype.addSearchHighlightBorder = function (lineWidget) { var highlighters = undefined; var collection = this.searchHighlighters; if (collection.containsKey(lineWidget)) { highlighters = collection.get(lineWidget); } else { highlighters = []; collection.add(lineWidget, highlighters); } var searchHighlight = new SearchWidgetInfo(0, 0); highlighters.push(searchHighlight); return searchHighlight; }; Search.prototype.highlightSearchResultParaWidget = function (widget, startIndex, endLine, endElement, endIndex) { var top = 0; var width = 0; var isRtlText = false; for (var j = startIndex; j < widget.childWidgets.length; j++) { var lineWidget = widget.childWidgets[j]; if (j === startIndex) { top = this.documentHelper.selection.getTop(lineWidget); } var left = this.documentHelper.selection.getLeft(lineWidget); if (endElement instanceof TextElementBox) { isRtlText = endElement.isRightToLeft; } if (lineWidget === endLine) { //Selection ends in current line. var right = 0; // Handled the highlighting using the element box highlighting approach as genric for normal and rtl text. if (isRtlText || widget.bidi) { var elementBox = this.documentHelper.selection.getElementsBackward(lineWidget, endElement, endElement, widget.bidi); for (var i = 0; i < elementBox.length; i++) { var element = elementBox[i]; var elementIsRTL = false; left = this.documentHelper.selection.getLeftInternal(lineWidget, element, 0); if (element === endElement) { right = this.documentHelper.selection.getLeftInternal(lineWidget, element, endIndex); } else { var index = element instanceof TextElementBox ? element.length : 1; right = this.documentHelper.selection.getLeftInternal(lineWidget, element, index); } if (element instanceof TextElementBox) { elementIsRTL = element.isRightToLeft; } width = Math.abs(right - left); this.createHighlightBorder(lineWidget, width, elementIsRTL ? right : left, top); } return; } else { right = this.documentHelper.selection.getLeftInternal(endLine, endElement, endIndex); width = Math.abs(right - left); this.createHighlightBorder(lineWidget, width, isRtlText ? right : left, top); return; } } else { width = this.documentHelper.selection.getWidth(lineWidget, true) - (left - widget.x); this.createHighlightBorder(lineWidget, width, left, top); top += lineWidget.height; } } }; //#endregion //#region Get find result view /** * @private * @param {HTMLElement} result - Specified the result. * @returns {void} */ Search.prototype.addSearchResultItems = function (result) { if (isNullOrUndefined(result)) { return; } if (isNullOrUndefined(this.owner.findResultsList)) { this.owner.findResultsList = []; } this.owner.findResultsList.push(result); }; /** * @private * @param {TextSearchResults} textSearchResults - Specified text search result. * @returns {void} */ Search.prototype.addFindResultView = function (textSearchResults) { for (var i = 0; i < textSearchResults.innerList.length; i++) { var result = textSearchResults.innerList[i]; this.addFindResultViewForSearch(result); } }; /** * @private * @returns {void} */ /* eslint-disable */ Search.prototype.addFindResultViewForSearch = function (result) { if (result.start != null && result.end != null && result.start.paragraph != null && result.end.paragraph != null) { var prefixText = void 0; var suffixtext = void 0; var currentText = void 0; var startIndex = 0; var inlineObj = result.start.currentWidget.getInline(result.start.offset, startIndex); var inline = inlineObj.element; startIndex = inlineObj.index; var prefix = ''; var lastIndex = 0; if (inline instanceof FieldElementBox) { var elementInfo = this.owner.selectionModule.getRenderedInline(inline, startIndex); if (elementInfo.element.nextNode instanceof TextElementBox) { inline = elementInfo.element.nextNode; startIndex = elementInfo.index; } else { inline = elementInfo.element; startIndex = elementInfo.index; } } var boxObj = this.owner.selectionModule.getElementBoxInternal(inline, startIndex); var box = boxObj.element; startIndex = boxObj.index; if (box != null) { if (box instanceof TextElementBox && startIndex > 0) { prefix = box.text.substring(0, startIndex); } var boxIndex = box.line.children.indexOf(box); lastIndex = prefix.lastIndexOf(' '); while (lastIndex < 0 && boxIndex > 0 && box.line.children[boxIndex - 1] instanceof TextElementBox) { prefix = box.line.children[boxIndex - 1].text + prefix; boxIndex--; lastIndex = prefix.lastIndexOf(' '); } } var shiftIndex = prefix.lastIndexOf('\v'); if (shiftIndex > 0) { prefix = prefix.substring(0, shiftIndex); } else { lastIndex = prefix.lastIndexOf(' '); prefixText = lastIndex < 0 ? prefix : prefix.substring(lastIndex + 1); } currentText = result.text; var endIndex = 0; var endInlineObj = result.end.currentWidget.getInline(result.end.offset, endIndex); var endInline = endInlineObj.element; endIndex = endInlineObj.index; suffixtext = ''; //Checks prefix element box is empty if (boxObj != null) { // Gets the element box using endIndex of the text and set as suffix boxObj = this.owner.selectionModule.getElementBoxInternal(endInline, endIndex); box = boxObj.element; endIndex = boxObj.index; } //Checks suffix element box is empty. if (box != null) { if (box instanceof TextElementBox && endIndex < box.length) { suffixtext = box.text.substring(endIndex); } var boxIndex = box.line.children.indexOf(box); while (boxIndex + 1 < box.line.children.length && (box.line.children[boxIndex + 1] instanceof TextElementBox) || (box.line.children[boxIndex + 1] instanceof FieldElementBox)) { if (box.line.children[boxIndex + 1] instanceof FieldElementBox) { boxIndex = boxIndex + 2; } else { suffixtext = suffixtext + box.line.children[boxIndex + 1].text; boxIndex = boxIndex + 1; } } } lastIndex = suffixtext.lastIndexOf(' '); suffixtext = suffixtext === '\v' ? suffixtext = '' : suffixtext; var headerFooterString = void 0; var containerWidget = result.start.paragraph.containerWidget; var type = containerWidget instanceof HeaderFooterWidget ? containerWidget.headerFooterType : ''; if (type.indexOf('Header') != -1) { headerFooterString = document.createElement('span'); headerFooterString.classList.add('e-de-header-footer-list'); headerFooterString.textContent = 'Header: '; } else if (type.indexOf('Footer') != -1) { headerFooterString = document.createElement('span'); headerFooterString.classList.add('e-de-header-footer-list'); headerFooterString.textContent = 'Footer: '; } var listElement = document.createElement('li'); listElement.setAttribute('tabindex', '0'); listElement.classList.add('e-de-search-result-item', 'e-de-op-search-txt'); if (headerFooterString) { listElement.appendChild(headerFooterString); } if (prefix) { var prefixNode = document.createTextNode(prefix); listElement.appendChild(prefixNode); } var resultSpan = document.createElement('span'); resultSpan.classList.add('e-de-op-search-word'); resultSpan.style.pointerEvents = 'none'; resultSpan.textContent = result.text; listElement.appendChild(resultSpan); if (suffixtext) { var suffixNode = document.createTextNode(suffixtext); listElement.appendChild(suffixNode); } this.addSearchResultItems(listElement); } }; //#endregion /** * Clears search highlight. * * @private * @returns {void} */ Search.prototype.clearSearchHighlight = function () { if (!isNullOrUndefined(this.searchHighlighters)) { this.searchHighlighters.clear(); this.searchHighlighters = undefined; } var eventArgs = { source: this.viewer.owner }; this.viewer.owner.trigger(searchResultsChangeEvent, eventArgs); }; /** * @private * @returns {void} */ Search.prototype.destroy = function () { if (this.textSearchResults) { this.textSearchResults.destroy(); } this.textSearchResults = undefined; this.owner = undefined; }; return Search; }()); export { Search };