@@ -255,6 +256,8 @@
+
+
diff --git a/apps/documenteditor/embed/index.html.deploy b/apps/documenteditor/embed/index.html.deploy
index 0bc36adf3..0bf2894db 100644
--- a/apps/documenteditor/embed/index.html.deploy
+++ b/apps/documenteditor/embed/index.html.deploy
@@ -190,6 +190,7 @@
+
of 0
diff --git a/apps/documenteditor/embed/js/ApplicationController.js b/apps/documenteditor/embed/js/ApplicationController.js
index 2ba02200b..35b006854 100644
--- a/apps/documenteditor/embed/js/ApplicationController.js
+++ b/apps/documenteditor/embed/js/ApplicationController.js
@@ -75,6 +75,8 @@ DE.ApplicationController = new(function(){
embedConfig = $.extend(embedConfig, data.config.embedded);
common.controller.modals.init(embedConfig);
+ common.controller.SearchBar.init(embedConfig);
+
// Docked toolbar
if (embedConfig.toolbarDocked === 'bottom') {
@@ -449,6 +451,10 @@ DE.ApplicationController = new(function(){
embed: '#idt-embed'
});
+ common.controller.SearchBar.attach({
+ search: '#id-search'
+ });
+
api.asc_registerCallback('asc_onStartAction', onLongActionBegin);
api.asc_registerCallback('asc_onEndAction', onLongActionEnd);
api.asc_registerCallback('asc_onMouseMoveStart', onDocMouseMoveStart);
@@ -911,6 +917,8 @@ DE.ApplicationController = new(function(){
Common.Gateway.on('opendocument', loadDocument);
Common.Gateway.on('showmessage', onExternalMessage);
Common.Gateway.appReady();
+
+ common.controller.SearchBar.setApi(api);
}
return me;
diff --git a/apps/documenteditor/embed/js/SearchBar.js b/apps/documenteditor/embed/js/SearchBar.js
new file mode 100644
index 000000000..0ac1b28e6
--- /dev/null
+++ b/apps/documenteditor/embed/js/SearchBar.js
@@ -0,0 +1,175 @@
+/*
+ *
+ * (c) Copyright Ascensio System SIA 2010-2020
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
+ * street, Riga, Latvia, EU, LV-1050.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * Pursuant to Section 7(b) of the License you must retain the original Product
+ * logo when distributing the program. Pursuant to Section 7(e) we decline to
+ * grant you any rights under trademark law for use of our trademarks.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+/**
+ * SearchBar.js
+ *
+ * Created by Julia Svinareva on 27.04.2022
+ * Copyright (c) 2022 Ascensio System SIA. All rights reserved.
+ *
+ */
+
++function () {
+ !window.common && (window.common = {});
+ !common.controller && (common.controller = {});
+
+ common.controller.SearchBar = new(function() {
+ var $searchBar,
+ $searchBtn,
+ $searchInput,
+ appConfig,
+ api,
+ _state = {
+ searchText: ''
+ },
+ _lastInputChange,
+ _searchTimer;
+
+ var setApi = function (appApi) {
+ api = appApi;
+ if (api) {
+ api.asc_registerCallback('asc_onSetSearchCurrent', onApiUpdateSearchCurrent);
+ }
+ };
+
+ var create = function () {
+ $searchBar = common.view.SearchBar.create();
+ if (appConfig.toolbarDocked === 'bottom') {
+ $searchBar.css({'right': '45px', 'bottom': '31px'});
+ } else {
+ $searchBar.css({'right': '45px', 'top': '31px'});
+ }
+
+ $searchInput = $searchBar.find('#search-bar-text');
+ $searchInput.on('input', function(e){
+ common.view.SearchBar.disableNavButtons();
+ onInputSearchChange($searchInput.val());
+ }).on('keydown', function (e) {
+ onSearchNext('keydown', $searchInput.val(), e);
+ });
+ $searchBar.find('#search-bar-back').on('click', function(e){
+ onSearchNext('back', $searchInput.val());
+ });
+ $searchBar.find('#search-bar-next').on('click', function(e){
+ onSearchNext('next', $searchInput.val());
+ });
+ $searchBar.find('#search-bar-close').on('click', function(e){
+ highlightResults(false);
+ $searchBar.hide();
+ $searchBtn.find('button').button('toggle');
+ });
+
+ common.view.SearchBar.disableNavButtons();
+ };
+
+ var attachToView = function(config) {
+ if ( !$searchBar ) {
+ create();
+ }
+
+ $searchBtn = $(config.search);
+ $searchBtn.on('click', function(e){
+ if ($searchBar.is(':visible')) {
+ highlightResults(false);
+ $searchBar.hide();
+ } else {
+ highlightResults(true);
+ var text = (api && api.asc_GetSelectedText()) || _state.searchText;
+ $searchInput.val(text);
+ (text.length > 0) && onInputSearchChange(text);
+
+ $searchBar.show();
+ $searchInput.focus();
+ $searchInput.select();
+ }
+ $searchBtn.find('button').button('toggle');
+ });
+ };
+
+ var onInputSearchChange = function (text) {
+ if (_state.searchText !== text) {
+ _state.newSearchText = text;
+ _lastInputChange = (new Date());
+ if (_searchTimer === undefined) {
+ _searchTimer = setInterval(function() {
+ if ((new Date()) - _lastInputChange < 400) return;
+
+ _state.searchText = _state.newSearchText;
+ (_state.newSearchText !== '') && onQuerySearch();
+ clearInterval(_searchTimer);
+ _searchTimer = undefined;
+ }, 10);
+ }
+ }
+ };
+
+ var onQuerySearch = function (d, w) {
+ var searchSettings = new AscCommon.CSearchSettings();
+ searchSettings.put_Text(_state.searchText);
+ searchSettings.put_MatchCase(false);
+ searchSettings.put_WholeWords(false);
+ if (!api.asc_findText(searchSettings, d != 'back')) {
+ common.view.SearchBar.disableNavButtons();
+ return false;
+ }
+ return true;
+ };
+
+ var onSearchNext = function (type, text, e) {
+ if (text && text.length > 0 && (type === 'keydown' && e.keyCode === 13 || type !== 'keydown')) {
+ _state.searchText = text;
+ if (onQuerySearch(type) && _searchTimer) {
+ clearInterval(_searchTimer);
+ _searchTimer = undefined;
+ }
+ }
+ };
+
+ var onApiUpdateSearchCurrent = function (current, all) {
+ common.view.SearchBar.disableNavButtons(current, all);
+ };
+
+ var highlightResults = function (val) {
+ if (_state.isHighlightedResults !== val) {
+ api.asc_selectSearchingResults(val);
+ _state.isHighlightedResults = val;
+ }
+ };
+
+ return {
+ init: function(config) { appConfig = config; },
+ attach: attachToView,
+ setApi: setApi
+ };
+ });
+}();
\ No newline at end of file
diff --git a/apps/documenteditor/embed/locale/en.json b/apps/documenteditor/embed/locale/en.json
index 81b268346..c84f6fd9f 100644
--- a/apps/documenteditor/embed/locale/en.json
+++ b/apps/documenteditor/embed/locale/en.json
@@ -4,6 +4,7 @@
"common.view.modals.txtHeight": "Height",
"common.view.modals.txtShare": "Share Link",
"common.view.modals.txtWidth": "Width",
+ "common.view.SearchBar.textFind": "Find",
"DE.ApplicationController.convertationErrorText": "Conversion failed.",
"DE.ApplicationController.convertationTimeoutText": "Conversion timeout exceeded.",
"DE.ApplicationController.criticalErrorTitle": "Error",
diff --git a/apps/documenteditor/main/app.js b/apps/documenteditor/main/app.js
index 9570797fc..e6a4ded43 100644
--- a/apps/documenteditor/main/app.js
+++ b/apps/documenteditor/main/app.js
@@ -158,6 +158,7 @@ require([
'LeftMenu',
'Main',
'ViewTab',
+ 'Search',
'Common.Controllers.Fonts',
'Common.Controllers.History'
/** coauthoring begin **/
@@ -191,6 +192,7 @@ require([
'documenteditor/main/app/controller/LeftMenu',
'documenteditor/main/app/controller/Main',
'documenteditor/main/app/controller/ViewTab',
+ 'documenteditor/main/app/controller/Search',
'documenteditor/main/app/view/FileMenuPanels',
'documenteditor/main/app/view/ParagraphSettings',
'documenteditor/main/app/view/HeaderFooterSettings',
diff --git a/apps/documenteditor/main/app/controller/LeftMenu.js b/apps/documenteditor/main/app/controller/LeftMenu.js
index 4f8f3eb0d..6a8b5fef6 100644
--- a/apps/documenteditor/main/app/controller/LeftMenu.js
+++ b/apps/documenteditor/main/app/controller/LeftMenu.js
@@ -96,19 +96,14 @@ define([
'file:close': this.clickToolbarTab.bind(this, 'other'),
'save:disabled': this.changeToolbarSaveState.bind(this)
},
- 'SearchDialog': {
- 'hide': _.bind(this.onSearchDlgHide, this),
- 'search:back': _.bind(this.onQuerySearch, this, 'back'),
- 'search:next': _.bind(this.onQuerySearch, this, 'next'),
- 'search:replace': _.bind(this.onQueryReplace, this),
- 'search:replaceall': _.bind(this.onQueryReplaceAll, this),
- 'search:highlight': _.bind(this.onSearchHighlight, this)
- },
'Common.Views.ReviewChanges': {
'collaboration:chat': _.bind(this.onShowHideChat, this)
},
'ViewTab': {
'viewtab:navigation': _.bind(this.onShowHideNavigation, this)
+ },
+ 'SearchBar': {
+ 'search:show': _.bind(this.onShowHideSearch, this)
}
});
@@ -122,7 +117,7 @@ define([
onLaunch: function() {
this.leftMenu = this.createView('LeftMenu').render();
- this.leftMenu.btnSearch.on('toggle', _.bind(this.onMenuSearch, this));
+ this.leftMenu.btnSearchBar.on('toggle', _.bind(this.onMenuSearchBar, this));
Common.util.Shortcuts.delegateShortcuts({
shortcuts: {
@@ -144,7 +139,6 @@ define([
setApi: function(api) {
this.api = api;
- this.api.asc_registerCallback('asc_onReplaceAll', _.bind(this.onApiTextReplaced, this));
this.api.asc_registerCallback('asc_onCoAuthoringDisconnect', _.bind(this.onApiServerDisconnect, this));
Common.NotificationCenter.on('api:disconnect', _.bind(this.onApiServerDisconnect, this));
this.api.asc_registerCallback('asc_onDownloadUrl', _.bind(this.onDownloadUrl, this));
@@ -171,6 +165,8 @@ define([
if (this.mode.canUseHistory)
this.getApplication().getController('Common.Controllers.History').setApi(this.api).setMode(this.mode);
this.getApplication().getController('PageThumbnails').setApi(this.api).setMode(this.mode);
+ this.getApplication().getController('Search').setApi(this.api).setMode(this.mode);
+ this.leftMenu.setOptionsPanel('advancedsearch', this.getApplication().getController('Search').getView('Common.Views.SearchPanel'));
return this;
},
@@ -570,112 +566,6 @@ define([
},
/** coauthoring end **/
- onQuerySearch: function(d, w, opts) {
- if (opts.textsearch && opts.textsearch.length) {
- var me = this;
- this.api.asc_findText(opts.textsearch, d != 'back', opts.matchcase, function(resultCount) {
- !resultCount && Common.UI.info({
- msg: me.textNoTextFound,
- callback: function() {
- me.dlgSearch.focus();
- }
- });
- });
- }
- },
-
- onQueryReplace: function(w, opts) {
- if (!_.isEmpty(opts.textsearch)) {
- var me = this;
- var str = this.api.asc_GetErrorForReplaceString(opts.textreplace);
- if (str) {
- Common.UI.warning({
- title: this.notcriticalErrorTitle,
- msg: Common.Utils.String.format(this.warnReplaceString, str),
- buttons: ['ok'],
- callback: function(btn){
- me.dlgSearch.focus('replace');
- }
- });
- return;
- }
-
- if (!this.api.asc_replaceText(opts.textsearch, opts.textreplace, false, opts.matchcase, opts.matchword)) {
- Common.UI.info({
- msg: this.textNoTextFound,
- callback: function() {
- me.dlgSearch.focus();
- }
- });
- }
- }
- },
-
- onQueryReplaceAll: function(w, opts) {
- if (!_.isEmpty(opts.textsearch)) {
- var me = this;
- var str = this.api.asc_GetErrorForReplaceString(opts.textreplace);
- if (str) {
- Common.UI.warning({
- title: this.notcriticalErrorTitle,
- msg: Common.Utils.String.format(this.warnReplaceString, str),
- buttons: ['ok'],
- callback: function(btn){
- me.dlgSearch.focus('replace');
- }
- });
- return;
- }
- this.api.asc_replaceText(opts.textsearch, opts.textreplace, true, opts.matchcase, opts.matchword);
- }
- },
-
- onSearchHighlight: function(w, highlight) {
- this.api.asc_selectSearchingResults(highlight);
- },
-
- showSearchDlg: function(show,action) {
- if ( !this.dlgSearch ) {
- this.dlgSearch = (new Common.UI.SearchDialog({
- matchcase: true,
- markresult: {applied: true}
- }));
- }
-
- if (show) {
- var mode = this.mode.isEdit && !this.viewmode ? (action || undefined) : 'no-replace';
- if (this.dlgSearch.isVisible()) {
- this.dlgSearch.setMode(mode);
- this.dlgSearch.setSearchText(this.api.asc_GetSelectedText());
- this.dlgSearch.focus();
- } else {
- this.dlgSearch.show(mode, this.api.asc_GetSelectedText());
- }
- } else this.dlgSearch['hide']();
- },
-
- onMenuSearch: function(obj, show) {
- this.showSearchDlg(show);
- },
-
- onSearchDlgHide: function() {
- this.leftMenu.btnSearch.toggle(false, true);
- this.api.asc_selectSearchingResults(false);
- $(this.leftMenu.btnSearch.el).blur();
- this.api.asc_enableKeyEvents(true);
- },
-
- onApiTextReplaced: function(found,replaced) {
- var me = this;
- if (found) {
- !(found - replaced > 0) ?
- Common.UI.info( {msg: Common.Utils.String.format(this.textReplaceSuccess, replaced)} ) :
- Common.UI.warning( {msg: Common.Utils.String.format(this.textReplaceSkipped, found-replaced)} );
- } else {
- Common.UI.info({msg: this.textNoTextFound});
- }
- },
-
onApiServerDisconnect: function(enableDownload) {
this.mode.isEdit = false;
this.leftMenu.close();
@@ -688,17 +578,13 @@ define([
this.leftMenu.btnNavigation.setDisabled(true);
this.leftMenu.getMenu('file').setMode({isDisconnected: true, enableDownload: !!enableDownload});
- if ( this.dlgSearch ) {
- this.leftMenu.btnSearch.toggle(false, true);
- this.dlgSearch['hide']();
- }
},
setPreviewMode: function(mode) {
if (this.viewmode === mode) return;
this.viewmode = mode;
- this.dlgSearch && this.dlgSearch.setMode(this.viewmode ? 'no-replace' : 'search');
+ this.leftMenu.panelSearch && this.leftMenu.panelSearch.setSearchMode(this.viewmode ? 'no-replace' : 'search');
},
SetDisabled: function(disable, options) {
@@ -792,12 +678,6 @@ define([
},
menuFilesShowHide: function(state) {
- if ( this.dlgSearch ) {
- if ( state == 'show' )
- this.dlgSearch.suspendKeyEvents();
- else
- Common.Utils.asyncCall(this.dlgSearch.resumeKeyEvents, this.dlgSearch);
- }
if (this.api && state == 'hide')
this.api.asc_enableKeyEvents(true);
},
@@ -815,6 +695,9 @@ define([
this.leftMenu.btnThumbnails.toggle(false);
this.leftMenu.panelThumbnails.hide();
this.leftMenu.onBtnMenuClick(this.leftMenu.btnThumbnails);
+ } else if (this.leftMenu.btnSearchBar.isActive()) {
+ this.leftMenu.btnSearchBar.toggle(false);
+ this.leftMenu.onBtnMenuClick(this.leftMenu.btnSearchBar);
}
else if (this.leftMenu.btnNavigation.isActive()) {
this.leftMenu.btnNavigation.toggle(false);
@@ -831,10 +714,31 @@ define([
switch (s) {
case 'replace':
case 'search':
- Common.UI.Menu.Manager.hideAll();
- this.showSearchDlg(true,s);
- this.leftMenu.btnSearch.toggle(true,true);
this.leftMenu.btnAbout.toggle(false);
+ var selectedText = this.api.asc_GetSelectedText();
+ if (this.isSearchPanelVisible()) {
+ selectedText && this.leftMenu.panelSearch.setFindText(selectedText);
+ this.leftMenu.panelSearch.focus(selectedText !== '' ? s : 'search');
+ this.leftMenu.fireEvent('search:aftershow', this.leftMenu, selectedText);
+ return false;
+ } else if (this.getApplication().getController('Viewport').isSearchBarVisible()) {
+ var viewport = this.getApplication().getController('Viewport');
+ if (s === 'replace') {
+ viewport.header.btnSearch.toggle(false);
+ this.onShowHideSearch(true, viewport.searchBar.inputSearch.val());
+ } else {
+ selectedText && viewport.searchBar.setText(selectedText);
+ viewport.searchBar.focus();
+ return false;
+ }
+ } else if (s === 'search') {
+ Common.NotificationCenter.trigger('search:show');
+ return false;
+ } else {
+ this.onShowHideSearch(true, selectedText);
+ }
+ this.leftMenu.btnSearchBar.toggle(true,true);
+ this.leftMenu.panelSearch.focus(selectedText !== '' ? s : 'search');
// this.leftMenu.menuFile.hide();
return false;
case 'save':
@@ -861,6 +765,10 @@ define([
return false;
case 'escape':
// if (!this.leftMenu.isOpened()) return true;
+ var btnSearch = this.getApplication().getController('Viewport').header.btnSearch;
+ btnSearch.pressed && btnSearch.toggle(false);
+ this.leftMenu._state.isSearchOpen && (this.leftMenu._state.isSearchOpen = false);
+
if ( this.leftMenu.menuFile.isVisible() ) {
if (Common.UI.HintManager.needCloseFileMenu())
this.leftMenu.menuFile.hide();
@@ -954,6 +862,29 @@ define([
}
},
+ onShowHideSearch: function (state, findText) {
+ if (state) {
+ Common.UI.Menu.Manager.hideAll();
+ this.leftMenu.showMenu('advancedsearch', undefined, true);
+ this.leftMenu.fireEvent('search:aftershow', this.leftMenu, findText);
+ } else {
+ this.leftMenu.btnSearchBar.toggle(false, true);
+ this.leftMenu.onBtnMenuClick(this.leftMenu.btnSearchBar);
+ }
+ },
+
+ onMenuSearchBar: function(obj, show) {
+ if (show) {
+ var mode = this.mode.isEdit && !this.viewmode ? undefined : 'no-replace';
+ this.leftMenu.panelSearch.setSearchMode(mode);
+ }
+ this.leftMenu._state.isSearchOpen = show;
+ },
+
+ isSearchPanelVisible: function () {
+ return this.leftMenu._state.isSearchOpen;
+ },
+
isCommentsVisible: function() {
return this.leftMenu && this.leftMenu.panelComments && this.leftMenu.panelComments.isVisible();
},
diff --git a/apps/documenteditor/main/app/controller/Main.js b/apps/documenteditor/main/app/controller/Main.js
index b266becf9..6515c7024 100644
--- a/apps/documenteditor/main/app/controller/Main.js
+++ b/apps/documenteditor/main/app/controller/Main.js
@@ -1257,6 +1257,7 @@ define([
pluginsController = application.getController('Common.Controllers.Plugins'),
navigationController = application.getController('Navigation');
+
leftmenuController.getView('LeftMenu').getMenu('file').loadDocument({doc:me.document});
leftmenuController.setMode(me.appOptions).createDelayedElements().setApi(me.api);
diff --git a/apps/documenteditor/main/app/controller/Search.js b/apps/documenteditor/main/app/controller/Search.js
new file mode 100644
index 000000000..bee8c9b64
--- /dev/null
+++ b/apps/documenteditor/main/app/controller/Search.js
@@ -0,0 +1,427 @@
+/*
+ *
+ * (c) Copyright Ascensio System SIA 2010-2020
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
+ * street, Riga, Latvia, EU, LV-1050.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * Pursuant to Section 7(b) of the License you must retain the original Product
+ * logo when distributing the program. Pursuant to Section 7(e) we decline to
+ * grant you any rights under trademark law for use of our trademarks.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+/**
+ * ViewTab.js
+ *
+ * Created by Julia Svinareva on 09.02.2022
+ * Copyright (c) 2022 Ascensio System SIA. All rights reserved.
+ *
+ */
+
+define([
+ 'core',
+ 'common/main/lib/view/SearchPanel'
+], function () {
+ 'use strict';
+
+ DE.Controllers.Search = Backbone.Controller.extend(_.extend({
+ sdkViewName : '#id_main',
+
+ views: [
+ 'Common.Views.SearchPanel'
+ ],
+
+ initialize: function () {
+ this.addListeners({
+ 'SearchBar': {
+ 'search:back': _.bind(this.onSearchNext, this, 'back'),
+ 'search:next': _.bind(this.onSearchNext, this, 'next'),
+ 'search:input': _.bind(this.onInputSearchChange, this),
+ 'search:keydown': _.bind(this.onSearchNext, this, 'keydown'),
+ 'show': _.bind(this.onSelectSearchingResults, this, true),
+ 'hide': _.bind(this.onSelectSearchingResults, this, false)
+ },
+ 'Common.Views.SearchPanel': {
+ 'search:back': _.bind(this.onSearchNext, this, 'back'),
+ 'search:next': _.bind(this.onSearchNext, this, 'next'),
+ 'search:replace': _.bind(this.onQueryReplace, this),
+ 'search:replaceall': _.bind(this.onQueryReplaceAll, this),
+ 'search:input': _.bind(this.onInputSearchChange, this),
+ 'search:options': _.bind(this.onChangeSearchOption, this),
+ 'search:keydown': _.bind(this.onSearchNext, this, 'keydown'),
+ 'show': _.bind(this.onShowPanel, this),
+ 'hide': _.bind(this.onHidePanel, this),
+ },
+ 'LeftMenu': {
+ 'search:aftershow': _.bind(this.onShowAfterSearch, this)
+ }
+ });
+ },
+ onLaunch: function () {
+ this._state = {
+ searchText: '',
+ matchCase: false,
+ matchWord: false,
+ useRegExp: false,
+ isHighlightedResults: false
+ };
+ },
+
+ setMode: function (mode) {
+ this.view = this.createView('Common.Views.SearchPanel', { mode: mode });
+ },
+
+ setApi: function (api) {
+ if (api) {
+ this.api = api;
+ this.api.asc_registerCallback('asc_onSetSearchCurrent', _.bind(this.onUpdateSearchCurrent, this));
+ this.api.asc_registerCallback('asc_onStartTextAroundSearch', _.bind(this.onStartTextAroundSearch, this));
+ this.api.asc_registerCallback('asc_onEndTextAroundSearch', _.bind(this.onEndTextAroundSearch, this));
+ this.api.asc_registerCallback('asc_onGetTextAroundSearchPack', _.bind(this.onApiGetTextAroundSearch, this));
+ this.api.asc_registerCallback('asc_onRemoveTextAroundSearch', _.bind(this.onApiRemoveTextAroundSearch, this));
+ }
+ return this;
+ },
+
+ getView: function(name) {
+ return !name && this.view ?
+ this.view : Backbone.Controller.prototype.getView.call(this, name);
+ },
+
+ onChangeSearchOption: function (option, checked) {
+ switch (option) {
+ case 'case-sensitive':
+ this._state.matchCase = checked;
+ break;
+ case 'match-word':
+ this._state.matchWord = checked;
+ break;
+ case 'regexp':
+ this._state.useRegExp = checked;
+ break;
+ }
+ if (this._state.searchText !== '') {
+ this.hideResults();
+ if (this.onQuerySearch()) {
+ if (this.searchTimer) {
+ clearInterval(this.searchTimer);
+ this.searchTimer = undefined;
+ }
+ this.api.asc_StartTextAroundSearch();
+ }
+ }
+ },
+
+ onSearchNext: function (type, text, e) {
+ var isReturnKey = type === 'keydown' && e.keyCode === Common.UI.Keys.RETURN;
+ if (text && text.length > 0 && (isReturnKey || type !== 'keydown')) {
+ this._state.searchText = text;
+ if (this.onQuerySearch(type) && (this.searchTimer || isReturnKey)) {
+ this.hideResults();
+ if (this.searchTimer) {
+ clearInterval(this.searchTimer);
+ this.searchTimer = undefined;
+ }
+ if (this.view.$el.is(':visible')) {
+ this.api.asc_StartTextAroundSearch();
+ }
+ }
+ }
+ },
+
+ onInputSearchChange: function (text) {
+ var me = this;
+ if (this._state.searchText !== text) {
+ this._state.newSearchText = text;
+ this._lastInputChange = (new Date());
+ if (this.searchTimer === undefined) {
+ this.searchTimer = setInterval(function(){
+ if ((new Date()) - me._lastInputChange < 400) return;
+
+ me.hideResults();
+ me._state.searchText = me._state.newSearchText;
+ if (me._state.newSearchText !== '' && me.onQuerySearch()) {
+ if (me.view.$el.is(':visible')) {
+ me.api.asc_StartTextAroundSearch();
+ }
+ me.view.disableReplaceButtons(false);
+ } else if (me._state.newSearchText === '') {
+ me.view.updateResultsNumber('no-results');
+ me.view.disableReplaceButtons(true);
+ }
+ clearInterval(me.searchTimer);
+ me.searchTimer = undefined;
+ }, 10);
+ }
+ }
+ },
+
+ onQuerySearch: function (d, w) {
+ var searchSettings = new AscCommon.CSearchSettings();
+ searchSettings.put_Text(this._state.searchText);
+ searchSettings.put_MatchCase(this._state.matchCase);
+ searchSettings.put_WholeWords(this._state.matchWord);
+ if (!this.api.asc_findText(searchSettings, d != 'back')) {
+ this.resultItems = [];
+ this.view.updateResultsNumber(undefined, 0);
+ this.view.disableReplaceButtons(true);
+ this._state.currentResult = 0;
+ this._state.resultsNumber = 0;
+ this.view.disableNavButtons();
+ return false;
+ }
+ return true;
+ },
+
+ onQueryReplace: function(textSearch, textReplace) {
+ if (textSearch !== '') {
+ var me = this;
+ var str = this.api.asc_GetErrorForReplaceString(textReplace);
+ if (str) {
+ Common.UI.warning({
+ title: this.notcriticalErrorTitle,
+ msg: Common.Utils.String.format(this.warnReplaceString, str),
+ buttons: ['ok'],
+ callback: function(){
+ me.view.focus('replace');
+ }
+ });
+ return;
+ }
+
+ var searchSettings = new AscCommon.CSearchSettings();
+ searchSettings.put_Text(textSearch);
+ searchSettings.put_MatchCase(this._state.matchCase);
+ searchSettings.put_WholeWords(this._state.matchWord);
+ if (!this.api.asc_replaceText(searchSettings, textReplace, false)) {
+ this.allResultsWasRemoved();
+ }
+ }
+ },
+
+ onQueryReplaceAll: function(textSearch, textReplace) {
+ if (textSearch !== '') {
+ var me = this;
+ var str = this.api.asc_GetErrorForReplaceString(textReplace);
+ if (str) {
+ Common.UI.warning({
+ title: this.notcriticalErrorTitle,
+ msg: Common.Utils.String.format(this.warnReplaceString, str),
+ buttons: ['ok'],
+ callback: function(){
+ me.view.focus('replace');
+ }
+ });
+ return;
+ }
+
+ var searchSettings = new AscCommon.CSearchSettings();
+ searchSettings.put_Text(textSearch);
+ searchSettings.put_MatchCase(this._state.matchCase);
+ searchSettings.put_WholeWords(this._state.matchWord);
+ this.api.asc_replaceText(searchSettings, textReplace, true);
+
+ this.allResultsWasRemoved();
+ }
+ },
+
+ allResultsWasRemoved: function () {
+ this.resultItems = [];
+ this.hideResults();
+ this.view.updateResultsNumber(undefined, 0);
+ this.view.disableReplaceButtons(true);
+ this._state.currentResult = 0;
+ this._state.resultsNumber = 0;
+ this.view.disableNavButtons();
+ },
+
+ onUpdateSearchCurrent: function (current, all) {
+ if (current === -1) return;
+ this._state.currentResult = current;
+ this._state.resultsNumber = all;
+ if (this.view) {
+ this.view.updateResultsNumber(current, all);
+ this.view.disableNavButtons(current, all);
+ if (this.resultItems && this.resultItems.length > 0) {
+ this.resultItems.forEach(function (item) {
+ item.selected = false;
+ });
+ if (this.resultItems[current]) {
+ this.resultItems[current].selected = true;
+ $('#search-results').find('.item').removeClass('selected');
+ $(this.resultItems[current].el).addClass('selected');
+ this.scrollToSelectedResult(current);
+ }
+ }
+ }
+ Common.NotificationCenter.trigger('search:updateresults', current, all);
+ },
+
+ scrollToSelectedResult: function (ind) {
+ var index = ind !== undefined ? ind : _.findIndex(this.resultItems, {selected: true});
+ if (index !== -1) {
+ var item = this.resultItems[index].$el,
+ itemHeight = item.outerHeight(),
+ itemTop = item.position().top,
+ container = this.view.$resultsContainer,
+ containerHeight = container.outerHeight(),
+ containerTop = container.scrollTop();
+ if (itemTop < 0 || (containerTop === 0 && itemTop > containerHeight)) {
+ container.scroller.scrollTop(containerTop + itemTop - 12);
+ } else if (itemTop + itemHeight > containerHeight) {
+ container.scroller.scrollTop(containerTop + itemHeight);
+ }
+ }
+ },
+
+ onStartTextAroundSearch: function () {
+ if (this.view) {
+ this._state.isStartedAddingResults = true;
+ }
+ },
+
+ onEndTextAroundSearch: function () {
+ if (this.view) {
+ this._state.isStartedAddingResults = false;
+ this.view.$resultsContainer.scroller.update({alwaysVisibleY: true});
+ }
+ },
+
+ onApiGetTextAroundSearch: function (data) {
+ if (this.view && this._state.isStartedAddingResults) {
+ if (data.length > 300 || !data.length) return;
+ var me = this;
+ me.resultItems = [];
+ data.forEach(function (item, ind) {
+ var el = document.createElement("div"),
+ isSelected = ind === me._state.currentResult;
+ el.className = 'item';
+ el.innerHTML = item[1].trim();
+ me.view.$resultsContainer.append(el);
+ if (isSelected) {
+ $(el).addClass('selected');
+ }
+
+ var resultItem = {id: item[0], $el: $(el), el: el, selected: isSelected};
+ me.resultItems.push(resultItem);
+ $(el).on('click', _.bind(function (el) {
+ var id = item[0];
+ me.api.asc_SelectSearchElement(id);
+ }, me));
+ });
+
+ this.view.$resultsContainer.show();
+ }
+ },
+
+ onApiRemoveTextAroundSearch: function (arr) {
+ var me = this;
+ arr.forEach(function (id) {
+ var ind = _.findIndex(me.resultItems, {id: id});
+ if (ind !== -1) {
+ me.resultItems[ind].$el.remove();
+ me.resultItems.splice(ind, 1);
+ }
+ });
+ },
+
+ hideResults: function () {
+ if (this.view) {
+ this.view.$resultsContainer.hide();
+ this.view.$resultsContainer.empty();
+ }
+ },
+
+ onShowAfterSearch: function (findText) {
+ var viewport = this.getApplication().getController('Viewport');
+ if (viewport.isSearchBarVisible()) {
+ viewport.searchBar.hide();
+ }
+
+ var text = typeof findText === 'string' ? findText : (this.api.asc_GetSelectedText() || this._state.searchText);
+ if (this.resultItems && this.resultItems.length > 0 &&
+ (!this._state.matchCase && text.toLowerCase() === this.view.inputText.getValue().toLowerCase() ||
+ this._state.matchCase && text === this.view.inputText.getValue())) { // show old results
+ return;
+ }
+ if (text) {
+ this.view.setFindText(text);
+ } else if (text !== undefined) { // panel was opened from empty searchbar, clear to start new search
+ this.view.setFindText('');
+ this._state.searchText = undefined;
+ }
+
+ this.hideResults();
+ if (text !== '' && text === this._state.searchText) { // search was made
+ this.view.disableReplaceButtons(false);
+ this.api.asc_StartTextAroundSearch();
+ } else if (text !== '') { // search wasn't made
+ this.onInputSearchChange(text);
+ } else {
+ this.resultItems = [];
+ this.view.disableReplaceButtons(true);
+ this.view.clearResultsNumber();
+ }
+ this.view.disableNavButtons(this._state.currentResult, this._state.resultsNumber);
+ },
+
+ onShowPanel: function () {
+ this.onSelectSearchingResults(true);
+ if (this.resultItems && this.resultItems.length > 0 && !this._state.isStartedAddingResults) {
+ var me = this;
+ this.view.$resultsContainer.show();
+ this.resultItems.forEach(function (item) {
+ me.view.$resultsContainer.append(item.el);
+ if (item.selected) {
+ $(item.el).addClass('selected');
+ }
+ $(item.el).on('click', function (el) {
+ me.api.asc_SelectSearchElement(item.id);
+ $('#search-results').find('.item').removeClass('selected');
+ $(el.currentTarget).addClass('selected');
+ });
+ });
+ this.scrollToSelectedResult();
+ }
+ },
+
+ onHidePanel: function () {
+ this.hideResults();
+ this.onSelectSearchingResults(false);
+ },
+
+ onSelectSearchingResults: function (val) {
+ if (!val && this.getApplication().getController('LeftMenu').isSearchPanelVisible()) return;
+
+ if (this._state.isHighlightedResults !== val) {
+ this.api.asc_selectSearchingResults(val);
+ this._state.isHighlightedResults = val;
+ }
+ },
+
+ notcriticalErrorTitle: 'Warning',
+ warnReplaceString: '{0} is not a valid special character for the Replace With box.'
+
+ }, DE.Controllers.Search || {}));
+});
\ No newline at end of file
diff --git a/apps/documenteditor/main/app/controller/Viewport.js b/apps/documenteditor/main/app/controller/Viewport.js
index c1fae7b2b..729e9ae2a 100644
--- a/apps/documenteditor/main/app/controller/Viewport.js
+++ b/apps/documenteditor/main/app/controller/Viewport.js
@@ -44,6 +44,7 @@
define([
'core',
'common/main/lib/view/Header',
+ 'common/main/lib/view/SearchBar',
'documenteditor/main/app/view/Viewport',
'documenteditor/main/app/view/LeftMenu'
], function (Viewport) {
@@ -148,6 +149,7 @@ define([
Common.NotificationCenter.on('app:face', this.onAppShowed.bind(this));
Common.NotificationCenter.on('app:ready', this.onAppReady.bind(this));
+ Common.NotificationCenter.on('search:show', _.bind(this.onSearchShow, this));
},
onAppShowed: function (config) {
@@ -267,6 +269,34 @@ define([
this.header && this.header.lockHeaderBtns( 'rename-user', disable);
},
+ onSearchShow: function () {
+ this.header.btnSearch && this.header.btnSearch.toggle(true);
+ },
+
+ onSearchToggle: function () {
+ var leftMenu = this.getApplication().getController('LeftMenu');
+ if (leftMenu.isSearchPanelVisible()) {
+ this.header.btnSearch.toggle(false, true);
+ leftMenu.getView('LeftMenu').panelSearch.focus();
+ return;
+ }
+ if (!this.searchBar) {
+ this.searchBar = new Common.UI.SearchBar({});
+ this.searchBar.on('hide', _.bind(function () {
+ this.header.btnSearch.toggle(false, true);
+ }, this));
+ }
+ if (this.header.btnSearch.pressed) {
+ this.searchBar.show(this.api.asc_GetSelectedText());
+ } else {
+ this.searchBar.hide();
+ }
+ },
+
+ isSearchBarVisible: function () {
+ return this.searchBar && this.searchBar.isVisible();
+ },
+
textFitPage: 'Fit to Page',
textFitWidth: 'Fit to Width',
txtDarkMode: 'Dark mode'
diff --git a/apps/documenteditor/main/app/template/LeftMenu.template b/apps/documenteditor/main/app/template/LeftMenu.template
index ea622cef6..1867ae481 100644
--- a/apps/documenteditor/main/app/template/LeftMenu.template
+++ b/apps/documenteditor/main/app/template/LeftMenu.template
@@ -1,6 +1,6 @@