2023-09-19 17:59:29 +00:00
|
|
|
import CodeMirror from './codemirror';
|
2024-03-11 21:03:28 +00:00
|
|
|
import { EditorSelection, Extension } from '@codemirror/state';
|
|
|
|
import { EditorView } from '@codemirror/view';
|
|
|
|
import { LanguageSupport } from '@codemirror/language';
|
2024-02-14 01:01:08 +00:00
|
|
|
|
2023-09-19 17:59:29 +00:00
|
|
|
/**
|
2024-03-19 03:10:11 +00:00
|
|
|
* CodeMirror integration with
|
|
|
|
* [WikiEditor](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:WikiEditor).
|
|
|
|
*
|
|
|
|
* Use this class if you want WikiEditor's toolbar. If you don't need the toolbar,
|
|
|
|
* using {@link CodeMirror} directly will be considerably more efficient.
|
|
|
|
*
|
|
|
|
* @extends CodeMirror
|
2023-09-19 17:59:29 +00:00
|
|
|
*/
|
2024-03-19 03:10:11 +00:00
|
|
|
class CodeMirrorWikiEditor extends CodeMirror {
|
2024-03-11 18:10:08 +00:00
|
|
|
/**
|
|
|
|
* @constructor
|
2024-03-19 03:10:11 +00:00
|
|
|
* @param {jQuery} $textarea The textarea to replace with CodeMirror.
|
|
|
|
* @param {LanguageSupport|Extension} langExtension Language support and its extension(s).
|
|
|
|
* @stable to call and override
|
2024-03-11 18:10:08 +00:00
|
|
|
*/
|
|
|
|
constructor( $textarea, langExtension ) {
|
2023-09-19 17:59:29 +00:00
|
|
|
super( $textarea );
|
2024-03-19 03:10:11 +00:00
|
|
|
/**
|
|
|
|
* Language support and its extension(s).
|
|
|
|
* @type {LanguageSupport|Extension}
|
|
|
|
*/
|
2024-03-11 18:10:08 +00:00
|
|
|
this.langExtension = langExtension;
|
2024-03-19 03:10:11 +00:00
|
|
|
/**
|
|
|
|
* Whether CodeMirror is currently enabled.
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
2023-12-06 18:49:40 +00:00
|
|
|
this.useCodeMirror = mw.user.options.get( 'usecodemirror' ) > 0;
|
2024-03-21 21:16:04 +00:00
|
|
|
/**
|
|
|
|
* The [Realtime Preview](https://w.wiki/9XgX) handler.
|
|
|
|
* @type {Function|null}
|
|
|
|
*/
|
|
|
|
this.realtimePreviewHandler = null;
|
2023-09-19 17:59:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
setCodeMirrorPreference( prefValue ) {
|
2024-03-11 18:10:08 +00:00
|
|
|
// Save state for function updateToolbarButton()
|
|
|
|
this.useCodeMirror = prefValue;
|
2023-09-19 17:59:29 +00:00
|
|
|
super.setCodeMirrorPreference( prefValue );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-03-19 03:10:11 +00:00
|
|
|
* Replaces the default textarea with CodeMirror.
|
|
|
|
*
|
|
|
|
* @fires CodeMirrorWikiEditor~'ext.CodeMirror.switch'
|
|
|
|
* @stable to call
|
2023-09-19 17:59:29 +00:00
|
|
|
*/
|
|
|
|
enableCodeMirror() {
|
2023-10-10 19:23:03 +00:00
|
|
|
// If CodeMirror is already loaded, abort.
|
|
|
|
if ( this.view ) {
|
2023-09-19 17:59:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const selectionStart = this.$textarea.prop( 'selectionStart' ),
|
|
|
|
selectionEnd = this.$textarea.prop( 'selectionEnd' ),
|
|
|
|
scrollTop = this.$textarea.scrollTop(),
|
|
|
|
hasFocus = this.$textarea.is( ':focus' );
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Default configuration, which we may conditionally add to later.
|
|
|
|
* @see https://codemirror.net/docs/ref/#state.Extension
|
|
|
|
*/
|
|
|
|
const extensions = [
|
2024-03-19 03:10:11 +00:00
|
|
|
this.defaultExtensions,
|
2024-03-11 18:10:08 +00:00
|
|
|
this.langExtension,
|
2024-03-21 21:16:04 +00:00
|
|
|
EditorView.updateListener.of( ( update ) => {
|
|
|
|
if ( update.docChanged && typeof this.realtimePreviewHandler === 'function' ) {
|
|
|
|
this.realtimePreviewHandler();
|
|
|
|
}
|
|
|
|
} )
|
2023-09-19 17:59:29 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
this.initialize( extensions );
|
2024-03-21 21:16:04 +00:00
|
|
|
this.addRealtimePreviewHandler();
|
2023-09-19 17:59:29 +00:00
|
|
|
|
|
|
|
// Sync scroll position, selections, and focus state.
|
2024-02-21 07:29:53 +00:00
|
|
|
requestAnimationFrame( () => {
|
|
|
|
this.view.scrollDOM.scrollTop = scrollTop;
|
2023-09-19 17:59:29 +00:00
|
|
|
} );
|
2024-02-21 07:29:53 +00:00
|
|
|
if ( selectionStart !== 0 || selectionEnd !== 0 ) {
|
|
|
|
const range = EditorSelection.range( selectionStart, selectionEnd ),
|
|
|
|
scrollEffect = EditorView.scrollIntoView( range );
|
|
|
|
scrollEffect.value.isSnapshot = true;
|
|
|
|
this.view.dispatch( {
|
|
|
|
selection: EditorSelection.create( [ range ] ),
|
|
|
|
effects: scrollEffect
|
|
|
|
} );
|
|
|
|
}
|
2023-09-19 17:59:29 +00:00
|
|
|
if ( hasFocus ) {
|
|
|
|
this.view.focus();
|
|
|
|
}
|
2023-10-10 20:11:15 +00:00
|
|
|
|
2024-03-19 03:10:11 +00:00
|
|
|
/**
|
|
|
|
* Called after CodeMirror is enabled or disabled in WikiEditor.
|
|
|
|
*
|
|
|
|
* @event CodeMirrorWikiEditor~'ext.CodeMirror.switch'
|
|
|
|
* @param {boolean} enabled Whether CodeMirror is enabled.
|
|
|
|
* @param {jQuery} $textarea The current "editor", either the
|
|
|
|
* original textarea or the `.cm-editor` element.
|
|
|
|
* @stable to use
|
|
|
|
*/
|
2023-10-10 20:11:15 +00:00
|
|
|
mw.hook( 'ext.CodeMirror.switch' ).fire( true, $( this.view.dom ) );
|
2023-09-19 17:59:29 +00:00
|
|
|
}
|
2024-02-28 05:14:04 +00:00
|
|
|
|
2024-03-21 21:16:04 +00:00
|
|
|
/**
|
|
|
|
* Adds the Realtime Preview handler. Realtime Preview reads from the textarea
|
|
|
|
* via jQuery.textSelection, which will bubble up to CodeMirror automatically.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
addRealtimePreviewHandler() {
|
|
|
|
mw.hook( 'ext.WikiEditor.realtimepreview.enable' ).add( ( realtimePreview ) => {
|
|
|
|
this.realtimePreviewHandler = realtimePreview.getEventHandler().bind( realtimePreview );
|
|
|
|
} );
|
|
|
|
mw.hook( 'ext.WikiEditor.realtimepreview.disable' ).add( () => {
|
|
|
|
this.realtimePreviewHandler = null;
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2023-09-19 17:59:29 +00:00
|
|
|
/**
|
2024-03-19 03:10:11 +00:00
|
|
|
* Adds the CodeMirror button to WikiEditor.
|
|
|
|
*
|
|
|
|
* @stable to call
|
2023-09-19 17:59:29 +00:00
|
|
|
*/
|
|
|
|
addCodeMirrorToWikiEditor() {
|
|
|
|
const context = this.$textarea.data( 'wikiEditor-context' );
|
|
|
|
const toolbar = context && context.modules && context.modules.toolbar;
|
|
|
|
|
|
|
|
// Guard against something having removed WikiEditor (T271457)
|
|
|
|
if ( !toolbar ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$textarea.wikiEditor(
|
|
|
|
'addToToolbar',
|
|
|
|
{
|
|
|
|
section: 'main',
|
|
|
|
groups: {
|
|
|
|
codemirror: {
|
|
|
|
tools: {
|
|
|
|
CodeMirror: {
|
|
|
|
label: mw.msg( 'codemirror-toggle-label' ),
|
|
|
|
type: 'toggle',
|
|
|
|
oouiIcon: 'highlight',
|
|
|
|
action: {
|
|
|
|
type: 'callback',
|
|
|
|
execute: () => this.switchCodeMirror()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
const $codeMirrorButton = toolbar.$toolbar.find( '.tool[rel=CodeMirror]' );
|
2024-01-10 00:02:44 +00:00
|
|
|
$codeMirrorButton.attr( 'id', 'mw-editbutton-codemirror' );
|
|
|
|
|
|
|
|
// Hide non-applicable buttons until WikiEditor better supports a read-only mode (T188817).
|
|
|
|
if ( this.readOnly ) {
|
|
|
|
this.$textarea.data( 'wikiEditor-context' ).$ui.addClass( 'ext-codemirror-readonly' );
|
|
|
|
}
|
2023-09-19 17:59:29 +00:00
|
|
|
|
|
|
|
if ( this.useCodeMirror ) {
|
|
|
|
this.enableCodeMirror();
|
|
|
|
}
|
|
|
|
this.updateToolbarButton();
|
|
|
|
|
|
|
|
this.logUsage( {
|
|
|
|
editor: 'wikitext',
|
|
|
|
enabled: this.useCodeMirror,
|
|
|
|
toggled: false,
|
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector,camelcase
|
|
|
|
edit_start_ts_ms: parseInt( $( 'input[name="wpStarttime"]' ).val(), 10 ) * 1000 || 0
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-03-19 03:10:11 +00:00
|
|
|
* Updates CodeMirror button on the toolbar according to the current state (on/off).
|
|
|
|
*
|
|
|
|
* @private
|
2023-09-19 17:59:29 +00:00
|
|
|
*/
|
|
|
|
updateToolbarButton() {
|
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
|
|
|
const $button = $( '#mw-editbutton-codemirror' );
|
|
|
|
$button.toggleClass( 'mw-editbutton-codemirror-active', this.useCodeMirror );
|
|
|
|
|
|
|
|
// WikiEditor2010 OOUI ToggleButtonWidget
|
|
|
|
if ( $button.data( 'setActive' ) ) {
|
|
|
|
$button.data( 'setActive' )( this.useCodeMirror );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-03-19 03:10:11 +00:00
|
|
|
* Enables or disables CodeMirror.
|
|
|
|
*
|
|
|
|
* @fires CodeMirrorWikiEditor~'ext.CodeMirror.switch'
|
|
|
|
* @stable to call
|
2023-09-19 17:59:29 +00:00
|
|
|
*/
|
|
|
|
switchCodeMirror() {
|
|
|
|
if ( this.view ) {
|
|
|
|
this.setCodeMirrorPreference( false );
|
2024-03-20 00:58:50 +00:00
|
|
|
this.destroy();
|
2023-10-10 20:11:15 +00:00
|
|
|
mw.hook( 'ext.CodeMirror.switch' ).fire( false, this.$textarea );
|
2023-09-19 17:59:29 +00:00
|
|
|
} else {
|
|
|
|
this.enableCodeMirror();
|
|
|
|
this.setCodeMirrorPreference( true );
|
|
|
|
}
|
|
|
|
this.updateToolbarButton();
|
|
|
|
|
|
|
|
this.logUsage( {
|
|
|
|
editor: 'wikitext',
|
|
|
|
enabled: this.useCodeMirror,
|
|
|
|
toggled: true,
|
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector,camelcase
|
|
|
|
edit_start_ts_ms: parseInt( $( 'input[name="wpStarttime"]' ).val(), 10 ) * 1000 || 0
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
}
|
2024-03-19 03:10:11 +00:00
|
|
|
|
|
|
|
export default CodeMirrorWikiEditor;
|