MediaWiki:Gadget-FDCodeEditor.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* FDCodeEditor - a real code editor for the FlexDiagrams "edit diagram" screen.
*
* FlexDiagrams renders the Mermaid and DOT source as a bare <textarea>
* (FD_SpecialEditDiagram.php:121,143), so there is no tab-to-indent, no line
* numbers, no undo history and no find-in-editor. This gadget attaches MediaWiki's
* CodeMirror 6 to that textarea via its documented, @stable
* `new CodeMirror( textarea )` / `cm.initialize()` API.
*
* THE PART THAT IS NOT OBVIOUS - the sync bridge.
*
* CodeMirror hides the original textarea and copies content back to it ONLY on
* `form.submit` (codemirror.js:429-441). FlexDiagrams has no form: it saves over
* AJAX by reading `$( '.mermaidCode' ).val()`, drives its live preview from a
* `keyup` handler, and arms its unsaved-changes warning from a `keypress` handler.
* So without the updateListener below, the preview freezes, the leave-page warning
* never arms, and SAVE WRITES THE PRE-EDIT TEXT - silent data loss. Keeping the
* hidden textarea in sync instead means every existing FlexDiagrams code path keeps
* working untouched, and nothing under extensions/FlexDiagrams needs patching -
* which matters, because a FlexDiagrams upgrade silently reverts files there.
*
* `jquery.textSelection` is a REQUIRED dependency even though it looks unrelated:
* ext.CodeMirror.v6 does not declare it, but codemirror.js:443 calls
* `$( ... ).textSelection( 'register', ... )` unconditionally, so initialize()
* throws without it. Upstream gets away with this because CodeMirror normally loads
* alongside WikiEditor, which does declare it.
*
* Registered in [[MediaWiki:Gadgets-definition]]; styles in
* [[MediaWiki:Gadget-FDCodeEditor.css]].
*/
( function () {
'use strict';
// Both diagram editors use identical markup, so one gadget covers both.
var SELECTOR = 'textarea.mermaidCode, textarea.dotCode';
// Mermaid's own documentation, and every diagram on this wiki, indents by two.
var INDENT = ' ';
function attach( require, textarea ) {
var CodeMirror = require( 'ext.CodeMirror.v6' );
var cm6 = require( 'ext.CodeMirror.v6.lib' );
var $textarea = $( textarea );
var cm = new CodeMirror( textarea );
cm.initialize( [
// Line numbers, undo/redo, bracket matching, find-and-replace,
// multiple cursors, line wrapping.
cm.defaultExtensions,
// Tab-to-indent is deliberately absent from defaultExtensions, because
// binding Tab traps keyboard navigation. Press Escape first, then Tab,
// to move focus out of the editor.
cm6.keymap.of( [ cm6.indentWithTab ] ),
cm6.indentUnit.of( INDENT ),
cm6.highlightActiveLine(),
// The sync bridge - see the header comment. Without this, saving
// silently writes the pre-edit text.
cm6.EditorView.updateListener.of( function ( update ) {
if ( !update.docChanged ) {
return;
}
$textarea
.val( update.state.doc.toString() )
.trigger( 'keyup' ) // FlexDiagrams live preview
.trigger( 'keypress' ); // FD_editWarning unsaved-changes flag
} )
] );
}
// Driven from DOM-ready rather than mw.hook( 'wikipage.content' ), which has
// been seen not firing on diagram pages.
$( function () {
var textareas = $( SELECTOR ).toArray();
if ( !textareas.length ) {
// Any page that is not the "edit diagram" UI.
return;
}
mw.loader.using( [
'ext.CodeMirror.v6',
'ext.CodeMirror.v6.lib',
'jquery.textSelection'
] ).then( function ( require ) {
textareas.forEach( function ( textarea ) {
attach( require, textarea );
} );
}, function ( err ) {
// Never fail silently: a guard that bails without saying so is
// indistinguishable from the gadget not being installed at all.
mw.log.warn( '[FDCodeEditor] CodeMirror failed to load; the diagram ' +
'editor stays a plain textarea.', err );
} );
} );
}() );