Insert special character tool

A tool to add special characters and diacritics to text.

Also added a new button type ve.ui.GroupButtonWidget that includes a
group of PushButtonWidget objects and returna the individual button's
value upon click

Wikis can edit <visualeditor-specialcharinspector-characterlist-insert>,
a JSON string, to include their own desird special characters to insert
through the tool.

Bug: 50296
Change-Id: I26d1f437feef1c8b61ed3be5f74ef524b33baf49
This commit is contained in:
Moriel Schottlender 2013-10-18 13:09:04 -07:00
parent d17e031d46
commit cf237b882e
11 changed files with 391 additions and 3 deletions

View file

@ -155,6 +155,55 @@ $messages['en'] = array(
'visualeditor-linkinspector-title' => 'Hyperlink',
'visualeditor-listbutton-bullet-tooltip' => 'Bullet list',
'visualeditor-listbutton-number-tooltip' => 'Numbered list',
'visualeditor-specialcharacter-button-tooltip' => 'Special character',
'visualeditor-specialcharacterinspector-title' => 'Special character',
'visualeditor-specialcharinspector-characterlist-insert' => '{
"symbols": {
"": "",
"": "",
"°": "°",
"": "",
"": "",
"": "",
"": "",
"·": "·",
"§": "§"
},
"accents": {
"à": "à",
"á": "á",
"â": "â",
"ä": "ä",
"ç": "ç",
"è": "è",
"é": "é",
"ê": "ê",
"ë": "ë",
"ì": "ì",
"í": "í",
"î": "î",
"ï": "ï",
"ò": "ò",
"ó": "ó",
"ô": "ô",
"ö": "ö",
"ø": "ø",
"ù": "ù",
"ú": "ú",
"û": "û",
"ü": "ü"
},
"math": {
"": "",
"×": "×",
"÷": "÷",
"": "",
"": "",
"": "",
"": "",
"±": "±"
}
}',
'visualeditor-loadwarning' => 'Error loading data from server: $1. Would you like to retry?',
'visualeditor-loadwarning-token' => 'Error loading edit token from server: $1. Would you like to retry?',
'visualeditor-mainnamespacepagelink' => 'Project:Main namespace',
@ -555,6 +604,9 @@ Parameters:
{{Identical|Hyperlink}}',
'visualeditor-listbutton-bullet-tooltip' => 'Tooltip text for the bullet list button',
'visualeditor-listbutton-number-tooltip' => 'Tooltip text for the numbered list button',
'visualeditor-specialcharacter-button-tooltip' => 'Tooltip text for the insert character button',
'visualeditor-specialcharacterinspector-title' => 'Used as title for special character inspector',
'visualeditor-specialcharinspector-characterlist-insert' => 'This is a JSON string defining the special characters that can be inserted using the special character insertion tool. Please make sure it is a valid JSON string.',
'visualeditor-loadwarning' => 'Text (JavaScript confirm()) shown when the editor fails to load properly.
Parameters:

View file

@ -511,6 +511,9 @@ $wgResourceModules += array(
've/ui/inspectors/ve.ui.LinkInspector.js',
've-mw/ui/inspectors/ve.ui.MWLinkInspector.js',
've-mw/ui/inspectors/ve.ui.MWExtensionInspector.js',
've/ui/widgets/ve.ui.GroupButtonWidget.js',
've/ui/inspectors/ve.ui.SpecialCharacterInspector.js',
),
'styles' => array(
// ce
@ -667,6 +670,9 @@ $wgResourceModules += array(
'visualeditor-savedialog-warning-dirty',
'visualeditor-saveerror',
'visualeditor-serializeerror',
'visualeditor-specialcharacter-button-tooltip',
'visualeditor-specialcharacterinspector-title',
'visualeditor-specialcharinspector-characterlist-insert',
'visualeditor-toolbar-cancel',
'visualeditor-toolbar-savedialog',
'visualeditor-version-label',

View file

@ -235,8 +235,9 @@ $html = file_get_contents( $page );
<script src="../../modules/ve/ui/actions/ve.ui.IndentationAction.js"></script>
<script src="../../modules/ve/ui/actions/ve.ui.InspectorAction.js"></script>
<script src="../../modules/ve/ui/actions/ve.ui.ListAction.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.SurfaceWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.GroupButtonWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.SurfaceWidget.js"></script>
<script src="../../modules/ve/ui/tools/ve.ui.AnnotationTool.js"></script>
<script src="../../modules/ve/ui/tools/ve.ui.ClearAnnotationTool.js"></script>
<script src="../../modules/ve/ui/tools/ve.ui.DialogTool.js"></script>
@ -247,6 +248,7 @@ $html = file_get_contents( $page );
<script src="../../modules/ve/ui/tools/ve.ui.ListTool.js"></script>
<script src="../../modules/ve/ui/inspectors/ve.ui.AnnotationInspector.js"></script>
<script src="../../modules/ve/ui/inspectors/ve.ui.LinkInspector.js"></script>
<script src="../../modules/ve/ui/inspectors/ve.ui.SpecialCharacterInspector.js"></script>
<!-- jquery.uls.data -->
<script src="../../modules/jquery.uls/src/jquery.uls.data.js"></script>
<script src="../../modules/jquery.uls/src/jquery.uls.data.utils.js"></script>

View file

@ -39,7 +39,8 @@ ve.init.Target.static.toolbarGroups = [
},
{ 'include': [ 'bold', 'italic', 'link', 'clear' ] },
{ 'include': [ 'number', 'bullet', 'outdent', 'indent' ] },
{ 'include': '*' }
{ 'include': '*', 'demote': [ 'specialcharacter' ] }
];
ve.init.Target.static.surfaceCommands = [

View file

@ -188,8 +188,9 @@
<script src="../../ve/ui/actions/ve.ui.IndentationAction.js"></script>
<script src="../../ve/ui/actions/ve.ui.InspectorAction.js"></script>
<script src="../../ve/ui/actions/ve.ui.ListAction.js"></script>
<script src="../../ve/ui/widgets/ve.ui.SurfaceWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.GroupButtonWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.SurfaceWidget.js"></script>
<script src="../../ve/ui/tools/ve.ui.AnnotationTool.js"></script>
<script src="../../ve/ui/tools/ve.ui.ClearAnnotationTool.js"></script>
<script src="../../ve/ui/tools/ve.ui.DialogTool.js"></script>
@ -200,6 +201,7 @@
<script src="../../ve/ui/tools/ve.ui.ListTool.js"></script>
<script src="../../ve/ui/inspectors/ve.ui.AnnotationInspector.js"></script>
<script src="../../ve/ui/inspectors/ve.ui.LinkInspector.js"></script>
<script src="../../ve/ui/inspectors/ve.ui.SpecialCharacterInspector.js"></script>
<!-- jquery.uls.data -->
<script src="../../jquery.uls/src/jquery.uls.data.js"></script>
<script src="../../jquery.uls/src/jquery.uls.data.utils.js"></script>

View file

@ -0,0 +1,232 @@
/*!
* VisualEditor UserInterface SpecialCharacterInspector class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Special character inspector.
*
* @class
* @extends ve.ui.Inspector
*
* @constructor
* @param {ve.ui.WindowSet} windowSet Window set this inspector is part of
* @param {Object} [config] Configuration options
*/
ve.ui.SpecialCharacterInspector = function VeUiSpecialCharacterInspector( windowSet, config ) {
// Parent constructor
ve.ui.Inspector.call( this, windowSet, config );
this.characters = null;
this.$buttonDomList = null;
this.initialSelection = null;
this.addedChar = null;
this.categories = null;
// Fallback character list in case no list is found anywhere
this.minimalCharacterList =
{
'accents': {
'à': 'à',
'á': 'á',
'â': 'â',
'ä': 'ä',
'ç': 'ç',
'è': 'è',
'é': 'é',
'ê': 'ê',
'ë': 'ë',
'ì': 'ì',
'í': 'í',
'î': 'î',
'ï': 'ï',
'ò': 'ò',
'ó': 'ó',
'ô': 'ô',
'ö': 'ö',
'ø': 'ø',
'ù': 'ù',
'ú': 'ú',
'û': 'û',
'ü': 'ü'
},
'symbols': {
'': '',
'—': '—',
'°': '°',
'″': '″',
'': '',
'←': '←',
'→': '→',
'·': '·',
'§': '§'
}
};
};
/* Inheritance */
OO.inheritClass( ve.ui.SpecialCharacterInspector, ve.ui.Inspector );
/* Static properties */
ve.ui.SpecialCharacterInspector.static.name = 'specialcharacter';
ve.ui.SpecialCharacterInspector.static.icon = 'specialcharacter';
ve.ui.SpecialCharacterInspector.static.titleMessage = 'visualeditor-specialcharacterinspector-title';
ve.ui.SpecialCharacterInspector.static.removeable = false;
/* Methods */
/**
* Handle frame ready events.
*
* @method
*/
ve.ui.SpecialCharacterInspector.prototype.initialize = function () {
// Parent method
ve.ui.Inspector.prototype.initialize.call( this );
this.$spinner = this.$( '<div>' ).addClass( 've-specialchar-spinner' );
this.$form.append( this.$spinner );
};
/**
* Handle the inspector being setup.
*
* @method
* @param {Object} [data] Inspector opening data
*/
ve.ui.SpecialCharacterInspector.prototype.setup = function ( data ) {
// Parent method
ve.ui.Inspector.prototype.setup.call( this, data );
// Preserve initial selection so we can collapse cursor position
// after we're done adding
this.initialSelection = this.surface.getModel().getSelection();
this.getCharList().done( ve.bind( function() {
// Now we can rebuild our button list
// We only want to rebuild the list if we don't already have it
if ( !this.$buttonDomList ) {
// Start with the spinner showing
this.$spinner.show();
this.buildButtonList().done( ve.bind( function() {
// Append the new button list
this.$form.append( this.$buttonDomList );
// Done, hide the spinner
this.$spinner.hide();
}, this ) );
}
}, this ) );
};
/**
* Get the special character list object
* This can also be an AJAX call with default fallback
*
* @returns {jQuery.Promise}
*/
ve.ui.SpecialCharacterInspector.prototype.getCharList = function () {
var charslist, charobj,
deferred = $.Deferred();
// Don't request the character list again if we already have it
if ( !this.characters ) {
// Get the character list
charslist = ve.msg( 'visualeditor-specialcharinspector-characterlist-insert' );
try {
charobj = $.parseJSON( charslist );
} catch ( err ) {
// There was no character list found, or the character list message is
// invalid json string. Force a fallback to the minimal character list
charobj = this.minimalCharacterList;
ve.log( 've.ui.SpecialCharacterInspector: Could not parse the Special Character list; using default.');
ve.log( err.message );
} finally {
this.characters = charobj;
deferred.resolve();
}
}
return deferred.promise();
};
/**
* Builds the button DOM list based on the character list
*
* @returns {jQuery.Promise}
*/
ve.ui.SpecialCharacterInspector.prototype.buildButtonList = function () {
var category, categoryButtons,
deferred = $.Deferred(),
$widgetOutput = this.$( '<div>' ).addClass( 've-specialchar-list' );
if ( !this.characters ) {
deferred.reject();
}
for ( category in this.characters ) {
categoryButtons = new ve.ui.GroupButtonWidget( {
'groupName': category,
'group': this.characters[category],
'aggregations': { 'click': 'click' }
} );
categoryButtons.connect( this, { 'click': 'onSpecialCharAdd' } );
$widgetOutput
.append( this.$( '<h2>').text( category ) )
.append( categoryButtons.$element );
}
this.$buttonDomList = $widgetOutput;
deferred.resolve();
return deferred.promise();
};
/**
* Handle the click event on the button groups. The value of the selection will be inserted
* into the text
*
* @param {OO.ui.PushButtonWidget} button The value attached to the clicked button
*/
ve.ui.SpecialCharacterInspector.prototype.onSpecialCharAdd = function ( button ) {
var fragment = this.surface.getModel().getFragment( null, true ),
value = button.returnValue;
// Insert the character
if ( value !== undefined ) {
// get the insertion value (it could be in any category)
this.addedChar = value;
fragment.insertContent( value, false );
}
};
/**
* @inheritdoc
*/
ve.ui.SpecialCharacterInspector.prototype.teardown = function ( data ) {
var selection;
// Collapse selection after the inserted content
if ( this.addedChar ) {
selection = new ve.Range( this.initialSelection.start + this.addedChar.length );
this.surface.execute( 'content', 'select', selection );
}
// reset
this.addedChar = null;
// Parent method
ve.ui.Inspector.prototype.teardown.call( this, data );
};
/* Registration */
ve.ui.inspectorFactory.register( ve.ui.SpecialCharacterInspector );

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -41,3 +41,20 @@
padding: 0;
white-space: nowrap;
}
/* ve.ui.SpecialCharacterInspector.js */
.ve-specialchar-spinner {
/* There might be a better place for a spinner? */
/* @embed */
background-image: url(images/spinner.gif);
width: 31px;
height: 31px;
margin-left: auto;
margin-right: auto;
}
.ve-specialchar-list h2 {
font-size: 1em;
text-transform: capitalize;
}

View file

@ -108,3 +108,9 @@
border-bottom-left-radius: 0;
border-bottom-width: 0;
}
/* ve.ui.GroupButtonWidget.js */
.ve-ui-groupButtonWidget {
white-space: normal;
}

View file

@ -119,3 +119,23 @@ ve.ui.LinkInspectorTool.static.titleMessage = 'visualeditor-annotationbutton-lin
ve.ui.LinkInspectorTool.static.inspector = 'link';
ve.ui.LinkInspectorTool.static.modelClasses = [ ve.dm.LinkAnnotation ];
ve.ui.toolFactory.register( ve.ui.LinkInspectorTool );
/**
* Insert characters tool.
*
* @class
* @extends ve.ui.InspectorTool
* @constructor
* @param {OO.ui.ToolGroup} toolGroup
* @param {Object} [config] Configuration options
*/
ve.ui.InsertCharacterInspectorTool = function VeUiInsertCharacterInspectorTool( toolGroup, config ) {
ve.ui.InspectorTool.call( this, toolGroup, config );
};
OO.inheritClass( ve.ui.InsertCharacterInspectorTool, ve.ui.InspectorTool );
ve.ui.InsertCharacterInspectorTool.static.name = 'specialcharacter';
ve.ui.InsertCharacterInspectorTool.static.group = 'insert';
ve.ui.InsertCharacterInspectorTool.static.icon = 'special-character';
ve.ui.InsertCharacterInspectorTool.static.titleMessage = 'visualeditor-specialcharacter-button-tooltip';
ve.ui.InsertCharacterInspectorTool.static.inspector = 'specialcharacter';
ve.ui.toolFactory.register( ve.ui.InsertCharacterInspectorTool );

View file

@ -0,0 +1,50 @@
/*!
* VisualEditor UserInterface GroupButtonWidget class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.ui.GroupButtonWidget object.
*
* @class
* @extends OO.ui.Widget
*
* @param {Object} [config] Configuration options
* @cfg {Object} [group] Button group parameters organized by { 'label': returnValue }
* where 'returnValue' is the value associated with the button
*/
ve.ui.GroupButtonWidget = function VeUiGroupButtonWidget( config ) {
var item, button, arrButtons = [];
// Parent constructor
OO.ui.Widget.call( this, config );
// Mixin constructors
OO.ui.GroupElement.call( this, this.$( '<div>' ), config );
// Initialization
this.value = null;
this.group = config.group;
this.buttons = {};
// Set up the buttons
for ( item in this.group ) {
button = new OO.ui.PushButtonWidget( {
'label': item,
} );
// store value
button.returnValue = this.group[item];
arrButtons.push( button );
}
this.addItems( arrButtons );
this.$element.append( this.$group.addClass( 've-ui-groupButtonWidget' ) );
};
/* Inheritance */
OO.inheritClass( ve.ui.GroupButtonWidget, OO.ui.Widget );
OO.mixinClass( ve.ui.GroupButtonWidget, OO.ui.GroupElement );