mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-23 13:56:44 +00:00
CodeMirrorPreferences: add panel to tweak prefs with the editor open
This is toggled by pressing Mod-Shift-, (or Command-Shift-, on MacOS), which then puts focus on the preferences panel. It can be closed with the Escape key, just like other CM panels. The CodeMirror class comes with these extension which can be toggled in preferences: * Bracket matching * Line numbering * Line wrapping * Highlight the active line * Show special characters Only bracket matching, line numbering, and line wrapping are available in the 2017 editor. The bidi isolation and template folding extensions are registered in CodeMirrorModeMediaWiki as they are MW-specific. CodeMirrorPreferences' new registerExtension() method allows any consumer of CodeMirror to add any arbitrary extensions to the preferences panel. This is expected to be called *after* CodeMirror has finished initializing. The 'ext.CodeMirror.ready' hook now passes the CodeMirror instance to accommodate this. The preferences are stored as a single user option in the database, called 'codemirror-preferences'. The defaults can be configured with the $wgCodeMirrorDefaultPreferences configuration setting. The sysadmin-facing values are the familiar boolean, but since CodeMirror is widely used, we make extra efforts to reduce the storage footprint (see T54777). This includes only storing preferences that differ from the defaults, and using binary representation instead of boolean values, since the user option is stored as a string. For now, all preferences are ignored in the 2017 editor. In a future patch, we may add some as toggleable Tools in the VE toolbar. Other changes: * Refactor CSS to use a .darkmode() mixin * Add a method to create a CSS-only fieldset in CodeMirrorPanel * Fix Jest tests now that there are more calls to mw.user.options.get() * Adjust Selenium tests to always use CM6 * Adjust Selenium tests to delete test pages (useful for local dev) * Remove unused code Bug: T359498 Change-Id: I70dcf2f49418cea632c452c1266440effad634f3
This commit is contained in:
parent
5d6d0ba56a
commit
13c9eae26e
|
@ -37,6 +37,19 @@
|
|||
"value": null,
|
||||
"description": "List of namespace IDs where line numbering should be enabled, or `null` to enable for all namespaces. Set to [] to disable everywhere.",
|
||||
"public": true
|
||||
},
|
||||
"CodeMirrorDefaultPreferences": {
|
||||
"value": {
|
||||
"activeLine": false,
|
||||
"bidiIsolation": false,
|
||||
"bracketMatching": true,
|
||||
"lineNumbering": true,
|
||||
"lineWrapping": true,
|
||||
"specialChars": true,
|
||||
"templateFolding": true
|
||||
},
|
||||
"description": "Defaults for CodeMirror user preferences. See https://w.wiki/BwzZ for more information.",
|
||||
"public": true
|
||||
}
|
||||
},
|
||||
"MessagesDirs": {
|
||||
|
@ -179,9 +192,10 @@
|
|||
"packageFiles": [
|
||||
"codemirror.js",
|
||||
"codemirror.textSelection.js",
|
||||
"codemirror.panel.js",
|
||||
"codemirror.search.js",
|
||||
"codemirror.gotoLine.js",
|
||||
"codemirror.panel.js",
|
||||
"codemirror.preferences.js",
|
||||
{
|
||||
"name": "ext.CodeMirror.data.js",
|
||||
"callback": "MediaWiki\\Extension\\CodeMirror\\DataScript::makeScript"
|
||||
|
@ -195,6 +209,7 @@
|
|||
"CdxButton",
|
||||
"CdxCheckbox",
|
||||
"CdxLabel",
|
||||
"CdxField",
|
||||
"CdxTextInput",
|
||||
"CdxToggleButton",
|
||||
"CdxToggleButtonGroup"
|
||||
|
@ -203,21 +218,27 @@
|
|||
"codemirror-all",
|
||||
"codemirror-all-tooltip",
|
||||
"codemirror-by-word",
|
||||
"codemirror-close",
|
||||
"codemirror-control-character",
|
||||
"codemirror-done",
|
||||
"codemirror-find",
|
||||
"codemirror-fold-template",
|
||||
"codemirror-find-results",
|
||||
"codemirror-folded-code",
|
||||
"codemirror-goto-line",
|
||||
"codemirror-goto-line-go",
|
||||
"codemirror-match-case",
|
||||
"codemirror-next",
|
||||
"codemirror-prefs-activeline",
|
||||
"codemirror-prefs-bracketmatching",
|
||||
"codemirror-prefs-linenumbering",
|
||||
"codemirror-prefs-linewrapping",
|
||||
"codemirror-prefs-specialchars",
|
||||
"codemirror-prefs-title",
|
||||
"codemirror-previous",
|
||||
"codemirror-regexp",
|
||||
"codemirror-replace",
|
||||
"codemirror-replace-all",
|
||||
"codemirror-replace-placeholder",
|
||||
"codemirror-find-results",
|
||||
"codemirror-special-char-backspace",
|
||||
"codemirror-special-char-bell",
|
||||
"codemirror-special-char-carriage-return",
|
||||
|
@ -272,6 +293,11 @@
|
|||
"dependencies": [
|
||||
"ext.CodeMirror.v6",
|
||||
"ext.CodeMirror.v6.lib"
|
||||
],
|
||||
"messages": [
|
||||
"codemirror-fold-template",
|
||||
"codemirror-prefs-bidiisolation",
|
||||
"codemirror-prefs-templatefolding"
|
||||
]
|
||||
},
|
||||
"ext.CodeMirror.v6.WikiEditor": {
|
||||
|
@ -334,7 +360,8 @@
|
|||
"ForeignResourcesDir": "resources/lib",
|
||||
"DefaultUserOptions": {
|
||||
"usecodemirror": 0,
|
||||
"usecodemirror-colorblind": 0
|
||||
"usecodemirror-colorblind": 0,
|
||||
"codemirror-preferences": ""
|
||||
},
|
||||
"QUnitTestModule": {
|
||||
"localBasePath": "resources/legacy/mode/mediawiki/tests",
|
||||
|
|
|
@ -10,9 +10,18 @@
|
|||
"codemirror-toggle-label-short": "Syntax",
|
||||
"codemirror-prefs-summary": "You can learn more about this feature by reading the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Extension:CodeMirror help page].",
|
||||
"codemirror-prefs-enable": "Enable syntax highlighting for wikitext",
|
||||
"codemirror-prefs-title": "Syntax highlighting preferences",
|
||||
"codemirror-prefs-templatefolding": "Enable folding of template parameters",
|
||||
"codemirror-prefs-bidiisolation": "Isolate bidirectional text",
|
||||
"codemirror-prefs-bracketmatching": "Enable bracket matching",
|
||||
"codemirror-prefs-linenumbering": "Show line numbers",
|
||||
"codemirror-prefs-linewrapping": "Wrap lines",
|
||||
"codemirror-prefs-activeline": "Highlight the active line",
|
||||
"codemirror-prefs-specialchars": "Show special characters",
|
||||
"codemirror-v6-prefs-colorblind": "Use colorblind-friendly scheme",
|
||||
"codemirror-prefs-colorblind": "Enable colorblind-friendly scheme for syntax highlighting when editing wikitext",
|
||||
"codemirror-prefs-colorblind-help": "If you use a gadget for syntax highlighting, this preference will not work.",
|
||||
"codemirror-close": "Close",
|
||||
"codemirror-find": "Find",
|
||||
"codemirror-next": "Find next",
|
||||
"codemirror-previous": "Find previous",
|
||||
|
|
|
@ -15,9 +15,18 @@
|
|||
"codemirror-toggle-label-short": "Label shown next to the CodeMirror icon in the editing toolbar. This message should be as brief as possible. {{msg-mw|codemirror-toggle-label}} is the full message, and is shown as the tooltip for the button.",
|
||||
"codemirror-prefs-summary": "Used in [[Special:Preferences]] in the section titled {{msg-mw|prefs-syntax-highlighting}}, at the top as a summary for the whole section.",
|
||||
"codemirror-prefs-enable": "Used in user preferences as label for enabling syntax highlighting.",
|
||||
"codemirror-prefs-title": "Syntax highlighting preferences",
|
||||
"codemirror-prefs-templatefolding": "Label for the option to enable folding of template parameters in the CodeMirror preferences panel.",
|
||||
"codemirror-prefs-bidiisolation": "Label for the option to enable bidirectional text isolation in the CodeMirror preferences panel.",
|
||||
"codemirror-prefs-bracketmatching": "Label for the option to enable bracket matching in the CodeMirror preferences panel.",
|
||||
"codemirror-prefs-linenumbering": "Label for the option to show line numbers in the CodeMirror preferences panel.",
|
||||
"codemirror-prefs-linewrapping": "Label for the option to wrap lines in the CodeMirror preferences panel.",
|
||||
"codemirror-prefs-activeline": "Label for the option to highlight the active line in the CodeMirror preferences panel.",
|
||||
"codemirror-prefs-specialchars": "Label for the option to show special characters in the CodeMirror preferences panel.",
|
||||
"codemirror-v6-prefs-colorblind": "Used in user preferences as label for enabling the colorblind-friendly option. This is a shorter version of {{msg-mw|codemirror-prefs-colorblind}} shown under section {{msg-mw|prefs-syntax-highlighting}} on wikis using CodeMirror 6.",
|
||||
"codemirror-prefs-colorblind": "Used in user preferences as label for enabling the colorblind-friendly option.",
|
||||
"codemirror-prefs-colorblind-help": "Used in user preferences as remark on the colorblind-friendly option.",
|
||||
"codemirror-close": "Tooltip text for the 'Close' button in CodeMirror panels.",
|
||||
"codemirror-find": "Placeholder text for the input in the CodeMirror search panel.",
|
||||
"codemirror-next": "Tooltip text for the 'Find next' button in the CodeMirror search panel.",
|
||||
"codemirror-previous": "Tooltip text for the 'Find previous' button in the CodeMirror search panel.",
|
||||
|
|
|
@ -59,6 +59,7 @@ class DataScript {
|
|||
// initialize configuration
|
||||
$config = [
|
||||
'useV6' => $mwConfig->get( 'CodeMirrorV6' ),
|
||||
'defaultPreferences' => $mwConfig->get( 'CodeMirrorDefaultPreferences' ),
|
||||
'lineNumberingNamespaces' => $mwConfig->get( 'CodeMirrorLineNumberingNamespaces' ),
|
||||
'templateFoldingNamespaces' => $mwConfig->get( 'CodeMirrorTemplateFoldingNamespaces' ),
|
||||
'pluginModules' => $registry->getAttribute( 'CodeMirrorPluginModules' ),
|
||||
|
|
|
@ -228,5 +228,9 @@ class Hooks implements
|
|||
'section' => 'editing/syntax-highlighting',
|
||||
'disable-if' => [ '!==', 'usecodemirror', '1' ]
|
||||
];
|
||||
|
||||
$defaultPreferences['codemirror-preferences'] = [
|
||||
'type' => 'api',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"maintitle": "CodeMirror",
|
||||
"repository": "https://gerrit.wikimedia.org/g/mediawiki/extensions/CodeMirror",
|
||||
"linkMap": {
|
||||
"jQuery.fn.textSelection": "https://doc.wikimedia.org/mediawiki-core/master/js/jQueryPlugins.html#.textSelection",
|
||||
"Compartment": "https://codemirror.net/docs/ref/#state.Compartment",
|
||||
"Decoration": "https://codemirror.net/docs/ref/#view.Decoration",
|
||||
"DecorationSet": "https://codemirror.net/docs/ref/#view.DecorationSet",
|
||||
|
@ -35,6 +34,7 @@
|
|||
"Extension": "https://codemirror.net/docs/ref/#state.Extension",
|
||||
"KeyBinding": "https://codemirror.net/docs/ref/#view.KeyBinding",
|
||||
"LanguageSupport": "https://codemirror.net/docs/ref/#language.LanguageSupport",
|
||||
"mw.Api": "https://doc.wikimedia.org/mediawiki-core/master/js/mw.Api.html",
|
||||
"Panel": "https://codemirror.net/docs/ref/#view.Panel",
|
||||
"PluginSpec": "https://codemirror.net/docs/ref/#view.PluginSpec",
|
||||
"RangeSet": "https://codemirror.net/docs/ref/#state.RangeSet",
|
||||
|
@ -49,7 +49,12 @@
|
|||
"Tooltip": "https://codemirror.net/docs/ref/#view.Tooltip",
|
||||
"Tree": "https://lezer.codemirror.net/docs/ref/#common.Tree",
|
||||
"ViewUpdate": "https://codemirror.net/docs/ref/#view.ViewUpdate",
|
||||
"ve.ui.Surface": "https://doc.wikimedia.org/visualeditor-standalone/master/ve.ui.Surface.html"
|
||||
"jQuery.fn.textSelection": "https://doc.wikimedia.org/mediawiki-core/master/js/jQueryPlugins.html#.textSelection",
|
||||
"ve.dm.Selection": "https://doc.wikimedia.org/visualeditor-standalone/master/ve.dm.Selection.html",
|
||||
"ve.dm.Transaction": "https://doc.wikimedia.org/visualeditor-standalone/master/ve.dm.Transaction.html",
|
||||
"ve.ui.Action": "https://doc.wikimedia.org/visualeditor-standalone/master/ve.ui.Action.html",
|
||||
"ve.ui.Surface": "https://doc.wikimedia.org/visualeditor-standalone/master/ve.ui.Surface.html",
|
||||
"ve.ui.Tool": "https://doc.wikimedia.org/visualeditor-standalone/master/ve.ui.Tool.html"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
const {
|
||||
Compartment,
|
||||
EditorState,
|
||||
EditorView,
|
||||
Extension,
|
||||
Compartment,
|
||||
KeyBinding,
|
||||
ViewUpdate,
|
||||
bracketMatching,
|
||||
crosshairCursor,
|
||||
defaultKeymap,
|
||||
drawSelection,
|
||||
highlightActiveLine,
|
||||
highlightSpecialChars,
|
||||
history,
|
||||
historyKeymap,
|
||||
|
@ -20,6 +21,7 @@ const {
|
|||
const CodeMirrorTextSelection = require( './codemirror.textSelection.js' );
|
||||
const CodeMirrorSearch = require( './codemirror.search.js' );
|
||||
const CodeMirrorGotoLine = require( './codemirror.gotoLine.js' );
|
||||
const CodeMirrorPreferences = require( './codemirror.preferences.js' );
|
||||
require( './ext.CodeMirror.data.js' );
|
||||
|
||||
/**
|
||||
|
@ -96,17 +98,23 @@ class CodeMirror {
|
|||
*/
|
||||
this.textSelection = null;
|
||||
/**
|
||||
* Compartment for the language direction Extension.
|
||||
* Compartment to control the direction of the editor.
|
||||
*
|
||||
* @type {Compartment}
|
||||
*/
|
||||
this.dirCompartment = new Compartment();
|
||||
/**
|
||||
* Compartment for the special characters Extension.
|
||||
* The CodeMirror preferences panel.
|
||||
*
|
||||
* @type {Compartment}
|
||||
* @type {CodeMirrorPreferences}
|
||||
*/
|
||||
this.specialCharsCompartment = new Compartment();
|
||||
this.preferences = new CodeMirrorPreferences( {
|
||||
bracketMatching: this.bracketMatchingExtension,
|
||||
lineNumbering: this.lineNumberingExtension,
|
||||
lineWrapping: this.lineWrappingExtension,
|
||||
activeLine: this.activeLineExtension,
|
||||
specialChars: this.specialCharsExtension
|
||||
}, !!this.surface );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,12 +129,11 @@ class CodeMirror {
|
|||
const extensions = [
|
||||
this.contentAttributesExtension,
|
||||
this.phrasesExtension,
|
||||
this.specialCharsCompartment.of( this.specialCharsExtension ),
|
||||
this.heightExtension,
|
||||
this.updateExtension,
|
||||
this.bracketMatchingExtension,
|
||||
this.dirExtension,
|
||||
this.searchExtension,
|
||||
this.preferences.extension,
|
||||
EditorState.readOnly.of( this.readOnly ),
|
||||
EditorView.domEventHandlers( {
|
||||
blur: () => {
|
||||
|
@ -136,7 +143,6 @@ class CodeMirror {
|
|||
this.$textarea[ 0 ].dispatchEvent( new Event( 'focus' ) );
|
||||
}
|
||||
} ),
|
||||
EditorView.lineWrapping,
|
||||
keymap.of( defaultKeymap ),
|
||||
EditorState.allowMultipleSelections.of( true ),
|
||||
drawSelection(),
|
||||
|
@ -161,15 +167,36 @@ class CodeMirror {
|
|||
) );
|
||||
}
|
||||
|
||||
// Set to [] to disable everywhere, or null to enable everywhere
|
||||
const namespaces = mw.config.get( 'extCodeMirrorConfig' ).lineNumberingNamespaces;
|
||||
if ( !namespaces || namespaces.includes( mw.config.get( 'wgNamespaceNumber' ) ) ) {
|
||||
extensions.push( lineNumbers() );
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for highlighting the active line.
|
||||
*
|
||||
* @return {Extension}
|
||||
*/
|
||||
get activeLineExtension() {
|
||||
return highlightActiveLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for line wrapping.
|
||||
*
|
||||
* @return {Extension}
|
||||
*/
|
||||
get lineWrappingExtension() {
|
||||
return EditorView.lineWrapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for line numbering.
|
||||
*
|
||||
* @return {Extension|Extension[]}
|
||||
*/
|
||||
get lineNumberingExtension() {
|
||||
return lineNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for search and goto line functionality.
|
||||
*
|
||||
|
@ -319,7 +346,7 @@ class CodeMirror {
|
|||
* @stable to call
|
||||
*/
|
||||
get specialCharsExtension() {
|
||||
// Keys are the decimal unicode number, values are the messages.
|
||||
// Keys are the decimal Unicode number, values are the messages.
|
||||
const messages = {
|
||||
0: mw.msg( 'codemirror-special-char-null' ),
|
||||
7: mw.msg( 'codemirror-special-char-bell' ),
|
||||
|
@ -368,6 +395,12 @@ class CodeMirror {
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* This extension adds the ability to change the direction of the editor.
|
||||
*
|
||||
* @type {Extension}
|
||||
* @stable to call
|
||||
*/
|
||||
get dirExtension() {
|
||||
return [
|
||||
this.dirCompartment.of( EditorView.editorAttributes.of( {
|
||||
|
@ -449,10 +482,11 @@ class CodeMirror {
|
|||
* Called just after CodeMirror is initialized.
|
||||
*
|
||||
* @event CodeMirror~'ext.CodeMirror.ready'
|
||||
* @param {jQuery} $view The CodeMirror view.
|
||||
* @param {jQuery} $view The CodeMirror view element.
|
||||
* @param {EditorState} state The CodeMirror instance.
|
||||
* @stable to use
|
||||
*/
|
||||
mw.hook( 'ext.CodeMirror.ready' ).fire( $( this.view.dom ) );
|
||||
mw.hook( 'ext.CodeMirror.ready' ).fire( $( this.view.dom ), this );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,38 +1,17 @@
|
|||
@import 'mediawiki.skin.variables.less';
|
||||
@import './codemirror.mixins.less';
|
||||
|
||||
.cm-editor {
|
||||
border: @border-width-base @border-style-base @border-color-subtle;
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: #d9d9d9;
|
||||
|
||||
@media screen {
|
||||
html.skin-theme-clientpref-night & {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os & {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
.darkmode( background, #222 );
|
||||
}
|
||||
|
||||
&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
|
||||
background: #d7d4f0;
|
||||
|
||||
@media screen {
|
||||
html.skin-theme-clientpref-night & {
|
||||
background: #233;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os & {
|
||||
background: #233;
|
||||
}
|
||||
}
|
||||
.darkmode( background, #233 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +64,11 @@
|
|||
border-left-color: @color-emphasized;
|
||||
}
|
||||
|
||||
.cm-editor .cm-activeLine {
|
||||
background-color: rgba( 204, 238, 255, 0.27 );
|
||||
.darkmode( background-color, rgba( 71, 71, 124, 0.2 ) );
|
||||
}
|
||||
|
||||
.cm-editor .cm-tooltip {
|
||||
background-color: @background-color-neutral-subtle;
|
||||
border-color: @border-color-base;
|
||||
|
@ -106,6 +90,11 @@
|
|||
.cm-mw-panel {
|
||||
border-bottom: @border-style-base @border-width-base @border-color-subtle;
|
||||
padding: @spacing-50;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cm-mw-panel--fieldset legend {
|
||||
margin-bottom: @spacing-50;
|
||||
}
|
||||
|
||||
.cm-mw-panel--text-input {
|
||||
|
@ -113,6 +102,10 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cm-mw-panel--checkbox {
|
||||
margin-bottom: @spacing-25;
|
||||
}
|
||||
|
||||
.cm-mw-panel--row {
|
||||
align-items: center;
|
||||
column-gap: @spacing-50;
|
||||
|
@ -136,6 +129,12 @@
|
|||
background-color: @color-inverted;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-mw-panel-close {
|
||||
position: absolute;
|
||||
right: @spacing-50;
|
||||
top: @spacing-50;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-mw-find-results {
|
||||
|
@ -170,3 +169,8 @@
|
|||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-next, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
||||
.cm-mw-icon--close {
|
||||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-close, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
|
|
@ -1286,19 +1286,16 @@ const mediaWikiLang = ( config = { bidiIsolation: false }, mwConfig = null ) =>
|
|||
)
|
||||
) ];
|
||||
|
||||
// Set to [] to disable everywhere, or null to enable everywhere.
|
||||
const templateFoldingNs = mwConfig.templateFoldingNamespaces;
|
||||
const shouldUseFolding = !templateFoldingNs || templateFoldingNs.includes( mw.config.get( 'wgNamespaceNumber' ) );
|
||||
// Add template folding if in supported namespace.
|
||||
if ( shouldUseFolding && ( config.templateFolding || config.templateFolding === undefined ) ) {
|
||||
langExtension.push( templateFoldingExtension );
|
||||
}
|
||||
|
||||
// Bundle the bidi isolation extension, as it's coded specifically for MediaWiki.
|
||||
// This is behind a config option for performance reasons (we only use it on RTL pages).
|
||||
if ( config.bidiIsolation ) {
|
||||
langExtension.push( bidiIsolationExtension );
|
||||
}
|
||||
// Register MW-specific Extensions into CodeMirror preferences. Whether they are enabled
|
||||
// or not is determined by the user's preferences and wiki configuration.
|
||||
mw.hook( 'ext.CodeMirror.ready' ).add( ( $textarea, cm ) => {
|
||||
if ( config.templateFolding !== false ) {
|
||||
cm.preferences.registerExtension( 'templateFolding', templateFoldingExtension, cm.view );
|
||||
}
|
||||
if ( config.bidiIsolation ) {
|
||||
cm.preferences.registerExtension( 'bidiIsolation', bidiIsolationExtension, cm.view );
|
||||
}
|
||||
} );
|
||||
|
||||
return new LanguageSupport( lang, langExtension );
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import 'mediawiki.skin.variables.less';
|
||||
@import './codemirror.mixins.less';
|
||||
|
||||
@error-color: @color-destructive;
|
||||
@link-color: @color-progressive;
|
||||
|
@ -23,20 +24,6 @@
|
|||
background-color: average( average( @template-shade, @ext-shade ), @link-shade );
|
||||
}
|
||||
|
||||
.darkmode( @prop, @value ) {
|
||||
@media screen {
|
||||
html.skin-theme-clientpref-night & {
|
||||
@{prop}: @value;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os & {
|
||||
@{prop}: @value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wikitext-formatting-color {
|
||||
color: @wikitext-formatting-color;
|
||||
.darkmode( color, @wikitext-formatting-color-dark );
|
||||
|
|
13
resources/codemirror.mixins.less
Normal file
13
resources/codemirror.mixins.less
Normal file
|
@ -0,0 +1,13 @@
|
|||
.darkmode( @prop, @value ) {
|
||||
@media screen {
|
||||
html.skin-theme-clientpref-night & {
|
||||
@{prop}: @value;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os & {
|
||||
@{prop}: @value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,9 +12,7 @@ class CodeMirrorPanel {
|
|||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* @type {EditorView}
|
||||
*/
|
||||
/** @type {EditorView} */
|
||||
this.view = undefined;
|
||||
}
|
||||
|
||||
|
@ -200,6 +198,27 @@ class CodeMirrorPanel {
|
|||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CSS-only Codex Fieldset.
|
||||
*
|
||||
* @param {string} legendText
|
||||
* @param {...HTMLElement[]} fields
|
||||
* @return {Element}
|
||||
*/
|
||||
getFieldset( legendText, ...fields ) {
|
||||
const fieldset = document.createElement( 'fieldset' );
|
||||
fieldset.className = 'cm-mw-panel--fieldset cdx-field';
|
||||
const legend = document.createElement( 'legend' );
|
||||
legend.className = 'cdx-label';
|
||||
const innerSpan = document.createElement( 'span' );
|
||||
innerSpan.className = 'cdx-label__label__text';
|
||||
innerSpan.textContent = legendText;
|
||||
legend.appendChild( innerSpan );
|
||||
fieldset.appendChild( legend );
|
||||
fieldset.append( ...fields );
|
||||
return fieldset;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeMirrorPanel;
|
||||
|
|
329
resources/codemirror.preferences.js
Normal file
329
resources/codemirror.preferences.js
Normal file
|
@ -0,0 +1,329 @@
|
|||
const {
|
||||
Compartment,
|
||||
EditorView,
|
||||
Extension,
|
||||
StateEffect,
|
||||
StateEffectType,
|
||||
StateField,
|
||||
keymap,
|
||||
showPanel
|
||||
} = require( 'ext.CodeMirror.v6.lib' );
|
||||
const CodeMirrorPanel = require( './codemirror.panel.js' );
|
||||
require( './ext.CodeMirror.data.js' );
|
||||
|
||||
/**
|
||||
* CodeMirrorPreferences is a panel that allows users to configure CodeMirror preferences.
|
||||
* It is toggled by pressing `Mod`-`Shift`-`,` (or `Command`+`Shift`+`,` on macOS).
|
||||
*
|
||||
* Note that this code, like MediaWiki Core, refers to the user's preferences as "options".
|
||||
* In this class, "preferences" refer to the user's preferences for CodeMirror, which
|
||||
* are stored as a single user 'option' in the database.
|
||||
*/
|
||||
class CodeMirrorPreferences extends CodeMirrorPanel {
|
||||
/**
|
||||
* @param {Object} extensionRegistry Key-value pairs of CodeMirror Extensions.
|
||||
* @param {boolean} [isVisualEditor=false] Whether the VE 2017 editor is being used.
|
||||
*/
|
||||
constructor( extensionRegistry, isVisualEditor = false ) {
|
||||
super();
|
||||
|
||||
/** @type {string} */
|
||||
this.optionName = 'codemirror-preferences';
|
||||
|
||||
/** @type {boolean} */
|
||||
this.isVisualEditor = isVisualEditor;
|
||||
|
||||
// VisualEditor only supports a subset of Extensions.
|
||||
const veSupportedExtensions = [
|
||||
'bracketMatching',
|
||||
'lineWrapping',
|
||||
'lineNumbering'
|
||||
];
|
||||
|
||||
/**
|
||||
* Registry of CodeMirror Extensions that are made available to CodeMirrorPreferences.
|
||||
*
|
||||
* @type {Object<Extension>}
|
||||
*/
|
||||
this.extensionRegistry = extensionRegistry;
|
||||
|
||||
/** @type {mw.Api} */
|
||||
this.api = new mw.Api();
|
||||
|
||||
/**
|
||||
* Registry of CodeMirror Compartments that are made available for
|
||||
* reconfiguration in CodeMirrorPreferences.
|
||||
*
|
||||
* @type {Object<Compartment>}
|
||||
*/
|
||||
this.compartmentRegistry = {};
|
||||
for ( const extName of Object.keys( extensionRegistry ) ) {
|
||||
if ( isVisualEditor && !veSupportedExtensions.includes( extName ) ) {
|
||||
delete this.extensionRegistry[ extName ];
|
||||
continue;
|
||||
}
|
||||
this.compartmentRegistry[ extName ] = new Compartment();
|
||||
}
|
||||
|
||||
/** @type {StateEffectType} */
|
||||
this.prefsToggleEffect = StateEffect.define();
|
||||
|
||||
/** @type {StateField} */
|
||||
this.panelStateField = StateField.define( {
|
||||
create: () => true,
|
||||
update: ( value, transaction ) => {
|
||||
for ( const e of transaction.effects ) {
|
||||
if ( e.is( this.prefsToggleEffect ) ) {
|
||||
value = e.value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
provide: ( stateField ) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
return showPanel.from( stateField, ( on ) => {
|
||||
return on ? () => this.panel : null;
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* The user's CodeMirror preferences.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.preferences = this.fetchPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default CodeMirror preferences, as defined by `$wgCodeMirrorPreferences`.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get defaultPreferences() {
|
||||
return mw.config.get( 'extCodeMirrorConfig' ).defaultPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user's CodeMirror preferences from the user options API,
|
||||
* or clientside storage for unnamed users.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
fetchPreferences() {
|
||||
let storageObj = this.defaultPreferences;
|
||||
|
||||
if ( mw.user.isNamed() ) {
|
||||
try {
|
||||
storageObj = JSON.parse( mw.user.options.get( this.optionName ) );
|
||||
} catch ( e ) {
|
||||
// Invalid JSON, or no preferences set.
|
||||
}
|
||||
} else {
|
||||
storageObj = mw.storage.getObject( this.optionName ) || this.defaultPreferences;
|
||||
}
|
||||
|
||||
storageObj = Object.assign( {}, this.defaultPreferences, storageObj );
|
||||
|
||||
// Convert binary representation to boolean.
|
||||
const preferences = {};
|
||||
for ( const prefName in storageObj ) {
|
||||
preferences[ prefName ] = !!storageObj[ prefName ];
|
||||
}
|
||||
return preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given CodeMirror preference and update the user option in the database,
|
||||
* or clientside storage for unnamed users.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {Mixed} value
|
||||
*/
|
||||
setPreference( key, value ) {
|
||||
this.preferences[ key ] = value;
|
||||
|
||||
// Only save the preferences that differ from the defaults,
|
||||
// and use a binary representation for storage. This is to prevent
|
||||
// bloat of the user_properties table (T54777).
|
||||
const storageObj = {};
|
||||
for ( const prefName in this.preferences ) {
|
||||
if ( !!this.preferences[ prefName ] !== !!this.defaultPreferences[ prefName ] ) {
|
||||
storageObj[ prefName ] = this.preferences[ prefName ] ? 1 : 0;
|
||||
}
|
||||
}
|
||||
mw.user.options.set( this.optionName, JSON.stringify( storageObj ) );
|
||||
|
||||
// Save the preferences to the database or clientside storage.
|
||||
if ( mw.user.isNamed() ) {
|
||||
this.api.saveOption( this.optionName, JSON.stringify( storageObj ) );
|
||||
} else {
|
||||
mw.storage.setObject( this.optionName, storageObj );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the given CodeMirror preference.
|
||||
*
|
||||
* @param {string} prefName
|
||||
* @return {boolean}
|
||||
*/
|
||||
getPreference( prefName ) {
|
||||
// First check the preference explicitly set by the user.
|
||||
// For now, we don't allow CodeMirror preferences to override
|
||||
// config settings in the 2017 editor, since there's no UI to set them.
|
||||
if ( !this.isVisualEditor && this.preferences[ prefName ] !== undefined ) {
|
||||
return this.preferences[ prefName ];
|
||||
}
|
||||
|
||||
// Otherwise, go by the defaults.
|
||||
|
||||
// Some preferences can be set per-namespace through wiki configuration.
|
||||
// Values are an array of namespace IDs, [] to disable everywhere,
|
||||
// or null to enable everywhere.
|
||||
const namespacePrefs = [ 'lineNumbering', 'templateFolding' ];
|
||||
if ( namespacePrefs.includes( prefName ) ) {
|
||||
const namespaces = mw.config.get( 'extCodeMirrorConfig' )[ prefName + 'Namespaces' ];
|
||||
return !namespaces || namespaces.includes( mw.config.get( 'wgNamespaceNumber' ) );
|
||||
}
|
||||
|
||||
// These preferences do not have configuration settings.
|
||||
return this.defaultPreferences[ prefName ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link Extension} with CodeMirrorPreferences, along with a
|
||||
* corresponding {@link Compartment} so that the Extension can be reconfigured.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Extension} extension
|
||||
* @param {EditorView} view
|
||||
* @internal
|
||||
*/
|
||||
registerExtension( name, extension, view ) {
|
||||
this.extensionRegistry[ name ] = extension;
|
||||
this.compartmentRegistry[ name ] = new Compartment();
|
||||
view.dispatch( {
|
||||
effects: StateEffect.appendConfig.of(
|
||||
this.compartmentRegistry[ name ].of(
|
||||
this.getPreference( name ) ? this.extensionRegistry[ name ] : []
|
||||
)
|
||||
)
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
get extension() {
|
||||
return [
|
||||
keymap.of( {
|
||||
key: 'Mod-Shift-,',
|
||||
run: ( view ) => {
|
||||
this.view = view;
|
||||
const effects = [ this.prefsToggleEffect.of( true ) ];
|
||||
if ( !this.view.state.field( this.panelStateField, false ) ) {
|
||||
effects.push( StateEffect.appendConfig.of( [ this.panelStateField ] ) );
|
||||
}
|
||||
this.view.dispatch( { effects } );
|
||||
this.view.dom.querySelector(
|
||||
'.cm-mw-preferences-panel input:first-child'
|
||||
).focus();
|
||||
return true;
|
||||
}
|
||||
} ),
|
||||
// Compartmentalized extensions
|
||||
Object.keys( this.extensionRegistry ).map(
|
||||
( name ) => this.compartmentRegistry[ name ].of(
|
||||
// Only apply the extension if the preference (or default pref) is enabled.
|
||||
this.getPreference( name ) ? this.extensionRegistry[ name ] : []
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
get panel() {
|
||||
const container = document.createElement( 'div' );
|
||||
container.className = 'cm-mw-preferences-panel cm-mw-panel';
|
||||
container.addEventListener( 'keydown', this.onKeydown.bind( this ) );
|
||||
|
||||
const wrappers = [];
|
||||
for ( const prefName in this.extensionRegistry ) {
|
||||
const [ wrapper ] = this.getCheckbox(
|
||||
prefName,
|
||||
`codemirror-prefs-${ prefName.toLowerCase() }`,
|
||||
this.getPreference( prefName )
|
||||
);
|
||||
wrappers.push( wrapper );
|
||||
}
|
||||
const fieldset = this.getFieldset( mw.msg( 'codemirror-prefs-title' ), ...wrappers );
|
||||
container.appendChild( fieldset );
|
||||
|
||||
const closeBtn = this.getButton( 'codemirror-close', 'close', true );
|
||||
closeBtn.classList.add( 'cdx-button--weight-quiet', 'cm-mw-panel-close' );
|
||||
container.appendChild( closeBtn );
|
||||
closeBtn.addEventListener( 'click', () => {
|
||||
this.toggle( this.view, false );
|
||||
} );
|
||||
|
||||
return {
|
||||
dom: container,
|
||||
top: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle display of the preferences panel.
|
||||
*
|
||||
* @param {EditorView} view
|
||||
* @param {boolean} [force]
|
||||
* @return {boolean}
|
||||
*/
|
||||
toggle( view, force ) {
|
||||
this.view = view;
|
||||
const bool = typeof force === 'boolean' ?
|
||||
force :
|
||||
!this.view.state.field( this.panelStateField );
|
||||
this.view.dispatch( {
|
||||
effects: this.prefsToggleEffect.of( bool )
|
||||
} );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keydown events on the preferences panel.
|
||||
*
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
onKeydown( event ) {
|
||||
if ( event.key === 'Escape' ) {
|
||||
event.preventDefault();
|
||||
this.toggle( this.view, false );
|
||||
this.view.focus();
|
||||
} else if ( event.key === 'Enter' ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
getCheckbox( name, label, checked ) {
|
||||
const compartment = this.compartmentRegistry[ name ];
|
||||
const extension = this.extensionRegistry[ name ];
|
||||
const [ wrapper, input ] = super.getCheckbox( name, label, checked );
|
||||
input.addEventListener( 'change', () => {
|
||||
this.view.dispatch( {
|
||||
effects: compartment.reconfigure( input.checked ? extension : [] )
|
||||
} );
|
||||
this.setPreference( name, input.checked );
|
||||
} );
|
||||
return [ wrapper, input ];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeMirrorPreferences;
|
|
@ -6,12 +6,10 @@ const urlParams = new URLSearchParams( window.location.search );
|
|||
|
||||
if ( mw.loader.getState( 'ext.wikiEditor' ) ) {
|
||||
mw.hook( 'wikiEditor.toolbarReady' ).add( ( $textarea ) => {
|
||||
const cmWE = new CodeMirrorWikiEditor(
|
||||
$textarea,
|
||||
mediaWikiLang( {
|
||||
bidiIsolation: $textarea.attr( 'dir' ) === 'rtl' && urlParams.get( 'cm6bidi' )
|
||||
} )
|
||||
);
|
||||
const mwLang = mediaWikiLang( {
|
||||
bidiIsolation: urlParams.get( 'cm6bidi' )
|
||||
} );
|
||||
const cmWE = new CodeMirrorWikiEditor( $textarea, mwLang );
|
||||
cmWE.addCodeMirrorToWikiEditor();
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -85,21 +85,16 @@ ve.ui.CodeMirrorAction.prototype.toggle = function ( enable ) {
|
|||
've-ce-documentNode-codeEditor-hide'
|
||||
);
|
||||
|
||||
// TODO: pass bidiIsolation option to mediawikiLang() when it's more stable.
|
||||
surface.mirror.initialize( surface.mirror.defaultExtensions.concat( mediawikiLang( {
|
||||
// These should never be enabled in VE
|
||||
bidiIsolation: false,
|
||||
templateFolding: false
|
||||
} ), lineHeightExtension ) );
|
||||
|
||||
// Force infinite viewport in CodeMirror to prevent misalignment of
|
||||
// the VE surface and the CodeMirror view. See T357482#10076432.
|
||||
surface.mirror.view.viewState.printing = true;
|
||||
|
||||
// Disable the Extension that highlights special characters.
|
||||
surface.mirror.view.dispatch( {
|
||||
effects: surface.mirror.specialCharsCompartment.reconfigure(
|
||||
codeMirrorLib.EditorView.editorAttributes.of( [] )
|
||||
)
|
||||
} );
|
||||
|
||||
// Account for the gutter width in the margin.
|
||||
action.updateGutterWidth( doc.getDir() );
|
||||
|
||||
|
@ -158,7 +153,12 @@ ve.ui.CodeMirrorAction.prototype.toggle = function ( enable ) {
|
|||
* @param {string} dir Document direction
|
||||
*/
|
||||
ve.ui.CodeMirrorAction.prototype.updateGutterWidth = function ( dir ) {
|
||||
const guttersWidth = this.surface.mirror.view.dom.querySelector( '.cm-gutters' ).getBoundingClientRect().width;
|
||||
const gutter = this.surface.mirror.view.dom.querySelector( '.cm-gutters' );
|
||||
if ( !gutter ) {
|
||||
// Line numbering is disabled.
|
||||
return;
|
||||
}
|
||||
const guttersWidth = gutter.getBoundingClientRect().width;
|
||||
this.surface.getView().$documentNode.css( {
|
||||
'margin-left': dir === 'rtl' ? 0 : guttersWidth,
|
||||
'margin-right': dir === 'rtl' ? guttersWidth : 0
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const CodeMirror = require( '../../resources/codemirror.js' );
|
||||
const mediaWikiLang = require( '../../resources/codemirror.mediawiki.js' );
|
||||
const bidiIsolationExtension = require( '../../resources/codemirror.mediawiki.bidiIsolation.js' );
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
|
@ -24,6 +25,8 @@ const mwLang = mediaWikiLang(
|
|||
{ tags: { ref: true } }
|
||||
);
|
||||
cm.initialize( [ ...cm.defaultExtensions, mwLang ] );
|
||||
// Normally ran by mw.hook, but we don't mock the hook system in the Jest tests.
|
||||
cm.preferences.registerExtension( 'bidiIsolation', bidiIsolationExtension, cm.view );
|
||||
|
||||
describe( 'CodeMirrorBidiIsolation', () => {
|
||||
it.each( testCases )(
|
||||
|
|
77
tests/jest/codemirror.preferences.test.js
Normal file
77
tests/jest/codemirror.preferences.test.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* eslint-disable-next-line n/no-missing-require */
|
||||
const { Compartment, EditorView } = require( 'ext.CodeMirror.v6.lib' );
|
||||
const CodeMirrorPreferences = require( '../../resources/codemirror.preferences.js' );
|
||||
|
||||
describe( 'CodeMirrorPreferences', () => {
|
||||
let mockDefaultPreferences, mockUserPreferences, getCodeMirrorPreferences;
|
||||
|
||||
beforeEach( () => {
|
||||
mockDefaultPreferences = ( config = { fooExtension: false, barExtension: true } ) => {
|
||||
mw.config.get = jest.fn().mockReturnValue( {
|
||||
defaultPreferences: config
|
||||
} );
|
||||
};
|
||||
mockUserPreferences = ( preferences = {} ) => {
|
||||
mw.user.options.get = jest.fn().mockReturnValue( preferences );
|
||||
};
|
||||
/* eslint-disable-next-line arrow-body-style */
|
||||
getCodeMirrorPreferences = () => {
|
||||
return new CodeMirrorPreferences( {
|
||||
fooExtension: EditorView.theme(),
|
||||
barExtension: EditorView.theme()
|
||||
} );
|
||||
};
|
||||
} );
|
||||
|
||||
it( 'defaultPreferences', () => {
|
||||
mockDefaultPreferences();
|
||||
const preferences = getCodeMirrorPreferences();
|
||||
expect( preferences.defaultPreferences ).toStrictEqual( {
|
||||
fooExtension: false,
|
||||
barExtension: true
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'fetchPreferences', () => {
|
||||
mockDefaultPreferences();
|
||||
mockUserPreferences( '{"fooExtension":1}' );
|
||||
mw.user.isNamed = jest.fn().mockReturnValue( true );
|
||||
const preferences = getCodeMirrorPreferences();
|
||||
expect( preferences.fetchPreferences() ).toStrictEqual( {
|
||||
fooExtension: true,
|
||||
barExtension: true
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'setPreference', () => {
|
||||
mockDefaultPreferences();
|
||||
mw.user.isNamed = jest.fn().mockReturnValue( true );
|
||||
const preferences = getCodeMirrorPreferences();
|
||||
preferences.setPreference( 'fooExtension', true );
|
||||
expect( preferences.preferences.fooExtension ).toStrictEqual( true );
|
||||
expect( mw.user.options.set ).toHaveBeenCalledWith( 'codemirror-preferences', '{"fooExtension":1}' );
|
||||
expect( mw.Api.prototype.saveOption ).toHaveBeenCalledWith( 'codemirror-preferences', '{"fooExtension":1}' );
|
||||
} );
|
||||
|
||||
it( 'getPreference', () => {
|
||||
mockDefaultPreferences();
|
||||
mockUserPreferences( '{"barExtension":0}' );
|
||||
const preferences = getCodeMirrorPreferences();
|
||||
expect( preferences.getPreference( 'fooExtension' ) ).toStrictEqual( false );
|
||||
expect( preferences.getPreference( 'barExtension' ) ).toStrictEqual( false );
|
||||
} );
|
||||
|
||||
it( 'registerExtension', () => {
|
||||
mockDefaultPreferences( { fooExtension: false, barExtension: false } );
|
||||
mockUserPreferences( '{"fooExtension":0,"barExtension":1}' );
|
||||
const fooExtension = EditorView.theme();
|
||||
const barExtension = EditorView.theme();
|
||||
const preferences = getCodeMirrorPreferences( { fooExtension, barExtension } );
|
||||
const view = new EditorView();
|
||||
preferences.registerExtension( 'barExtension', barExtension, view );
|
||||
expect( preferences.extensionRegistry.barExtension ).toStrictEqual( barExtension );
|
||||
expect( preferences.compartmentRegistry.barExtension ).toBeInstanceOf( Compartment );
|
||||
expect( preferences.compartmentRegistry.barExtension.get( view.state ).length )
|
||||
.toStrictEqual( 2 );
|
||||
} );
|
||||
} );
|
|
@ -6,8 +6,13 @@ jest.mock( '../../resources/ext.CodeMirror.data.js', () => jest.fn(), { virtual:
|
|||
global.mw = require( '@wikimedia/mw-node-qunit/src/mockMediaWiki.js' )();
|
||||
mw.user = Object.assign( mw.user, {
|
||||
options: {
|
||||
// Only called for 'usecodemirror' option.
|
||||
get: jest.fn().mockReturnValue( 1 ),
|
||||
get: jest.fn().mockImplementation( ( key ) => {
|
||||
if ( key === 'codemirror-preferences' ) {
|
||||
return '{"bracketMatching":1,"lineWrapping":1,"activeLine":0,"specialChars":1,"bidiIsolation":1}';
|
||||
}
|
||||
// Only called for 'usecodemirror' option.
|
||||
return '1';
|
||||
} ),
|
||||
set: jest.fn()
|
||||
},
|
||||
sessionId: jest.fn().mockReturnValue( 'abc' ),
|
||||
|
|
|
@ -4,15 +4,13 @@ const Page = require( 'wdio-mediawiki/Page' );
|
|||
|
||||
// Copied from mediawiki-core edit.page.js
|
||||
class EditPage extends Page {
|
||||
async openForEditing( title, cm6enable = false ) {
|
||||
async openForEditing( title ) {
|
||||
const queryParams = {
|
||||
action: 'edit',
|
||||
vehidebetadialog: 1,
|
||||
hidewelcomedialog: 1
|
||||
hidewelcomedialog: 1,
|
||||
cm6enable: 1
|
||||
};
|
||||
if ( cm6enable ) {
|
||||
queryParams.cm6enable = '1';
|
||||
}
|
||||
await super.openTitle( title, queryParams );
|
||||
}
|
||||
|
||||
|
@ -67,7 +65,7 @@ class EditPage extends Page {
|
|||
}
|
||||
|
||||
get highlightedBrackets() {
|
||||
return $$( '.CodeMirror-line .cm-mw-matchingbracket' );
|
||||
return $$( '.cm-line .cm-matchingBracket' );
|
||||
}
|
||||
|
||||
async getHighlightedMatchingBrackets() {
|
||||
|
|
|
@ -4,6 +4,7 @@ const assert = require( 'assert' ),
|
|||
EditPage = require( '../pageobjects/edit.page' ),
|
||||
FixtureContent = require( '../fixturecontent' ),
|
||||
UserPreferences = require( '../userpreferences' ),
|
||||
Api = require( 'wdio-mediawiki/Api.js' ),
|
||||
Util = require( 'wdio-mediawiki/Util' );
|
||||
|
||||
describe( 'CodeMirror bracket match highlighting for the wikitext 2010 editor', () => {
|
||||
|
@ -31,4 +32,9 @@ describe( 'CodeMirror bracket match highlighting for the wikitext 2010 editor',
|
|||
await EditPage.cursorToPosition( 3 );
|
||||
assert.strictEqual( await EditPage.getHighlightedMatchingBrackets(), '{}' );
|
||||
} );
|
||||
|
||||
after( async () => {
|
||||
const bot = await Api.bot();
|
||||
bot.delete( title, 'Test cleanup' ).catch( ( e ) => console.error( e ) );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -15,7 +15,7 @@ describe( 'CodeMirror template folding for the wikitext 2010 editor', () => {
|
|||
await LoginPage.loginAdmin();
|
||||
await FixtureContent.createFixturePage( title );
|
||||
await UserPreferences.enableWikitext2010EditorWithCodeMirror();
|
||||
await EditPage.openForEditing( title, true );
|
||||
await EditPage.openForEditing( title );
|
||||
await EditPage.wikiEditorToolbar.waitForDisplayed();
|
||||
await browser.execute( () => {
|
||||
$( '.cm-editor' ).textSelection( 'setContents', '{{foo|1={{bar|{{baz|{{PAGENAME}}}}}}}}' );
|
||||
|
|
|
@ -15,7 +15,7 @@ describe( 'CodeMirror textSelection for the wikitext 2010 editor', () => {
|
|||
await LoginPage.loginAdmin();
|
||||
await FixtureContent.createFixturePage( title );
|
||||
await UserPreferences.enableWikitext2010EditorWithCodeMirror();
|
||||
await EditPage.openForEditing( title, true );
|
||||
await EditPage.openForEditing( title );
|
||||
await EditPage.wikiEditorToolbar.waitForDisplayed();
|
||||
await EditPage.clickText();
|
||||
} );
|
||||
|
|
|
@ -24,19 +24,11 @@ class UserPreferences {
|
|||
await this.setPreferences( {
|
||||
usebetatoolbar: '1',
|
||||
usecodemirror: '1',
|
||||
'codemirror-preferences': '{"bracketMatching":1,"lineWrapping":1,"activeLine":0,"specialChars":1,"bidiIsolation":0}',
|
||||
'visualeditor-enable': '0',
|
||||
'visualeditor-newwikitext': '0'
|
||||
} );
|
||||
}
|
||||
|
||||
async enableWikitext2017EditorWithCodeMirror() {
|
||||
await this.setPreferences( {
|
||||
usebetatoolbar: null,
|
||||
usecodemirror: '1',
|
||||
'visualeditor-enable': '1',
|
||||
'visualeditor-newwikitext': '1'
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new UserPreferences();
|
||||
|
|
Loading…
Reference in a new issue