codemirror.mediawiki.js: add Mod-Shift-x

This patch adds a keyboard shortcut `Mod-Shift-x` to toggle between left-to-right (LTR) and right-to-left (RTL) text directions.

Bug: T170001
Change-Id: Ia857ad0b0aff0bb206b45e4d27dee6e91a3effce
This commit is contained in:
bhsd 2024-06-08 11:10:11 +08:00
parent 999382fd16
commit 0e0e4927ab
7 changed files with 66 additions and 31 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
"use strict";var e=require("ext.CodeMirror.v6"),i=require("ext.CodeMirror.v6.mode.mediawiki");require("ext.CodeMirror.v6.lib");var r=document.getElementById("wpTextbox1"),o=new e(r),t=new URLSearchParams(window.location.search);o.initialize([o.defaultExtensions,i({bidiIsolation:"rtl"===r.dir&&t.get("cm6bidi")})]); "use strict";var e=require("ext.CodeMirror.v6"),i=require("ext.CodeMirror.v6.mode.mediawiki");require("ext.CodeMirror.v6.lib");var r=new e(document.getElementById("wpTextbox1")),o=new URLSearchParams(window.location.search);r.initialize([r.defaultExtensions,i({bidiIsolation:o.get("cm6bidi")})]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -28,29 +28,31 @@ const isolate = Decoration.mark( {
function computeIsolates( view ) { function computeIsolates( view ) {
const set = new RangeSetBuilder(); const set = new RangeSetBuilder();
for ( const { from, to } of view.visibleRanges ) { if ( view.editorAttrs.dir === 'rtl' ) {
let startPos = null; for ( const { from, to } of view.visibleRanges ) {
syntaxTree( view.state ).iterate( { let startPos = null;
from, syntaxTree( view.state ).iterate( {
to, from,
enter( node ) { to,
// Determine if this is a bracket node (start or end of a tag). enter( node ) {
const isBracket = node.name.split( '_' ) // Determine if this is a bracket node (start or end of a tag).
.some( ( tag ) => [ const isBracket = node.name.split( '_' )
mwModeConfig.tags.htmlTagBracket, .some( ( tag ) => [
mwModeConfig.tags.extTagBracket mwModeConfig.tags.htmlTagBracket,
].includes( tag ) ); mwModeConfig.tags.extTagBracket
].includes( tag ) );
if ( startPos === null && isBracket ) { if ( startPos === null && isBracket ) {
// If we find a bracket node, we keep track of the start position. // If we find a bracket node, we keep track of the start position.
startPos = node.from; startPos = node.from;
} else if ( isBracket ) { } else if ( isBracket ) {
// When we find the closing bracket, add the isolate. // When we find the closing bracket, add the isolate.
set.add( startPos, node.to, isolate ); set.add( startPos, node.to, isolate );
startPos = null; startPos = null;
}
} }
} } );
} ); }
} }
return set.finish(); return set.finish();
@ -69,6 +71,8 @@ class CodeMirrorBidiIsolation {
this.isolates = computeIsolates( view ); this.isolates = computeIsolates( view );
/** @type {Tree} */ /** @type {Tree} */
this.tree = syntaxTree( view.state ); this.tree = syntaxTree( view.state );
/** @type {Direction} */
this.dir = view.textDirection;
} }
/** /**
@ -76,7 +80,8 @@ class CodeMirrorBidiIsolation {
*/ */
update( update ) { update( update ) {
if ( update.docChanged || update.viewportChanged || if ( update.docChanged || update.viewportChanged ||
syntaxTree( update.state ) !== this.tree syntaxTree( update.state ) !== this.tree ||
update.view.textDirection !== this.dir
) { ) {
this.isolates = computeIsolates( update.view ); this.isolates = computeIsolates( update.view );
this.tree = syntaxTree( update.state ); this.tree = syntaxTree( update.state );

View file

@ -1,4 +1,4 @@
import { EditorState, Extension } from '@codemirror/state'; import { EditorState, Extension, Compartment } from '@codemirror/state';
import { import {
EditorView, EditorView,
drawSelection, drawSelection,
@ -6,7 +6,8 @@ import {
highlightSpecialChars, highlightSpecialChars,
keymap, keymap,
rectangularSelection, rectangularSelection,
crosshairCursor crosshairCursor,
ViewUpdate
} from '@codemirror/view'; } from '@codemirror/view';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
import { searchKeymap } from '@codemirror/search'; import { searchKeymap } from '@codemirror/search';
@ -73,6 +74,12 @@ class CodeMirror {
* @type {CodeMirrorTextSelection} * @type {CodeMirrorTextSelection}
*/ */
this.textSelection = null; this.textSelection = null;
/**
* Language direction extension.
*
* @type {Compartment}
*/
this.dirCompartment = new Compartment();
} }
/** /**
@ -91,6 +98,7 @@ class CodeMirror {
this.heightExtension, this.heightExtension,
this.updateExtension, this.updateExtension,
this.bracketMatchingExtension, this.bracketMatchingExtension,
this.dirExtension,
EditorState.readOnly.of( this.readOnly ), EditorState.readOnly.of( this.readOnly ),
EditorView.domEventHandlers( { EditorView.domEventHandlers( {
blur: () => this.$textarea.triggerHandler( 'blur' ), blur: () => this.$textarea.triggerHandler( 'blur' ),
@ -221,9 +229,8 @@ class CodeMirror {
} ), } ),
// .cm-editor element (contains the whole CodeMirror UI) // .cm-editor element (contains the whole CodeMirror UI)
EditorView.editorAttributes.of( { EditorView.editorAttributes.of( {
// Use direction and language of the original textbox. // Use language of the original textbox.
// These should be attributes of .cm-editor, not the .cm-content (T359589) // These should be attributes of .cm-editor, not the .cm-content (T359589)
dir: this.$textarea.attr( 'dir' ),
lang: this.$textarea.attr( 'lang' ) lang: this.$textarea.attr( 'lang' )
} ), } ),
// The search panel should use the same direction as the interface language (T359611) // The search panel should use the same direction as the interface language (T359611)
@ -321,6 +328,29 @@ class CodeMirror {
} ); } );
} }
get dirExtension() {
return [
this.dirCompartment.of( EditorView.editorAttributes.of( {
// Use direction of the original textbox.
// These should be attributes of .cm-editor, not the .cm-content (T359589)
dir: this.$textarea.attr( 'dir' )
} ) ),
keymap.of( [ {
key: 'Mod-Shift-x',
run: ( view ) => {
const dir = this.$textarea.attr( 'dir' ) === 'rtl' ? 'ltr' : 'rtl';
this.$textarea.attr( 'dir', dir );
view.dispatch( {
effects: this.dirCompartment.reconfigure(
EditorView.editorAttributes.of( { dir } )
)
} );
return true;
}
} ] )
];
}
/** /**
* Setup CodeMirror and add it to the DOM. This will hide the original textarea. * Setup CodeMirror and add it to the DOM. This will hide the original textarea.
* *

View file

@ -8,6 +8,6 @@ const urlParams = new URLSearchParams( window.location.search );
cm.initialize( [ cm.initialize( [
cm.defaultExtensions, cm.defaultExtensions,
mediaWikiLang( { mediaWikiLang( {
bidiIsolation: textarea.dir === 'rtl' && urlParams.get( 'cm6bidi' ) bidiIsolation: urlParams.get( 'cm6bidi' )
} ) } )
] ); ] );