mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-12-12 14:25:06 +00:00
7d3482f89e
CodeMirror 6 requires the use of NPM, but we can still bundle all CM packages into one file, and then everything else (i.e. our code) is managed by ResourceLoader as per usual. This makes contribution considerably easier as we no longer need a build step for each change. CM5 files are now under resources/legacy, and the CM6 files are moved to the root of the resources/ directory. Only one file, codemirror.bundle.js, is managed by Rollup, while everything else is RL. The Rollup output for now is put under resources/lib/ alongside the CM5 upstream files. This patch is *mostly* renames of files, along with changing ECMAScript Module (ESM) syntax into the CommonJS style that ResourceLoader prefers. We also remove more modern JS syntax (i.e. private class methods) that we were able to use before because we had a build step with Babel. This patch should effectively make no user-facing changes, or to the ResourceLoader modules we offer in Extension:CodeMirror. Finally, bump version in extension.json to 6, to match the upstream lib, and add Bhsd as an author :-) Bug: T368053 Change-Id: Ie258e49f5df8db23a7344ac3c4c9300aaa991042
525 lines
14 KiB
JavaScript
525 lines
14 KiB
JavaScript
const {
|
|
StreamParser,
|
|
Tag,
|
|
TagStyle,
|
|
tags
|
|
} = require( 'ext.CodeMirror.v6.lib' );
|
|
|
|
/**
|
|
* Configuration for the MediaWiki highlighting mode for CodeMirror.
|
|
* This is a separate class mainly to keep static configuration out of
|
|
* the logic in {@link CodeMirrorModeMediaWiki}.
|
|
*
|
|
* @module CodeMirrorModeMediaWikiConfig
|
|
*
|
|
* @example
|
|
* // within MediaWiki:
|
|
* const { mwModeConfig } = require( 'ext.CodeMirror.v6.mode.mediawiki' );
|
|
* // Reference tags by their constants in the tags property.
|
|
* if ( tag === mwModeConfig.tags.htmlTagBracket ) {
|
|
* // …
|
|
* }
|
|
*/
|
|
class CodeMirrorModeMediaWikiConfig {
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
constructor() {
|
|
this.extHighlightStyles = [];
|
|
this.tokenTable = this.defaultTokenTable;
|
|
}
|
|
|
|
/**
|
|
* Register a token for the given tag in CodeMirror. The generated CSS class will be of
|
|
* the form 'cm-mw-ext-tagname'. This is for internal use to dynamically register tags
|
|
* from other MediaWiki extensions.
|
|
*
|
|
* @see https://www.mediawiki.org/wiki/Extension:CodeMirror#Extension_integration
|
|
* @param {string} tag
|
|
* @param {Tag} [parent]
|
|
* @private
|
|
* @internal
|
|
*/
|
|
addTag( tag, parent = null ) {
|
|
if ( this.tokenTable[ `mw-tag-${ tag }` ] ) {
|
|
return;
|
|
}
|
|
this.addToken( `mw-tag-${ tag }`, parent );
|
|
this.addToken( `mw-ext-${ tag }`, parent );
|
|
}
|
|
|
|
/**
|
|
* Dynamically register a token in CodeMirror.
|
|
* This is solely for use by this.addTag() and CodeMirrorModeMediaWiki.makeLocalStyle().
|
|
*
|
|
* @param {string} token
|
|
* @param {Tag} [parent]
|
|
* @private
|
|
* @internal
|
|
*/
|
|
addToken( token, parent = null ) {
|
|
if ( this.tokenTable[ token ] ) {
|
|
return;
|
|
}
|
|
this.tokenTable[ token ] = Tag.define( parent );
|
|
this.extHighlightStyles.push( {
|
|
tag: this.tokenTable[ token ],
|
|
class: `cm-${ token }`
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* All HTML/XML tags permitted in MediaWiki Core.
|
|
*
|
|
* Extensions should use the CodeMirrorTagModes extension attribute to register tags
|
|
* instead of adding them here.
|
|
*
|
|
* @see https://www.mediawiki.org/wiki/Extension:CodeMirror#Extension_integration
|
|
* @return {Object}
|
|
*/
|
|
get permittedHtmlTags() {
|
|
return {
|
|
b: true, bdi: true, del: true, i: true, ins: true,
|
|
u: true, font: true, big: true, small: true, sub: true, sup: true,
|
|
h1: true, h2: true, h3: true, h4: true, h5: true, h6: true, cite: true,
|
|
code: true, em: true, s: true, strike: true, strong: true, tt: true,
|
|
var: true, div: true, center: true, blockquote: true, q: true, ol: true, ul: true,
|
|
dl: true, table: true, caption: true, pre: true, ruby: true, rb: true,
|
|
rp: true, rt: true, rtc: true, p: true, span: true, abbr: true, dfn: true,
|
|
kbd: true, samp: true, data: true, time: true, mark: true, br: true,
|
|
wbr: true, hr: true, li: true, dt: true, dd: true, td: true, th: true,
|
|
tr: true, noinclude: true, includeonly: true, onlyinclude: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* HTML tags that are only self-closing.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
get implicitlyClosedHtmlTags() {
|
|
return {
|
|
br: true, hr: true, wbr: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Mapping of MediaWiki-esque token identifiers to
|
|
* [standardized lezer highlighting tags]{@link https://lezer.codemirror.net/docs/ref/#highlight.tags}.
|
|
* Values are one of the default highlighting tags. The idea is to use as many default tags as
|
|
* possible so that theming (such as dark mode) can be applied with minimal effort. The
|
|
* semantic meaning of the tag may not really match how it is used, but as per CodeMirror docs,
|
|
* this is fine. It's still better to make use of the standard tags in some way.
|
|
*
|
|
* Once we allow use of other themes, we may want to tweak these values for aesthetic reasons.
|
|
* The values here can freely be changed. The actual CSS class used is defined further down
|
|
* in highlightStyle().
|
|
*
|
|
* @see https://lezer.codemirror.net/docs/ref/#highlight.tags
|
|
* @member CodeMirrorModeMediaWikiConfig
|
|
* @type {Object<string>}
|
|
* @return {Object<string>}
|
|
*/
|
|
get tags() {
|
|
return Object.assign( {
|
|
apostrophes: 'character',
|
|
apostrophesBold: 'strong',
|
|
apostrophesItalic: 'emphasis',
|
|
comment: 'comment',
|
|
doubleUnderscore: 'controlKeyword',
|
|
extLink: 'url',
|
|
extLinkBracket: 'modifier',
|
|
extLinkProtocol: 'namespace',
|
|
extLinkText: 'labelName',
|
|
hr: 'contentSeparator',
|
|
htmlTagAttribute: 'attributeName',
|
|
htmlTagBracket: 'angleBracket',
|
|
htmlTagName: 'tagName',
|
|
indenting: 'operatorKeyword',
|
|
linkBracket: 'squareBracket',
|
|
linkDelimiter: 'operator',
|
|
linkText: 'string',
|
|
linkToSection: 'className',
|
|
list: 'list',
|
|
parserFunction: 'unit',
|
|
parserFunctionBracket: 'paren',
|
|
parserFunctionDelimiter: 'punctuation',
|
|
parserFunctionName: 'keyword',
|
|
sectionHeader: 'heading',
|
|
sectionHeader1: 'heading1',
|
|
sectionHeader2: 'heading2',
|
|
sectionHeader3: 'heading3',
|
|
sectionHeader4: 'heading4',
|
|
sectionHeader5: 'heading5',
|
|
sectionHeader6: 'heading6',
|
|
signature: 'quote',
|
|
tableBracket: 'null',
|
|
tableDefinition: 'definitionOperator',
|
|
tableDelimiter: 'typeOperator',
|
|
template: 'attributeValue',
|
|
templateArgumentName: 'definitionKeyword',
|
|
templateBracket: 'bracket',
|
|
templateDelimiter: 'separator',
|
|
templateName: 'moduleKeyword',
|
|
templateVariable: 'atom',
|
|
templateVariableBracket: 'brace',
|
|
templateVariableName: 'variableName'
|
|
}, this.customTags );
|
|
}
|
|
|
|
/**
|
|
* Custom tags. These are not mapped to any standard highlighting tag.
|
|
* These are here so that they, like tags(), serve as constants that we can
|
|
* reference in CodeMirrorModeMediaWiki.
|
|
*
|
|
* IMPORTANT: There should be a row in defaultTokenTable() for each of these.
|
|
*
|
|
* @return {Object<string>}
|
|
* @private
|
|
*/
|
|
get customTags() {
|
|
return {
|
|
em: 'mw-em',
|
|
error: 'mw-error',
|
|
extNowiki: 'mw-ext-nowiki',
|
|
extPre: 'mw-ext-pre',
|
|
extTag: 'mw-exttag',
|
|
extTagAttribute: 'mw-exttag-attribute',
|
|
extTagBracket: 'mw-exttag-bracket',
|
|
extTagName: 'mw-exttag-name',
|
|
freeExtLink: 'mw-free-extlink',
|
|
freeExtLinkProtocol: 'mw-free-extlink-protocol',
|
|
htmlEntity: 'mw-html-entity',
|
|
link: 'mw-link',
|
|
linkPageName: 'mw-link-pagename',
|
|
nowiki: 'mw-tag-nowiki',
|
|
pageName: 'mw-pagename',
|
|
pre: 'mw-tag-pre',
|
|
section: 'mw-section',
|
|
skipFormatting: 'mw-skipformatting',
|
|
strong: 'mw-strong',
|
|
tableCaption: 'mw-table-caption',
|
|
templateVariableDelimiter: 'mw-templatevariable-delimiter'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* These are custom tokens (a.k.a. tags) that aren't mapped to any of the standardized tags.
|
|
* Make sure these are also defined in customTags() above.
|
|
*
|
|
* TODO: pass parent Tags in Tag.define() where appropriate for better theming.
|
|
*
|
|
* @see https://codemirror.net/docs/ref/#language.StreamParser.tokenTable
|
|
* @see https://lezer.codemirror.net/docs/ref/#highlight.Tag%5Edefine
|
|
* @return {Object<Tag>}
|
|
* @internal
|
|
*/
|
|
get defaultTokenTable() {
|
|
return {
|
|
[ this.tags.em ]: Tag.define(),
|
|
[ this.tags.error ]: Tag.define(),
|
|
[ this.tags.extNowiki ]: Tag.define(),
|
|
[ this.tags.extPre ]: Tag.define(),
|
|
[ this.tags.extTag ]: Tag.define(),
|
|
[ this.tags.extTagAttribute ]: Tag.define(),
|
|
[ this.tags.extTagBracket ]: Tag.define(),
|
|
[ this.tags.extTagName ]: Tag.define(),
|
|
[ this.tags.freeExtLink ]: Tag.define(),
|
|
[ this.tags.freeExtLinkProtocol ]: Tag.define(),
|
|
[ this.tags.htmlEntity ]: Tag.define(),
|
|
[ this.tags.link ]: Tag.define(),
|
|
[ this.tags.linkPageName ]: Tag.define(),
|
|
[ this.tags.nowiki ]: Tag.define(),
|
|
[ this.tags.pageName ]: Tag.define(),
|
|
[ this.tags.pre ]: Tag.define(),
|
|
[ this.tags.section ]: Tag.define(),
|
|
[ this.tags.skipFormatting ]: Tag.define(),
|
|
[ this.tags.strong ]: Tag.define(),
|
|
[ this.tags.tableCaption ]: Tag.define(),
|
|
[ this.tags.templateVariableDelimiter ]: Tag.define()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This defines the actual CSS class assigned to each tag/token.
|
|
* Keep this in sync and in the same order as tags().
|
|
*
|
|
* @see https://codemirror.net/docs/ref/#language.TagStyle
|
|
* @param {StreamParser} context
|
|
* @return {TagStyle[]}
|
|
* @internal
|
|
*/
|
|
getTagStyles( context ) {
|
|
return [
|
|
{
|
|
tag: tags[ this.tags.apostrophes ],
|
|
class: 'cm-mw-apostrophes'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.apostrophesBold ],
|
|
class: 'cm-mw-apostrophes-bold'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.apostrophesItalic ],
|
|
class: 'cm-mw-apostrophes-italic'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.comment ],
|
|
class: 'cm-mw-comment'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.doubleUnderscore ],
|
|
class: 'cm-mw-double-underscore'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.extLink ],
|
|
class: 'cm-mw-extlink'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.extLinkBracket ],
|
|
class: 'cm-mw-extlink-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.extLinkProtocol ],
|
|
class: 'cm-mw-extlink-protocol'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.extLinkText ],
|
|
class: 'cm-mw-extlink-text'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.hr ],
|
|
class: 'cm-mw-hr'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.htmlTagAttribute ],
|
|
class: 'cm-mw-htmltag-attribute'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.htmlTagBracket ],
|
|
class: 'cm-mw-htmltag-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.htmlTagName ],
|
|
class: 'cm-mw-htmltag-name'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.indenting ],
|
|
class: 'cm-mw-indenting'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.linkBracket ],
|
|
class: 'cm-mw-link-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.linkDelimiter ],
|
|
class: 'cm-mw-link-delimiter'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.linkText ],
|
|
class: 'cm-mw-link-text'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.linkToSection ],
|
|
class: 'cm-mw-link-tosection'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.list ],
|
|
class: 'cm-mw-list'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.parserFunction ],
|
|
class: 'cm-mw-parserfunction'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.parserFunctionBracket ],
|
|
class: 'cm-mw-parserfunction-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.parserFunctionDelimiter ],
|
|
class: 'cm-mw-parserfunction-delimiter'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.parserFunctionName ],
|
|
class: 'cm-mw-parserfunction-name'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader ],
|
|
class: 'cm-mw-section-header'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader1 ],
|
|
class: 'cm-mw-section-1'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader2 ],
|
|
class: 'cm-mw-section-2'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader3 ],
|
|
class: 'cm-mw-section-3'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader4 ],
|
|
class: 'cm-mw-section-4'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader5 ],
|
|
class: 'cm-mw-section-5'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.sectionHeader6 ],
|
|
class: 'cm-mw-section-6'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.signature ],
|
|
class: 'cm-mw-signature'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.tableBracket ],
|
|
class: 'cm-mw-table-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.tableDefinition ],
|
|
class: 'cm-mw-table-definition'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.tableDelimiter ],
|
|
class: 'cm-mw-table-delimiter'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.template ],
|
|
class: 'cm-mw-template'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateArgumentName ],
|
|
class: 'cm-mw-template-argument-name'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateBracket ],
|
|
class: 'cm-mw-template-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateDelimiter ],
|
|
class: 'cm-mw-template-delimiter'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateName ],
|
|
class: 'cm-mw-pagename cm-mw-template-name'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateVariable ],
|
|
class: 'cm-mw-templatevariable'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateVariableBracket ],
|
|
class: 'cm-mw-templatevariable-bracket'
|
|
},
|
|
{
|
|
tag: tags[ this.tags.templateVariableName ],
|
|
class: 'cm-mw-templatevariable-name'
|
|
},
|
|
|
|
/**
|
|
* Custom tags.
|
|
* IMPORTANT: These need to reference the CodeMirrorModeMediaWiki context.
|
|
*/
|
|
{
|
|
tag: context.tokenTable[ this.tags.em ],
|
|
class: 'cm-mw-em'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.error ],
|
|
class: 'cm-mw-error'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.extNowiki ],
|
|
class: 'cm-mw-ext-nowiki'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.extPre ],
|
|
class: 'cm-mw-ext-pre'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.extTagBracket ],
|
|
class: 'cm-mw-exttag-bracket'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.extTag ],
|
|
class: 'cm-mw-exttag'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.extTagAttribute ],
|
|
class: 'cm-mw-exttag-attribute'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.extTagName ],
|
|
class: 'cm-mw-exttag-name'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.freeExtLink ],
|
|
class: 'cm-mw-free-extlink'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.freeExtLinkProtocol ],
|
|
class: 'cm-mw-free-extlink-protocol'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.htmlEntity ],
|
|
class: 'cm-mw-html-entity'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.link ],
|
|
class: 'cm-mw-link'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.linkPageName ],
|
|
class: 'cm-mw-link-pagename'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.nowiki ],
|
|
class: 'cm-mw-tag-nowiki'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.pageName ],
|
|
class: 'cm-mw-pagename'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.pre ],
|
|
class: 'cm-mw-tag-pre'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.section ],
|
|
class: 'cm-mw-section'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.skipFormatting ],
|
|
class: 'cm-mw-skipformatting'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.strong ],
|
|
class: 'cm-mw-strong'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.tableCaption ],
|
|
class: 'cm-mw-table-caption'
|
|
},
|
|
{
|
|
tag: context.tokenTable[ this.tags.templateVariableDelimiter ],
|
|
class: 'cm-mw-templatevariable-delimiter'
|
|
},
|
|
|
|
...this.extHighlightStyles
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @member CodeMirrorModeMediaWikiConfig
|
|
* @type {CodeMirrorModeMediaWikiConfig}
|
|
*/
|
|
const mwModeConfig = new CodeMirrorModeMediaWikiConfig();
|
|
|
|
module.exports = mwModeConfig;
|