Merge "CodeMirror 6: show wikitext highlighting on protected pages"

This commit is contained in:
jenkins-bot 2024-03-12 18:47:28 +00:00 committed by Gerrit Code Review
commit 8dd82a8b86
11 changed files with 88 additions and 39 deletions

View file

@ -50,6 +50,8 @@ Some may be removed pending user feedback:
* Closing HTML tags that highlighted as an error now also highlight the closing '>'
* Allow link titles to be both emboldened and italicized.
* Wikitext syntax highlighting is shown on protected pages
([T301615](https://phabricator.wikimedia.org/T301615))
### Deprecations and other changes

View file

@ -164,6 +164,7 @@
},
"ext.CodeMirror.v6.WikiEditor": {
"dependencies": [
"ext.wikiEditor",
"web2017-polyfills",
"mediawiki.api",
"mediawiki.user",
@ -250,6 +251,7 @@
},
"Hooks": {
"EditPage::showEditForm:initial": "main",
"EditPage::showReadOnlyForm:initial": "main",
"GetPreferences": "main",
"ResourceLoaderGetConfigVars": "main"
},

View file

@ -8,6 +8,7 @@ use MediaWiki\Config\Config;
use MediaWiki\EditPage\EditPage;
use MediaWiki\Extension\Gadgets\GadgetRepo;
use MediaWiki\Hook\EditPage__showEditForm_initialHook;
use MediaWiki\Hook\EditPage__showReadOnlyForm_initialHook;
use MediaWiki\Output\OutputPage;
use MediaWiki\Preferences\Hook\GetPreferencesHook;
use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
@ -19,6 +20,7 @@ use MediaWiki\User\User;
*/
class Hooks implements
EditPage__showEditForm_initialHook,
EditPage__showReadOnlyForm_initialHook,
ResourceLoaderGetConfigVarsHook,
GetPreferencesHook
{
@ -120,6 +122,18 @@ class Hooks implements
}
}
/**
* Load CodeMirror 6 on read-only pages.
*
* @param EditPage $editor
* @param OutputPage $out
*/
public function onEditPage__showReadOnlyForm_initial( $editor, $out ): void {
if ( $this->shouldUseV6( $out ) && $this->shouldLoadCodeMirror( $out ) ) {
$out->addModules( 'ext.CodeMirror.v6.WikiEditor' );
}
}
/**
* @param OutputPage $out
* @return bool

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -20,3 +20,15 @@
direction: ltr;
unicode-bidi: isolate;
}
// Hide all buttons except CodeMirror on read only pages (T301615)
// This is the same hack that CodeEditor uses to customize the toolbar.
// WikiEditor should be updated to better handle read only pages (T188817).
.ext-codemirror-readonly {
.wikiEditor-section-secondary,
.group:not( .group-codemirror ),
.tabs,
.sections {
display: none;
}
}

View file

@ -7,6 +7,7 @@ import bidiIsolationExtension from './codemirror.bidiIsolation';
* @property {jQuery} $textarea
* @property {EditorView} view
* @property {EditorState} state
* @property {boolean} readOnly
*/
export default class CodeMirror {
/**
@ -17,6 +18,7 @@ export default class CodeMirror {
this.$textarea = $( textarea );
this.view = null;
this.state = null;
this.readOnly = this.$textarea.prop( 'readonly' );
}
/**
@ -32,7 +34,8 @@ export default class CodeMirror {
this.contentAttributesExtension,
this.phrasesExtension,
this.specialCharsExtension,
this.heightExtension
this.heightExtension,
EditorState.readOnly.of( this.readOnly )
];
// Add bidi isolation to tags on RTL pages (T358804).

View file

@ -1214,6 +1214,7 @@ class CodeMirrorModeMediaWiki {
* Gets a LanguageSupport instance for the MediaWiki mode.
*
* @example
* import CodeMirror from './codemirror';
* import { mediaWikiLang } from './codemirror.mode.mediawiki';
* const cm = new CodeMirror( textarea );
* cm.initialize( [ ...cm.defaultExtensions, mediaWikiLang() ] );

View file

@ -153,8 +153,12 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
);
const $codeMirrorButton = toolbar.$toolbar.find( '.tool[rel=CodeMirror]' );
$codeMirrorButton
.attr( 'id', 'mw-editbutton-codemirror' );
$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' );
}
if ( this.useCodeMirror ) {
this.enableCodeMirror();

View file

@ -38,6 +38,16 @@ describe( 'addCodeMirrorToWikiEditor', () => {
} )
);
} );
it( 'should be readonly when the textarea is also readonly', () => {
const textarea = document.createElement( 'textarea' );
textarea.readOnly = true;
const cmWe2 = new CodeMirrorWikiEditor( textarea );
cmWe2.initialize();
cmWe2.addCodeMirrorToWikiEditor();
expect( cmWe2.readOnly ).toEqual( true );
expect( cmWe2.state.readOnly ).toEqual( true );
} );
} );
describe( 'enableCodeMirror', () => {

View file

@ -28,13 +28,18 @@ class HookTest extends MediaWikiIntegrationTestCase {
/**
* @covers ::shouldLoadCodeMirror
* @covers ::onEditPage__showEditForm_initial
* @covers ::onEditPage__showReadOnlyForm_initial
* @param bool $useCodeMirrorV6
* @param int $expectedAddModuleCalls
* @param string|null $expectedFirstModule
* @param bool $readOnly
* @dataProvider provideOnEditPageShowEditFormInitial
*/
public function testOnEditPageShowEditFormInitial(
bool $useCodeMirrorV6, int $expectedAddModuleCalls, ?string $expectedFirstModule
bool $useCodeMirrorV6,
int $expectedAddModuleCalls,
?string $expectedFirstModule,
bool $readOnly = false
) {
$this->overrideConfigValues( [
'CodeMirrorV6' => $useCodeMirrorV6,
@ -55,16 +60,19 @@ class HookTest extends MediaWikiIntegrationTestCase {
} );
$hooks = new Hooks( $userOptionsLookup, $this->getServiceContainer()->getMainConfig() );
$hooks->onEditPage__showEditForm_initial( $this->createMock( EditPage::class ), $out );
$method = $readOnly ? 'onEditPage__showReadOnlyForm_initial' : 'onEditPage__showEditForm_initial';
$hooks->{$method}( $this->createMock( EditPage::class ), $out );
}
/**
* @return Generator
*/
public static function provideOnEditPageShowEditFormInitial(): Generator {
// useCodeMirrorV6, expectedAddModuleCalls, expectedFirstModule
// useCodeMirrorV6, expectedAddModuleCalls, expectedFirstModule, readOnly
yield 'CM5' => [ false, 2, 'ext.CodeMirror.WikiEditor' ];
yield 'CM6' => [ true, 1, 'ext.CodeMirror.v6.WikiEditor' ];
yield 'CM5 read-only' => [ false, 0, null, true ];
yield 'CM6 read-only' => [ true, 1, 'ext.CodeMirror.v6.WikiEditor', true ];
}
/**
@ -82,39 +90,35 @@ class HookTest extends MediaWikiIntegrationTestCase {
/**
* @covers ::shouldLoadCodeMirror
* @dataProvider provideShouldLoadCodeMirror
* @param string|null $module
* @param string|null $gadget
* @param array $conds
* @param bool $expectation
* @param string $contentModel
* @param bool $useCodeMirrorV6
* @param bool $isRTL
*/
public function testShouldLoadCodeMirror(
?string $module,
?string $gadget,
bool $expectation,
string $contentModel = CONTENT_MODEL_WIKITEXT,
bool $useCodeMirrorV6 = false,
bool $isRTL = false
): void {
public function testShouldLoadCodeMirror( array $conds, bool $expectation ): void {
$conds = array_merge( [
'module' => null,
'gadget' => null,
'contentModel' => CONTENT_MODEL_WIKITEXT,
'useV6' => false,
'isRTL' => false
], $conds );
$this->overrideConfigValues( [
'CodeMirrorV6' => $useCodeMirrorV6,
'CodeMirrorV6' => $conds['useV6'],
] );
$out = $this->getMockOutputPage( $contentModel, $isRTL );
$out->method( 'getModules' )->willReturn( $module ? [ $module ] : [] );
$out = $this->getMockOutputPage( $conds['contentModel'], $conds['isRTL'] );
$out->method( 'getModules' )->willReturn( $conds['module'] ? [ $conds['module'] ] : [] );
$userOptionsLookup = $this->createMock( UserOptionsLookup::class );
$userOptionsLookup->method( 'getOption' )->willReturn( true );
if ( $gadget && !ExtensionRegistry::getInstance()->isLoaded( 'Gadgets' ) ) {
if ( $conds['gadget'] && !ExtensionRegistry::getInstance()->isLoaded( 'Gadgets' ) ) {
$this->markTestSkipped( 'Skipped as Gadgets extension is not available' );
}
$extensionRegistry = $this->getMockExtensionRegistry( (bool)$gadget );
$extensionRegistry = $this->getMockExtensionRegistry( (bool)$conds['gadget'] );
$extensionRegistry->method( 'getAttribute' )
->with( 'CodeMirrorContentModels' )
->willReturn( [ CONTENT_MODEL_WIKITEXT ] );
if ( $gadget ) {
if ( $conds['gadget'] ) {
$gadgetMock = $this->createMock( Gadget::class );
$gadgetMock->expects( $this->once() )
->method( 'isEnabled' )
@ -125,14 +129,11 @@ class HookTest extends MediaWikiIntegrationTestCase {
->willReturn( $gadgetMock );
$gadgetRepoMock->expects( $this->once() )
->method( 'getGadgetIds' )
->willReturn( [ $gadget ] );
->willReturn( [ $conds['gadget'] ] );
GadgetRepo::setSingleton( $gadgetRepoMock );
}
$hooks = new Hooks(
$userOptionsLookup,
$this->getServiceContainer()->getMainConfig()
);
$hooks = new Hooks( $userOptionsLookup, $this->getServiceContainer()->getMainConfig() );
self::assertSame( $expectation, $hooks->shouldLoadCodeMirror( $out, $extensionRegistry ) );
}
@ -140,13 +141,13 @@ class HookTest extends MediaWikiIntegrationTestCase {
* @return Generator
*/
public function provideShouldLoadCodeMirror(): Generator {
// module, gadget, expectation, contentModel, shouldUseV6, isRTL
yield 'no modules, no gadgets, wikitext' => [ null, null, true ];
yield 'codeEditor, no gadgets, wikitext' => [ 'ext.codeEditor', null, false ];
yield 'no modules, wikEd, wikitext' => [ null, 'wikEd', false ];
yield 'no modules, no gadgets, CSS' => [ null, null, false, CONTENT_MODEL_CSS ];
yield 'CM5 wikitext RTL' => [ null, null, false, CONTENT_MODEL_WIKITEXT, false, true ];
yield 'CM6 wikitext RTL' => [ null, null, true, CONTENT_MODEL_WIKITEXT, true, true ];
// [ conditions, expectation ]
yield [ [], true ];
yield [ [ 'module' => 'ext.codeEditor' ], false ];
yield [ [ 'gadget' => 'wikEd' ], false ];
yield [ [ 'contentModel' => CONTENT_FORMAT_CSS ], false ];
yield [ [ 'isRTL' => true ], false ];
yield [ [ 'isRTL' => true, 'useV6' => true ], true ];
}
/**