From be8ea2576f1dae682365668345909b1d8c89a913 Mon Sep 17 00:00:00 2001 From: Stuzer05 Date: Sun, 20 Sep 2020 04:35:53 +0300 Subject: [PATCH] Initial --- .gitignore | 1 + MonacoEditorHooks.php | 9 + assets/js/scripts.js | 416 ++++++++++++++++++++++++++++++++++++++++++ extension.json | 44 +++++ package-lock.json | 11 ++ package.json | 15 ++ 6 files changed, 496 insertions(+) create mode 100644 .gitignore create mode 100644 MonacoEditorHooks.php create mode 100644 assets/js/scripts.js create mode 100644 extension.json create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/MonacoEditorHooks.php b/MonacoEditorHooks.php new file mode 100644 index 0000000..dabc2a9 --- /dev/null +++ b/MonacoEditorHooks.php @@ -0,0 +1,9 @@ +addScript(''); + return true; + } +} diff --git a/assets/js/scripts.js b/assets/js/scripts.js new file mode 100644 index 0000000..7115584 --- /dev/null +++ b/assets/js/scripts.js @@ -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. + string: [ + [/[^\\"']+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/["']/, { + cases: { + '$#==$S2': { token: 'string', next: '@pop' }, + '@default': 'string' + } + }] + ], + whitespace: [ + [/[ \t\r\n]+/, 'white'], + [//, 'comment', '@pop'], + [/', + '' + ].join('\n'), + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + }, { + label: 'inline comment', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: [ + '', + ].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: [ + '$1', + ].join('\n'), + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + }, { + label: 'reference internal link', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: [ + '[[$1]]', + ].join('\n'), + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + }, { + label: 'reference external link', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: [ + '[$1 $2]', + ].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: [ + '$1', + ].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(); +})(); \ No newline at end of file diff --git a/extension.json b/extension.json new file mode 100644 index 0000000..5c6d92c --- /dev/null +++ b/extension.json @@ -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 +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c2b333f --- /dev/null +++ b/package-lock.json @@ -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==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e417e1 --- /dev/null +++ b/package.json @@ -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" +}