1
0
This commit is contained in:
Illya Marchenko 2020-09-20 04:35:53 +03:00
commit be8ea2576f
6 changed files with 496 additions and 0 deletions

1
.gitignore vendored Normal file

@ -0,0 +1 @@
node_modules

9
MonacoEditorHooks.php Normal file

@ -0,0 +1,9 @@
<?php
class MonacoEditorHooks {
public static function editPageShowEditFormInitial(EditPage $editpage, OutputPage $output){
$output->addScript('<script type="text/javascript" src="/extensions/MonacoEditor/assets/js/scripts.js"></script>');
return true;
}
}

416
assets/js/scripts.js Normal file

@ -0,0 +1,416 @@
// @see https://microsoft.github.io/monaco-editor/playground.html#interacting-with-the-editor-adding-an-action-to-an-editor-instance
(function(){
let init = function() {
let MediaWikiTokenizer = (function () {
function MediaWikiTokenizer() {
this.ignorecase = true;
this.empty = [
'area', 'base', 'basefont', 'br', 'col', 'frame',
'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'
];
// escape codes for javascript/CSS strings
this.escapes = /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/;
this.tokenizer = {
root: [
{ include: '@whitespace' },
// Link reference
[/\[\[/, { token: 'string.quote', bracket: '@open', next: '@linkReferenceBlock' }],
// Template usage
[/{{/, { token: 'type.type-$1', bracket: '@open', next: '@templateBlock.$1' }],
[/}}/, { token: 'type.type-$1', bracket: '@close' }],
// Bold
[/'''/, { token: 'bold.quote', bracket: '@open', next: '@boldBlock' }],
// Italic
[/''/, { token: 'italic.quote', bracket: '@open', next: '@italicBlock' }],
// HTML-Style blocks
[/<(\w+)\/>/, 'tag.tag-$1'],
[/<(\w+)/, {
cases: {
'@empty': { token: 'tag.tag-$1', next: '@tag.$1', log: 'Push stack to tag.$1' },
'@default': { token: 'tag.tag-$1', bracket: '@open', next: '@tag.$1', log: 'Push stack to tag.$1, bracket open' }
}
}],
[/<\/(\w+)\s*>/, { token: 'tag.tag-$1', bracket: '@close', log: 'Close bracket of tag.$1' }],
[/&\w+;/, 'string.escape']
],
boldBlock: [
[/'''/, { token: 'bold.quote', bracket: '@close', next: '@pop' }],
[/[^''']+/, { token: 'bold' }],
// Nested italic + bold, need a style to present both
[/''/, { token: "italic.quote", bracket: "@open", next: "@italicBlock" }]
],
italicBlock: [
[/''/, { token: "italic.quote", bracket: "@close", next: "@pop" }],
[/[^'']+/, { token: "italic" }]
],
linkReferenceBlock: [
[/\]\]/, { token: "string.quote", bracket: "@close", next: "@pop" }],
[/[^\]\]]+/, { token: "string" }]
],
templateBlock: [
[/{{/, { token: 'type.type-$1', bracket: '@open', next: '@templateBlock.$1' }],
[/}}/, { token: 'type.type-$2', next: '@pop' }],
[/\[\[/, { token: 'string.quote', bracket: '@open', next: '@linkReferenceBlock' }],
[/'''/, { token: 'bold.quote', bracket: '@open', next: '@boldBlock' }],
[/''/, { token: 'italic.quote', bracket: '@open', next: '@italicBlock' }]
],
// Tags
tag: [
[/[ \t\r\n]+/, 'white'],
[/(type)(\s*=\s*)(")([^"]+)(")/, ['attribute.name', 'delimiter', 'attribute.value',
{ token: 'attribute.value', switchTo: '@tag.$S2.$4' },
'attribute.value']],
[/(type)(\s*=\s*)(')([^']+)(')/, ['attribute.name', 'delimiter', 'attribute.value',
{ token: 'attribute.value', switchTo: '@tag.$S2.$4' },
'attribute.value']],
[/(\w+)(\s*=\s*)("[^"]*"|'[^']*')/, ['attribute.name', 'delimiter', 'attribute.value']],
[/\w+/, 'attribute.name'],
[/\/>/, 'tag.tag-$S2', '@pop'],
[/>/, {
cases: {
'$S2==style': { token: 'tag.tag-$S2', switchTo: '@inlineStyle.$S2', nextEmbedded: 'text/css', log: 'Entering CSS section ($S2)' },
'$S2==script': {
cases: {
'$S3': { token: 'tag.tag-$S2', switchTo: '@inlineScript.$S2', nextEmbedded: '$S3' },
'@default': { token: 'tag.tag-$S2', switchTo: '@inlineScript.$S2', nextEmbedded: 'javascript', log: 'Entering JS section ($S2)' }
}
},
'@default': { token: 'tag.tag-$S2', next: '@pop', log: 'Entering $S2 section' }
}
}],
],
inlineStyle: [
[/<\/style\s*>/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop', log: 'Pop stack ($S2)' }]
],
inlineScript: [
[/<\/script\s*>/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop', log: 'Pop stack ($S2)' }]
],
// scan embedded strings in javascript or css
// string.<delimiter>
string: [
[/[^\\"']+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/["']/, {
cases: {
'$#==$S2': { token: 'string', next: '@pop' },
'@default': 'string'
}
}]
],
whitespace: [
[/[ \t\r\n]+/, 'white'],
[/<!--/, 'comment', '@comment']
],
comment: [
[/[^<\-]+/, 'comment.content'],
[/-->/, 'comment', '@pop'],
[/<!--/, 'comment.content.invalid'],
[/[<\-]/, 'comment.content']
]
};
this.tokenPostfix = "";
}
return MediaWikiTokenizer;
}());
window.require.config({ paths: { 'vs': '/extensions/MonacoEditor/node_modules/monaco-editor/min/vs' }});
window.require(['vs/editor/editor.main'], function() {
let el_stock_editor = document.getElementById("wpTextbox1");
el_stock_editor.style.display = "none";
// Register a new language
monaco.languages.register({ id: 'wikitext' });
// Register a tokens provider for the language
monaco.languages.setMonarchTokensProvider('wikitext', new MediaWikiTokenizer());
// Register a completion item provider for the new language
monaco.languages.registerCompletionItemProvider('wikitext', {
provideCompletionItems: () => {
var suggestions = [{
label: 'syntax',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<syntaxhighlight lang="$1">',
'$2',
'</syntaxhighlight>'
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'syntax inline',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<syntaxhighlight lang="$1" inline>$2</syntaxhighlight>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'code',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<code>$1</code>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'poem',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<poem>$1</poem>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'bullet list',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'* $1',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'numbered list',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'# $1',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'bold',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
"'''$1'''",
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'italic',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
"''$1''",
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'heading 1',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
"= $1 =",
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'heading 2',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'== $1 ==',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'heading 3',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'=== $1 ===',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'heading 4',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'==== $1 ====',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'heading 5',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'===== $1 =====',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'heading 6',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'====== $1 ======',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'category',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'[[Category:$1]]',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'internal link',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'[[$1]]',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'external link',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'[$1 $2]',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'comment',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<!--',
'$1',
'-->',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'inline comment',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<!-- $1 -->',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'template',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'{{$1}}',
''
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'chunk: references',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'== References ==',
'{{Reflist}}',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'reference',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<ref>$1</ref>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'reference internal link',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<ref>[[$1]]</ref>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'reference external link',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<ref>[$1 $2]</ref>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'expanded quote',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'{{Quote | $1 | $2 | $3}}',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'quote',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'{{Quote | $1 || $2}}',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'expandable content',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'{{Expandable content | $1}}',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'nowiki',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'<nowiki>$1</nowiki>',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'horizontal rule',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'----',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}, {
label: 'related article',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'{{#related:$1}}',
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
}];
return { suggestions: suggestions };
}
});
let language = 'wikitext';
if (typeof RLCONF.wgPageContentModel !== 'undefined') {
switch (RLCONF.wgPageContentModel) {
case 'css':
language = 'css';
break;
case 'javascript':
language = 'javascript';
break;
}
}
// Create new editor
let monaco_editor_container = document.createElement('div');
monaco_editor_container.id = 'monaco-editor'
monaco_editor_container.style["min-height"] = '35em';
monaco_editor_container.style["max-height"] = '100vh';
el_stock_editor.after(monaco_editor_container);
window.monaco_editor = monaco.editor.create(document.getElementById('monaco-editor'), {
value: el_stock_editor.value,
scrollBeyondLastLine: false,
wordWrap: 'on',
fontSize: '13px',
language: language,
// theme: "vs-dark",
});
// On preview
let el_btn_preview = document.querySelector('#wpPreview');
el_btn_preview.addEventListener('click', event => {
el_stock_editor.value = window.monaco_editor.getValue();
});
// On save
let el_btn_save = document.querySelector('#wpSave');
el_btn_save.addEventListener('click', event => {
el_stock_editor.value = window.monaco_editor.getValue();
});
});
};
function initializer() {
if (typeof window.require !== 'undefined') {
init();
} else {
setTimeout(initializer, 20);
}
}
initializer();
})();

44
extension.json Normal file

@ -0,0 +1,44 @@
{
"name": "MonacoEditor",
"version": "0.1.0",
"author": [
"Illya Marchenko"
],
"url": "https://stuzer.link",
"description": "MonacoEditor for MediaWiki",
"license-name": "",
"type": "other",
"requires": {
"MediaWiki": ">= 1.35.0"
},
"Hooks": {
"EditPage::showEditForm:initial": [
"MonacoEditorHooks::editPageShowEditFormInitial"
],
"EditPage::showReadOnlyForm:initial": [
"MonacoEditorHooks::editPageShowEditFormInitial"
]
},
"AutoloadClasses": {
"MonacoEditorHooks": "MonacoEditorHooks.php"
},
"ResourceModules": {
"ext.RequireJs": {
"packageFiles": [
"assets/js/require.js"
],
"position": "bottom"
},
"ext.MonacoEditor": {
"packageFiles": [
"assets/js/scripts.js"
],
"position": "bottom"
}
},
"ResourceFileModulePaths": {
"localBasePath": "",
"remoteExtPath": "MonacoEditor"
},
"manifest_version": 1
}

11
package-lock.json generated Normal file

@ -0,0 +1,11 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"monaco-editor": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.20.0.tgz",
"integrity": "sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ=="
}
}
}

15
package.json Normal file

@ -0,0 +1,15 @@
{
"name": "mediawiki-monacoeditor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"monaco-editor": "^0.20.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}