mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-09-24 18:58:42 +00:00
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:
parent
d17e031d46
commit
cf237b882e
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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>
|
||||
|
|
232
modules/ve/ui/inspectors/ve.ui.SpecialCharacterInspector.js
Normal file
232
modules/ve/ui/inspectors/ve.ui.SpecialCharacterInspector.js
Normal 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 );
|
BIN
modules/ve/ui/styles/images/spinner.gif
Normal file
BIN
modules/ve/ui/styles/images/spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -108,3 +108,9 @@
|
|||
border-bottom-left-radius: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
/* ve.ui.GroupButtonWidget.js */
|
||||
|
||||
.ve-ui-groupButtonWidget {
|
||||
white-space: normal;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
50
modules/ve/ui/widgets/ve.ui.GroupButtonWidget.js
Normal file
50
modules/ve/ui/widgets/ve.ui.GroupButtonWidget.js
Normal 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 );
|
Loading…
Reference in a new issue