mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-27 15:40:00 +00:00
Rework extensions setup (v 3.1.0)
* Cleanup the init process of this extension (PHP & JS setup) * Make it useable for other extensions like MobileFrontend. * Call new hook CodeMirrorGetAdditionalResources instead of CodeMirrorGetExtensionMode Bug: T91796 Change-Id: I9763c40835c2edddafb0dcbacdf53a86f663b8cd
This commit is contained in:
parent
dd933b665a
commit
3253edba8b
|
@ -2,72 +2,180 @@
|
|||
|
||||
|
||||
class CodeMirrorHooks {
|
||||
|
||||
static $globalVariableScript = array();
|
||||
/** @var null|array Cached version of global variables, if available, otherwise null */
|
||||
private static $globalVariableScript = null;
|
||||
/** @var null|boolean Saves, if CodeMirror should be loaded on this page or not */
|
||||
private static $isEnabled = null;
|
||||
/** @var array values passed from other extensions for use in self::getGlobalVariables() */
|
||||
private static $extModes = array();
|
||||
|
||||
/**
|
||||
* ResourceLoaderRegisterModules hook handler to conditionally register CodeMirror modules
|
||||
*
|
||||
* @global Parser $wgParser
|
||||
* @global Language $wgContLang
|
||||
* @param EditPage $editPage
|
||||
* @param OutputPage $output
|
||||
* @return boolean
|
||||
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules
|
||||
*
|
||||
* @param ResourceLoader &$resourceLoader The ResourceLoader object
|
||||
* @return bool Always true
|
||||
*/
|
||||
public static function onEditPageShowEditFormInitial( EditPage $editPage, OutputPage $output ) {
|
||||
global $wgParser, $wgContLang;
|
||||
public static function onResourceLoaderRegisterModules( ResourceLoader $rl ) {
|
||||
global $wgCodeMirrorResourceTemplate;
|
||||
|
||||
self::$globalVariableScript['ExtMode'] = array(
|
||||
self::$extModes = array(
|
||||
'tag' => array(
|
||||
'pre' => 'mw-tag-pre',
|
||||
'nowiki' => 'mw-tag-nowiki',
|
||||
),
|
||||
'func' => array(),
|
||||
'data' => array()
|
||||
'data' => array(),
|
||||
);
|
||||
$extResources = array(
|
||||
'scripts' => array(),
|
||||
'styles' => array(),
|
||||
'messages' => array(),
|
||||
'dependencies' => array( 'ext.CodeMirror.lib' => true ),
|
||||
);
|
||||
\wfRunHooks( 'CodeMirrorGetExtensionMode', array( &self::$globalVariableScript['ExtMode'], &$module, &$output ) );
|
||||
|
||||
if ( false === isset( $wgParser->mFunctionSynonyms ) ) {
|
||||
$wgParser->initialiseVariables();
|
||||
$wgParser->firstCallInit();
|
||||
}
|
||||
self::$globalVariableScript['Tags'] = array_fill_keys( $wgParser->getTags(), true );
|
||||
|
||||
$mw = $wgContLang->getMagicWords();
|
||||
self::$globalVariableScript['DoubleUnderscore'] = array( array(), array() );
|
||||
foreach ( MagicWord::getDoubleUnderscoreArray()->names as $name ) {
|
||||
if ( isset( $mw[$name] ) ) {
|
||||
$caseSensitive = array_shift( $mw[$name] ) == 0 ? 0 : 1;
|
||||
foreach ( $mw[$name] as $n ) {
|
||||
self::$globalVariableScript['DoubleUnderscore'][$caseSensitive][ $caseSensitive ? $n : $wgContLang->lc( $n ) ] = $name;
|
||||
}
|
||||
} else {
|
||||
self::$globalVariableScript['DoubleUnderscore'][0][] = $name;
|
||||
}
|
||||
// Check if WikiEditor is installed and add it as a dependency
|
||||
// FIXME: Is there no better solution doing it?
|
||||
$resourceModules = $rl->getConfig()->get( 'ResourceModules' );
|
||||
if ( isset( $resourceModules['ext.wikiEditor'] ) ) {
|
||||
$extResources['dependencies']['ext.wikiEditor'] = true;
|
||||
}
|
||||
|
||||
self::$globalVariableScript['FunctionSynonyms'] = $wgParser->mFunctionSynonyms;
|
||||
foreach ( MagicWord::getVariableIDs() as $name ) {
|
||||
if ( isset( $mw[$name] ) ) {
|
||||
$caseSensitive = array_shift( $mw[$name] ) == 0 ? 0 : 1;
|
||||
foreach ( $mw[$name] as $n ) {
|
||||
self::$globalVariableScript['FunctionSynonyms'][$caseSensitive][ $caseSensitive ? $n : $wgContLang->lc( $n ) ] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
// enable other extensions to add additional resources and modes
|
||||
Hooks::run( 'CodeMirrorGetAdditionalResources', array( &$extResources, &self::$extModes ) );
|
||||
|
||||
// Prepare array of resources for ResourceLoader
|
||||
$codeMirror = array(
|
||||
'scripts' => array_keys( $extResources['scripts'] ),
|
||||
'styles' => array_keys( $extResources['styles'] ),
|
||||
'messages' => array_keys( $extResources['messages'] ),
|
||||
'dependencies' => array_keys( $extResources['dependencies'] ),
|
||||
'group' => 'ext.CodeMirror',
|
||||
) + $wgCodeMirrorResourceTemplate;
|
||||
|
||||
$rl->register( array( 'ext.CodeMirror.other' => $codeMirror ) );
|
||||
|
||||
self::$globalVariableScript['UrlProtocols'] = $wgParser->mUrlProtocols;// wfUrlProtocolsWithoutProtRel();
|
||||
// self::$globalVariableScript['LinkTrailCharacters'] = $wgContLang->linkTrail();
|
||||
$output->addModules( 'ext.CodeMirror.init' );
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function onMakeGlobalVariablesScript( array &$vars ) {
|
||||
foreach ( self::$globalVariableScript as $key=> $value ) {
|
||||
$vars["extCodeMirror$key"] = $value;
|
||||
/**
|
||||
* Checks, if CodeMirror should be loaded on this page or not.
|
||||
*
|
||||
* @param IContextSource $context The current ContextSource object
|
||||
* @return boolean
|
||||
*/
|
||||
private static function isCodeMirrorEnabled( IContextSource $context ) {
|
||||
// Check, if we already checked, if page action is editing, if not, do it now
|
||||
if ( is_null( self::$isEnabled ) ) {
|
||||
// edit can be 'edit' and 'submit'
|
||||
self::$isEnabled = in_array(
|
||||
Action::getActionName( $context ),
|
||||
array( 'edit', 'submit' )
|
||||
);
|
||||
}
|
||||
|
||||
return self::$isEnabled;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of variables for CodeMirror to work (tags and so on)
|
||||
*
|
||||
* @param IContextSource $context The current ContextSource object
|
||||
* @return array
|
||||
*/
|
||||
public static function getGlobalVariables( IContextSource $context ) {
|
||||
global $wgParser;
|
||||
|
||||
// if we already created these variable array, return it
|
||||
if ( !self::$globalVariableScript ) {
|
||||
$contObj = $context->getLanguage();
|
||||
|
||||
if ( !isset( $wgParser->mFunctionSynonyms ) ) {
|
||||
$wgParser->initialiseVariables();
|
||||
$wgParser->firstCallInit();
|
||||
}
|
||||
|
||||
// initialize global vars
|
||||
$globalVariableScript = array(
|
||||
'ExtModes' => self::$extModes,
|
||||
'Tags' => array_fill_keys( $wgParser->getTags(), true ),
|
||||
'DoubleUnderscore' => array( array(), array() ),
|
||||
'FunctionSynonyms' => $wgParser->mFunctionSynonyms,
|
||||
'UrlProtocols' => $wgParser->mUrlProtocols,
|
||||
'LinkTrailCharacters' => $contObj->linkTrail(),
|
||||
);
|
||||
|
||||
$mw = $contObj->getMagicWords();
|
||||
foreach ( MagicWord::getDoubleUnderscoreArray()->names as $name ) {
|
||||
if ( isset( $mw[$name] ) ) {
|
||||
$caseSensitive = array_shift( $mw[$name] ) == 0 ? 0 : 1;
|
||||
foreach ( $mw[$name] as $n ) {
|
||||
$globalVariableScript['DoubleUnderscore'][$caseSensitive][ $caseSensitive ? $n : $contObj->lc( $n ) ] = $name;
|
||||
}
|
||||
} else {
|
||||
$globalVariableScript['DoubleUnderscore'][0][] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( MagicWord::getVariableIDs() as $name ) {
|
||||
if ( isset( $mw[$name] ) ) {
|
||||
$caseSensitive = array_shift( $mw[$name] ) == 0 ? 0 : 1;
|
||||
foreach ( $mw[$name] as $n ) {
|
||||
$globalVariableScript['FunctionSynonyms'][$caseSensitive][ $caseSensitive ? $n : $contObj->lc( $n ) ] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prefix all variables and save it into class variable
|
||||
foreach ( $globalVariableScript as $key=> $value ) {
|
||||
self::$globalVariableScript["extCodeMirror$key"] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$globalVariableScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* MakeGlobalVariablesScript hook handler
|
||||
*
|
||||
* @see https://www.mediawiki.org/wiki/Manual:Hooks/MakeGlobalVariablesScript
|
||||
*
|
||||
* @param ResourceLoader &$resourceLoader The ResourceLoader object
|
||||
* @return bool Always true
|
||||
*/
|
||||
public static function onMakeGlobalVariablesScript( array &$vars, OutputPage $out ) {
|
||||
$context = $out->getContext();
|
||||
// add CodeMirror vars only for edit pages
|
||||
if ( self::isCodeMirrorEnabled( $context ) ) {
|
||||
$vars += self::getGlobalVariables( $context );
|
||||
}
|
||||
}
|
||||
|
||||
public static function getPreferences( $user, &$defaultPreferences ) {
|
||||
/**
|
||||
* BeforePageDisplay hook handler
|
||||
*
|
||||
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay
|
||||
*
|
||||
* @param ResourceLoader &$resourceLoader The ResourceLoader object
|
||||
* @return bool Always true
|
||||
*/
|
||||
public static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) {
|
||||
if ( self::isCodeMirrorEnabled( $out->getContext() ) ) {
|
||||
$out->addModules( 'ext.CodeMirror.init' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GetPreferences hook handler
|
||||
*
|
||||
* @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences
|
||||
*
|
||||
* @param ResourceLoader &$resourceLoader The ResourceLoader object
|
||||
* @return bool Always true
|
||||
*/
|
||||
public static function onGetPreferences( User $user, &$defaultPreferences ) {
|
||||
$defaultPreferences['usecodemirror'] = array(
|
||||
'type' => 'api',
|
||||
'default' => '1',
|
||||
|
|
|
@ -15,7 +15,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
|
|||
die( 'This file is an extension to MediaWiki and thus not a valid entry point.' );
|
||||
}
|
||||
|
||||
const EXT_CODEMIRROR_VERSION = '3.0.1';
|
||||
const EXT_CODEMIRROR_VERSION = '3.1.0';
|
||||
|
||||
// Register this extension on Special:Version
|
||||
$wgExtensionCredits['parserhook'][] = array(
|
||||
|
@ -23,7 +23,7 @@ $wgExtensionCredits['parserhook'][] = array(
|
|||
'name' => 'CodeMirror',
|
||||
'version' => EXT_CODEMIRROR_VERSION,
|
||||
'url' => 'https://www.mediawiki.org/wiki/Extension:CodeMirror',
|
||||
'author' => '[https://www.mediawiki.org/wiki/User:Pastakhov Pavel Astakhov]',
|
||||
'author' => array( '[https://www.mediawiki.org/wiki/User:Pastakhov Pavel Astakhov]', 'Florian Schmidt' ),
|
||||
'descriptionmsg' => 'codemirror-desc'
|
||||
);
|
||||
|
||||
|
@ -33,60 +33,41 @@ $wgExtensionMessagesFiles['CodeMirror'] = __DIR__ . '/CodeMirror.i18n.php';
|
|||
|
||||
$wgAutoloadClasses['CodeMirrorHooks'] = __DIR__ . '/CodeMirror.hooks.php';
|
||||
|
||||
$wgHooks['EditPage::showEditForm:initial'][] = 'CodeMirrorHooks::onEditPageShowEditFormInitial';
|
||||
//$wgHooks['EditPage::showReadOnlyForm:initial'][] = 'CodeMirrorHooks::onEditPageShowEditFormInitial';
|
||||
$wgHooks['MakeGlobalVariablesScript'][] = 'CodeMirrorHooks::onMakeGlobalVariablesScript';
|
||||
$wgHooks['GetPreferences'][] = 'CodeMirrorHooks::getPreferences';
|
||||
$wgHooks['BeforePageDisplay'][] = 'CodeMirrorHooks::onBeforePageDisplay';
|
||||
$wgHooks['GetPreferences'][] = 'CodeMirrorHooks::onGetPreferences';
|
||||
$wgHooks['ResourceLoaderRegisterModules'][] = 'CodeMirrorHooks::onResourceLoaderRegisterModules';
|
||||
|
||||
$wgHooks['ResourceLoaderRegisterModules'][] = function () {
|
||||
global $wgResourceModules, $wgCodeMirrorResources;
|
||||
if ( isset($wgResourceModules['ext.wikiEditor']) ) {
|
||||
$wgCodeMirrorResources['dependencies']['ext.wikiEditor'] = true;
|
||||
}
|
||||
if ( isset( $wgCodeMirrorResources['scripts'] ) ) {
|
||||
$wgResourceModules['ext.CodeMirror.other']['scripts'] = array_keys( $wgCodeMirrorResources['scripts'] );
|
||||
}
|
||||
if ( isset( $wgCodeMirrorResources['styles'] ) ) {
|
||||
$wgResourceModules['ext.CodeMirror.other']['styles'] = array_keys( $wgCodeMirrorResources['styles'] );
|
||||
}
|
||||
if ( isset( $wgCodeMirrorResources['messages'] ) ) {
|
||||
$wgResourceModules['ext.CodeMirror.other']['messages'] = array_keys( $wgCodeMirrorResources['messages'] );
|
||||
}
|
||||
if ( isset( $wgCodeMirrorResources['dependencies'] ) ) {
|
||||
$wgResourceModules['ext.CodeMirror.other']['dependencies'] = array_keys( $wgCodeMirrorResources['dependencies'] );
|
||||
}
|
||||
};
|
||||
|
||||
$tpl = array(
|
||||
$wgCodeMirrorResourceTemplate = array(
|
||||
'localBasePath' => __DIR__ . '/resources',
|
||||
'remoteExtPath' => 'CodeMirror/resources',
|
||||
);
|
||||
$wgResourceModules['ext.CodeMirror.init'] = array(
|
||||
'group' => 'ext.CodeMirror',
|
||||
'scripts' => 'ext.CodeMirror.js',
|
||||
'dependencies' => array( 'ext.CodeMirror.lib', 'ext.CodeMirror.other' ),
|
||||
) + $tpl;
|
||||
|
||||
$wgResourceModules['ext.CodeMirror.lib'] = array(
|
||||
$wgResourceModules['ext.CodeMirror.init'] = $wgCodeMirrorResourceTemplate + array(
|
||||
'dependencies' => array(
|
||||
'ext.CodeMirror.lib',
|
||||
'ext.CodeMirror.other',
|
||||
'mediawiki.api',
|
||||
'jquery.textSelection',
|
||||
'user.options',
|
||||
),
|
||||
'scripts' => array(
|
||||
'ext.CodeMirror.js'
|
||||
),
|
||||
'group' => 'ext.CodeMirror',
|
||||
);
|
||||
|
||||
$wgResourceModules['ext.CodeMirror.lib'] = $wgCodeMirrorResourceTemplate + array(
|
||||
'scripts' => array(
|
||||
'lib/codemirror/lib/codemirror.js',
|
||||
'lib/codemirror/addon/selection/active-line.js',
|
||||
'mode/mediawiki/mediawiki.js',
|
||||
//'mode/mediawiki/matchMW.js',
|
||||
),
|
||||
'styles' => array(
|
||||
'lib/codemirror/lib/codemirror.css',
|
||||
'lib/codemirror/addon/lint/lint.css',
|
||||
'mode/mediawiki/mediawiki.css',
|
||||
),
|
||||
) + $tpl;
|
||||
|
||||
$wgResourceModules['ext.CodeMirror.other'] = array(
|
||||
'group' => 'ext.CodeMirror',
|
||||
) + $tpl;
|
||||
|
||||
if ( false === isset( $wgCodeMirrorResources ) ) {
|
||||
$wgCodeMirrorResources = array();
|
||||
}
|
||||
$wgCodeMirrorResources['dependencies']['ext.CodeMirror.lib'] = true;
|
||||
'targets' => array( 'mobile', 'desktop' ),
|
||||
);
|
||||
|
|
|
@ -1,281 +1,261 @@
|
|||
/* global CodeMirror, mw, $ */
|
||||
/* global CodeMirror, mediaWiki */
|
||||
( function ( mw, $ ) {
|
||||
// codeMirror needs a special textselection jQuery function to work, save the current one to restore when
|
||||
// CodeMirror get's disabled.
|
||||
var origTextSelection = $.fn.textSelection,
|
||||
codeMirror = mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1,
|
||||
api = new mw.Api(),
|
||||
// function for a textselection function for CodeMirror
|
||||
cmTextSelection = function ( command, options ) {
|
||||
if ( !codeMirror ) {
|
||||
return origTextSelection( command, options );
|
||||
}
|
||||
var fn, retval;
|
||||
|
||||
var initExtCodeMirror = function() {
|
||||
var origTextSelection = $.fn.textSelection, codeMirror = false;
|
||||
fn = {
|
||||
/**
|
||||
* Get the contents of the textarea
|
||||
*/
|
||||
getContents: function () {
|
||||
return codeMirror.doc.getValue();
|
||||
},
|
||||
|
||||
// Replace jquery.textSelection.js
|
||||
var cmTextSelection = function ( command, options ) {
|
||||
if ( ! codeMirror ) {
|
||||
return origTextSelection( command, options );
|
||||
}
|
||||
var fn, retval;
|
||||
/**
|
||||
* Get the currently selected text in this textarea. Will focus the textarea
|
||||
* in some browsers (IE/Opera)
|
||||
*/
|
||||
getSelection: function () {
|
||||
return codeMirror.doc.getSelection();
|
||||
},
|
||||
|
||||
fn = {
|
||||
/**
|
||||
* Get the contents of the textarea
|
||||
*/
|
||||
getContents: function () {
|
||||
return codeMirror.doc.getValue();
|
||||
},
|
||||
/**
|
||||
* Inserts text at the beginning and end of a text selection, optionally
|
||||
* inserting text at the caret when selection is empty.
|
||||
*/
|
||||
encapsulateSelection: function ( options ) {
|
||||
return this.each( function () {
|
||||
var insertText, selText,
|
||||
selectPeri = options.selectPeri,
|
||||
pre = options.pre, post = options.post;
|
||||
|
||||
/**
|
||||
* Get the currently selected text in this textarea. Will focus the textarea
|
||||
* in some browsers (IE/Opera)
|
||||
*/
|
||||
getSelection: function () {
|
||||
return codeMirror.doc.getSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts text at the beginning and end of a text selection, optionally
|
||||
* inserting text at the caret when selection is empty.
|
||||
*/
|
||||
encapsulateSelection: function ( options ) {
|
||||
return this.each( function () {
|
||||
var insertText, selText,
|
||||
selectPeri = options.selectPeri,
|
||||
pre = options.pre, post = options.post;
|
||||
|
||||
if ( options.selectionStart !== undefined ) {
|
||||
//fn[command].call( this, options );
|
||||
fn.setSelection( { 'start': options.selectionStart, 'end': options.selectionEnd } ); // not tested
|
||||
}
|
||||
|
||||
selText = codeMirror.doc.getSelection();
|
||||
if ( !selText ) {
|
||||
selText = options.peri;
|
||||
} else if ( options.replace ) {
|
||||
selectPeri = false;
|
||||
selText = options.peri;
|
||||
} else {
|
||||
selectPeri = false;
|
||||
while ( selText.charAt( selText.length - 1 ) === ' ' ) {
|
||||
// Exclude ending space char
|
||||
selText = selText.substring( 0, selText.length - 1 );
|
||||
post += ' ';
|
||||
if ( options.selectionStart !== undefined ) {
|
||||
//fn[command].call( this, options );
|
||||
fn.setSelection( { 'start': options.selectionStart, 'end': options.selectionEnd } ); // not tested
|
||||
}
|
||||
while ( selText.charAt( 0 ) === ' ' ) {
|
||||
// Exclude prepending space char
|
||||
selText = selText.substring( 1, selText.length );
|
||||
pre = ' ' + pre;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the splitlines stuff.
|
||||
*
|
||||
* Wrap each line of the selected text with pre and post
|
||||
*/
|
||||
function doSplitLines( selText, pre, post ) {
|
||||
var i,
|
||||
insertText = '',
|
||||
selTextArr = selText.split( '\n' );
|
||||
for ( i = 0; i < selTextArr.length; i++ ) {
|
||||
insertText += pre + selTextArr[i] + post;
|
||||
if ( i !== selTextArr.length - 1 ) {
|
||||
insertText += '\n';
|
||||
selText = codeMirror.doc.getSelection();
|
||||
if ( !selText ) {
|
||||
selText = options.peri;
|
||||
} else if ( options.replace ) {
|
||||
selectPeri = false;
|
||||
selText = options.peri;
|
||||
} else {
|
||||
selectPeri = false;
|
||||
while ( selText.charAt( selText.length - 1 ) === ' ' ) {
|
||||
// Exclude ending space char
|
||||
selText = selText.substring( 0, selText.length - 1 );
|
||||
post += ' ';
|
||||
}
|
||||
while ( selText.charAt( 0 ) === ' ' ) {
|
||||
// Exclude prepending space char
|
||||
selText = selText.substring( 1, selText.length );
|
||||
pre = ' ' + pre;
|
||||
}
|
||||
}
|
||||
return insertText;
|
||||
}
|
||||
|
||||
if ( options.splitlines ) {
|
||||
selectPeri = false;
|
||||
insertText = doSplitLines( selText, pre, post );
|
||||
} else {
|
||||
insertText = pre + selText + post;
|
||||
}
|
||||
|
||||
var startCursor = codeMirror.doc.getCursor( true );
|
||||
if ( options.ownline ) {
|
||||
if ( startCursor.ch !== 0 ) {
|
||||
insertText = '\n' + insertText;
|
||||
pre += '\n';
|
||||
/**
|
||||
* Do the splitlines stuff.
|
||||
*
|
||||
* Wrap each line of the selected text with pre and post
|
||||
*/
|
||||
function doSplitLines( selText, pre, post ) {
|
||||
var i,
|
||||
insertText = '',
|
||||
selTextArr = selText.split( '\n' );
|
||||
for ( i = 0; i < selTextArr.length; i++ ) {
|
||||
insertText += pre + selTextArr[i] + post;
|
||||
if ( i !== selTextArr.length - 1 ) {
|
||||
insertText += '\n';
|
||||
}
|
||||
}
|
||||
return insertText;
|
||||
}
|
||||
var endCursor = codeMirror.doc.getCursor( false );
|
||||
if ( codeMirror.doc.getLine( endCursor.line ).length !== endCursor.ch ) {
|
||||
insertText += '\n';
|
||||
post += '\n';
|
||||
|
||||
if ( options.splitlines ) {
|
||||
selectPeri = false;
|
||||
insertText = doSplitLines( selText, pre, post );
|
||||
} else {
|
||||
insertText = pre + selText + post;
|
||||
}
|
||||
|
||||
var startCursor = codeMirror.doc.getCursor( true );
|
||||
if ( options.ownline ) {
|
||||
if ( startCursor.ch !== 0 ) {
|
||||
insertText = '\n' + insertText;
|
||||
pre += '\n';
|
||||
}
|
||||
var endCursor = codeMirror.doc.getCursor( false );
|
||||
if ( codeMirror.doc.getLine( endCursor.line ).length !== endCursor.ch ) {
|
||||
insertText += '\n';
|
||||
post += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
codeMirror.doc.replaceSelection( insertText );
|
||||
|
||||
if ( selectPeri ) {
|
||||
codeMirror.doc.setSelection(
|
||||
codeMirror.doc.posFromIndex( codeMirror.doc.indexFromPos( startCursor ) + pre.length ),
|
||||
codeMirror.doc.posFromIndex( codeMirror.doc.indexFromPos( startCursor ) + pre.length + selText.length )
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the position (in resolution of bytes not necessarily characters)
|
||||
* in a textarea
|
||||
*/
|
||||
getCaretPosition: function ( options ) {
|
||||
var caretPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( true ) );
|
||||
if ( options.startAndEnd ) {
|
||||
var endPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( false ) );
|
||||
return [ caretPos, endPos ];
|
||||
}
|
||||
return caretPos;
|
||||
},
|
||||
|
||||
codeMirror.doc.replaceSelection( insertText );
|
||||
setSelection: function ( options ) {
|
||||
return this.each( function () {
|
||||
codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) );
|
||||
});
|
||||
},
|
||||
|
||||
if ( selectPeri ) {
|
||||
codeMirror.doc.setSelection(
|
||||
codeMirror.doc.posFromIndex( codeMirror.doc.indexFromPos( startCursor ) + pre.length ),
|
||||
codeMirror.doc.posFromIndex( codeMirror.doc.indexFromPos( startCursor ) + pre.length + selText.length )
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the position (in resolution of bytes not necessarily characters)
|
||||
* in a textarea
|
||||
*/
|
||||
getCaretPosition: function ( options ) {
|
||||
var caretPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( true ) );
|
||||
if ( options.startAndEnd ) {
|
||||
var endPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( false ) );
|
||||
return [ caretPos, endPos ];
|
||||
/**
|
||||
* Scroll a textarea to the current cursor position. You can set the cursor
|
||||
* position with setSelection()
|
||||
* @param options boolean Whether to force a scroll even if the caret position
|
||||
* is already visible. Defaults to false
|
||||
*/
|
||||
scrollToCaretPosition: function ( /* options */ ) {
|
||||
return this.each(function () {
|
||||
codeMirror.scrollIntoView( null );
|
||||
});
|
||||
}
|
||||
return caretPos;
|
||||
},
|
||||
};
|
||||
|
||||
setSelection: function ( options ) {
|
||||
return this.each( function () {
|
||||
codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) );
|
||||
});
|
||||
},
|
||||
switch ( command ) {
|
||||
//case 'getContents': // no params
|
||||
//case 'setContents': // no params with defaults
|
||||
//case 'getSelection': // no params
|
||||
case 'encapsulateSelection':
|
||||
options = $.extend( {
|
||||
pre: '', // Text to insert before the cursor/selection
|
||||
peri: '', // Text to insert between pre and post and select afterwards
|
||||
post: '', // Text to insert after the cursor/selection
|
||||
ownline: false, // Put the inserted text on a line of its own
|
||||
replace: false, // If there is a selection, replace it with peri instead of leaving it alone
|
||||
selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
|
||||
splitlines: false, // If multiple lines are selected, encapsulate each line individually
|
||||
selectionStart: undefined, // Position to start selection at
|
||||
selectionEnd: undefined // Position to end selection at. Defaults to start
|
||||
}, options );
|
||||
break;
|
||||
case 'getCaretPosition':
|
||||
options = $.extend( {
|
||||
// Return [start, end] instead of just start
|
||||
startAndEnd: false
|
||||
}, options );
|
||||
// FIXME: We may not need character position-based functions if we insert markers in the right places
|
||||
break;
|
||||
case 'setSelection':
|
||||
options = $.extend( {
|
||||
// Position to start selection at
|
||||
start: undefined,
|
||||
// Position to end selection at. Defaults to start
|
||||
end: undefined,
|
||||
// Element to start selection in (iframe only)
|
||||
startContainer: undefined,
|
||||
// Element to end selection in (iframe only). Defaults to startContainer
|
||||
endContainer: undefined
|
||||
}, options );
|
||||
|
||||
/**
|
||||
* Scroll a textarea to the current cursor position. You can set the cursor
|
||||
* position with setSelection()
|
||||
* @param options boolean Whether to force a scroll even if the caret position
|
||||
* is already visible. Defaults to false
|
||||
*/
|
||||
scrollToCaretPosition: function ( /* options */ ) {
|
||||
return this.each(function () {
|
||||
codeMirror.scrollIntoView( null );
|
||||
});
|
||||
if ( options.end === undefined ) {
|
||||
options.end = options.start;
|
||||
}
|
||||
if ( options.endContainer === undefined ) {
|
||||
options.endContainer = options.startContainer;
|
||||
}
|
||||
// FIXME: We may not need character position-based functions if we insert markers in the right places
|
||||
break;
|
||||
case 'scrollToCaretPosition':
|
||||
options = $.extend( {
|
||||
force: false // Force a scroll even if the caret position is already visible
|
||||
}, options );
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
switch ( command ) {
|
||||
//case 'getContents': // no params
|
||||
//case 'setContents': // no params with defaults
|
||||
//case 'getSelection': // no params
|
||||
case 'encapsulateSelection':
|
||||
options = $.extend( {
|
||||
pre: '', // Text to insert before the cursor/selection
|
||||
peri: '', // Text to insert between pre and post and select afterwards
|
||||
post: '', // Text to insert after the cursor/selection
|
||||
ownline: false, // Put the inserted text on a line of its own
|
||||
replace: false, // If there is a selection, replace it with peri instead of leaving it alone
|
||||
selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
|
||||
splitlines: false, // If multiple lines are selected, encapsulate each line individually
|
||||
selectionStart: undefined, // Position to start selection at
|
||||
selectionEnd: undefined // Position to end selection at. Defaults to start
|
||||
}, options );
|
||||
break;
|
||||
case 'getCaretPosition':
|
||||
options = $.extend( {
|
||||
// Return [start, end] instead of just start
|
||||
startAndEnd: false
|
||||
}, options );
|
||||
// FIXME: We may not need character position-based functions if we insert markers in the right places
|
||||
break;
|
||||
case 'setSelection':
|
||||
options = $.extend( {
|
||||
// Position to start selection at
|
||||
start: undefined,
|
||||
// Position to end selection at. Defaults to start
|
||||
end: undefined,
|
||||
// Element to start selection in (iframe only)
|
||||
startContainer: undefined,
|
||||
// Element to end selection in (iframe only). Defaults to startContainer
|
||||
endContainer: undefined
|
||||
}, options );
|
||||
retval = fn[command].call( this, options );
|
||||
codeMirror.focus();
|
||||
|
||||
if ( options.end === undefined ) {
|
||||
options.end = options.start;
|
||||
}
|
||||
if ( options.endContainer === undefined ) {
|
||||
options.endContainer = options.startContainer;
|
||||
}
|
||||
// FIXME: We may not need character position-based functions if we insert markers in the right places
|
||||
break;
|
||||
case 'scrollToCaretPosition':
|
||||
options = $.extend( {
|
||||
force: false // Force a scroll even if the caret position is already visible
|
||||
}, options );
|
||||
break;
|
||||
}
|
||||
|
||||
retval = fn[command].call( this, options );
|
||||
codeMirror.focus();
|
||||
|
||||
return retval;
|
||||
};
|
||||
|
||||
function setCodeEditorPreference( prefValue ) {
|
||||
var api = new mw.Api();
|
||||
api.postWithToken( 'options', {
|
||||
action: 'options',
|
||||
optionname: 'usecodemirror',
|
||||
optionvalue: prefValue ? 1 : 0
|
||||
} ).fail( function ( code, result ) {
|
||||
mw.log.error( 'Failed to set code editor preference: ' + code + '\n' + result.error );
|
||||
} );
|
||||
}
|
||||
|
||||
if ( mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1 ) {
|
||||
codeMirror = true;
|
||||
}
|
||||
|
||||
function addCodeMirrorToWikiEditor() {
|
||||
(function waitWikiEditor() {
|
||||
return retval;
|
||||
},
|
||||
/**
|
||||
* Adds the CodeMirror button to WikiEditor
|
||||
*/
|
||||
addCodeMirrorToWikiEditor = function () {
|
||||
if ( $( '#wikiEditor-section-main' ).length > 0 ) {
|
||||
$( '#wpTextbox1' ).wikiEditor(
|
||||
'addToToolbar',
|
||||
{
|
||||
'section': 'main',
|
||||
'groups': {
|
||||
'codemirror':{
|
||||
// 'label': 'CodeMirror',
|
||||
'tools': {
|
||||
'CodeMirror': {
|
||||
label: 'CodeMirror',
|
||||
type: 'button',
|
||||
icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/cm-' + (codeMirror ? 'on.png' : 'off.png'),
|
||||
action: {type: 'callback', execute: function( context ){ switchCodeMirror( context ); } }
|
||||
// },
|
||||
// 'Undo':{
|
||||
// label: 'Undo',
|
||||
// type: 'button',
|
||||
// icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/undo.png',
|
||||
// action: {type: 'callback', execute: function(context){alert(context);} }
|
||||
// },
|
||||
// 'Redo':{
|
||||
// label: 'Redo',
|
||||
// type: 'button',
|
||||
// icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/redo.png',
|
||||
// action: {type: 'callback', execute: function(context){alert(context);} }
|
||||
'addToToolbar',
|
||||
{
|
||||
'section': 'main',
|
||||
'groups': {
|
||||
'codemirror':{
|
||||
'tools': {
|
||||
'CodeMirror': {
|
||||
label: 'CodeMirror',
|
||||
type: 'button',
|
||||
// FIXME: There should be a better way?
|
||||
icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/cm-' + ( codeMirror ? 'on.png' : 'off.png' ),
|
||||
action: {
|
||||
type: 'callback',
|
||||
execute: function( context ) {
|
||||
switchCodeMirror( context );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
setTimeout( waitWikiEditor, 500 );
|
||||
}
|
||||
);
|
||||
}
|
||||
})();
|
||||
}
|
||||
};
|
||||
|
||||
if ( $( '#wpTextbox1' ).wikiEditor && mw.user.options.get( 'usebetatoolbar' ) === 1 && mw.user.options.get( 'showtoolbar' ) === 1 ) {
|
||||
addCodeMirrorToWikiEditor();
|
||||
} else {
|
||||
var $image = $( '<img>' ).attr( {
|
||||
width: 23,
|
||||
height: 22,
|
||||
src: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/old-cm-' + (codeMirror ? 'on.png' : 'off.png'),
|
||||
alt: 'CodeMirror',
|
||||
title: 'CodeMirror',
|
||||
id: 'CodeMirrorButton',
|
||||
'class': 'mw-toolbar-editbutton'
|
||||
} ).click( function () {
|
||||
switchCodeMirror( false );
|
||||
return false;
|
||||
/**
|
||||
* Save CodeMirror enabled pref.
|
||||
*
|
||||
* @param {Boolean} prefValue True, if CodeMirror should be enabled by default, otherwise false.
|
||||
*/
|
||||
function setCodeEditorPreference( prefValue ) {
|
||||
api.postWithToken( 'options', {
|
||||
action: 'options',
|
||||
optionname: 'usecodemirror',
|
||||
optionvalue: prefValue ? 1 : 0
|
||||
} ).fail( function ( code, result ) {
|
||||
// FIXME: Should this throw an user visible error message?
|
||||
mw.log.warn( 'Failed to set code editor preference: ' + code + '\n' + result.error );
|
||||
} );
|
||||
|
||||
$( '#toolbar' ).append( $image );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables CodeMirror
|
||||
*
|
||||
* @param {undefined} context Doc needed
|
||||
*/
|
||||
function switchCodeMirror( context ) {
|
||||
//alert( 'switchCodeMirror: ' + codeMirror );
|
||||
var $img, $src;
|
||||
|
||||
if ( context !== false ) {
|
||||
$img = context.modules.toolbar.$toolbar.find( 'img.tool[rel=CodeMirror]' );
|
||||
} else {
|
||||
|
@ -288,18 +268,22 @@ var initExtCodeMirror = function() {
|
|||
codeMirror.toTextArea();
|
||||
codeMirror = false;
|
||||
$.fn.textSelection = origTextSelection;
|
||||
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + (context ? 'cm-off.png' : 'old-cm-off.png' );
|
||||
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + ( context ? 'cm-off.png' : 'old-cm-off.png' );
|
||||
$img.attr( 'src', $src );
|
||||
} else {
|
||||
enableCodeMirror();
|
||||
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + (context ? 'cm-on.png' : 'old-cm-on.png' );
|
||||
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + ( context ? 'cm-on.png' : 'old-cm-on.png' );
|
||||
$img.attr( 'src', $src );
|
||||
setCodeEditorPreference( true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the default textarea with CodeMirror
|
||||
*/
|
||||
function enableCodeMirror() {
|
||||
var textbox1 = $( '#wpTextbox1' );
|
||||
|
||||
if ( textbox1[0].style.display === 'none' ) {
|
||||
return;
|
||||
}
|
||||
|
@ -308,33 +292,55 @@ var initExtCodeMirror = function() {
|
|||
mwextTags: mw.config.get( 'extCodeMirrorTags' ),
|
||||
mwextDoubleUnderscore: mw.config.get( 'extCodeMirrorDoubleUnderscore' ),
|
||||
mwextUrlProtocols: mw.config.get( 'extCodeMirrorUrlProtocols' ),
|
||||
mwextMode: mw.config.get( 'extCodeMirrorExtMode' ),
|
||||
//matchMW: true,
|
||||
mwextModes: mw.config.get( 'extCodeMirrorExtModes' ),
|
||||
styleActiveLine: true,
|
||||
//gutters: ['CodeMirror-mediawiki-gutter'],
|
||||
//lint: true,
|
||||
lineWrapping: true,
|
||||
//indentUnit: 4,
|
||||
//indentWithTabs: true,
|
||||
//matchBrackets: true,
|
||||
//autoCloseBrackets: true,
|
||||
// select mediawiki as text input mode
|
||||
mode: 'text/mediawiki'
|
||||
} );
|
||||
|
||||
if ( window.navigator.userAgent.indexOf('Trident/') > -1 ) { //IE specific code goes here
|
||||
// Our best friend, IE, needs some special css
|
||||
if ( window.navigator.userAgent.indexOf('Trident/') > -1 ) {
|
||||
$( '.CodeMirror' ).addClass( 'CodeMirrorIE' );
|
||||
}
|
||||
|
||||
// set the hight of the textarea
|
||||
codeMirror.setSize( null, textbox1.height() );
|
||||
// Overwrite default textselection of WikiEditor to work with CodeMirror, too
|
||||
$.fn.textSelection = cmTextSelection;
|
||||
}
|
||||
|
||||
/* Check if view is in edit mode and that the required modules are available. Then, customize the toolbar … */
|
||||
if ( $.inArray( mw.config.get( 'wgAction' ), [ 'edit', 'submit' ] ) !== -1 ) {
|
||||
// This function shouldn't be called without user.options is loaded, but it's not guaranteed
|
||||
mw.loader.using( 'user.options', function () {
|
||||
// This can be the string "0" if the user disabled the preference - Bug T54542#555387
|
||||
if ( mw.user.options.get( 'usebetatoolbar' ) === 1 || mw.user.options.get( 'usebetatoolbar' ) === '1' ) {
|
||||
// load wikiEditor's toolbar (if not already) and add our button
|
||||
$.when(
|
||||
mw.loader.using( 'ext.wikiEditor.toolbar' ), $.ready
|
||||
).then( addCodeMirrorToWikiEditor );
|
||||
} else {
|
||||
// If WikiEditor isn't enabled, add CodeMirror button to the default wiki editor toolbar
|
||||
var $image = $( '<img>' ).attr( {
|
||||
width: 23,
|
||||
height: 22,
|
||||
src: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/old-cm-' + (codeMirror ? 'on.png' : 'off.png'),
|
||||
alt: 'CodeMirror',
|
||||
title: 'CodeMirror',
|
||||
id: 'CodeMirrorButton',
|
||||
'class': 'mw-toolbar-editbutton'
|
||||
} ).click( function () {
|
||||
switchCodeMirror( false );
|
||||
return false;
|
||||
} );
|
||||
|
||||
$( '#toolbar' ).append( $image );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// enable CodeMirror
|
||||
if ( codeMirror ) {
|
||||
enableCodeMirror();
|
||||
}
|
||||
};
|
||||
|
||||
$.when(
|
||||
$.ready,
|
||||
mw.loader.using( ['user.options', 'jquery.textSelection', 'mediawiki.api'] )
|
||||
).done( initExtCodeMirror() );
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
@ -433,8 +433,8 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
|
|||
}
|
||||
if ( stream.eat( '>' ) ) {
|
||||
state.extName = name;
|
||||
if ( name in config.mwextMode.tag ) {
|
||||
state.extMode = CodeMirror.getMode( config, config.mwextMode.tag[name] );
|
||||
if ( name in config.mwextModes.tag ) {
|
||||
state.extMode = CodeMirror.getMode( config, config.mwextModes.tag[name] );
|
||||
state.extState = CodeMirror.startState( state.extMode );
|
||||
}
|
||||
state.tokenize = eatExtTagArea( name );
|
||||
|
|
Loading…
Reference in a new issue