Page MenuHomePhabricator

Allow CodeMirror to be used for other textareas with WikiEditor
Closed, ResolvedPublic

Description

While CodeMirror is extremely helpful for regular wiki editing tools, it has no means for third-party usage outside of #wpTextbox1:

https://github.com/wikimedia/mediawiki-extensions-CodeMirror/blob/master/resources/ext.CodeMirror.js

I think making the code more agnostic and exposing the function for enabling CodeMirror in any other libraries working with WikiEditor, for example, Convenient-Discussions, will be beneficial for them.

Acceptance criteria

  • CodeMirror should be usable on any textarea, with or without WikiEditor

To test without WikiEditor, you can go to Special:BlankPage and enter the following in your JS console:

$( '.mw-body-content' ).append( $('<textarea>' ) );
mw.loader.using( [ 'ext.CodeMirror.v6', 'ext.CodeMirror.v6.mode.mediawiki' ] ).then( ( require ) => {
	const CodeMirror = require( 'ext.CodeMirror.v6' );
	const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
	const cm = new CodeMirror( $( 'textarea' ) );
	cm.initialize( [ cm.defaultExtensions, mediawikiLang() ] );
} );

Integration with WikiEditor only works for wikitext (aka "mediawiki mode"). Also at Special:BlankPage:

await mw.loader.using( [ 'ext.CodeMirror.v6.WikiEditor' ] );
$( '.mw-body-content' ).append( $( '<textarea>' ) );
mw.addWikiEditor( $( 'textarea' ) );

Related Objects

Event Timeline

Currently, together with T256988: Allow to insert a template not only into a form with 'wpTextbox1' id (that task is about TemplateWizard), these are the two only buttons that can not be added and work properly in WikiEditor, attached to an arbitrary textarea. So please fix, Convenient-Discussions needs it so much.

@Esanders Hi, do you think this your commit is enough to mark this task as resolved?

I'm being hinted by @SerDIDG that CodeMirror can be added to any form by using CodeMirror.fromTextArea like this:

mw.loader.using( [ 'ext.CodeMirror', 'ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki' ] ).done( function () {
  var config = mw.config.get( 'extCodeMirrorConfig' );
  var $text = $( '<textarea>' ).appendTo( document.body );
  CodeMirror.fromTextArea( $text.get( 0 ), {
    mwConfig: config,
    lineNumbers: true,
    mode: 'text/mediawiki'
  } );
} );

Not yet sure though which approach will work betterβ€”having addCodeMirrorToWikiEditor() run and then hacking into the form to make everything work as desired or adding a new CodeMirror configuration from scratch. What complicates things is that once mw.loader.using( [ 'ext.CodeMirror', 'ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki' ] ) has run, CodeMirror adds itself to new WikiEditor instances making a CD form look like this:

image.png (411Γ—723 px, 16 KB)

Restricted Application added a subscriber: Base. Β· View Herald TranscriptJan 17 2024, 8:37 PM

Change 1010319 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CodeMirror@master] CM6: move more Extensions to CodeMirror so they don't require WikiEditor

https://gerrit.wikimedia.org/r/1010319

Change 1010952 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CodeMirror@master] CM6: Switch to using Rollup instead of Webpack; make RL-compatible

https://gerrit.wikimedia.org/r/1010952

Change 1011163 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CodeMirror@master] CM6: move bidiIsolation to be part of CodeMirrorModeMediaWiki

https://gerrit.wikimedia.org/r/1011163

This comment was removed by MusikAnimal.

Following r1010952, loading CodeMirror on an arbitrary textarea would look something like:

mw.loader.using( [ 'ext.CodeMirror.v6', 'ext.CodeMirror.v6.mode.mediawiki' ] ).then( ( require ) => {
	const CodeMirror = require( 'ext.CodeMirror.v6' );
	const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
	const cm = new CodeMirror( $( '.my-textarea' ) );
	cm.initialize( [ ...cm.defaultExtensions, mediawikiLang ] );
} );

(docs for the CodeMirror and other classes to published soon, following T359986)

This loads CodeMirror without WikiEditor, which I think satisfies @Jack_who_built_the_house's use-case mentioned at T214989#7287480.

I'm still working on providing a means to use CodeMirror + WikiEditor from user scripts and gadgets, but unless you want the toolbar, there's really no need to involve WikiEditor. You could however load ext.WikiEditor and do all the instantiation yourself, but you won't get the syntax highlighting icon in the toolbar. To satisfy this use-case, I plan to make ext.CodeMirror.v6.WikiEditor a consumable module, after which you could do:

mw.loader.using( [ 'ext.CodeMirror.v6.WikiEditor', 'ext.CodeMirror.v6.mode.mediawiki' ] ).then( ( require ) => {
	const CodeMirrorWikiEditor = require( 'ext.CodeMirror.v6.WikiEditor' );
	const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
	const cmWe = new CodeMirrorWikiEditor( $( '.my-textarea' ), mediawikiLang() );
        cmWe.addCodeMirrorToWikiEditor();
} );

Change 1010319 merged by jenkins-bot:

[mediawiki/extensions/CodeMirror@master] CM6: move more Extensions to CodeMirror so they don't require WikiEditor

https://gerrit.wikimedia.org/r/1010319

Change 1011163 merged by jenkins-bot:

[mediawiki/extensions/CodeMirror@master] CM6: move bidiIsolation to be part of CodeMirrorModeMediaWiki

https://gerrit.wikimedia.org/r/1011163

Change 1010952 merged by jenkins-bot:

[mediawiki/extensions/CodeMirror@master] CM6: Switch to using Rollup instead of Webpack; make RL-compatible

https://gerrit.wikimedia.org/r/1010952

Change #1014134 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CodeMirror@master] CM6: Add module for standalone CodeMirror + WikiEditor on any textarea

https://gerrit.wikimedia.org/r/1014134

Change #1014134 abandoned by MusikAnimal:

[mediawiki/extensions/CodeMirror@master] CM6: Add module for standalone CodeMirror + WikiEditor on any textarea

Reason:

complications; we won't be adding a module for standalone CodeMirror + WikiEditor. This was never requested, anyway

https://gerrit.wikimedia.org/r/1014134

I've tested Convenient Discussions using the integration method described here and in the docs, and it seems to work fine.

I'm still working on providing a means to use CodeMirror + WikiEditor from user scripts and gadgets, but unless you want the toolbar, there's really no need to involve WikiEditor …

As it turns out, this is a lot more complicated than it seemed. Given there's no request for this functionality, I've abandoned the related patch and am declaring this task completed and ready for QA. So the acceptance criterion to have a standalone module for CM + WikiEditor has been axed. For clarity, I've also renamed the task.


QA notes: I don't think you need to test with Convenient Discussions. It worked in my testing, but there are probably additional changes the authors of that gadget will want to make. Instead you can focus on the acceptance criteria mentioned in the task description.

MusikAnimal renamed this task from Allow CodeMirror to be used for other textareas with WikiEditor to Allow CodeMirror to be used on any textarea.Wed, Apr 10, 12:36 AM
MusikAnimal updated the task description. (Show Details)

@MusikAnimal Sorry for not replying to T214989#9632641! I should have made it clear that, of course, the desired way of integration would be to have it all like in WikiEditor, with a switched button in the toolbar. The idea is to make sure editors are able to edit wikitext from any tool like they do in WikiEditor – with the toolbar and a syntax highlight button in it.

In my case, it's not only for Convenient Discussions, by the way. But the textarea in Convenient Discussions does not even have the monospace font by default – it is expected that most users use with very little markup in it. So syntax highlighting is supposed to be a pluggable function. The other tool I'm working on is based on BrandonXLF's QuickEdit allowing to edit sections in-place. The user experience there should definitely be close to the user experience on native edit pages.

If there are difficulties with providing a straightforward, streamlined way to integrate CodeMirror and WikiEditor for external tools within MediaWiki, then could you please share your findings and code? It would be much appreciated! I will then fiddle with it and implement everything myself, however hacky.

Jack_who_built_the_house renamed this task from Allow CodeMirror to be used on any textarea to Allow CodeMirror to be used for other textareas with WikiEditor.Wed, Apr 10, 5:52 AM
Jack_who_built_the_house updated the task description. (Show Details)

(Sorry to have the edits to task reverted – I believe the integration with WikiEditor was indeed requested πŸ™‚)

@MusikAnimal I see that you removed this from the task:

And to test CodeMirror + WikiEditor, use:

mw.loader.using( [ 'ext.CodeMirror.v6.WikiEditor', 'ext.CodeMirror.v6.mode.mediawiki' ] ).then( ( require ) => {
	const CodeMirrorWikiEditor = require( 'ext.CodeMirror.v6.WikiEditor' );
	const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
	mw.hook( 'wikiEditor.toolbarReady' ).add( ( $textarea ) => {
		const cmWe = new CodeMirrorWikiEditor( $( 'textarea' ), mediawikiLang() );
		cmWe.addCodeMirrorToWikiEditor();
	} );
	mw.addWikiEditor( $( 'textarea' ) );
} );

It does the job of adding a working switcher button and converting the textarea for me, but the changes to the text made in CodeMirror are not synchronized with the textarea. Are there other issues? What do you think should be done to overcome them? Thanks in advance!

P.S. I figured out that dissynchronization with the textarea is due to OOUI using .val(), not .textSelection( 'getContents' ), under the hood, as it probably should, since OOjs is a standalone project, not required to be used with MediaWiki. So that probably is solvable in a trivial manner.

@MusikAnimal Sorry for not replying to T214989#9632641! I should have made it clear that, of course, the desired way of integration would be to have it all like in WikiEditor, with a switched button in the toolbar. The idea is to make sure editors are able to edit wikitext from any tool like they do in WikiEditor – with the toolbar and a syntax highlight button in it.

I just noticed with CD, you already have a toolbar, so adding CodeMirror (and a toggle button, if you wish) is trivial. You shouldn't need a new module just for CodeMirror + WikiEditor.

In my case, it's not only for Convenient Discussions, by the way. But the textarea in Convenient Discussions does not even have the monospace font by default – it is expected that most users use with very little markup in it. So syntax highlighting is supposed to be a pluggable function. The other tool I'm working on is based on BrandonXLF's QuickEdit allowing to edit sections in-place. The user experience there should definitely be close to the user experience on native edit pages.

If there are difficulties with providing a straightforward, streamlined way to integrate CodeMirror and WikiEditor for external tools within MediaWiki, then could you please share your findings and code? It would be much appreciated! I will then fiddle with it and implement everything myself, however hacky.

Indeed there are difficulties. See the comments at r1014134 for more. I don't think offering a standalone CodeMirror + fully functional WikiEditor for use on any arbitrary textarea is something we can do in the short-term. Fixes need to be made to WikiEditor, first.

I could revive r1014134 and let people go the hacky route, but it's not something we would consider "stable" until WikiEditor better supports arbitrary textareas.

I got confused here... So, ext.CodeMirror.v6.WikiEditor is in production already, and it successfully does the exact thing that was requested (adds CodeMirror integration into existing and newly created WikiEditor instances on the page), just with loading it, without even running any additional instructions and without ext.CodeMirror.v6.mode.mediawiki. But the patch adding it is at the same time abandoned, and you claim incompatibility and instability. What am I missing? The only difference that I see is that this code

const require = await mw.loader.using( [ 'ext.CodeMirror.v6.WikiEditor'] );
require('ext.CodeMirror.v6.WikiEditor');

doesn't return the CodeMirrorWikiEditor class.

Run this in the console on Special:BlankPage:

await mw.loader.using( [ 'ext.wikiEditor', 'ext.CodeMirror.v6.WikiEditor' ] );
$( '.mw-body-content' ).append( $( '<textarea>' ) );
mw.addWikiEditor( $( 'textarea' ) );

It works. No?

I tried to use it, and the issue I'm facing now (unrelated to the hussle with modules/loading/WikiEditor integration) is that an input with CodeMirror is absolutely mute, not emitting any events except for focus and blur which I specifically asked for in T197632. The integration with OOUI is non-existent. But that's for another task (and I will probably ask to pass the CodeMirror instance somewhere in .data()).

As for the technical reasons you and @Samwilson mentioned:

Indeed there are difficulties. See the comments at r1014134 for more. I don't think offering a standalone CodeMirror + fully functional WikiEditor for use on any arbitrary textarea is something we can do in the short-term. Fixes need to be made to WikiEditor, first.

As I said in the patch comments, it seems we can safely bypass the unfortunate fact that doing mw.loader.load( 'ext.wikiEditor' ); on any DOM, with or without #wpTextbox1, fires the hook wikiEditor.toolbarReady. In what way are user script authors reliant on this hook given that CodeMirror handles firings of this hook by itself in codemirror.wikieditor.mediawiki.js?

But as for this hardcoded #wpTextbox1, I think I had issues twice or thrice with it in my history of MediaWiki front-end development and was angry at that poor use of an id – the last time was the attempt to make CD compatible with DiscussionTools's New Topic feature, during which I had to resort to using MutationObserver (well, that attempt was a hack by itself, but still). And the fact that DiscussionTools itself has to use it... And by DiscussionTools we mean VisualEditor:

image.png (64Γ—537 px, 7 KB)

So I pretty much believe that getting rid of that reliance on #wpTextbox1 would benefit the whole MediaWiki landscape – it has produced enough technical debt up to this point for both MediaWiki devs and user script authors to struggle with. But yeah, ripping out legacy code could be tricky...

Run this in the console on Special:BlankPage:

await mw.loader.using( [ 'ext.wikiEditor', 'ext.CodeMirror.v6.WikiEditor' ] );
$( '.mw-body-content' ).append( $( '<textarea>' ) );
mw.addWikiEditor( $( 'textarea' ) );

It works. No?

Cool, that does work!

But I should probably hold off on using this until it's clear if this will stay?

So I pretty much believe that getting rid of that reliance on #wpTextbox1 would benefit the whole MediaWiki landscape – it has produced enough technical debt up to this point for both MediaWiki devs and user script authors to struggle with. But yeah, ripping out legacy code could be tricky...

+1

I got confused here... So, ext.CodeMirror.v6.WikiEditor is in production already, and it successfully does the exact thing that was requested (adds CodeMirror integration into existing and newly created WikiEditor instances on the page), just with loading it, without even running any additional instructions and without ext.CodeMirror.v6.mode.mediawiki. But the patch adding it is at the same time abandoned, and you claim incompatibility and instability. What am I missing?

…

Run this in the console on Special:BlankPage:

await mw.loader.using( [ 'ext.wikiEditor', 'ext.CodeMirror.v6.WikiEditor' ] );
$( '.mw-body-content' ).append( $( '<textarea>' ) );
mw.addWikiEditor( $( 'textarea' ) );

It works. No?

Incidentally, yes! I don't think we designed it for use in this way, but reading through the code, this seems harmless and future-proof (at least on the CodeMirror side). This would only work for wikitext, though. If we want to use WikiEditor + CodeMirror for a different language (none exist yet for CodeMirror 6, but there will be eventually), then we'd have to create a new standalone module as was proposed at r1014134.

ext.WikiEditor is a dependency of ext.CodeMirror.v6.WikiEditor, so you could even trim it down further:

await mw.loader.using( [ 'ext.CodeMirror.v6.WikiEditor' ] );
$( '.mw-body-content' ).append( $( '<textarea>' ) );
mw.addWikiEditor( $( 'textarea' ) );

Barring objections from @Samwilson (who is much more familiar with WikiEdithor than myself), I am comfortable advertising this as example usage in our documentation. Most folks only want CodeMirror for wikitext, anyway.

I tried to use it, and the issue I'm facing now (unrelated to the hussle with modules/loading/WikiEditor integration) is that an input with CodeMirror is absolutely mute, not emitting any events except for focus and blur which I specifically asked for in T197632.

What you're asking for sounds like T174811. I nearly made a patch to add a "change" event, but stopped after reading the comments. I'll comment there shortly, and we can continue discussion over there.

The integration with OOUI is non-existent. But that's for another task (and I will probably ask to pass the CodeMirror instance somewhere in .data()).

Yeah, I'm not sure what to do about that. In the wiki world, jQuery.textSelection is meant to be the go-to stable API for interacting with textareas, but OOUI isn't specific to MediaWiki I guess and that's probably why it doesn't use it. If you could create the task detailing what you're looking for (with some example code), I'll see what can be done.

So I pretty much believe that getting rid of that reliance on #wpTextbox1 would benefit the whole MediaWiki landscape – it has produced enough technical debt up to this point for both MediaWiki devs and user script authors to struggle with. But yeah, ripping out legacy code could be tricky...

Agreed! But certainly outside the scope of this task :)


Anyway, it sounds like you do have what you need after all, so I'm going to move this back to QA.

Anyway, it sounds like you do have what you need after all, so I'm going to move this back to QA.

Yes, please go ahead. You could also add my use case to acceptance criteria.

Indeed there are difficulties. See the comments at r1014134 for more. I don't think offering a standalone CodeMirror + fully functional WikiEditor for use on any arbitrary textarea is something we can do in the short-term. Fixes need to be made to WikiEditor, first.

I could revive r1014134 and let people go the hacky route, but it's not something we would consider "stable" until WikiEditor better supports arbitrary textareas.

I created T362356: Make WikiEditor officially support use outside action=edit pages. In general, it seems that support for arbitrary textareas is already there, just not official. I can't vouch there aren't pitfalls, but what if just adding a condition to stop the execution before it reaches the hook will solve our immediate problem with this task?

I ask this because, if I'm right in T174811#9709005 and you do need an uninitialized CodeMirror instance to listen to events on it and have full control of it, then any support on a post-initialization basis will be of a stripped down nature.

dom_walden subscribed.

The two snippets in the Acceptance Criteria appear to give you an editor with syntax highlighting like CodeMirror.

If that is all we want, I guess that is fine.

Test environment: https://en.wikipedia.beta.wmflabs.org CodeMirror 5.0.0 (7f57f70) 07:27, 15 April 2024.