mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-23 13:56:44 +00:00
Merge "Implement dark mode styles and use Codex CSS components in search panel"
This commit is contained in:
commit
74aeccf9c6
|
@ -165,13 +165,15 @@
|
|||
"legacy/modules/ve-cm/ve.ui.CodeMirrorTool.js"
|
||||
],
|
||||
"styles": [
|
||||
"legacy/modules/ve-cm/ve.ui.CodeMirror.less"
|
||||
"legacy/modules/ve-cm/ve.ui.CodeMirror.less",
|
||||
"legacy/ext.CodeMirror.less"
|
||||
],
|
||||
"messages": [
|
||||
"codemirror-toggle-label"
|
||||
]
|
||||
},
|
||||
"ext.CodeMirror.v6": {
|
||||
"class": "MediaWiki\\ResourceLoader\\CodexModule",
|
||||
"dependencies": [
|
||||
"mediawiki.api",
|
||||
"mediawiki.user",
|
||||
|
@ -181,6 +183,9 @@
|
|||
"packageFiles": [
|
||||
"codemirror.js",
|
||||
"codemirror.textSelection.js",
|
||||
"codemirror.search.js",
|
||||
"codemirror.gotoLine.js",
|
||||
"codemirror.panel.js",
|
||||
{
|
||||
"name": "ext.CodeMirror.data.js",
|
||||
"callback": "MediaWiki\\Extension\\CodeMirror\\DataScript::makeScript"
|
||||
|
@ -189,44 +194,57 @@
|
|||
"styles": [
|
||||
"codemirror.less"
|
||||
],
|
||||
"codexStyleOnly": true,
|
||||
"codexComponents": [
|
||||
"CdxButton",
|
||||
"CdxCheckbox",
|
||||
"CdxLabel",
|
||||
"CdxTextInput",
|
||||
"CdxToggleButton",
|
||||
"CdxToggleButtonGroup"
|
||||
],
|
||||
"messages": [
|
||||
"codemirror-all",
|
||||
"codemirror-all-tooltip",
|
||||
"codemirror-by-word",
|
||||
"codemirror-control-character",
|
||||
"codemirror-done",
|
||||
"codemirror-find",
|
||||
"codemirror-fold-template",
|
||||
"codemirror-folded-code",
|
||||
"codemirror-goto-line",
|
||||
"codemirror-goto-line-go",
|
||||
"codemirror-match-case",
|
||||
"codemirror-next",
|
||||
"codemirror-previous",
|
||||
"codemirror-all",
|
||||
"codemirror-match-case",
|
||||
"codemirror-regexp",
|
||||
"codemirror-by-word",
|
||||
"codemirror-replace",
|
||||
"codemirror-replace-placeholder",
|
||||
"codemirror-replace-all",
|
||||
"codemirror-control-character",
|
||||
"codemirror-special-char-null",
|
||||
"codemirror-special-char-bell",
|
||||
"codemirror-replace-placeholder",
|
||||
"codemirror-special-char-backspace",
|
||||
"codemirror-special-char-newline",
|
||||
"codemirror-special-char-vertical-tab",
|
||||
"codemirror-special-char-bell",
|
||||
"codemirror-special-char-carriage-return",
|
||||
"codemirror-special-char-escape",
|
||||
"codemirror-special-char-nbsp",
|
||||
"codemirror-special-char-zero-width-space",
|
||||
"codemirror-special-char-zero-width-non-joiner",
|
||||
"codemirror-special-char-zero-width-joiner",
|
||||
"codemirror-special-char-left-to-right-mark",
|
||||
"codemirror-special-char-right-to-left-mark",
|
||||
"codemirror-special-char-line-separator",
|
||||
"codemirror-special-char-left-to-right-override",
|
||||
"codemirror-special-char-right-to-left-override",
|
||||
"codemirror-special-char-narrow-nbsp",
|
||||
"codemirror-special-char-left-to-right-isolate",
|
||||
"codemirror-special-char-right-to-left-isolate",
|
||||
"codemirror-special-char-pop-directional-isolate",
|
||||
"codemirror-special-char-paragraph-separator",
|
||||
"codemirror-special-char-zero-width-no-break-space",
|
||||
"codemirror-special-char-left-to-right-mark",
|
||||
"codemirror-special-char-left-to-right-override",
|
||||
"codemirror-special-char-line-separator",
|
||||
"codemirror-special-char-narrow-nbsp",
|
||||
"codemirror-special-char-nbsp",
|
||||
"codemirror-special-char-newline",
|
||||
"codemirror-special-char-null",
|
||||
"codemirror-special-char-object-replacement",
|
||||
"codemirror-fold-template",
|
||||
"codemirror-unfold",
|
||||
"codemirror-folded-code"
|
||||
"codemirror-special-char-paragraph-separator",
|
||||
"codemirror-special-char-pop-directional-isolate",
|
||||
"codemirror-special-char-right-to-left-isolate",
|
||||
"codemirror-special-char-right-to-left-mark",
|
||||
"codemirror-special-char-right-to-left-override",
|
||||
"codemirror-special-char-vertical-tab",
|
||||
"codemirror-special-char-zero-width-joiner",
|
||||
"codemirror-special-char-zero-width-no-break-space",
|
||||
"codemirror-special-char-zero-width-non-joiner",
|
||||
"codemirror-special-char-zero-width-space",
|
||||
"codemirror-unfold"
|
||||
]
|
||||
},
|
||||
"ext.CodeMirror.v6.init": {
|
||||
|
@ -266,6 +284,9 @@
|
|||
"packageFiles": [
|
||||
"codemirror.wikieditor.js"
|
||||
],
|
||||
"styles": [
|
||||
"codemirror.wikieditor.less"
|
||||
],
|
||||
"messages": [
|
||||
"codemirror-toggle-label",
|
||||
"codemirror-toggle-label-short"
|
||||
|
|
20
i18n/en.json
20
i18n/en.json
|
@ -14,15 +14,19 @@
|
|||
"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-find": "Find",
|
||||
"codemirror-next": "next",
|
||||
"codemirror-previous": "previous",
|
||||
"codemirror-all": "all",
|
||||
"codemirror-match-case": "match case",
|
||||
"codemirror-regexp": "regexp",
|
||||
"codemirror-by-word": "by word",
|
||||
"codemirror-replace": "replace",
|
||||
"codemirror-next": "Find next",
|
||||
"codemirror-previous": "Find previous",
|
||||
"codemirror-all": "All",
|
||||
"codemirror-all-tooltip": "Select all matches",
|
||||
"codemirror-match-case": "Match case",
|
||||
"codemirror-regexp": "Regular expression",
|
||||
"codemirror-by-word": "By word",
|
||||
"codemirror-replace": "Replace",
|
||||
"codemirror-replace-placeholder": "Replace",
|
||||
"codemirror-replace-all": "replace all",
|
||||
"codemirror-replace-all": "Replace all",
|
||||
"codemirror-done": "Done",
|
||||
"codemirror-goto-line": "Go to line",
|
||||
"codemirror-goto-line-go": "Go",
|
||||
"codemirror-control-character": "Control character $1",
|
||||
"codemirror-special-char-null": "Null character",
|
||||
"codemirror-special-char-bell": "Bell character",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"Raymond",
|
||||
"Shirayuki",
|
||||
"SkyDaisy9",
|
||||
"pastakhov"
|
||||
"pastakhov",
|
||||
"MusikAnimal"
|
||||
]
|
||||
},
|
||||
"codemirror-desc": "{{desc|name=Code Mirror|url=https://www.mediawiki.org/wiki/Extension:CodeMirror}}\n\nAdditional info: Description of \"Syntax highlighting\" in wiki\n[[mw:Extension:SyntaxHighlight GeSHi]]",
|
||||
|
@ -17,16 +18,20 @@
|
|||
"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-find": "Placeholder text for the input in the CodeMirror search dialog.",
|
||||
"codemirror-next": "Label for the 'Next' button in the CodeMirror search dialog.",
|
||||
"codemirror-previous": "Label for the 'Previous' button in the CodeMirror search dialog.",
|
||||
"codemirror-all": "Label for the 'All' button in the CodeMirror search dialog.",
|
||||
"codemirror-match-case": "Label for the 'match case' option in the CodeMirror search dialog.",
|
||||
"codemirror-regexp": "Label for the 'regexp' button in the CodeMirror search dialog. This enables the user to search using regular expressions.",
|
||||
"codemirror-by-word": "Label for the 'by word' button in the CodeMirror search dialog.",
|
||||
"codemirror-replace": "Label for the 'replace' button in the CodeMirror search dialog.",
|
||||
"codemirror-replace-placeholder": "Placeholder text for the 'Replace' input in the CodeMirror search dialog.",
|
||||
"codemirror-replace-all": "Label for the 'replace all' button in the CodeMirror search dialog.",
|
||||
"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.",
|
||||
"codemirror-all": "Label for the 'All' button in the CodeMirror search panel, which finds all the results. See also {{msg-mw|codemirror-all-tooltip}}.",
|
||||
"codemirror-all-tooltip": "Tooltip shown when hovering over the 'All' button in the CodeMirror search panel.",
|
||||
"codemirror-match-case": "Tooltip for the 'Match case' button in the CodeMirror search panel.",
|
||||
"codemirror-regexp": "Tooltip for the 'Regular expression' button in the CodeMirror search panel.",
|
||||
"codemirror-by-word": "Tooltip for the 'By word' button in the CodeMirror search panel.",
|
||||
"codemirror-replace": "Label for the 'Replace' button in the CodeMirror search panel.",
|
||||
"codemirror-replace-placeholder": "Placeholder text for the 'Replace' input in the CodeMirror search panel.",
|
||||
"codemirror-replace-all": "Label for the 'Replace all' button in the CodeMirror search panel.",
|
||||
"codemirror-done": "Label for the 'Done' button in CodeMirror panels.\n{{Identical|Done}}",
|
||||
"codemirror-goto-line": "Label for the 'Go to line' input field.",
|
||||
"codemirror-goto-line-go": "Label for the 'Go to line' submit button.\n{{Identical|Go}}",
|
||||
"codemirror-control-character": "Tooltip text shown when hovering over special characters. $1 is the Unicode value of the special character.",
|
||||
"codemirror-special-char-null": "Tooltip text shown when hovering over a null character. See [[wikidata:Q617945]] for possible translations.",
|
||||
"codemirror-special-char-bell": "Tooltip text shown when hovering over a bell character. See [[wikidata:Q815674]] for possible translations.",
|
||||
|
|
|
@ -35,8 +35,12 @@
|
|||
"Extension": "https://codemirror.net/docs/ref/#state.Extension",
|
||||
"KeyBinding": "https://codemirror.net/docs/ref/#view.KeyBinding",
|
||||
"LanguageSupport": "https://codemirror.net/docs/ref/#language.LanguageSupport",
|
||||
"Panel": "https://codemirror.net/docs/ref/#view.Panel",
|
||||
"PluginSpec": "https://codemirror.net/docs/ref/#view.PluginSpec",
|
||||
"RangeSet": "https://codemirror.net/docs/ref/#state.RangeSet",
|
||||
"SearchQuery": "https://codemirror.net/docs/ref/#search.SearchQuery",
|
||||
"StateEffectType": "https://codemirror.net/docs/ref/#state.StateEffectType",
|
||||
"StateField": "https://codemirror.net/docs/ref/#state.StateField",
|
||||
"StreamParser": "https://codemirror.net/docs/ref/#language.StreamParser",
|
||||
"StringStream": "https://codemirror.net/docs/ref/#language.StringStream",
|
||||
"SyntaxNode": "https://lezer.codemirror.net/docs/ref/#common.SyntaxNode",
|
||||
|
|
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -8,7 +8,7 @@
|
|||
"devDependencies": {
|
||||
"@codemirror/commands": "6.2.5",
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/search": "6.5.4",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.2.1",
|
||||
"@codemirror/view": "6.22.2",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
|
@ -524,9 +524,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.4",
|
||||
"version": "6.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
|
||||
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"devDependencies": {
|
||||
"@codemirror/commands": "6.2.5",
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/search": "6.5.4",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.2.1",
|
||||
"@codemirror/view": "6.22.2",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
"ve": "readonly"
|
||||
},
|
||||
"rules": {
|
||||
"max-len": "off",
|
||||
"es-x/no-array-prototype-includes": "off"
|
||||
},
|
||||
"overrides": [
|
||||
|
|
173
resources/codemirror.gotoLine.js
Normal file
173
resources/codemirror.gotoLine.js
Normal file
|
@ -0,0 +1,173 @@
|
|||
const {
|
||||
EditorSelection,
|
||||
EditorView,
|
||||
Prec,
|
||||
StateEffect,
|
||||
StateEffectType,
|
||||
StateField,
|
||||
keymap,
|
||||
showPanel
|
||||
} = require( 'ext.CodeMirror.v6.lib' );
|
||||
const CodeMirrorPanel = require( './codemirror.panel.js' );
|
||||
|
||||
/**
|
||||
* Custom goto line panel for CodeMirror using CSS-only Codex components.
|
||||
*
|
||||
* Using the Alt-g keybinding, this shows a panel asking the user for a line number,
|
||||
* when a valid position is provided, moves the cursor to that line.
|
||||
*
|
||||
* This feature supports line numbers, relative line offsets prefixed with `+` or `-`,
|
||||
* document percentages suffixed with `%`, and an optional column position by adding `:`
|
||||
* and a second number after the line number.
|
||||
*
|
||||
* Based on the CodeMirror implementation (MIT).
|
||||
*
|
||||
* @see https://github.com/codemirror/search/blob/0d8af3e4cc/src/goto-line.ts
|
||||
* @extends CodeMirrorPanel
|
||||
*/
|
||||
class CodeMirrorGotoLine extends CodeMirrorPanel {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* @type {StateEffectType}
|
||||
*/
|
||||
this.toggleEffect = StateEffect.define();
|
||||
|
||||
/**
|
||||
* @type {StateField}
|
||||
*/
|
||||
this.panelStateField = StateField.define( {
|
||||
create: () => true,
|
||||
update: ( value, transaction ) => {
|
||||
for ( const e of transaction.effects ) {
|
||||
if ( e.is( this.toggleEffect ) ) {
|
||||
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;
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
this.input = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
get extension() {
|
||||
// Use Prec.highest to ensure that this keymap is used before the default searchKeymap.
|
||||
return Prec.highest(
|
||||
keymap.of( {
|
||||
key: 'Mod-Alt-g',
|
||||
run: ( view ) => {
|
||||
this.view = view;
|
||||
const effects = [ this.toggleEffect.of( true ) ];
|
||||
if ( !this.view.state.field( this.panelStateField, false ) ) {
|
||||
effects.push( StateEffect.appendConfig.of( [ this.panelStateField ] ) );
|
||||
}
|
||||
this.view.dispatch( { effects } );
|
||||
return true;
|
||||
}
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
get panel() {
|
||||
const container = document.createElement( 'div' );
|
||||
container.className = 'cm-mw-goto-line-panel cm-mw-panel cm-mw-panel--row';
|
||||
container.addEventListener( 'keydown', this.onKeydown.bind( this ) );
|
||||
|
||||
// Line input.
|
||||
const [ inputWrapper, input ] = this.getTextInput( 'line', this.line );
|
||||
this.input = input;
|
||||
container.appendChild( inputWrapper );
|
||||
|
||||
// Go button.
|
||||
const button = this.getButton( 'codemirror-goto-line-go' );
|
||||
button.addEventListener( 'click', this.go.bind( this ) );
|
||||
container.appendChild( button );
|
||||
|
||||
return {
|
||||
dom: container,
|
||||
top: true,
|
||||
mount: () => {
|
||||
this.input.value = String(
|
||||
this.view.state.doc.lineAt(
|
||||
this.view.state.selection.main.head
|
||||
).number
|
||||
);
|
||||
this.input.focus();
|
||||
this.input.select();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to keydown events.
|
||||
*
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
onKeydown( event ) {
|
||||
if ( event.key === 'Escape' ) {
|
||||
event.preventDefault();
|
||||
this.view.dispatch( { effects: this.toggleEffect.of( false ) } );
|
||||
this.view.focus();
|
||||
} else if ( event.key === 'Enter' ) {
|
||||
event.preventDefault();
|
||||
this.go();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the specified line.
|
||||
*/
|
||||
go() {
|
||||
const match = /^([+-])?(\d+)?(:\d+)?(%)?$/.exec( this.input.value );
|
||||
if ( !match ) {
|
||||
return;
|
||||
}
|
||||
const { state } = this.view;
|
||||
const startLine = state.doc.lineAt( state.selection.main.head );
|
||||
const [ , sign, ln, cl, percent ] = match;
|
||||
const col = cl ? +cl.slice( 1 ) : 0;
|
||||
let line = ln ? +ln : startLine.number;
|
||||
if ( ln && percent ) {
|
||||
let pc = line / 100;
|
||||
if ( sign ) {
|
||||
pc = pc * ( sign === '-' ? -1 : 1 ) + ( startLine.number / state.doc.lines );
|
||||
}
|
||||
line = Math.round( state.doc.lines * pc );
|
||||
} else if ( ln && sign ) {
|
||||
line = line * ( sign === '-' ? -1 : 1 ) + startLine.number;
|
||||
}
|
||||
const docLine = state.doc.line( Math.max( 1, Math.min( state.doc.lines, line ) ) );
|
||||
const selection = EditorSelection.cursor(
|
||||
docLine.from + Math.max( 0, Math.min( col, docLine.length ) )
|
||||
);
|
||||
this.view.dispatch( {
|
||||
effects: [
|
||||
this.toggleEffect.of( false ),
|
||||
EditorView.scrollIntoView( selection.from, { y: 'center' } )
|
||||
],
|
||||
selection
|
||||
} );
|
||||
this.view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeMirrorGotoLine;
|
|
@ -15,10 +15,11 @@ const {
|
|||
keymap,
|
||||
lineNumbers,
|
||||
rectangularSelection,
|
||||
redo,
|
||||
searchKeymap
|
||||
redo
|
||||
} = require( 'ext.CodeMirror.v6.lib' );
|
||||
const CodeMirrorTextSelection = require( './codemirror.textSelection.js' );
|
||||
const CodeMirrorSearch = require( './codemirror.search.js' );
|
||||
const CodeMirrorGotoLine = require( './codemirror.gotoLine.js' );
|
||||
require( './ext.CodeMirror.data.js' );
|
||||
|
||||
/**
|
||||
|
@ -125,6 +126,7 @@ class CodeMirror {
|
|||
this.updateExtension,
|
||||
this.bracketMatchingExtension,
|
||||
this.dirExtension,
|
||||
this.searchExtension,
|
||||
EditorState.readOnly.of( this.readOnly ),
|
||||
EditorView.domEventHandlers( {
|
||||
blur: () => {
|
||||
|
@ -135,10 +137,7 @@ class CodeMirror {
|
|||
}
|
||||
} ),
|
||||
EditorView.lineWrapping,
|
||||
keymap.of( [
|
||||
...defaultKeymap,
|
||||
...searchKeymap
|
||||
] ),
|
||||
keymap.of( defaultKeymap ),
|
||||
EditorState.allowMultipleSelections.of( true ),
|
||||
drawSelection(),
|
||||
rectangularSelection(),
|
||||
|
@ -171,6 +170,18 @@ class CodeMirror {
|
|||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for search and goto line functionality.
|
||||
*
|
||||
* @return {Extension|Extension[]}
|
||||
*/
|
||||
get searchExtension() {
|
||||
return [
|
||||
new CodeMirrorSearch().extension,
|
||||
new CodeMirrorGotoLine().extension
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This extension adds bracket matching to the CodeMirror editor.
|
||||
*
|
||||
|
@ -293,16 +304,6 @@ class CodeMirror {
|
|||
*/
|
||||
get phrasesExtension() {
|
||||
return EditorState.phrases.of( {
|
||||
Find: mw.msg( 'codemirror-find' ),
|
||||
next: mw.msg( 'codemirror-next' ),
|
||||
previous: mw.msg( 'codemirror-previous' ),
|
||||
all: mw.msg( 'codemirror-all' ),
|
||||
'match case': mw.msg( 'codemirror-match-case' ),
|
||||
regexp: mw.msg( 'codemirror-regexp' ),
|
||||
'by word': mw.msg( 'codemirror-by-word' ),
|
||||
replace: mw.msg( 'codemirror-replace' ),
|
||||
Replace: mw.msg( 'codemirror-replace-placeholder' ),
|
||||
'replace all': mw.msg( 'codemirror-replace-all' ),
|
||||
'Control character': mw.msg( 'codemirror-control-character' )
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -1,7 +1,39 @@
|
|||
@import 'mediawiki.skin.variables.less';
|
||||
|
||||
.cm-editor {
|
||||
border: 1px solid #c8ccd1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-matchingBracket,
|
||||
|
@ -11,16 +43,26 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cm-editor .cm-specialChar {
|
||||
color: @color-destructive--hover;
|
||||
}
|
||||
|
||||
.cm-special-char-nbsp {
|
||||
color: #888;
|
||||
color: @color-placeholder;
|
||||
}
|
||||
|
||||
.cm-tooltip-fold {
|
||||
cursor: pointer;
|
||||
cursor: @cursor-base--hover;
|
||||
line-height: 1.2;
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.cm-editor .cm-foldPlaceholder {
|
||||
background-color: @background-color-interactive;
|
||||
border-color: @border-color-subtle;
|
||||
color: @color-base;
|
||||
}
|
||||
|
||||
.cm-bidi-isolate {
|
||||
/* @noflip */
|
||||
direction: ltr;
|
||||
|
@ -31,56 +73,92 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
// Overrides for WikiEditor.
|
||||
|
||||
.wikiEditor-ui-text .cm-editor {
|
||||
border: inherit;
|
||||
// The various .cm-editor prefixed styles are required to have higher
|
||||
// specificity than CodeMirror's default styles, which are set by JS.
|
||||
.cm-editor .cm-gutters {
|
||||
background-color: @background-color-interactive-subtle;
|
||||
border-right-color: @border-color-subtle;
|
||||
color: @color-subtle;
|
||||
}
|
||||
|
||||
.cm-mw-toggle-wikieditor {
|
||||
.oo-ui-icon-syntax-highlight {
|
||||
background-color: @color-base;
|
||||
// The SVG is just barely over 300 bytes, and is also only temporary
|
||||
// until an official icon has been established in Codex/OOUI (T174145).
|
||||
/* @embed */
|
||||
@url: url( codemirror.icon.svg );
|
||||
-webkit-mask-image: @url;
|
||||
mask-image: @url;
|
||||
-webkit-mask-size: @size-icon-medium;
|
||||
mask-size: @size-icon-medium;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
}
|
||||
.cm-editor .cm-cursor {
|
||||
border-left-color: @color-emphasized;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @background-color-interactive;
|
||||
}
|
||||
.cm-editor .cm-tooltip {
|
||||
background-color: @background-color-neutral-subtle;
|
||||
border-color: @border-color-base;
|
||||
}
|
||||
|
||||
&.oo-ui-toggleWidget-on {
|
||||
.oo-ui-labelElement-label {
|
||||
color: @color-progressive;
|
||||
}
|
||||
.cm-editor .cm-panels {
|
||||
background-color: @background-color-neutral-subtle;
|
||||
border-bottom: 0;
|
||||
color: @color-base;
|
||||
z-index: @z-index-above-content;
|
||||
|
||||
.oo-ui-icon-syntax-highlight {
|
||||
background-color: @color-progressive;
|
||||
.cdx-button-group {
|
||||
.cdx-button,
|
||||
.cdx-toggle-button {
|
||||
min-width: @min-width-toggle-switch;
|
||||
}
|
||||
}
|
||||
|
||||
&.oo-ui-buttonElement-frameless.oo-ui-labelElement.oo-ui-iconElement:first-child {
|
||||
margin-left: 0;
|
||||
.cm-mw-panel {
|
||||
border-bottom: @border-style-base @border-width-base @border-color-subtle;
|
||||
padding: @spacing-50;
|
||||
}
|
||||
|
||||
.cm-mw-panel--text-input {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cm-mw-panel--row {
|
||||
align-items: center;
|
||||
column-gap: @spacing-50;
|
||||
display: flex;
|
||||
|
||||
&:not( :last-child ) {
|
||||
margin-bottom: @spacing-50;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-mw-panel--button {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.cm-mw-panel--toggle-button.cdx-toggle-button--toggled-on {
|
||||
&:enabled:active {
|
||||
background-color: @color-progressive--active;
|
||||
}
|
||||
|
||||
.cdx-icon {
|
||||
background-color: @color-inverted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
.cm-mw-icon--match-case {
|
||||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-search-case-sensitive, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
||||
.cm-mw-icon--regexp {
|
||||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-search-regular-expression, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
||||
.cm-mw-icon--quotes {
|
||||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-quotes, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
||||
.cm-mw-icon--previous {
|
||||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-previous, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
||||
.cm-mw-icon--next {
|
||||
background-color: @color-base;
|
||||
.cdx-mixin-css-icon( @cdx-icon-next, @color-base, @size-icon-medium, true );
|
||||
}
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
@comment-color: #72777d;
|
||||
@error-color: #d73333;
|
||||
@link-color: #000aaa;
|
||||
@parser-function-color: #d73333;
|
||||
@table-color: #d08;
|
||||
@template-color: #80c;
|
||||
@template-variable-color: #ac6600;
|
||||
@wikitext-formatting-color: #0076dd;
|
||||
@xml-tag-color: #14866d;
|
||||
@import 'mediawiki.skin.variables.less';
|
||||
|
||||
@error-color: @color-destructive;
|
||||
@link-color: @color-progressive;
|
||||
@parser-function-color: @color-destructive;
|
||||
@table-color: #d08;
|
||||
@table-color-dark: #ff5edd;
|
||||
@template-color: #80c;
|
||||
@template-color-dark: #af84e6;
|
||||
@template-variable-color: #ac6600;
|
||||
@wikitext-formatting-color: @color-progressive--focus;
|
||||
@wikitext-formatting-color-dark: @color-progressive--hover;
|
||||
@xml-tag-color: @color-content-added;
|
||||
@template-background-color: #a11;
|
||||
@ext-background-color: #70a;
|
||||
@ext-background-color: #eee;
|
||||
@link-background-color: #219;
|
||||
@skip-formatting-color: #adf;
|
||||
|
||||
.ground( @template: 0, @ext: 0, @link: 0 ) {
|
||||
@template-shade: fade( @template-background-color, 4% * @template );
|
||||
|
@ -19,44 +23,76 @@
|
|||
background-color: average( average( @template-shade, @ext-shade ), @link-shade );
|
||||
}
|
||||
|
||||
/* stylelint-disable declaration-block-single-line-max-declarations */
|
||||
/* stylelint-disable @stylistic/block-closing-brace-space-after */
|
||||
/* stylelint-disable @stylistic/block-opening-brace-newline-after */
|
||||
/* stylelint-disable @stylistic/block-opening-brace-newline-before */
|
||||
/* stylelint-disable @stylistic/declaration-block-semicolon-newline-after */
|
||||
/* stylelint-disable @stylistic/selector-list-comma-newline-after */
|
||||
.darkmode( @prop, @value ) {
|
||||
@media screen {
|
||||
html.skin-theme-clientpref-night & {
|
||||
@{prop}: @value;
|
||||
}
|
||||
}
|
||||
|
||||
// See T365311
|
||||
.CodeMirror { color: inherit; }
|
||||
@media screen and ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os & {
|
||||
@{prop}: @value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-mw-pagename { text-decoration: underline; }
|
||||
.wikitext-formatting-color {
|
||||
color: @wikitext-formatting-color;
|
||||
.darkmode( color, @wikitext-formatting-color-dark );
|
||||
}
|
||||
|
||||
// TODO: It appears like this was never used. Remove?
|
||||
.cm-mw-matching { background-color: #ffd700; }
|
||||
.cm-mw-pagename {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cm-mw-skipformatting { background-color: #adf; }
|
||||
.cm-mw-skipformatting {
|
||||
background-color: @skip-formatting-color;
|
||||
}
|
||||
.cm-mw-list,
|
||||
.cm-mw-indenting { color: @wikitext-formatting-color; font-weight: bold; }
|
||||
.cm-mw-indenting {
|
||||
.wikitext-formatting-color();
|
||||
font-weight: bold;
|
||||
}
|
||||
// FIXME: Remove camelCase variant after CM6 upgrade is complete (also check Global Search)
|
||||
.cm-mw-doubleUnderscore,
|
||||
.cm-mw-double-underscore,
|
||||
.cm-mw-signature, .cm-mw-hr { color: @wikitext-formatting-color; font-weight: bold; background-color: #eee; }
|
||||
.cm-mw-signature,
|
||||
.cm-mw-hr {
|
||||
.wikitext-formatting-color();
|
||||
font-weight: bold;
|
||||
background-color: @background-color-disabled-subtle;
|
||||
}
|
||||
|
||||
// TODO: Deprecate .cm-mw-mnemonic in favor of -html-entity
|
||||
.cm-mw-mnemonic, .cm-mw-html-entity { color: @xml-tag-color; }
|
||||
.cm-mw-comment { color: @comment-color; font-weight: normal; }
|
||||
.cm-mw-apostrophes-bold, .cm-mw-apostrophes-italic { color: @wikitext-formatting-color; }
|
||||
.cm-mw-strong { font-weight: bold; }
|
||||
.cm-mw-mnemonic,
|
||||
.cm-mw-html-entity {
|
||||
color: @xml-tag-color;
|
||||
}
|
||||
.cm-mw-comment {
|
||||
color: @color-subtle;
|
||||
font-weight: normal;
|
||||
}
|
||||
.cm-mw-apostrophes-bold,
|
||||
.cm-mw-apostrophes-italic {
|
||||
.wikitext-formatting-color();
|
||||
}
|
||||
.cm-mw-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// FIXME: Remove .CodeMirror-line rules after CM6 upgrade
|
||||
pre.CodeMirror-line.cm-mw-section-1,
|
||||
pre.CodeMirror-line-like.cm-mw-section-1,
|
||||
.cm-mw-section-1, .cm-mw-section-1 ~ * {
|
||||
.cm-mw-section-1,
|
||||
.cm-mw-section-1 ~ * {
|
||||
font-size: 1.8em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
pre.CodeMirror-line.cm-mw-section-2,
|
||||
pre.CodeMirror-line-like.cm-mw-section-2,
|
||||
.cm-mw-section-2, .cm-mw-section-2 ~ * {
|
||||
.cm-mw-section-2,
|
||||
.cm-mw-section-2 ~ * {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
@ -76,48 +112,92 @@ span.cm-mw-section-6 ~ * {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cm-mw-template { color: @template-color; font-weight: normal; }
|
||||
.cm-mw-template {
|
||||
color: @template-color;
|
||||
font-weight: normal;
|
||||
|
||||
html.skin-theme-clientpref-night & {
|
||||
color: @template-color-dark;
|
||||
}
|
||||
|
||||
@media ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os & {
|
||||
color: @template-color-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: deprecate/remove after CM6 upgrade
|
||||
.cm-mw-template-name-mnemonic { font-weight: normal; }
|
||||
.cm-mw-template-name-mnemonic {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.cm-mw-template-name,
|
||||
.cm-mw-template-argument-name,
|
||||
.cm-mw-template-delimiter,
|
||||
.cm-mw-template-bracket { color: @template-color; font-weight: bold; }
|
||||
.cm-mw-template-bracket {
|
||||
color: @template-color;
|
||||
font-weight: bold;
|
||||
.darkmode( color, @template-color-dark );
|
||||
}
|
||||
|
||||
.cm-mw-templatevariable,
|
||||
.cm-mw-templatevariable-bracket { color: @template-variable-color; font-weight: normal; }
|
||||
.cm-mw-templatevariable-bracket {
|
||||
color: @template-variable-color;
|
||||
font-weight: normal;
|
||||
}
|
||||
.cm-mw-templatevariable-name,
|
||||
.cm-mw-templatevariable-delimiter { color: @template-variable-color; font-weight: bold; }
|
||||
.cm-mw-templatevariable-delimiter {
|
||||
color: @template-variable-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cm-mw-parserfunction { font-weight: normal; }
|
||||
.cm-mw-parserfunction {
|
||||
font-weight: normal;
|
||||
}
|
||||
.cm-mw-parserfunction-name,
|
||||
.cm-mw-parserfunction-bracket,
|
||||
.cm-mw-parserfunction-delimiter { color: @parser-function-color; font-weight: bold; }
|
||||
.cm-mw-parserfunction-delimiter {
|
||||
color: @parser-function-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pre.CodeMirror-line.cm-mw-exttag,
|
||||
pre.CodeMirror-line-like.cm-mw-exttag {
|
||||
.ground( @ext: 0.5 );
|
||||
}
|
||||
.cm-mw-exttag { .ground( @ext: 1 ); }
|
||||
.cm-mw-exttag {
|
||||
.ground( @ext: 1 );
|
||||
}
|
||||
.cm-mw-exttag-name,
|
||||
.cm-mw-htmltag-name { color: @xml-tag-color; font-weight: bold; }
|
||||
.cm-mw-htmltag-name {
|
||||
color: @xml-tag-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
.cm-mw-exttag-bracket,
|
||||
.cm-mw-exttag-attribute,
|
||||
.cm-mw-htmltag-bracket,
|
||||
.cm-mw-htmltag-attribute { color: @xml-tag-color; font-weight: normal; }
|
||||
.cm-mw-htmltag-attribute {
|
||||
color: @xml-tag-color;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.cm-mw-tag-pre,
|
||||
.cm-mw-tag-nowiki,
|
||||
pre.CodeMirror-line.cm-mw-tag-pre,
|
||||
pre.CodeMirror-line-like.cm-mw-tag-pre,
|
||||
.cm-mw-tag-pre,
|
||||
pre.CodeMirror-line.cm-mw-tag-nowiki,
|
||||
pre.CodeMirror-line-like.cm-mw-tag-nowiki,
|
||||
.cm-mw-tag-nowiki {
|
||||
pre.CodeMirror-line-like.cm-mw-tag-nowiki {
|
||||
background-color: rgba( 0, 0, 0, 0.04 );
|
||||
.darkmode( background-color, rgba( 255, 255, 255, 0.06 ) );
|
||||
}
|
||||
|
||||
.cm-mw-link,
|
||||
.cm-mw-link-tosection,
|
||||
.cm-mw-section-header { color: @wikitext-formatting-color; font-weight: normal; }
|
||||
.cm-mw-section-header {
|
||||
.wikitext-formatting-color();
|
||||
font-weight: normal;
|
||||
}
|
||||
.cm-mw-link-pagename,
|
||||
.cm-mw-link-bracket,
|
||||
.cm-mw-link-delimiter,
|
||||
|
@ -130,52 +210,109 @@ pre.CodeMirror-line-like.cm-mw-tag-nowiki,
|
|||
}
|
||||
.cm-mw-extlink-protocol,
|
||||
.cm-mw-free-extlink-protocol,
|
||||
.cm-mw-extlink-bracket { color: @link-color; font-weight: bold; }
|
||||
|
||||
.cm-mw-table-bracket,
|
||||
.cm-mw-table-delimiter { color: @table-color; font-weight: bold; }
|
||||
.cm-mw-table-definition { color: @table-color; font-weight: normal; }
|
||||
.cm-mw-table-caption { font-weight: bold; }
|
||||
|
||||
.cm-mw-template2-ground { .ground( @template: 1 ); }
|
||||
.cm-mw-template3-ground { .ground( @template: 2 ); }
|
||||
.cm-mw-ext-ground,
|
||||
.cm-mw-template-ext-ground { .ground( @ext: 1 ); }
|
||||
.cm-mw-ext2-ground,
|
||||
.cm-mw-template-ext2-ground { .ground( @ext: 2 ); }
|
||||
.cm-mw-ext3-ground,
|
||||
.cm-mw-template-ext3-ground { .ground( @ext: 3 ); }
|
||||
.cm-mw-link-ground,
|
||||
.cm-mw-ext-link-ground,
|
||||
.cm-mw-template-link-ground { .ground( @link: 1 ); }
|
||||
.cm-mw-ext2-link-ground,
|
||||
.cm-mw-template-ext-link-ground { .ground( @ext: 1, @link: 1 ); }
|
||||
.cm-mw-ext3-link-ground,
|
||||
.cm-mw-template-ext2-link-ground { .ground( @ext: 2, @link: 1 ); }
|
||||
.cm-mw-template-ext3-link-ground { .ground( @ext: 3, @link: 1 ); }
|
||||
|
||||
.cm-mw-template2-ext-ground { .ground( @template: 1, @ext: 1 ); }
|
||||
.cm-mw-template2-ext2-ground { .ground( @template: 1, @ext: 2 ); }
|
||||
.cm-mw-template2-ext3-ground { .ground( @template: 1, @ext: 3 ); }
|
||||
.cm-mw-template2-link-ground { .ground( @template: 1, @link: 1 ); }
|
||||
.cm-mw-template2-ext-link-ground { .ground( @template: 1, @ext: 1, @link: 1 ); }
|
||||
.cm-mw-template2-ext2-link-ground { .ground( @template: 1, @ext: 2, @link: 1 ); }
|
||||
.cm-mw-template2-ext3-link-ground { .ground( @template: 1, @ext: 3, @link: 1 ); }
|
||||
|
||||
.cm-mw-template3-ext-ground { .ground( @template: 2, @ext: 1 ); }
|
||||
.cm-mw-template3-ext2-ground { .ground( @template: 2, @ext: 2 ); }
|
||||
.cm-mw-template3-ext3-ground { .ground( @template: 2, @ext: 3 ); }
|
||||
.cm-mw-template3-link-ground { .ground( @template: 2, @link: 1 ); }
|
||||
.cm-mw-template3-ext-link-ground { .ground( @template: 2, @ext: 1, @link: 1 ); }
|
||||
.cm-mw-template3-ext2-link-ground { .ground( @template: 2, @ext: 2, @link: 1 ); }
|
||||
.cm-mw-template3-ext3-link-ground { .ground( @template: 2, @ext: 3, @link: 1 ); }
|
||||
|
||||
.cm-mw-error { color: @error-color; }
|
||||
|
||||
.cm-mw-em { font-style: italic; }
|
||||
|
||||
.cm-mw-matchingbracket {
|
||||
background-color: #eee;
|
||||
box-shadow: inset 0 0 1px 1px #999;
|
||||
.cm-mw-extlink-bracket {
|
||||
color: @link-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cm-mw-table-bracket,
|
||||
.cm-mw-table-delimiter {
|
||||
color: @table-color;
|
||||
font-weight: bold;
|
||||
.darkmode( color, @table-color-dark );
|
||||
}
|
||||
.cm-mw-table-definition {
|
||||
color: @table-color;
|
||||
font-weight: normal;
|
||||
.darkmode( color, @table-color-dark );
|
||||
}
|
||||
.cm-mw-table-caption {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cm-mw-template2-ground {
|
||||
.ground( @template: 1 );
|
||||
}
|
||||
.cm-mw-template3-ground {
|
||||
.ground( @template: 2 );
|
||||
}
|
||||
.cm-mw-ext-ground,
|
||||
.cm-mw-template-ext-ground {
|
||||
.ground( @ext: 1 );
|
||||
}
|
||||
.cm-mw-ext2-ground,
|
||||
.cm-mw-template-ext2-ground {
|
||||
.ground( @ext: 2 );
|
||||
}
|
||||
.cm-mw-ext3-ground,
|
||||
.cm-mw-template-ext3-ground {
|
||||
.ground( @ext: 3 );
|
||||
}
|
||||
.cm-mw-link-ground,
|
||||
.cm-mw-ext-link-ground,
|
||||
.cm-mw-template-link-ground {
|
||||
.ground( @link: 1 );
|
||||
}
|
||||
.cm-mw-ext2-link-ground,
|
||||
.cm-mw-template-ext-link-ground {
|
||||
.ground( @ext: 1, @link: 1 );
|
||||
}
|
||||
.cm-mw-ext3-link-ground,
|
||||
.cm-mw-template-ext2-link-ground {
|
||||
.ground( @ext: 2, @link: 1 );
|
||||
}
|
||||
.cm-mw-template-ext3-link-ground {
|
||||
.ground( @ext: 3, @link: 1 );
|
||||
}
|
||||
|
||||
.cm-mw-template2-ext-ground {
|
||||
.ground( @template: 1, @ext: 1 );
|
||||
}
|
||||
.cm-mw-template2-ext2-ground {
|
||||
.ground( @template: 1, @ext: 2 );
|
||||
}
|
||||
.cm-mw-template2-ext3-ground {
|
||||
.ground( @template: 1, @ext: 3 );
|
||||
}
|
||||
.cm-mw-template2-link-ground {
|
||||
.ground( @template: 1, @link: 1 );
|
||||
}
|
||||
.cm-mw-template2-ext-link-ground {
|
||||
.ground( @template: 1, @ext: 1, @link: 1 );
|
||||
}
|
||||
.cm-mw-template2-ext2-link-ground {
|
||||
.ground( @template: 1, @ext: 2, @link: 1 );
|
||||
}
|
||||
.cm-mw-template2-ext3-link-ground {
|
||||
.ground( @template: 1, @ext: 3, @link: 1 );
|
||||
}
|
||||
|
||||
.cm-mw-template3-ext-ground {
|
||||
.ground( @template: 2, @ext: 1 );
|
||||
}
|
||||
.cm-mw-template3-ext2-ground {
|
||||
.ground( @template: 2, @ext: 2 );
|
||||
}
|
||||
.cm-mw-template3-ext3-ground {
|
||||
.ground( @template: 2, @ext: 3 );
|
||||
}
|
||||
.cm-mw-template3-link-ground {
|
||||
.ground( @template: 2, @link: 1 );
|
||||
}
|
||||
.cm-mw-template3-ext-link-ground {
|
||||
.ground( @template: 2, @ext: 1, @link: 1 );
|
||||
}
|
||||
.cm-mw-template3-ext2-link-ground {
|
||||
.ground( @template: 2, @ext: 2, @link: 1 );
|
||||
}
|
||||
.cm-mw-template3-ext3-link-ground {
|
||||
.ground( @template: 2, @ext: 3, @link: 1 );
|
||||
}
|
||||
|
||||
.cm-mw-error {
|
||||
color: @error-color;
|
||||
}
|
||||
|
||||
.cm-mw-em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
205
resources/codemirror.panel.js
Normal file
205
resources/codemirror.panel.js
Normal file
|
@ -0,0 +1,205 @@
|
|||
const { EditorView, Extension, Panel } = require( 'ext.CodeMirror.v6.lib' );
|
||||
|
||||
/**
|
||||
* Abstract class for a panel that can be used with CodeMirror.
|
||||
* This class provides methods to create CSS-only Codex components.
|
||||
*
|
||||
* @see https://codemirror.net/docs/ref/#h_panels
|
||||
* @abstract
|
||||
*/
|
||||
class CodeMirrorPanel {
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* @type {EditorView}
|
||||
*/
|
||||
this.view = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the panel and any associated keymaps as a CodeMirror Extension.
|
||||
*
|
||||
* @abstract
|
||||
* @type {Extension}
|
||||
*/
|
||||
// eslint-disable-next-line getter-return
|
||||
get extension() {}
|
||||
|
||||
/**
|
||||
* Get the Panel object.
|
||||
*
|
||||
* @abstract
|
||||
* @type {Panel}
|
||||
*/
|
||||
// eslint-disable-next-line getter-return
|
||||
get panel() {}
|
||||
|
||||
/**
|
||||
* Get a CSS-only Codex TextInput.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} [value='']
|
||||
* @param {string} placeholder
|
||||
* @return {Array<HTMLElement>} [HTMLDivElement, HTMLInputElement]
|
||||
* @internal
|
||||
*/
|
||||
getTextInput( name, value = '', placeholder = '' ) {
|
||||
const wrapper = document.createElement( 'div' );
|
||||
wrapper.className = 'cdx-text-input cm-mw-panel--text-input';
|
||||
const input = document.createElement( 'input' );
|
||||
input.className = 'cdx-text-input__input';
|
||||
input.type = 'text';
|
||||
input.name = name;
|
||||
// The following messages may be used here:
|
||||
// * codemirror-find
|
||||
// * codemirror-replace-placeholder
|
||||
input.placeholder = placeholder ? mw.msg( placeholder ) : '';
|
||||
input.value = value;
|
||||
wrapper.appendChild( input );
|
||||
return [ wrapper, input ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CSS-only Codex Button.
|
||||
*
|
||||
* @param {string} label
|
||||
* @param {string|null} [icon=null]
|
||||
* @param {boolean} [iconOnly=false]
|
||||
* @return {HTMLButtonElement}
|
||||
* @internal
|
||||
*/
|
||||
getButton( label, icon = null, iconOnly = false ) {
|
||||
const button = document.createElement( 'button' );
|
||||
button.className = 'cdx-button cm-mw-panel--button';
|
||||
button.type = 'button';
|
||||
|
||||
if ( icon ) {
|
||||
const iconSpan = document.createElement( 'span' );
|
||||
// The following CSS classes may be used here:
|
||||
// * cm-mw-icon--previous
|
||||
// * cm-mw-icon--next
|
||||
// * cm-mw-icon--all
|
||||
// * cm-mw-icon--replace
|
||||
// * cm-mw-icon--replace-all
|
||||
// * cm-mw-icon--done
|
||||
// * cm-mw-icon--goto-line-go
|
||||
iconSpan.className = 'cdx-button__icon cm-mw-icon--' + icon;
|
||||
|
||||
if ( !iconOnly ) {
|
||||
iconSpan.setAttribute( 'aria-hidden', 'true' );
|
||||
}
|
||||
|
||||
button.appendChild( iconSpan );
|
||||
}
|
||||
|
||||
// The following messages may be used here:
|
||||
// * codemirror-next
|
||||
// * codemirror-previous
|
||||
// * codemirror-all
|
||||
// * codemirror-replace
|
||||
// * codemirror-replace-all
|
||||
const message = mw.msg( label );
|
||||
if ( iconOnly ) {
|
||||
button.classList.add( 'cdx-button--icon-only' );
|
||||
button.title = message;
|
||||
button.setAttribute( 'aria-label', message );
|
||||
} else {
|
||||
button.append( message );
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CSS-only Codex Checkbox.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} label
|
||||
* @param {boolean} [checked=false]
|
||||
* @return {Array<HTMLElement>} [HTMLSpanElement, HTMLInputElement]
|
||||
* @internal
|
||||
*/
|
||||
getCheckbox( name, label, checked = false ) {
|
||||
const wrapper = document.createElement( 'span' );
|
||||
wrapper.className = 'cdx-checkbox cdx-checkbox--inline cm-mw-panel--checkbox';
|
||||
const input = document.createElement( 'input' );
|
||||
input.className = 'cdx-checkbox__input';
|
||||
input.id = `cm-mw-panel--checkbox-${ name }`;
|
||||
input.type = 'checkbox';
|
||||
input.name = name;
|
||||
input.checked = checked;
|
||||
wrapper.appendChild( input );
|
||||
const emptyIcon = document.createElement( 'span' );
|
||||
emptyIcon.className = 'cdx-checkbox__icon';
|
||||
wrapper.appendChild( emptyIcon );
|
||||
const labelWrapper = document.createElement( 'div' );
|
||||
labelWrapper.className = 'cdx-checkbox__label cdx-label';
|
||||
const labelElement = document.createElement( 'label' );
|
||||
labelElement.className = 'cdx-label__label';
|
||||
labelElement.htmlFor = input.id;
|
||||
const innerSpan = document.createElement( 'span' );
|
||||
innerSpan.className = 'cdx-label__label__text';
|
||||
// The following messages may be used here:
|
||||
// * codemirror-match-case
|
||||
// * codemirror-regexp
|
||||
// * codemirror-by-word
|
||||
innerSpan.textContent = mw.msg( label );
|
||||
labelElement.appendChild( innerSpan );
|
||||
labelWrapper.appendChild( labelElement );
|
||||
wrapper.appendChild( labelWrapper );
|
||||
return [ wrapper, input ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CSS-only Codex ToggleButton.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} label
|
||||
* @param {string} icon
|
||||
* @param {boolean} [checked=false]
|
||||
* @return {HTMLButtonElement}
|
||||
* @internal
|
||||
*/
|
||||
getToggleButton( name, label, icon, checked = false ) {
|
||||
const btn = document.createElement( 'button' );
|
||||
// The following CSS classes may be used here:
|
||||
// * cdx-toggle-button--toggled-on
|
||||
// * cdx-toggle-button--toggled-off
|
||||
btn.className = 'cdx-toggle-button cdx-toggle-button--framed ' +
|
||||
`cdx-toggle-button--toggled-${ checked ? 'on' : 'off' } cm-mw-panel--toggle-button`;
|
||||
btn.dataset.checked = String( checked );
|
||||
btn.setAttribute( 'aria-pressed', checked );
|
||||
// The following messages may be used here:
|
||||
// * codemirror-match-case
|
||||
// * codemirror-regexp
|
||||
// * codemirror-by-word
|
||||
const message = mw.msg( label );
|
||||
btn.title = message;
|
||||
btn.setAttribute( 'aria-label', message );
|
||||
|
||||
// Add the icon.
|
||||
const iconWrapper = document.createElement( 'span' );
|
||||
// The following CSS classes may be used here:
|
||||
// * cm-mw-icon--match-case
|
||||
// * cm-mw-icon--regexp
|
||||
// * cm-mw-icon--quotes
|
||||
iconWrapper.className = 'cdx-icon cdx-icon--medium cm-mw-icon--' + icon;
|
||||
btn.appendChild( iconWrapper );
|
||||
|
||||
// Add the click handler.
|
||||
btn.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
const toggled = btn.dataset.checked === 'true';
|
||||
btn.dataset.checked = String( !toggled );
|
||||
btn.setAttribute( 'aria-pressed', String( !toggled ) );
|
||||
btn.classList.toggle( 'cdx-toggle-button--toggled-on', !toggled );
|
||||
btn.classList.toggle( 'cdx-toggle-button--toggled-off', toggled );
|
||||
} );
|
||||
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeMirrorPanel;
|
307
resources/codemirror.search.js
Normal file
307
resources/codemirror.search.js
Normal file
|
@ -0,0 +1,307 @@
|
|||
const {
|
||||
EditorView,
|
||||
SearchQuery,
|
||||
closeSearchPanel,
|
||||
findNext,
|
||||
findPrevious,
|
||||
keymap,
|
||||
replaceAll,
|
||||
replaceNext,
|
||||
runScopeHandlers,
|
||||
search,
|
||||
searchKeymap,
|
||||
selectMatches,
|
||||
setSearchQuery
|
||||
} = require( 'ext.CodeMirror.v6.lib' );
|
||||
const CodeMirrorPanel = require( './codemirror.panel.js' );
|
||||
|
||||
/**
|
||||
* Custom search panel for CodeMirror using CSS-only Codex components.
|
||||
*
|
||||
* @extends CodeMirrorPanel
|
||||
*/
|
||||
class CodeMirrorSearch extends CodeMirrorPanel {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* @type {SearchQuery}
|
||||
*/
|
||||
this.searchQuery = {
|
||||
search: ''
|
||||
};
|
||||
/**
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
this.searchInput = undefined;
|
||||
/**
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
this.replaceInput = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.matchCaseButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.regexpButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.wholeWordButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.nextButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.prevButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.allButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.replaceButton = undefined;
|
||||
/**
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
this.replaceAllButton = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
get extension() {
|
||||
return [
|
||||
search( {
|
||||
createPanel: ( view ) => {
|
||||
this.view = view;
|
||||
return this.panel;
|
||||
}
|
||||
} ),
|
||||
keymap.of( searchKeymap )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
get panel() {
|
||||
const container = document.createElement( 'div' );
|
||||
container.className = 'cm-mw-panel cm-mw-panel--search-panel';
|
||||
container.addEventListener( 'keydown', this.onKeydown.bind( this ) );
|
||||
|
||||
const firstRow = document.createElement( 'div' );
|
||||
firstRow.className = 'cm-mw-panel--row';
|
||||
container.appendChild( firstRow );
|
||||
|
||||
// Search input.
|
||||
const [ searchInputWrapper, searchInput ] = this.getTextInput(
|
||||
'search',
|
||||
this.searchQuery.search || '',
|
||||
'codemirror-find'
|
||||
);
|
||||
this.searchInput = searchInput;
|
||||
this.searchInput.setAttribute( 'main-field', 'true' );
|
||||
firstRow.appendChild( searchInputWrapper );
|
||||
|
||||
this.appendPrevAndNextButtons( firstRow );
|
||||
|
||||
// "All" button.
|
||||
this.allButton = this.getButton( 'codemirror-all' );
|
||||
this.allButton.title = mw.msg( 'codemirror-all-tooltip' );
|
||||
this.allButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
selectMatches( this.view );
|
||||
} );
|
||||
firstRow.appendChild( this.allButton );
|
||||
|
||||
this.appendSearchOptions( firstRow );
|
||||
this.appendSecondRow( container );
|
||||
|
||||
return {
|
||||
dom: container,
|
||||
top: true,
|
||||
mount: () => {
|
||||
this.searchInput.focus();
|
||||
this.searchInput.select();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} firstRow
|
||||
* @private
|
||||
*/
|
||||
appendPrevAndNextButtons( firstRow ) {
|
||||
const buttonGroup = document.createElement( 'div' );
|
||||
buttonGroup.className = 'cdx-button-group';
|
||||
|
||||
// "Previous" button.
|
||||
this.prevButton = this.getButton( 'codemirror-previous', 'previous', true );
|
||||
buttonGroup.appendChild( this.prevButton );
|
||||
this.prevButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
findPrevious( this.view );
|
||||
} );
|
||||
|
||||
// "Next" button.
|
||||
this.nextButton = this.getButton( 'codemirror-next', 'next', true );
|
||||
buttonGroup.appendChild( this.nextButton );
|
||||
this.nextButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
findNext( this.view );
|
||||
} );
|
||||
|
||||
firstRow.appendChild( buttonGroup );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} firstRow
|
||||
* @private
|
||||
*/
|
||||
appendSearchOptions( firstRow ) {
|
||||
const buttonGroup = document.createElement( 'div' );
|
||||
buttonGroup.className = 'cdx-toggle-button-group';
|
||||
|
||||
// "Match case" ToggleButton.
|
||||
this.matchCaseButton = this.getToggleButton(
|
||||
'case',
|
||||
'codemirror-match-case',
|
||||
'match-case',
|
||||
this.searchQuery.caseSensitive
|
||||
);
|
||||
buttonGroup.appendChild( this.matchCaseButton );
|
||||
|
||||
// "Regexp" ToggleButton.
|
||||
this.regexpButton = this.getToggleButton(
|
||||
're',
|
||||
'codemirror-regexp',
|
||||
'regexp',
|
||||
this.searchQuery.regexp
|
||||
);
|
||||
buttonGroup.appendChild( this.regexpButton );
|
||||
|
||||
// "Whole word" checkbox.
|
||||
this.wholeWordButton = this.getToggleButton(
|
||||
'word',
|
||||
'codemirror-by-word',
|
||||
'quotes',
|
||||
this.searchQuery.wholeWord
|
||||
);
|
||||
buttonGroup.appendChild( this.wholeWordButton );
|
||||
|
||||
firstRow.appendChild( buttonGroup );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} container
|
||||
* @private
|
||||
*/
|
||||
appendSecondRow( container ) {
|
||||
const shouldBeDisabled = this.view.state.readOnly;
|
||||
const row = document.createElement( 'div' );
|
||||
row.className = 'cm-mw-panel--row';
|
||||
container.appendChild( row );
|
||||
|
||||
// Replace input.
|
||||
const [ replaceInputWrapper, replaceInput ] = this.getTextInput(
|
||||
'replace',
|
||||
this.searchQuery.replace || '',
|
||||
'codemirror-replace-placeholder'
|
||||
);
|
||||
this.replaceInput = replaceInput;
|
||||
this.replaceInput.disabled = shouldBeDisabled;
|
||||
row.appendChild( replaceInputWrapper );
|
||||
|
||||
// "Replace" button.
|
||||
this.replaceButton = this.getButton( 'codemirror-replace' );
|
||||
this.replaceButton.disabled = shouldBeDisabled;
|
||||
row.appendChild( this.replaceButton );
|
||||
this.replaceButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
replaceNext( this.view );
|
||||
} );
|
||||
|
||||
// "Replace all" button.
|
||||
this.replaceAllButton = this.getButton( 'codemirror-replace-all' );
|
||||
this.replaceAllButton.disabled = shouldBeDisabled;
|
||||
row.appendChild( this.replaceAllButton );
|
||||
this.replaceAllButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
replaceAll( this.view );
|
||||
} );
|
||||
|
||||
// "Done" button.
|
||||
const doneButton = this.getButton( 'codemirror-done' );
|
||||
row.appendChild( doneButton );
|
||||
doneButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
closeSearchPanel( this.view );
|
||||
this.view.focus();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to keydown events.
|
||||
*
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
onKeydown( event ) {
|
||||
if ( runScopeHandlers( this.view, event, 'search-panel' ) ) {
|
||||
event.preventDefault();
|
||||
} else if ( event.key === 'Enter' && event.target === this.searchInput ) {
|
||||
event.preventDefault();
|
||||
( event.shiftKey ? findPrevious : findNext )( this.view );
|
||||
} else if ( event.key === 'Enter' && event.target === this.replaceInput ) {
|
||||
event.preventDefault();
|
||||
replaceNext( this.view );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SearchQuery} and dispatch it to the {@link EditorView}.
|
||||
*/
|
||||
commit() {
|
||||
const query = new SearchQuery( {
|
||||
search: this.searchInput.value,
|
||||
caseSensitive: this.matchCaseButton.dataset.checked === 'true',
|
||||
regexp: this.regexpButton.dataset.checked === 'true',
|
||||
wholeWord: this.wholeWordButton.dataset.checked === 'true',
|
||||
replace: this.replaceInput.value,
|
||||
// Makes i.e. "\n" match the literal string "\n" instead of a newline.
|
||||
literal: true
|
||||
} );
|
||||
if ( !this.searchQuery || !query.eq( this.searchQuery ) ) {
|
||||
this.searchQuery = query;
|
||||
this.view.dispatch( {
|
||||
effects: setSearchQuery.of( query )
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
getTextInput( name, value = '', placeholder = '' ) {
|
||||
const [ container, input ] = super.getTextInput( name, value, placeholder );
|
||||
input.addEventListener( 'change', this.commit.bind( this ) );
|
||||
input.addEventListener( 'keyup', this.commit.bind( this ) );
|
||||
return [ container, input ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
getToggleButton( name, label, icon, checked = false ) {
|
||||
const button = super.getToggleButton( name, label, icon, checked );
|
||||
button.addEventListener( 'click', this.commit.bind( this ) );
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeMirrorSearch;
|
55
resources/codemirror.wikieditor.less
Normal file
55
resources/codemirror.wikieditor.less
Normal file
|
@ -0,0 +1,55 @@
|
|||
@import 'mediawiki.skin.variables.less';
|
||||
|
||||
// Overrides for WikiEditor.
|
||||
|
||||
.wikiEditor-ui-text .cm-editor {
|
||||
border: inherit;
|
||||
}
|
||||
|
||||
.cm-mw-toggle-wikieditor {
|
||||
.oo-ui-icon-syntax-highlight {
|
||||
background-color: @color-base;
|
||||
// The SVG is just barely over 300 bytes, and is also only temporary
|
||||
// until an official icon has been established in Codex/OOUI (T174145).
|
||||
/* @embed */
|
||||
@url: url( codemirror.icon.svg );
|
||||
-webkit-mask-image: @url;
|
||||
mask-image: @url;
|
||||
-webkit-mask-size: @size-icon-medium;
|
||||
mask-size: @size-icon-medium;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @background-color-interactive;
|
||||
}
|
||||
|
||||
&.oo-ui-toggleWidget-on {
|
||||
.oo-ui-labelElement-label {
|
||||
color: @color-progressive;
|
||||
}
|
||||
|
||||
.oo-ui-icon-syntax-highlight {
|
||||
background-color: @color-progressive;
|
||||
}
|
||||
}
|
||||
|
||||
&.oo-ui-buttonElement-frameless.oo-ui-labelElement.oo-ui-iconElement:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
|
@ -236,9 +236,6 @@ function init() {
|
|||
$codeMirror.addClass( 'cm-mw-colorblind-colors' );
|
||||
}
|
||||
|
||||
// T365311: Apply color inversion until dark syntax styles are chosen
|
||||
$codeMirror.addClass( 'notheme skin-invert' );
|
||||
|
||||
// T305333: Reload CodeMirror to fix a cursor caret issue.
|
||||
codeMirror.refresh();
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
@import 'mediawiki.mixins';
|
||||
@import 'mediawiki.skin.variables.less';
|
||||
|
||||
/* TODO: Replace with ext.CodeMirror.v6.less following CM6 upgrade */
|
||||
@matching-bracket-border-color: #eee;
|
||||
@matching-bracket-box-shadow-color: #999;
|
||||
|
||||
// CM5 dark mode fixes, see T365311
|
||||
.mw-body-content .CodeMirror {
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wikiEditor-ui .CodeMirror {
|
||||
line-height: 1.5em;
|
||||
|
@ -13,3 +20,32 @@
|
|||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
background-color: @background-color-interactive-subtle;
|
||||
border-right-color: @border-color-subtle;
|
||||
}
|
||||
|
||||
.CodeMirror-line::selection,
|
||||
.CodeMirror-line > span::selection,
|
||||
.CodeMirror-line > span > span::selection {
|
||||
background: #d9d9d9;
|
||||
|
||||
@media screen {
|
||||
html.skin-theme-clientpref-night & {
|
||||
background: #233;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and ( prefers-color-scheme: dark ) {
|
||||
html.skin-theme-clientpref-os {
|
||||
background: #233;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-mw-matchingbracket {
|
||||
background-color: @matching-bracket-border-color;
|
||||
box-shadow: inset 0 0 1px 1px @matching-bracket-box-shadow-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
@ -21477,7 +21477,7 @@ class SearchCursor {
|
|||
let norm = this.normalize(str);
|
||||
for (let i = 0, pos = start;; i++) {
|
||||
let code = norm.charCodeAt(i);
|
||||
let match = this.match(code, pos);
|
||||
let match = this.match(code, pos, this.bufferPos + this.bufferStart);
|
||||
if (i == norm.length - 1) {
|
||||
if (match) {
|
||||
this.value = match;
|
||||
|
@ -21490,13 +21490,13 @@ class SearchCursor {
|
|||
}
|
||||
}
|
||||
}
|
||||
match(code, pos) {
|
||||
match(code, pos, end) {
|
||||
let match = null;
|
||||
for (let i = 0; i < this.matches.length; i += 2) {
|
||||
let index = this.matches[i], keep = false;
|
||||
if (this.query.charCodeAt(index) == code) {
|
||||
if (index == this.query.length - 1) {
|
||||
match = { from: this.matches[i + 1], to: pos + 1 };
|
||||
match = { from: this.matches[i + 1], to: end };
|
||||
}
|
||||
else {
|
||||
this.matches[i]++;
|
||||
|
@ -21510,7 +21510,7 @@ class SearchCursor {
|
|||
}
|
||||
if (this.query.charCodeAt(0) == code) {
|
||||
if (this.query.length == 1)
|
||||
match = { from: pos, to: pos + 1 };
|
||||
match = { from: pos, to: end };
|
||||
else
|
||||
this.matches.push(1, pos);
|
||||
}
|
||||
|
@ -21858,12 +21858,12 @@ const matchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|||
if (conf.wholeWords) {
|
||||
query = state.sliceDoc(range.from, range.to); // TODO: allow and include leading/trailing space?
|
||||
check = state.charCategorizer(range.head);
|
||||
if (!(insideWordBoundaries(check, state, range.from, range.to)
|
||||
&& insideWord(check, state, range.from, range.to)))
|
||||
if (!(insideWordBoundaries(check, state, range.from, range.to) &&
|
||||
insideWord(check, state, range.from, range.to)))
|
||||
return Decoration.none;
|
||||
}
|
||||
else {
|
||||
query = state.sliceDoc(range.from, range.to).trim();
|
||||
query = state.sliceDoc(range.from, range.to);
|
||||
if (!query)
|
||||
return Decoration.none;
|
||||
}
|
||||
|
@ -22126,10 +22126,10 @@ class RegExpQuery extends QueryType {
|
|||
this.prevMatchInRange(state, curTo, state.doc.length);
|
||||
}
|
||||
getReplacement(result) {
|
||||
return this.spec.unquote(this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
|
||||
return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
|
||||
: i == "&" ? result.match[0]
|
||||
: i != "0" && +i < result.match.length ? result.match[i]
|
||||
: m));
|
||||
: m);
|
||||
}
|
||||
matchAll(state, limit) {
|
||||
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
|
||||
|
@ -22422,7 +22422,7 @@ Default search-related key bindings.
|
|||
- Mod-f: [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSearchPanel)
|
||||
- F3, Mod-g: [`findNext`](https://codemirror.net/6/docs/ref/#search.findNext)
|
||||
- Shift-F3, Shift-Mod-g: [`findPrevious`](https://codemirror.net/6/docs/ref/#search.findPrevious)
|
||||
- Alt-g: [`gotoLine`](https://codemirror.net/6/docs/ref/#search.gotoLine)
|
||||
- Mod-Alt-g: [`gotoLine`](https://codemirror.net/6/docs/ref/#search.gotoLine)
|
||||
- Mod-d: [`selectNextOccurrence`](https://codemirror.net/6/docs/ref/#search.selectNextOccurrence)
|
||||
*/
|
||||
const searchKeymap = [
|
||||
|
@ -22431,7 +22431,7 @@ const searchKeymap = [
|
|||
{ key: "Mod-g", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
|
||||
{ key: "Escape", run: closeSearchPanel, scope: "editor search-panel" },
|
||||
{ key: "Mod-Shift-l", run: selectSelectionMatches },
|
||||
{ key: "Alt-g", run: gotoLine },
|
||||
{ key: "Mod-Alt-g", run: gotoLine },
|
||||
{ key: "Mod-d", run: selectNextOccurrence, preventDefault: true },
|
||||
];
|
||||
class SearchPanel {
|
||||
|
|
94
tests/jest/codemirror.panel.test.js
Normal file
94
tests/jest/codemirror.panel.test.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
const CodeMirrorPanel = require( '../../resources/codemirror.panel.js' );
|
||||
|
||||
// CodeMirrorPanel is tagged as abstract, but being JavaScript it isn't a
|
||||
// "real" abstract class, so we can instantiate it directly for testing purposes.
|
||||
const cmPanel = new CodeMirrorPanel();
|
||||
|
||||
describe( 'CodeMirrorPanel', () => {
|
||||
it( 'should create a Codex TextInput', () => {
|
||||
const [ inputWrapper, input ] = cmPanel.getTextInput( 'foo', 'bar', 'codemirror-find' );
|
||||
expect( inputWrapper.className ).toBe( 'cdx-text-input cm-mw-panel--text-input' );
|
||||
expect( input.className ).toBe( 'cdx-text-input__input' );
|
||||
expect( input.type ).toBe( 'text' );
|
||||
expect( input.name ).toBe( 'foo' );
|
||||
// No i18n in unit tests, so we only check for the key.
|
||||
expect( input.placeholder ).toBe( 'codemirror-find' );
|
||||
expect( input.value ).toBe( 'bar' );
|
||||
} );
|
||||
|
||||
it( 'should create a Codex Button with no icon', () => {
|
||||
const buttonNoIcon = cmPanel.getButton( 'foo' );
|
||||
expect( buttonNoIcon.tagName ).toBe( 'BUTTON' );
|
||||
expect( buttonNoIcon.className ).toBe( 'cdx-button cm-mw-panel--button' );
|
||||
expect( buttonNoIcon.type ).toBe( 'button' );
|
||||
expect( buttonNoIcon.children.length ).toBe( 0 );
|
||||
} );
|
||||
|
||||
it( 'should create a Codex button with an icon and a label', () => {
|
||||
const buttonWithIcon = cmPanel.getButton( 'foo', 'bar' );
|
||||
expect( buttonWithIcon.tagName ).toBe( 'BUTTON' );
|
||||
expect( buttonWithIcon.className ).toBe( 'cdx-button cm-mw-panel--button' );
|
||||
expect( buttonWithIcon.type ).toBe( 'button' );
|
||||
expect( buttonWithIcon.children.length ).toBe( 1 );
|
||||
const iconSpan = buttonWithIcon.children[ 0 ];
|
||||
expect( iconSpan.tagName ).toBe( 'SPAN' );
|
||||
expect( iconSpan.className ).toBe( 'cdx-button__icon cm-mw-icon--bar' );
|
||||
expect( iconSpan.getAttribute( 'aria-hidden' ) ).toBe( 'true' );
|
||||
} );
|
||||
|
||||
it( 'should create an icon-only Codex button', () => {
|
||||
const buttonIconOnly = cmPanel.getButton( 'foo', 'bar', true );
|
||||
expect( buttonIconOnly.tagName ).toBe( 'BUTTON' );
|
||||
expect( buttonIconOnly.className ).toBe(
|
||||
'cdx-button cm-mw-panel--button cdx-button--icon-only'
|
||||
);
|
||||
expect( buttonIconOnly.type ).toBe( 'button' );
|
||||
expect( buttonIconOnly.children.length ).toBe( 1 );
|
||||
expect( buttonIconOnly.getAttribute( 'aria-label' ) ).toBe( 'foo' );
|
||||
expect( buttonIconOnly.title ).toBe( 'foo' );
|
||||
const iconSpan = buttonIconOnly.children[ 0 ];
|
||||
expect( iconSpan.tagName ).toBe( 'SPAN' );
|
||||
expect( iconSpan.className ).toBe( 'cdx-button__icon cm-mw-icon--bar' );
|
||||
expect( iconSpan.getAttribute( 'aria-hidden' ) ).toBeNull();
|
||||
} );
|
||||
|
||||
it( 'should create a Codex Checkbox', () => {
|
||||
const [ checkboxWrapper, checkbox ] = cmPanel.getCheckbox( 'foo', 'bar', true );
|
||||
expect( checkboxWrapper.className ).toBe( 'cdx-checkbox cdx-checkbox--inline cm-mw-panel--checkbox' );
|
||||
expect( checkboxWrapper.children.length ).toBe( 3 );
|
||||
const labelWrapper = checkboxWrapper.children[ 2 ];
|
||||
expect( labelWrapper.tagName ).toBe( 'DIV' );
|
||||
expect( labelWrapper.className ).toBe( 'cdx-checkbox__label cdx-label' );
|
||||
const label = labelWrapper.children[ 0 ];
|
||||
expect( label.tagName ).toBe( 'LABEL' );
|
||||
expect( label.className ).toBe( 'cdx-label__label' );
|
||||
expect( label.textContent ).toBe( 'bar' );
|
||||
expect( checkbox.className ).toBe( 'cdx-checkbox__input' );
|
||||
expect( checkbox.type ).toBe( 'checkbox' );
|
||||
expect( checkbox.name ).toBe( 'foo' );
|
||||
expect( checkbox.checked ).toBe( true );
|
||||
} );
|
||||
|
||||
it( 'should create a Codex ToggleButton', () => {
|
||||
const toggleButtonOn = cmPanel.getToggleButton( 'foo', 'bar', 'baz', true );
|
||||
expect( toggleButtonOn.tagName ).toBe( 'BUTTON' );
|
||||
expect( toggleButtonOn.className ).toBe(
|
||||
'cdx-toggle-button cdx-toggle-button--framed cdx-toggle-button--toggled-on cm-mw-panel--toggle-button'
|
||||
);
|
||||
expect( toggleButtonOn.dataset.checked ).toBe( 'true' );
|
||||
expect( toggleButtonOn.getAttribute( 'aria-pressed' ) ).toBe( 'true' );
|
||||
expect( toggleButtonOn.title ).toBe( 'bar' );
|
||||
expect( toggleButtonOn.getAttribute( 'aria-label' ) ).toBe( 'bar' );
|
||||
expect( toggleButtonOn.children.length ).toBe( 1 );
|
||||
const iconSpan = toggleButtonOn.children[ 0 ];
|
||||
expect( iconSpan.tagName ).toBe( 'SPAN' );
|
||||
expect( iconSpan.className ).toBe( 'cdx-icon cdx-icon--medium cm-mw-icon--baz' );
|
||||
|
||||
const toggleButtonOff = cmPanel.getToggleButton( 'foo', 'bar', 'baz', false );
|
||||
expect( toggleButtonOff.className ).toBe(
|
||||
'cdx-toggle-button cdx-toggle-button--framed cdx-toggle-button--toggled-off cm-mw-panel--toggle-button'
|
||||
);
|
||||
expect( toggleButtonOff.dataset.checked ).toBe( 'false' );
|
||||
expect( toggleButtonOff.getAttribute( 'aria-pressed' ) ).toBe( 'false' );
|
||||
} );
|
||||
} );
|
|
@ -19,3 +19,4 @@ mw.track = jest.fn();
|
|||
mw.Api.prototype.saveOption = jest.fn();
|
||||
global.$ = require( 'jquery' );
|
||||
$.fn.textSelection = () => {};
|
||||
window.matchMedia = jest.fn().mockReturnValue( { matches: false } );
|
||||
|
|
Loading…
Reference in a new issue