jenkins-bot has submitted this change and it was merged.
Change subject: Find and replace
......................................................................
Find and replace
Bug: T50368
Change-Id: I8dc406ffe8455dedf35eef02a1909de2d79adeb0
---
M .docs/eg-iframe.html
M build/modules.json
M demos/ve/desktop.html
M demos/ve/mobile.html
M i18n/en.json
M i18n/qqq.json
M src/ce/ve.ce.Surface.js
M src/dm/ve.dm.Document.js
M src/init/ve.init.Target.js
M src/ui/actions/ve.ui.WindowAction.js
M src/ui/dialogs/ve.ui.CommandHelpDialog.js
A src/ui/dialogs/ve.ui.FindAndReplaceDialog.js
A src/ui/dialogs/ve.ui.ToolbarDialog.js
M src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css
A src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css
A src/ui/styles/dialogs/ve.ui.ToolbarDialog.css
M src/ui/ve.ui.CommandRegistry.js
M src/ui/ve.ui.Surface.js
M src/ui/ve.ui.Toolbar.js
M src/ui/ve.ui.TriggerRegistry.js
A src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js
M tests/index.html
22 files changed, 648 insertions(+), 8 deletions(-)
Approvals:
Jforrester: Looks good to me, approved
jenkins-bot: Verified
diff --git a/.docs/eg-iframe.html b/.docs/eg-iframe.html
index c6170a1..44a82ef 100644
--- a/.docs/eg-iframe.html
+++ b/.docs/eg-iframe.html
@@ -24,7 +24,9 @@
<link rel=stylesheet
href="../src/ce/styles/nodes/ve.ce.TableNode.css">
<link rel=stylesheet href="../src/ce/styles/ve.ce.css">
<link rel=stylesheet href="../src/ce/styles/ve.ce.Surface.css">
+ <link rel=stylesheet
href="../src/ui/styles/dialogs/ve.ui.ToolbarDialog.css">
<link rel=stylesheet
href="../src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css">
+ <link rel=stylesheet
href="../src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css">
<link rel=stylesheet
href="../src/ui/styles/dialogs/ve.ui.ProgressDialog.css">
<link rel=stylesheet
href="../src/ui/styles/tools/ve.ui.FormatTool.css">
<link rel=stylesheet
href="../src/ui/styles/widgets/ve.ui.LanguageInputWidget.css">
@@ -309,13 +311,16 @@
<script
src="../src/ui/commands/ve.ui.IndentationCommand.js"></script>
<script
src="../src/ui/commands/ve.ui.MergeCellsCommand.js"></script>
<script
src="../src/ui/commands/ve.ui.TableCaptionCommand.js"></script>
- <script
src="../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
<script
src="../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
<script src="../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
+ <script src="../src/ui/dialogs/ve.ui.ToolbarDialog.js"></script>
+ <script
src="../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
+ <script
src="../src/ui/dialogs/ve.ui.FindAndReplaceDialog.js"></script>
<script
src="../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
<script
src="../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
<script
src="../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
<script
src="../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
+ <script
src="../src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js"></script>
<script
src="../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
<script
src="../src/ui/widgets/ve.ui.LanguageResultWidget.js"></script>
<script
src="../src/ui/dialogs/ve.ui.LanguageSearchDialog.js"></script>
diff --git a/build/modules.json b/build/modules.json
index 2edf6ca..feb75bb 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -341,13 +341,16 @@
"src/ui/commands/ve.ui.IndentationCommand.js",
"src/ui/commands/ve.ui.MergeCellsCommand.js",
"src/ui/commands/ve.ui.TableCaptionCommand.js",
- "src/ui/dialogs/ve.ui.CommandHelpDialog.js",
"src/ui/dialogs/ve.ui.FragmentDialog.js",
"src/ui/dialogs/ve.ui.NodeDialog.js",
+ "src/ui/dialogs/ve.ui.ToolbarDialog.js",
+ "src/ui/dialogs/ve.ui.CommandHelpDialog.js",
+ "src/ui/dialogs/ve.ui.FindAndReplaceDialog.js",
"src/ui/dialogs/ve.ui.ProgressDialog.js",
"src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js",
"src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js",
"src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js",
+
"src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js",
"src/ui/widgets/ve.ui.LanguageSearchWidget.js",
"src/ui/widgets/ve.ui.LanguageResultWidget.js",
"src/ui/dialogs/ve.ui.LanguageSearchDialog.js",
@@ -390,7 +393,9 @@
"src/ce/styles/nodes/ve.ce.TableNode.css",
"src/ce/styles/ve.ce.css",
"src/ce/styles/ve.ce.Surface.css",
+ "src/ui/styles/dialogs/ve.ui.ToolbarDialog.css",
"src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css",
+ "src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css",
"src/ui/styles/dialogs/ve.ui.ProgressDialog.css",
"src/ui/styles/tools/ve.ui.FormatTool.css",
"src/ui/styles/widgets/ve.ui.LanguageInputWidget.css",
diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html
index 31e8a72..774ea58 100644
--- a/demos/ve/desktop.html
+++ b/demos/ve/desktop.html
@@ -33,7 +33,9 @@
<link rel=stylesheet
href="../../src/ce/styles/nodes/ve.ce.TableNode.css">
<link rel=stylesheet href="../../src/ce/styles/ve.ce.css">
<link rel=stylesheet
href="../../src/ce/styles/ve.ce.Surface.css">
+ <link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.ToolbarDialog.css">
<link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css">
+ <link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css">
<link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.ProgressDialog.css">
<link rel=stylesheet
href="../../src/ui/styles/tools/ve.ui.FormatTool.css">
<link rel=stylesheet
href="../../src/ui/styles/widgets/ve.ui.LanguageInputWidget.css">
@@ -322,13 +324,16 @@
<script
src="../../src/ui/commands/ve.ui.IndentationCommand.js"></script>
<script
src="../../src/ui/commands/ve.ui.MergeCellsCommand.js"></script>
<script
src="../../src/ui/commands/ve.ui.TableCaptionCommand.js"></script>
- <script
src="../../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
<script
src="../../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
<script src="../../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
+ <script
src="../../src/ui/dialogs/ve.ui.ToolbarDialog.js"></script>
+ <script
src="../../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
+ <script
src="../../src/ui/dialogs/ve.ui.FindAndReplaceDialog.js"></script>
<script
src="../../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
<script
src="../../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
<script
src="../../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
<script
src="../../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
+ <script
src="../../src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js"></script>
<script
src="../../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
<script
src="../../src/ui/widgets/ve.ui.LanguageResultWidget.js"></script>
<script
src="../../src/ui/dialogs/ve.ui.LanguageSearchDialog.js"></script>
diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html
index 501b96b..d5bd99d 100644
--- a/demos/ve/mobile.html
+++ b/demos/ve/mobile.html
@@ -33,7 +33,9 @@
<link rel=stylesheet
href="../../src/ce/styles/nodes/ve.ce.TableNode.css">
<link rel=stylesheet href="../../src/ce/styles/ve.ce.css">
<link rel=stylesheet
href="../../src/ce/styles/ve.ce.Surface.css">
+ <link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.ToolbarDialog.css">
<link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css">
+ <link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css">
<link rel=stylesheet
href="../../src/ui/styles/dialogs/ve.ui.ProgressDialog.css">
<link rel=stylesheet
href="../../src/ui/styles/tools/ve.ui.FormatTool.css">
<link rel=stylesheet
href="../../src/ui/styles/widgets/ve.ui.LanguageInputWidget.css">
@@ -323,13 +325,16 @@
<script
src="../../src/ui/commands/ve.ui.IndentationCommand.js"></script>
<script
src="../../src/ui/commands/ve.ui.MergeCellsCommand.js"></script>
<script
src="../../src/ui/commands/ve.ui.TableCaptionCommand.js"></script>
- <script
src="../../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
<script
src="../../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
<script src="../../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
+ <script
src="../../src/ui/dialogs/ve.ui.ToolbarDialog.js"></script>
+ <script
src="../../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
+ <script
src="../../src/ui/dialogs/ve.ui.FindAndReplaceDialog.js"></script>
<script
src="../../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
<script
src="../../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
<script
src="../../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
<script
src="../../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
+ <script
src="../../src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js"></script>
<script
src="../../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
<script
src="../../src/ui/widgets/ve.ui.LanguageResultWidget.js"></script>
<script
src="../../src/ui/dialogs/ve.ui.LanguageSearchDialog.js"></script>
diff --git a/i18n/en.json b/i18n/en.json
index 2a94ca0..5329f16 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -49,6 +49,13 @@
"visualeditor-dialog-language-search-title": "Select language",
"visualeditor-dimensionswidget-px": "px",
"visualeditor-dimensionswidget-times": "×",
+ "visualeditor-find-and-replace-find-text": "Find",
+ "visualeditor-find-and-replace-match-case": "Match case",
+ "visualeditor-find-and-replace-replace-all-button": "Replace all",
+ "visualeditor-find-and-replace-replace-button": "Replace",
+ "visualeditor-find-and-replace-replace-text": "Replace",
+ "visualeditor-find-and-replace-results": "$1 of $2",
+ "visualeditor-find-and-replace-title": "Find and replace",
"visualeditor-formatdropdown-format-blockquote": "Block quote",
"visualeditor-formatdropdown-format-heading-label": "Heading (1-6)",
"visualeditor-formatdropdown-format-heading1": "Heading 1",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index b574fb6..8a4a712 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -55,6 +55,13 @@
"visualeditor-dialog-language-search-title": "Title for language search
dialog\n{{Identical|Select language}}",
"visualeditor-dimensionswidget-px": "{{optional}}\nLabel for the
dimensions properties denoting pixel units.",
"visualeditor-dimensionswidget-times": "{{optional}}\nLabel for the
dimensions properties denoting 'by', as in width x height.",
+ "visualeditor-find-and-replace-find-text": "Label for find text in find
and replace",
+ "visualeditor-find-and-replace-match-case": "Label for match case
toggle in find and replace",
+ "visualeditor-find-and-replace-replace-all-button": "Label for replace
all button in find and replace",
+ "visualeditor-find-and-replace-replace-button": "Label for replace
button in find and replace",
+ "visualeditor-find-and-replace-replace-text": "Label for replace text
in find and replace",
+ "visualeditor-find-and-replace-results": "Label for find results
showing how many results were found ($2), and which one is currently
highlighted ($1)",
+ "visualeditor-find-and-replace-title": "Title for find and replace",
"visualeditor-formatdropdown-format-blockquote": "Item in the
formatting dropdown for block quote text. A block quote is a quotation which is
a whole paragraph, or several paragraphs.",
"visualeditor-formatdropdown-format-heading-label": "Label for heading
level commands.\n{{Identical|Heading}}",
"visualeditor-formatdropdown-format-heading1": "Item in the generic
formatting dropdown for a level 1 heading.\n{{Identical|Heading}}",
diff --git a/src/ce/ve.ce.Surface.js b/src/ce/ve.ce.Surface.js
index 110fe4c..e9d3efa 100644
--- a/src/ce/ve.ce.Surface.js
+++ b/src/ce/ve.ce.Surface.js
@@ -57,6 +57,7 @@
this.$highlights = this.$( '<div>' ).append(
this.$highlightsFocused, this.$highlightsBlurred
);
+ this.$findResults = this.$( '<div>' );
this.$dropMarker = this.$( '<div>' ).addClass(
've-ce-focusableNode-dropMarker' );
this.$lastDropTarget = null;
this.lastDropPosition = null;
diff --git a/src/dm/ve.dm.Document.js b/src/dm/ve.dm.Document.js
index e8dae5e..b9cd0d2 100644
--- a/src/dm/ve.dm.Document.js
+++ b/src/dm/ve.dm.Document.js
@@ -1242,6 +1242,35 @@
};
/**
+ * Find a text string within the document
+ *
+ * @param {string} query Text to find
+ * @param {boolean} [caseSensitive] Case sensitive search
+ * @param {boolean} [noOverlaps] Avoid overlapping matches
+ * @return {number[]} List of offsets where the string was found
+ */
+ve.dm.Document.prototype.findText = function ( query, caseSensitive,
noOverlaps ) {
+ var offset = -1,
+ offsets = [],
+ len = query.length,
+ text = this.data.getText(
+ true,
+ new ve.Range( 0,
this.getInternalList().getListNode().getOuterRange().start )
+ );
+
+ if ( !caseSensitive ) {
+ text = text.toLowerCase();
+ query = query.toLowerCase();
+ }
+
+ while ( ( offset = text.indexOf( query, offset ) ) !== -1 ) {
+ offsets.push( offset );
+ offset += noOverlaps ? len : 1;
+ }
+ return offsets;
+};
+
+/**
* Get the length of the complete history stack. This is also the current
pointer.
* @returns {number} Length of the complete history stack
*/
diff --git a/src/init/ve.init.Target.js b/src/init/ve.init.Target.js
index 009ab4e..feb58a3 100644
--- a/src/init/ve.init.Target.js
+++ b/src/init/ve.init.Target.js
@@ -223,6 +223,7 @@
this.toolbar = new ve.ui.TargetToolbar( this, this.surface, config );
this.toolbar.setup( this.constructor.static.toolbarGroups );
this.toolbar.$element.insertBefore( this.surface.$element );
+ this.toolbar.$bar.append( this.surface.toolbarDialogs.$element );
};
/**
diff --git a/src/ui/actions/ve.ui.WindowAction.js
b/src/ui/actions/ve.ui.WindowAction.js
index c23dc31..17402d0 100644
--- a/src/ui/actions/ve.ui.WindowAction.js
+++ b/src/ui/actions/ve.ui.WindowAction.js
@@ -58,7 +58,9 @@
data = ve.extendObject( { dir: dir }, data, { fragment: fragment } );
- if ( windowType === 'dialog' ) {
+ if ( windowType === 'toolbar' ) {
+ data = ve.extendObject( data, { surface: surface } );
+ } else if ( windowType === 'dialog' ) {
// For non-isolated dialogs, remove the selection and re-apply
on close
surface.getView().nativeSelection.removeAllRanges();
onOpen = function ( opened ) {
@@ -131,12 +133,14 @@
* Get the type of a window class
*
* @param {string} name Window name
- * @return {string|null} Window type: 'inspector' or 'dialog'
+ * @return {string|null} Window type: 'inspector', 'toolbar' or 'dialog'
*/
ve.ui.WindowAction.prototype.getWindowType = function ( name ) {
var windowClass = ve.ui.windowFactory.lookup( name );
if ( windowClass.prototype instanceof ve.ui.FragmentInspector ) {
return 'inspector';
+ } else if ( windowClass.prototype instanceof ve.ui.ToolbarDialog ) {
+ return 'toolbar';
} else if ( windowClass.prototype instanceof OO.ui.Dialog ) {
return 'dialog';
}
@@ -153,6 +157,8 @@
switch ( windowType ) {
case 'inspector':
return this.surface.getContext().getInspectors();
+ case 'toolbar':
+ return this.surface.toolbarDialogs;
case 'dialog':
return this.surface.getDialogs();
}
diff --git a/src/ui/dialogs/ve.ui.CommandHelpDialog.js
b/src/ui/dialogs/ve.ui.CommandHelpDialog.js
index c4a3446..7580b60 100644
--- a/src/ui/dialogs/ve.ui.CommandHelpDialog.js
+++ b/src/ui/dialogs/ve.ui.CommandHelpDialog.js
@@ -183,6 +183,7 @@
other: {
title: 'visualeditor-shortcuts-other',
commands: [
+ { trigger: 'findAndReplace', msg:
'visualeditor-find-and-replace-title' },
{ trigger: 'selectAll', msg:
'visualeditor-content-select-all' },
{ trigger: 'commandHelp', msg:
'visualeditor-dialog-command-help-title' }
]
diff --git a/src/ui/dialogs/ve.ui.FindAndReplaceDialog.js
b/src/ui/dialogs/ve.ui.FindAndReplaceDialog.js
new file mode 100644
index 0000000..c55c74f
--- /dev/null
+++ b/src/ui/dialogs/ve.ui.FindAndReplaceDialog.js
@@ -0,0 +1,364 @@
+/*!
+ * VisualEditor UserInterface FindAndReplaceDialog class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Find and replace dialog.
+ *
+ * @class
+ * @extends ve.ui.ToolbarDialog
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+ve.ui.FindAndReplaceDialog = function VeUiFindAndReplaceDialog( config ) {
+ // Parent constructor
+ ve.ui.FindAndReplaceDialog.super.call( this, config );
+
+ // Properties
+ this.surface = null;
+
+ // Pre-initialization
+ this.$element.addClass( 've-ui-findAndReplaceDialog' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.FindAndReplaceDialog, ve.ui.ToolbarDialog );
+
+ve.ui.FindAndReplaceDialog.static.name = 'findAndReplace';
+
+ve.ui.FindAndReplaceDialog.static.title = OO.ui.deferMsg(
'visualeditor-find-and-replace-title' );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.FindAndReplaceDialog.prototype.initialize = function () {
+ // Parent method
+ ve.ui.FindAndReplaceDialog.super.prototype.initialize.call( this );
+
+ this.$findResults = this.$( '<div>' ).addClass(
've-ui-findAndReplaceDialog-findResults' );
+ this.fragments = [];
+ this.replacing = false;
+ this.focusedIndex = 0;
+ this.findText = new OO.ui.TextInputWidget( {
+ $: this.$,
+ classes: ['ve-ui-findAndReplaceDialog-cell',
've-ui-findAndReplaceDialog-findText'],
+ placeholder: ve.msg( 'visualeditor-find-and-replace-find-text' )
+ } );
+ this.matchCaseToggle = new OO.ui.ToggleSwitchWidget( { $: this.$ } );
+ this.focusedIndexLabel = new OO.ui.LabelWidget( {
+ $: this.$,
+ classes: ['ve-ui-findAndReplaceDialog-focusedIndexLabel']
+ } );
+ this.previousButton = new OO.ui.ButtonWidget( {
+ $: this.$,
+ icon: 'previous'
+ } );
+ this.nextButton = new OO.ui.ButtonWidget( {
+ $: this.$,
+ icon: 'next'
+ } );
+ this.replaceText = new OO.ui.TextInputWidget( {
+ $: this.$,
+ classes: ['ve-ui-findAndReplaceDialog-cell'],
+ placeholder: ve.msg(
'visualeditor-find-and-replace-replace-text' )
+ } );
+ this.replaceButton = new OO.ui.ButtonWidget( {
+ $: this.$,
+ label: ve.msg( 'visualeditor-find-and-replace-replace-button' )
+ } );
+ this.replaceAllButton = new OO.ui.ButtonWidget( {
+ $: this.$,
+ label: ve.msg(
'visualeditor-find-and-replace-replace-all-button' )
+ } );
+
+ var checkboxField = new OO.ui.FieldLayout(
+ this.matchCaseToggle,
+ {
+ $: this.$,
+ classes: ['ve-ui-findAndReplaceDialog-cell'],
+ align: 'inline',
+ label: ve.msg(
'visualeditor-find-and-replace-match-case' )
+ }
+ ),
+ navigateGroup = new OO.ui.ButtonGroupWidget( {
+ $: this.$,
+ classes: ['ve-ui-findAndReplaceDialog-cell'],
+ items: [
+ this.previousButton,
+ this.nextButton
+ ]
+ } ),
+ replaceGroup = new OO.ui.ButtonGroupWidget( {
+ $: this.$,
+ classes: ['ve-ui-findAndReplaceDialog-cell'],
+ items: [
+ this.replaceButton,
+ this.replaceAllButton
+ ]
+ } ),
+ $findRow = this.$( '<div>' ).addClass(
've-ui-findAndReplaceDialog-row' ),
+ $replaceRow = this.$( '<div>' ).addClass(
've-ui-findAndReplaceDialog-row' );
+
+ // Events
+ this.updateFragmentsDebounced = ve.debounce( this.updateFragments.bind(
this ) );
+ this.positionResultsDebounced = ve.debounce( this.positionResults.bind(
this ) );
+ this.findText.connect( this, {
+ change: 'onFindChange',
+ enter: 'onFindTextEnter'
+ } );
+ this.matchCaseToggle.connect( this, { change: 'onFindChange' } );
+ this.nextButton.connect( this, { click: 'onNextButtonClick' } );
+ this.previousButton.connect( this, { click: 'onPreviousButtonClick' } );
+ this.replaceButton.connect( this, { click: 'onReplaceButtonClick' } );
+ this.replaceAllButton.connect( this, { click: 'onReplaceAllButtonClick'
} );
+ this.$content.on( 'keydown', this.onContentKeyDown.bind( this ) );
+
+ // Initialization
+ this.findText.$input.attr( 'tabIndex', 1 );
+ this.replaceText.$input.attr( 'tabIndex', 2 );
+ this.$content.addClass( 've-ui-findAndReplaceDialog-content' );
+ this.$body
+ .append(
+ $findRow.append(
+ this.findText.$element.append(
+ this.focusedIndexLabel.$element
+ ),
+ navigateGroup.$element,
+ checkboxField.$element
+ ),
+ $replaceRow.append(
+ this.replaceText.$element,
+ replaceGroup.$element
+ )
+ );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.FindAndReplaceDialog.prototype.getSetupProcess = function ( data ) {
+ data = data || {};
+ return ve.ui.FindAndReplaceDialog.super.prototype.getSetupProcess.call(
this, data )
+ .first( function () {
+ this.surface = data.surface;
+ this.surface.$controls.append( this.$findResults );
+ this.surface.getModel().connect( this, {
documentUpdate: this.updateFragmentsDebounced } );
+ this.surface.getView().connect( this, { position:
this.positionResultsDebounced } );
+
+ var text = data.fragment.getText();
+ if ( text ) {
+ this.findText.setValue( text );
+ } else {
+ this.updateFragments();
+ }
+ }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.FindAndReplaceDialog.prototype.getReadyProcess = function ( data ) {
+ return ve.ui.FindAndReplaceDialog.super.prototype.getReadyProcess.call(
this, data )
+ .next( function () {
+ this.findText.focus().select();
+ }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.FindAndReplaceDialog.prototype.getTeardownProcess = function ( data ) {
+ return
ve.ui.FindAndReplaceDialog.super.prototype.getTeardownProcess.call( this, data )
+ .next( function () {
+ var surfaceView = this.surface.getView();
+ this.surface.getModel().disconnect( this );
+ surfaceView.disconnect( this );
+ surfaceView.focus();
+ this.$findResults.empty().detach();
+ this.fragment = [];
+ this.surface = null;
+ }, this );
+};
+
+/**
+ * Handle keydown events inside the dialog
+ */
+ve.ui.FindAndReplaceDialog.prototype.onContentKeyDown = function ( e ) {
+ var command, trigger = new ve.ui.Trigger( e );
+ if ( trigger.isComplete() ) {
+ command = this.surface.getCommandByTrigger( trigger.toString()
);
+ if ( command && command.getName() === 'findAndReplace' ) {
+ e.preventDefault();
+ this.close();
+ }
+ }
+};
+
+/**
+ * Handle change events to the find inputs (text or match case)
+ */
+ve.ui.FindAndReplaceDialog.prototype.onFindChange = function () {
+ this.updateFragments();
+ this.positionResults();
+ this.highlightFocused( true );
+};
+
+/**
+ * Handle enter events on the find text input
+ */
+ve.ui.FindAndReplaceDialog.prototype.onFindTextEnter = function () {
+ this.onNextButtonClick();
+};
+
+/**
+ * Update search result fragments
+ */
+ve.ui.FindAndReplaceDialog.prototype.updateFragments = function () {
+ var i, l, findLen,
+ endOffset = 0,
+ surfaceModel = this.surface.getModel(),
+ documentModel = surfaceModel.getDocument(),
+ offsets = [],
+ matchCase = this.matchCaseToggle.getValue(),
+ find = this.findText.getValue();
+
+ this.fragments = [];
+ if ( find ) {
+ offsets = documentModel.findText( find, matchCase, true );
+ findLen = find.length;
+ for ( i = 0, l = offsets.length; i < l; i++ ) {
+ endOffset = offsets[i] + findLen;
+ this.fragments.push( surfaceModel.getLinearFragment(
new ve.Range( offsets[i], endOffset ), true, true ) );
+ }
+ }
+ this.focusedIndex = Math.min( this.focusedIndex, this.fragments.length
);
+ this.nextButton.setDisabled( !this.fragments.length );
+ this.previousButton.setDisabled( !this.fragments.length );
+ this.replaceButton.setDisabled( !this.fragments.length );
+ this.replaceAllButton.setDisabled( !this.fragments.length );
+};
+
+/**
+ * Position results markers
+ */
+ve.ui.FindAndReplaceDialog.prototype.positionResults = function () {
+ if ( this.replacing ) {
+ return;
+ }
+
+ var i, l, j, rects, $result, top;
+
+ this.$findResults.empty();
+ for ( i = 0, l = this.fragments.length; i < l; i++ ) {
+ rects = this.surface.getView().getSelectionRects(
this.fragments[i].getSelection() );
+ $result = this.$( '<div>' ).addClass(
've-ui-findAndReplaceDialog-findResult' );
+ top = Infinity;
+ for ( j in rects ) {
+ top = Math.min( top, rects[j].top );
+ $result.append( this.$( '<div>' ).css( rects[j] ) );
+ }
+ $result.data( 'top', top );
+ this.$findResults.append( $result );
+ }
+ this.highlightFocused();
+};
+
+/**
+ * Highlight the focused result marker
+ *
+ * @param {boolean} scrollIntoView Scroll the marker into view
+ */
+ve.ui.FindAndReplaceDialog.prototype.highlightFocused = function (
scrollIntoView ) {
+ var windowScrollTop, windowScrollHeight, offset,
+ surfaceView = this.surface.getView(),
+ $result = this.$findResults.children().eq( this.focusedIndex );
+
+ this.$findResults
+ .find( '.ve-ui-findAndReplaceDialog-findResult-focused' )
+ .removeClass( 've-ui-findAndReplaceDialog-findResult-focused' );
+ $result.addClass( 've-ui-findAndReplaceDialog-findResult-focused' );
+
+ if ( this.fragments.length ) {
+ this.focusedIndexLabel.setLabel(
+ ve.msg( 'visualeditor-find-and-replace-results',
this.focusedIndex + 1, this.fragments.length )
+ );
+ } else {
+ this.focusedIndexLabel.setLabel( '' );
+ }
+
+ if ( scrollIntoView ) {
+ offset = $result.data( 'top' ) +
surfaceView.$element.offset().top;
+ windowScrollTop = surfaceView.$window.scrollTop() +
this.surface.toolbarHeight;
+ windowScrollHeight = surfaceView.$window.height() -
this.surface.toolbarHeight;
+ if ( offset < windowScrollTop || offset > windowScrollTop +
windowScrollHeight ) {
+ surfaceView.$( 'body, html' ).animate( { scrollTop:
offset - ( windowScrollHeight / 2 ) }, 'fast' );
+ }
+ }
+};
+
+/**
+ * Handle click events on the next button
+ */
+ve.ui.FindAndReplaceDialog.prototype.onNextButtonClick = function () {
+ this.focusedIndex = ( this.focusedIndex + 1 ) % this.fragments.length;
+ this.highlightFocused( true );
+};
+
+/**
+ * Handle click events on the previous button
+ */
+ve.ui.FindAndReplaceDialog.prototype.onPreviousButtonClick = function () {
+ this.focusedIndex = ( this.focusedIndex + this.fragments.length - 1 ) %
this.fragments.length;
+ this.highlightFocused( true );
+};
+
+/**
+ * Handle click events on the replace button
+ */
+ve.ui.FindAndReplaceDialog.prototype.onReplaceButtonClick = function () {
+ var end, replace = this.replaceText.getValue();
+
+ if ( !this.fragments.length ) {
+ return;
+ }
+
+ this.fragments[this.focusedIndex].insertContent( replace, true );
+
+ // Find the next fragment after this one ends. Ensures that if we
replace
+ // 'foo' with 'foofoo' we don't select the just-inserted text.
+ end = this.fragments[this.focusedIndex].getSelection().getRange().end;
+ // updateFragmentsDebounced is triggered by insertContent, but call it
immediately
+ // so we can find the next fragment to select.
+ this.updateFragments();
+ if ( !this.fragments.length ) {
+ this.focusedIndex = 0;
+ return;
+ }
+ while ( this.fragments[this.focusedIndex] &&
this.fragments[this.focusedIndex].getSelection().getRange().end <= end ) {
+ this.focusedIndex++;
+ }
+ // We may have iterated off the end
+ this.focusedIndex = this.focusedIndex % this.fragments.length;
+};
+
+/**
+ * Handle click events on the previous all button
+ */
+ve.ui.FindAndReplaceDialog.prototype.onReplaceAllButtonClick = function () {
+ var i, l,
+ replace = this.replaceText.getValue();
+
+ for ( i = 0, l = this.fragments.length; i < l; i++ ) {
+ this.fragments[i].insertContent( replace, true );
+ }
+};
+
+/* Registration */
+
+ve.ui.windowFactory.register( ve.ui.FindAndReplaceDialog );
diff --git a/src/ui/dialogs/ve.ui.ToolbarDialog.js
b/src/ui/dialogs/ve.ui.ToolbarDialog.js
new file mode 100644
index 0000000..8a221e6
--- /dev/null
+++ b/src/ui/dialogs/ve.ui.ToolbarDialog.js
@@ -0,0 +1,32 @@
+/*!
+ * VisualEditor UserInterface ToolbarDialog class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Toolbar dialog.
+ *
+ * @class
+ * @abstract
+ * @extends OO.ui.Dialog
+ *
+ * @constructor
+ * @param {ve.ui.Surface} surface
+ * @param {Object} [config] Configuration options
+ */
+ve.ui.ToolbarDialog = function VeUiToolbarDialog( config ) {
+ // Parent constructor
+ ve.ui.ToolbarDialog.super.call( this, config );
+
+ // Pre-initialization
+ // This class needs to exist before setup to constrain the height
+ // of the dialog when it first loads.
+ this.$element.addClass( 've-ui-toolbarDialog' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.ToolbarDialog, OO.ui.Dialog );
+
+ve.ui.ToolbarDialog.static.size = 'full';
diff --git a/src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css
b/src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css
index 9447ca7..9dc22b4 100644
--- a/src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css
+++ b/src/ui/styles/dialogs/ve.ui.CommandHelpDialog.css
@@ -14,7 +14,7 @@
* Hack 1.1: The height isn't calculated correctly with the inline-block
* hack so fix it to prevent unnecessary scroll bars.
*/
- height: 37em;
+ height: 39em;
}
.ve-ui-commandHelpDialog-section {
diff --git a/src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css
b/src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css
new file mode 100644
index 0000000..e254ede
--- /dev/null
+++ b/src/ui/styles/dialogs/ve.ui.FindAndReplaceDialog.css
@@ -0,0 +1,64 @@
+/*!
+ * VisualEditor UserInterface FindAndReplaceDialog styles.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-findAndReplaceDialog-row {
+ display: table;
+ min-width: 30em;
+ padding-bottom: 0.3em;
+}
+
+.ve-ui-findAndReplaceDialog-row:last-child {
+ /* Extra pixel for button shadows */
+ padding-bottom: 1px;
+}
+
+.ve-ui-findAndReplaceDialog-row .ve-ui-findAndReplaceDialog-cell {
+ display: table-cell;
+ vertical-align: middle;
+ white-space: nowrap;
+ padding-right: 1em;
+}
+
+.ve-ui-findAndReplaceDialog-row .ve-ui-findAndReplaceDialog-cell:last-child {
+ padding-right: 0;
+}
+
+.ve-ui-findAndReplaceDialog-findText input {
+ padding-right: 6em;
+}
+
+.ve-ui-findAndReplaceDialog-focusedIndexLabel {
+ position: absolute;
+ right: 1.4em;
+ color: #888;
+}
+
+.ve-ui-findAndReplaceDialog-findResults {
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+}
+
+.ve-ui-findAndReplaceDialog-findResult {
+ opacity: 0.2;
+}
+
+.ve-ui-findAndReplaceDialog-findResult div {
+ background: #28bb0b;
+ position: absolute;
+ margin-top: -0.15em;
+ padding: 0.15em 0;
+ border-radius: 0.15em;
+}
+
+.ve-ui-findAndReplaceDialog-findResult-focused {
+ opacity: 0.4;
+}
+
+.ve-ui-findAndReplaceDialog-findResult-focused div {
+ background: #1f850b;
+}
diff --git a/src/ui/styles/dialogs/ve.ui.ToolbarDialog.css
b/src/ui/styles/dialogs/ve.ui.ToolbarDialog.css
new file mode 100644
index 0000000..1e10dbe
--- /dev/null
+++ b/src/ui/styles/dialogs/ve.ui.ToolbarDialog.css
@@ -0,0 +1,26 @@
+/*!
+ * VisualEditor UserInterface ToolbarDialog styles.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-toolbarDialog {
+ padding: 0.375em; /* 0.3em / 0.8 */
+ padding-top: 0;
+ overflow-y: hidden;
+ max-height: 0;
+ -webkit-transition: max-height 250ms;
+ -moz-transition: max-height 250ms;
+ -o-transition: max-height 250ms;
+ transition: max-height 250ms;
+}
+
+.ve-ui-toolbarDialog.oo-ui-window-ready {
+ /* approximate max height for transition */
+ max-height: 150px;
+}
+
+.ve-ui-toolbarDialog-content > .oo-ui-window-body {
+ bottom: auto;
+ box-shadow: none;
+}
diff --git a/src/ui/ve.ui.CommandRegistry.js b/src/ui/ve.ui.CommandRegistry.js
index 085c767..1161ac3 100644
--- a/src/ui/ve.ui.CommandRegistry.js
+++ b/src/ui/ve.ui.CommandRegistry.js
@@ -141,6 +141,11 @@
);
ve.ui.commandRegistry.register(
new ve.ui.Command(
+ 'findAndReplace', 'window', 'toggle', { args:
['findAndReplace'] }
+ )
+);
+ve.ui.commandRegistry.register(
+ new ve.ui.Command(
'code', 'annotation', 'toggle',
{ args: ['textStyle/code'], supportedSelections: ['linear',
'table'] }
)
diff --git a/src/ui/ve.ui.Surface.js b/src/ui/ve.ui.Surface.js
index d648ffd..15d850a 100644
--- a/src/ui/ve.ui.Surface.js
+++ b/src/ui/ve.ui.Surface.js
@@ -57,6 +57,12 @@
this.filibuster = null;
this.toolbarHeight = 0;
+ this.toolbarDialogs = new ve.ui.ToolbarDialogWindowManager( {
+ $: this.$,
+ factory: ve.ui.windowFactory,
+ modal: false,
+ isolate: true
+ } );
// Initialization
this.setupCommands( config.excludeCommands );
diff --git a/src/ui/ve.ui.Toolbar.js b/src/ui/ve.ui.Toolbar.js
index b24dd13..9fd5f7a 100644
--- a/src/ui/ve.ui.Toolbar.js
+++ b/src/ui/ve.ui.Toolbar.js
@@ -49,6 +49,10 @@
.addClass( 've-ui-dir-block-' + this.contextDirection.block );
// Events
this.surface.getModel().connect( this, { contextChange:
'onContextChange' } );
+ this.surface.toolbarDialogs.connect( this, {
+ opening: 'onToolbarWindowOpeningOrClosing',
+ closing: 'onToolbarWindowOpeningOrClosing'
+ } );
};
/* Inheritance */
@@ -119,6 +123,27 @@
};
/**
+ * Handle windows opening or closing in the toolbar window manager.
+ *
+ * @param {OO.ui.Window} win
+ * @param {jQuery.Promise} openingOrClosing
+ * @param {Object} data
+ */
+ve.ui.Toolbar.prototype.onToolbarWindowOpeningOrClosing = function ( win,
openingOrClosing ) {
+ var toolbar = this;
+ openingOrClosing.then( function () {
+ // Wait for window transition
+ setTimeout( function () {
+ if ( toolbar.floating ) {
+ // Re-calculate height
+ toolbar.unfloat();
+ toolbar.float();
+ }
+ }, 250 );
+ } );
+};
+
+/**
* Handle context changes on the surface.
*
* @fires updateState
diff --git a/src/ui/ve.ui.TriggerRegistry.js b/src/ui/ve.ui.TriggerRegistry.js
index 865f0a3..bc620b0 100644
--- a/src/ui/ve.ui.TriggerRegistry.js
+++ b/src/ui/ve.ui.TriggerRegistry.js
@@ -168,3 +168,6 @@
ve.ui.triggerRegistry.register(
'pasteSpecial', { mac: new ve.ui.Trigger( 'cmd+shift+v' ), pc: new
ve.ui.Trigger( 'ctrl+shift+v' ) }
);
+ve.ui.triggerRegistry.register(
+ 'findAndReplace', { mac: new ve.ui.Trigger( 'cmd+f' ), pc: new
ve.ui.Trigger( 'ctrl+f' ) }
+);
diff --git a/src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js
b/src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js
new file mode 100644
index 0000000..b4aea54
--- /dev/null
+++ b/src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js
@@ -0,0 +1,40 @@
+/*!
+ * VisualEditor UserInterface ToolbarDialogWindowManager class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Window manager for toolbar dialogs.
+ *
+ * @class
+ * @extends ve.ui.WindowManager
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {ve.ui.Overlay} [overlay] Overlay to use for menus
+ */
+ve.ui.ToolbarDialogWindowManager = function VeUiToolbarDialogWindowManager(
config ) {
+ // Parent constructor
+ ve.ui.ToolbarDialogWindowManager.super.call( this, config );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.ToolbarDialogWindowManager, ve.ui.WindowManager );
+
+/* Static Properties */
+
+ve.ui.ToolbarDialogWindowManager.static.sizes.full = {
+ width: '100%',
+ maxHeight: '100%'
+};
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.ToolbarDialogWindowManager.prototype.getTeardownDelay = function () {
+ return 250;
+};
diff --git a/tests/index.html b/tests/index.html
index a7c2daa..b6da759 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -272,13 +272,16 @@
<script
src="../src/ui/commands/ve.ui.IndentationCommand.js"></script>
<script
src="../src/ui/commands/ve.ui.MergeCellsCommand.js"></script>
<script
src="../src/ui/commands/ve.ui.TableCaptionCommand.js"></script>
- <script
src="../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
<script
src="../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
<script src="../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
+ <script src="../src/ui/dialogs/ve.ui.ToolbarDialog.js"></script>
+ <script
src="../src/ui/dialogs/ve.ui.CommandHelpDialog.js"></script>
+ <script
src="../src/ui/dialogs/ve.ui.FindAndReplaceDialog.js"></script>
<script
src="../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
<script
src="../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
<script
src="../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
<script
src="../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
+ <script
src="../src/ui/windowmanagers/ve.ui.ToolbarDialogWindowManager.js"></script>
<script
src="../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
<script
src="../src/ui/widgets/ve.ui.LanguageResultWidget.js"></script>
<script
src="../src/ui/dialogs/ve.ui.LanguageSearchDialog.js"></script>
--
To view, visit https://gerrit.wikimedia.org/r/174491
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I8dc406ffe8455dedf35eef02a1909de2d79adeb0
Gerrit-PatchSet: 18
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits