From 943a46d9dada96b39c54f41c0336f6e421287b1f Mon Sep 17 00:00:00 2001 From: Stephan Robotta Date: Fri, 31 Mar 2023 15:21:33 +0200 Subject: [PATCH] fix codechecker, js lint and phpcs issues --- README.md | 6 +-- amd/build/ui.min.js | 2 +- amd/build/ui.min.js.map | 2 +- amd/src/ui.js | 46 ++++++++++--------- classes/plugininfo.php | 2 + db/access.php | 2 +- settings.php | 2 +- ...g.php => behat_editor_tiny_multilang2.php} | 15 ++++-- version.php | 2 +- 9 files changed, 46 insertions(+), 33 deletions(-) rename tests/behat/{behat_editor_tiny_multilang.php => behat_editor_tiny_multilang2.php} (92%) diff --git a/README.md b/README.md index 894cfb7..9eefd5c 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ TinyMCE multilanguage plugin [![Moodle Plugin CI](https://github.com/bfh/moodle-tiny_multilang2/workflows/Moodle%20Plugin%20CI/badge.svg?branch=master)](https://github.com/bfh/moodle-tiny_multilang2/actions?query=workflow%3A%22Moodle+Plugin+CI%22+branch%3Amaster) ![Release](https://img.shields.io/badge/release-v0.2.0-blue.svg) -![Supported](https://img.shields.io/badge/supported-4.1%2C%204.2-green.svg) +![Supported](https://img.shields.io/badge/supported-4.1-green.svg) This plugin will make the creation of multilingual contents on Moodle much easier with the TinyMCE editor. The plugin is developed to work with [Iñaki Arenaza's multilang2 filter](https://github.com/iarenaza/moodle-filter_multilang2), and this plugin is the adaption on [his plugin for TinyMCE editor](https://github.com/iarenaza/moodle-tinymce_moodlelang2) and will work with TinyMCE 6 -that is included in Moodle 4.x. +that is included in Moodle ≥ 4.1. After the installation there is a new Button (with a globe icon) and a menu entry in the Format section where you can select a language. Clicking on a language entry adds a language opening and closing tag to your text at the current @@ -36,5 +36,5 @@ The plugin [filter_multilang2](https://github.com/iarenaza/moodle-filter_multila ## Troubleshooting If the language selection does not appear in the editor: - - Check that the multilang filter is installed and enabled. + - Check that the multilang2 filter is installed and enabled. - Check that your site has at least two languages installed. diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js index 2ff36c8..c87d0a5 100644 --- a/amd/build/ui.min.js +++ b/amd/build/ui.min.js @@ -8,6 +8,6 @@ define("tiny_multilang2/ui",["exports","./options"],(function(_exports,_options) * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const _span_class='class="multilang-begin mceNonEditable"',_span_fixed_attrs="{mlang %lang}',_span_multilang_end=_span_fixed_attrs.replace("begin","end")+">{mlang}",isNull=a=>null==a,_add_visual_styling=function(ed){let content=ed.getContent();return-1!==content.indexOf(_span_class)||(content=content.replace(new RegExp("{\\s*mlang\\s+([^}]+?)\\s*}","ig"),(function(match,p1){return _span_multilang_begin.replace(new RegExp("%lang","g"),p1)})),content=content.replace(new RegExp("{\\s*mlang\\s*}","ig"),_span_multilang_end)),content},_remove_visual_styling=function(ed){["begin","end"].forEach((function(t){let nodes=ed.dom.select("span.multilang-"+t);for(let n=0,l=nodes.length;n{if(!isNull(elm.classList)){const pair="begin"===search?"end":"begin";if(elm.classList.contains("multilang-"+pair)){span=elm;do{span="begin"===search?span.previousSibling:span.nextSibling}while(!isNull(span)&&(isNull(span.classList)||!span.classList.contains("multilang-"+search)))}else elm.classList.contains("multilang-"+search)&&(span=elm)}})),span};_exports.onInit=function(ed){(0,_options.isContentToHighlight)(ed)&&(ed.dom.addStyle((0,_options.getHighlightCss)(ed)),ed.setContent(_add_visual_styling(ed)))};_exports.onBeforeGetContent=function(ed,content){if(void 0!==content.source_view&&!0===content.source_view){var onClose=function(ed){ed.off("close",onClose),ed.setContent(_add_visual_styling(ed))};ed.on("CloseWindow",(()=>{onClose(ed)})),(0,_options.isContentToHighlight)(ed)&&_remove_visual_styling(ed)}};_exports.onPreProcess=function(ed,node){void 0!==node.save&&!0===node.save&&(0,_options.isContentToHighlight)(ed)&&_remove_visual_styling(ed)};_exports.onDelete=function(ed,event){if(event.isComposing||46!==event.keyCode&&8!==event.keyCode||!(0,_options.isContentToHighlight)(ed))return;const begin=_get_highlight_node_from_select(ed,"begin"),end=_get_highlight_node_from_select(ed,"end");isNull(begin)||isNull(end)||(event.preventDefault(),ed.dom.remove(begin),ed.dom.remove(end))};_exports.applyLanguage=function(ed,iso){if(null===iso)return;let text=ed.selection.getContent();if(""===text.toString().replace(/^\s+/,"").replace(/\s+$/,"")){let newtext;return newtext=(0,_options.isContentToHighlight)(ed)?_span_multilang_begin.replace(new RegExp("%lang","g"),iso)+" "+_span_multilang_end:"{mlang "+iso+"} {mlang}",void ed.insertContent(newtext)}if(!(0,_options.isContentToHighlight)(ed))return void ed.selection.setContent("{mlang "+iso+"}"+text+"{mlang}");const span=_get_highlight_node_from_select(ed,"begin");if(!isNull(span))return void ed.dom.setOuterHTML(span,_span_multilang_begin.replace(new RegExp("%lang","g"),iso));const newtext=_span_multilang_begin.replace(new RegExp("%lang","g"),iso)+text+_span_multilang_end;ed.selection.setContent(newtext)}})); +const spanClass='class="multilang-begin mceNonEditable"',spanFixedAttrs="{mlang %lang}',spanMultilangEnd=spanFixedAttrs.replace("begin","end")+">{mlang}",isNull=a=>null==a,addVisualStyling=function(ed){let content=ed.getContent();return-1!==content.indexOf(spanClass)||(content=content.replace(new RegExp("{\\s*mlang\\s+([^}]+?)\\s*}","ig"),(function(match,p1){return spanMultilangBegin.replace(new RegExp("%lang","g"),p1)})),content=content.replace(new RegExp("{\\s*mlang\\s*}","ig"),spanMultilangEnd)),content},removeVisualStyling=function(ed){["begin","end"].forEach((function(t){let nodes=ed.dom.select("span.multilang-"+t);for(let n=0,l=nodes.length;n{if(!isNull(elm.classList)){const pair="begin"===search?"end":"begin";if(elm.classList.contains("multilang-"+pair)){span=elm;do{span="begin"===search?span.previousSibling:span.nextSibling}while(!isNull(span)&&(isNull(span.classList)||!span.classList.contains("multilang-"+search)))}else elm.classList.contains("multilang-"+search)&&(span=elm)}})),span};_exports.onInit=function(ed){(0,_options.isContentToHighlight)(ed)&&(ed.dom.addStyle((0,_options.getHighlightCss)(ed)),ed.setContent(addVisualStyling(ed)))};_exports.onBeforeGetContent=function(ed,content){if(void 0!==content.source_view&&!0===content.source_view){var onClose=function(ed){ed.off("close",onClose),ed.setContent(addVisualStyling(ed))};ed.on("CloseWindow",(()=>{onClose(ed)})),(0,_options.isContentToHighlight)(ed)&&removeVisualStyling(ed)}};_exports.onPreProcess=function(ed,node){void 0!==node.save&&!0===node.save&&(0,_options.isContentToHighlight)(ed)&&removeVisualStyling(ed)};_exports.onDelete=function(ed,event){if(event.isComposing||46!==event.keyCode&&8!==event.keyCode||!(0,_options.isContentToHighlight)(ed))return;const begin=getHighlightNodeFromSelect(ed,"begin"),end=getHighlightNodeFromSelect(ed,"end");isNull(begin)||isNull(end)||(event.preventDefault(),ed.dom.remove(begin),ed.dom.remove(end))};_exports.applyLanguage=function(ed,iso){if(null===iso)return;let text=ed.selection.getContent();if(""===text.toString().replace(/^\s+/,"").replace(/\s+$/,"")){let newtext;return newtext=(0,_options.isContentToHighlight)(ed)?spanMultilangBegin.replace(new RegExp("%lang","g"),iso)+" "+spanMultilangEnd:"{mlang "+iso+"} {mlang}",void ed.insertContent(newtext)}if(!(0,_options.isContentToHighlight)(ed))return void ed.selection.setContent("{mlang "+iso+"}"+text+"{mlang}");const span=getHighlightNodeFromSelect(ed,"begin");if(!isNull(span))return void ed.dom.setOuterHTML(span,spanMultilangBegin.replace(new RegExp("%lang","g"),iso));const newtext=spanMultilangBegin.replace(new RegExp("%lang","g"),iso)+text+spanMultilangEnd;ed.selection.setContent(newtext)}})); //# sourceMappingURL=ui.min.js.map \ No newline at end of file diff --git a/amd/build/ui.min.js.map b/amd/build/ui.min.js.map index b89606e..a0b9471 100644 --- a/amd/build/ui.min.js.map +++ b/amd/build/ui.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Commands for the plugin logic of the Moodle tiny_multilang2 plugin.\n *\n * @module tiny_multilang2\n * @author Iñaki Arenaza \n * @author Stephan Robotta \n * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getHighlightCss, isContentToHighlight} from './options';\n\n\n// This class inside a identified the {mlang} tag that is encapsulated in a span.\nconst _span_class = 'class=\"multilang-begin mceNonEditable\"';\n// This is the element with the data attribute.\nconst _span_fixed_attrs = '{mlang %lang}';\n// The end span doesn't need information about the used language.\nconst _span_multilang_end = _span_fixed_attrs.replace('begin', 'end') + '>{mlang}';\n// Helper functions\nconst trim = v => v.toString().replace(/^\\s+/, '').replace(/\\s+$/, '');\nconst isNull = a => a === null || a === undefined;\n\n/**\n * Convert {mlang xx} and {mlang} strings to spans, so we can style them visually.\n * Remove superflous whitespace while at it.\n * @param {tinymce.Editor} ed\n */\nconst _add_visual_styling = function(ed) {\n let content = ed.getContent();\n\n // Do not use a variable whether text is already highlighted, do a check for the existing class\n // because this is safe for many tiny element windows at one page.\n if (content.indexOf(_span_class) !== -1) {\n return content;\n }\n\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s+([^}]+?)\\\\s*}', 'ig'), function(match, p1) {\n return _span_multilang_begin.replace(new RegExp('%lang', 'g'), p1);\n });\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s*}', 'ig'), _span_multilang_end);\n\n return content;\n};\n\n/**\n * Remove the spans we added in _add_visual_styling() to leave only the {mlang xx} and {mlang} tags.\n * Also make sure we lowercase the multilang 'tags'\n * @param {tinymce.Editor} ed\n */\nconst _remove_visual_styling = function(ed) {\n ['begin', 'end'].forEach(function (t) {\n let nodes = ed.dom.select('span.multilang-' + t);\n for (let n = 0, l = nodes.length; n < l; n++) {\n const span = nodes[n];\n ed.dom.setOuterHTML(span, span.innerHTML.toLowerCase());\n }\n });\n};\n\n/**\n * At the current selection lookup for the current node. If we are inside a special span that encapsulates\n * the {lang} tag, then look for the corresponding opening or closing tag, depending on what's set in the\n * search param.\n * @param {tinymce.Editor} ed\n * @param {string} search\n */\nconst _get_highlight_node_from_select = function(ed, search) {\n let span;\n ed.dom.getParents(ed.selection.getStart(), elm => {\n // Are we in a span that highlights the lang tag.\n if (!isNull(elm.classList)) {\n // If we are on an opening/closing lang tag, we need to search for the corresponding opening/closing tag.\n const pair = search === 'begin' ? 'end' : 'begin';\n if (elm.classList.contains('multilang-' + pair)) {\n span = elm;\n do {\n // If we look for begin, go back siblings, otherwise look fnext siblings until end is found.\n span = search === 'begin' ? span.previousSibling : span.nextSibling;\n } while (!isNull(span) && (isNull(span.classList) || !span.classList.contains('multilang-' + search)));\n } else if (elm.classList.contains('multilang-' + search)) {\n // We are already on the correct tag we search for\n span = elm;\n }\n }\n });\n return span;\n};\n\n/**\n * When loading the editor for the first time, add the spans for highlighting the content.\n * @param {tinymce.Editor} ed\n */\nconst onInit = function(ed) {\n if (isContentToHighlight(ed)) {\n ed.dom.addStyle(getHighlightCss(ed));\n ed.setContent(_add_visual_styling(ed));\n }\n};\n\n/**\n * When the source code view dialogue is show, we must remove the highlight spans from the editor content\n * and also add them again when the dialogue is closed.\n * @param {tinymce.Editor} ed\n * @param {object} content\n */\nconst onBeforeGetContent = function(ed, content) {\n if (typeof content.source_view !== 'undefined' && content.source_view === true) {\n // If the user clicks on 'Cancel' or the close button on the html\n // source code dialog view, make sure we re-add the visual styling.\n var onClose = function(ed) {\n ed.off('close', onClose);\n ed.setContent(_add_visual_styling(ed));\n };\n ed.on('CloseWindow', () => {\n onClose(ed);\n });\n\n if (isContentToHighlight(ed)) {\n _remove_visual_styling(ed);\n }\n }\n};\n\n/**\n * Add an observer to the onPreProcess event to remove the highlighting spans while saving the content.\n * @param {tinymce.Editor} ed\n * @param {Element} node\n */\nconst onPreProcess = function(ed, node) {\n if (typeof node.save !== 'undefined' && node.save === true) {\n if (isContentToHighlight(ed)) {\n _remove_visual_styling(ed);\n }\n }\n};\n\n/**\n * Check for key press when something is deleted. If that happens inside a highlight span\n * tag, then remove this tag and the corresponding that open/closes this lang tag.\n * @param {tinymce.Editor} ed\n * @param {Object} event\n */\nconst onDelete = function (ed, event) {\n if (event.isComposing || (event.keyCode !== 46 && event.keyCode !== 8) || !isContentToHighlight(ed)) {\n return;\n }\n // Key was pressed, to delete some content. Check if we are inside a span for the lang.\n const begin = _get_highlight_node_from_select(ed, 'begin');\n const end = _get_highlight_node_from_select(ed, 'end');\n // Only if both, start and end tag are found, then delete the nodes here and prevent the default handling\n // because the stuff to be deleted is already gone.\n if (!isNull(begin) && !isNull(end)) {\n event.preventDefault();\n ed.dom.remove(begin);\n ed.dom.remove(end);\n }\n};\n\n/**\n * The action when a language icon or menu entry is clicked. This adds the {mlang} tags at the current content\n * position or around the selection.\n * @param {tinymce.Editor} ed\n * @param {string} iso\n */\nconst applyLanguage = function(ed, iso) {\n if (iso === null) {\n return;\n }\n let text = ed.selection.getContent();\n // Selection is empty, just insert the lang opening and closing tag\n // together with a space where the user may add the content.\n if (trim(text) === '') {\n let newtext;\n if (isContentToHighlight(ed)) {\n newtext = _span_multilang_begin.replace(new RegExp('%lang', 'g'), iso) + ' ' + _span_multilang_end;\n } else {\n newtext = '{mlang ' + iso + '}' + ' ' + '{mlang}';\n }\n ed.insertContent(newtext);\n return;\n }\n // Selection contains something, we need to place the open and closing lang tags around the selection.\n // However, there are a few exceptions, e.g. when the selection is inside the lang tag itself. In this case\n // just change the tag without encapsulating the selection.\n if (!isContentToHighlight(ed)) {\n ed.selection.setContent('{mlang ' + iso + '}' + text + '{mlang}');\n return;\n }\n // Syntax highlighting is on. Check if we are on a special span that encapsulates the language tags. Search\n // for the start span tag.\n const span = _get_highlight_node_from_select(ed, 'begin');\n // If we have a span, then it's the opening tag, and we just replace this one with the new iso.\n if (!isNull(span)) {\n ed.dom.setOuterHTML(span, _span_multilang_begin.replace(new RegExp('%lang', 'g'), iso));\n return;\n }\n // Not inside a lang tag, insert a new opening and closing tag with the selection inside.\n const newtext = _span_multilang_begin.replace(new RegExp('%lang', 'g'), iso) + text + _span_multilang_end;\n ed.selection.setContent(newtext);\n};\n\n\nexport {\n onInit,\n onBeforeGetContent,\n onPreProcess,\n onDelete,\n applyLanguage\n};"],"names":["_span_class","_span_fixed_attrs","_span_multilang_begin","_span_multilang_end","replace","isNull","a","_add_visual_styling","ed","content","getContent","indexOf","RegExp","match","p1","_remove_visual_styling","forEach","t","nodes","dom","select","n","l","length","span","setOuterHTML","innerHTML","toLowerCase","_get_highlight_node_from_select","search","getParents","selection","getStart","elm","classList","pair","contains","previousSibling","nextSibling","addStyle","setContent","source_view","onClose","off","on","node","save","event","isComposing","keyCode","begin","end","preventDefault","remove","iso","text","toString","newtext","insertContent"],"mappings":";;;;;;;;;;MA6BMA,YAAc,yCAEdC,kBAAoB,SAAWD,YAAc,oCAE7CE,sBAAwBD,kBAAoB,sDAE5CE,oBAAsBF,kBAAkBG,QAAQ,QAAS,OAAS,kBAGlEC,OAASC,GAAKA,MAAAA,EAOdC,oBAAsB,SAASC,QAC7BC,QAAUD,GAAGE,oBAIqB,IAAlCD,QAAQE,QAAQX,eAIpBS,QAAUA,QAAQL,QAAQ,IAAIQ,OAAO,8BAA+B,OAAO,SAASC,MAAOC,WAChFZ,sBAAsBE,QAAQ,IAAIQ,OAAO,QAAS,KAAME,OAEnEL,QAAUA,QAAQL,QAAQ,IAAIQ,OAAO,kBAAmB,MAAOT,sBANpDM,SAgBTM,uBAAyB,SAASP,KACnC,QAAS,OAAOQ,SAAQ,SAAUC,OAC3BC,MAAQV,GAAGW,IAAIC,OAAO,kBAAoBH,OACzC,IAAII,EAAI,EAAGC,EAAIJ,MAAMK,OAAQF,EAAIC,EAAGD,IAAK,OACpCG,KAAON,MAAMG,GACnBb,GAAGW,IAAIM,aAAaD,KAAMA,KAAKE,UAAUC,oBAY/CC,gCAAkC,SAASpB,GAAIqB,YAC7CL,YACJhB,GAAGW,IAAIW,WAAWtB,GAAGuB,UAAUC,YAAYC,UAElC5B,OAAO4B,IAAIC,WAAY,OAElBC,KAAkB,UAAXN,OAAqB,MAAQ,WACtCI,IAAIC,UAAUE,SAAS,aAAeD,MAAO,CAC7CX,KAAOS,OAGHT,KAAkB,UAAXK,OAAqBL,KAAKa,gBAAkBb,KAAKc,mBAClDjC,OAAOmB,QAAUnB,OAAOmB,KAAKU,aAAeV,KAAKU,UAAUE,SAAS,aAAeP,eACtFI,IAAIC,UAAUE,SAAS,aAAeP,UAE7CL,KAAOS,SAIZT,sBAOI,SAAShB,KAChB,iCAAqBA,MACrBA,GAAGW,IAAIoB,UAAS,4BAAgB/B,KAChCA,GAAGgC,WAAWjC,oBAAoBC,mCAUf,SAASA,GAAIC,iBACD,IAAxBA,QAAQgC,cAAuD,IAAxBhC,QAAQgC,YAAsB,KAGxEC,QAAU,SAASlC,IACnBA,GAAGmC,IAAI,QAASD,SAChBlC,GAAGgC,WAAWjC,oBAAoBC,MAEtCA,GAAGoC,GAAG,eAAe,KACjBF,QAAQlC,QAGR,iCAAqBA,KACrBO,uBAAuBP,4BAUd,SAASA,GAAIqC,WACL,IAAdA,KAAKC,OAAsC,IAAdD,KAAKC,OACrC,iCAAqBtC,KACrBO,uBAAuBP,uBAWlB,SAAUA,GAAIuC,UACvBA,MAAMC,aAAkC,KAAlBD,MAAME,SAAoC,IAAlBF,MAAME,WAAmB,iCAAqBzC,iBAI1F0C,MAAQtB,gCAAgCpB,GAAI,SAC5C2C,IAAMvB,gCAAgCpB,GAAI,OAG3CH,OAAO6C,QAAW7C,OAAO8C,OAC1BJ,MAAMK,iBACN5C,GAAGW,IAAIkC,OAAOH,OACd1C,GAAGW,IAAIkC,OAAOF,8BAUA,SAAS3C,GAAI8C,QACnB,OAARA,eAGAC,KAAO/C,GAAGuB,UAAUrB,gBAGL,KAAV6C,KAxJOC,WAAWpD,QAAQ,OAAQ,IAAIA,QAAQ,OAAQ,IAwJxC,KACfqD,eAEAA,SADA,iCAAqBjD,IACXN,sBAAsBE,QAAQ,IAAIQ,OAAO,QAAS,KAAM0C,KAAO,IAAMnD,oBAErE,UAAYmD,IAAZ,iBAEd9C,GAAGkD,cAAcD,cAMhB,iCAAqBjD,gBACtBA,GAAGuB,UAAUS,WAAW,UAAYc,IAAM,IAAMC,KAAO,iBAKrD/B,KAAOI,gCAAgCpB,GAAI,aAE5CH,OAAOmB,kBACRhB,GAAGW,IAAIM,aAAaD,KAAMtB,sBAAsBE,QAAQ,IAAIQ,OAAO,QAAS,KAAM0C,YAIhFG,QAAUvD,sBAAsBE,QAAQ,IAAIQ,OAAO,QAAS,KAAM0C,KAAOC,KAAOpD,oBACtFK,GAAGuB,UAAUS,WAAWiB"} \ No newline at end of file +{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Commands for the plugin logic of the Moodle tiny_multilang2 plugin.\n *\n * @module tiny_multilang2\n * @author Iñaki Arenaza \n * @author Stephan Robotta \n * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getHighlightCss, isContentToHighlight} from './options';\n\n\n// This class inside a identified the {mlang} tag that is encapsulated in a span.\nconst spanClass = 'class=\"multilang-begin mceNonEditable\"';\n// This is the element with the data attribute.\nconst spanFixedAttrs = '{mlang %lang}';\n// The end span doesn't need information about the used language.\nconst spanMultilangEnd = spanFixedAttrs.replace('begin', 'end') + '>{mlang}';\n// Helper functions\nconst trim = v => v.toString().replace(/^\\s+/, '').replace(/\\s+$/, '');\nconst isNull = a => a === null || a === undefined;\n\n/**\n * Convert {mlang xx} and {mlang} strings to spans, so we can style them visually.\n * Remove superflous whitespace while at it.\n * @param {tinymce.Editor} ed\n * @return {string}\n */\nconst addVisualStyling = function(ed) {\n let content = ed.getContent();\n\n // Do not use a variable whether text is already highlighted, do a check for the existing class\n // because this is safe for many tiny element windows at one page.\n if (content.indexOf(spanClass) !== -1) {\n return content;\n }\n\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s+([^}]+?)\\\\s*}', 'ig'), function(match, p1) {\n return spanMultilangBegin.replace(new RegExp('%lang', 'g'), p1);\n });\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s*}', 'ig'), spanMultilangEnd);\n\n return content;\n};\n\n/**\n * Remove the spans we added in _add_visual_styling() to leave only the {mlang xx} and {mlang} tags.\n * Also make sure we lowercase the multilang 'tags'\n * @param {tinymce.Editor} ed\n */\nconst removeVisualStyling = function(ed) {\n ['begin', 'end'].forEach(function(t) {\n let nodes = ed.dom.select('span.multilang-' + t);\n for (let n = 0, l = nodes.length; n < l; n++) {\n const span = nodes[n];\n ed.dom.setOuterHTML(span, span.innerHTML.toLowerCase());\n }\n });\n};\n\n/**\n * At the current selection lookup for the current node. If we are inside a special span that encapsulates\n * the {lang} tag, then look for the corresponding opening or closing tag, depending on what's set in the\n * search param.\n * @param {tinymce.Editor} ed\n * @param {string} search\n * @return {Node|null} The encapsulating span tag if found.\n */\nconst getHighlightNodeFromSelect = function(ed, search) {\n let span;\n ed.dom.getParents(ed.selection.getStart(), elm => {\n // Are we in a span that highlights the lang tag.\n if (!isNull(elm.classList)) {\n // If we are on an opening/closing lang tag, we need to search for the corresponding opening/closing tag.\n const pair = search === 'begin' ? 'end' : 'begin';\n if (elm.classList.contains('multilang-' + pair)) {\n span = elm;\n do {\n // If we look for begin, go back siblings, otherwise look fnext siblings until end is found.\n span = search === 'begin' ? span.previousSibling : span.nextSibling;\n } while (!isNull(span) && (isNull(span.classList) || !span.classList.contains('multilang-' + search)));\n } else if (elm.classList.contains('multilang-' + search)) {\n // We are already on the correct tag we search for\n span = elm;\n }\n }\n });\n return span;\n};\n\n/**\n * When loading the editor for the first time, add the spans for highlighting the content.\n * @param {tinymce.Editor} ed\n */\nconst onInit = function(ed) {\n if (isContentToHighlight(ed)) {\n ed.dom.addStyle(getHighlightCss(ed));\n ed.setContent(addVisualStyling(ed));\n }\n};\n\n/**\n * When the source code view dialogue is show, we must remove the highlight spans from the editor content\n * and also add them again when the dialogue is closed.\n * @param {tinymce.Editor} ed\n * @param {object} content\n */\nconst onBeforeGetContent = function(ed, content) {\n if (typeof content.source_view !== 'undefined' && content.source_view === true) {\n // If the user clicks on 'Cancel' or the close button on the html\n // source code dialog view, make sure we re-add the visual styling.\n var onClose = function(ed) {\n ed.off('close', onClose);\n ed.setContent(addVisualStyling(ed));\n };\n ed.on('CloseWindow', () => {\n onClose(ed);\n });\n\n if (isContentToHighlight(ed)) {\n removeVisualStyling(ed);\n }\n }\n};\n\n/**\n * Add an observer to the onPreProcess event to remove the highlighting spans while saving the content.\n * @param {tinymce.Editor} ed\n * @param {Element} node\n */\nconst onPreProcess = function(ed, node) {\n if (typeof node.save !== 'undefined' && node.save === true) {\n if (isContentToHighlight(ed)) {\n removeVisualStyling(ed);\n }\n }\n};\n\n/**\n * Check for key press when something is deleted. If that happens inside a highlight span\n * tag, then remove this tag and the corresponding that open/closes this lang tag.\n * @param {tinymce.Editor} ed\n * @param {Object} event\n */\nconst onDelete = function(ed, event) {\n if (event.isComposing || (event.keyCode !== 46 && event.keyCode !== 8) || !isContentToHighlight(ed)) {\n return;\n }\n // Key was pressed, to delete some content. Check if we are inside a span for the lang.\n const begin = getHighlightNodeFromSelect(ed, 'begin');\n const end = getHighlightNodeFromSelect(ed, 'end');\n // Only if both, start and end tag are found, then delete the nodes here and prevent the default handling\n // because the stuff to be deleted is already gone.\n if (!isNull(begin) && !isNull(end)) {\n event.preventDefault();\n ed.dom.remove(begin);\n ed.dom.remove(end);\n }\n};\n\n/**\n * The action when a language icon or menu entry is clicked. This adds the {mlang} tags at the current content\n * position or around the selection.\n * @param {tinymce.Editor} ed\n * @param {string} iso\n */\nconst applyLanguage = function(ed, iso) {\n if (iso === null) {\n return;\n }\n let text = ed.selection.getContent();\n // Selection is empty, just insert the lang opening and closing tag\n // together with a space where the user may add the content.\n if (trim(text) === '') {\n let newtext;\n if (isContentToHighlight(ed)) {\n newtext = spanMultilangBegin.replace(new RegExp('%lang', 'g'), iso) + ' ' + spanMultilangEnd;\n } else {\n newtext = '{mlang ' + iso + '}' + ' ' + '{mlang}';\n }\n ed.insertContent(newtext);\n return;\n }\n // Selection contains something, we need to place the open and closing lang tags around the selection.\n // However, there are a few exceptions, e.g. when the selection is inside the lang tag itself. In this case\n // just change the tag without encapsulating the selection.\n if (!isContentToHighlight(ed)) {\n ed.selection.setContent('{mlang ' + iso + '}' + text + '{mlang}');\n return;\n }\n // Syntax highlighting is on. Check if we are on a special span that encapsulates the language tags. Search\n // for the start span tag.\n const span = getHighlightNodeFromSelect(ed, 'begin');\n // If we have a span, then it's the opening tag, and we just replace this one with the new iso.\n if (!isNull(span)) {\n ed.dom.setOuterHTML(span, spanMultilangBegin.replace(new RegExp('%lang', 'g'), iso));\n return;\n }\n // Not inside a lang tag, insert a new opening and closing tag with the selection inside.\n const newtext = spanMultilangBegin.replace(new RegExp('%lang', 'g'), iso) + text + spanMultilangEnd;\n ed.selection.setContent(newtext);\n};\n\n\nexport {\n onInit,\n onBeforeGetContent,\n onPreProcess,\n onDelete,\n applyLanguage\n};"],"names":["spanClass","spanFixedAttrs","spanMultilangBegin","spanMultilangEnd","replace","isNull","a","addVisualStyling","ed","content","getContent","indexOf","RegExp","match","p1","removeVisualStyling","forEach","t","nodes","dom","select","n","l","length","span","setOuterHTML","innerHTML","toLowerCase","getHighlightNodeFromSelect","search","getParents","selection","getStart","elm","classList","pair","contains","previousSibling","nextSibling","addStyle","setContent","source_view","onClose","off","on","node","save","event","isComposing","keyCode","begin","end","preventDefault","remove","iso","text","toString","newtext","insertContent"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/ui.js b/amd/src/ui.js index ae0f01b..8e25aa6 100644 --- a/amd/src/ui.js +++ b/amd/src/ui.js @@ -27,13 +27,13 @@ import {getHighlightCss, isContentToHighlight} from './options'; // This class inside a identified the {mlang} tag that is encapsulated in a span. -const _span_class = 'class="multilang-begin mceNonEditable"'; +const spanClass = 'class="multilang-begin mceNonEditable"'; // This is the element with the data attribute. -const _span_fixed_attrs = '{mlang %lang}'; +const spanMultilangBegin = spanFixedAttrs + ' lang="%lang" xml:lang="%lang">{mlang %lang}'; // The end span doesn't need information about the used language. -const _span_multilang_end = _span_fixed_attrs.replace('begin', 'end') + '>{mlang}'; +const spanMultilangEnd = spanFixedAttrs.replace('begin', 'end') + '>{mlang}'; // Helper functions const trim = v => v.toString().replace(/^\s+/, '').replace(/\s+$/, ''); const isNull = a => a === null || a === undefined; @@ -42,20 +42,21 @@ const isNull = a => a === null || a === undefined; * Convert {mlang xx} and {mlang} strings to spans, so we can style them visually. * Remove superflous whitespace while at it. * @param {tinymce.Editor} ed + * @return {string} */ -const _add_visual_styling = function(ed) { +const addVisualStyling = function(ed) { let content = ed.getContent(); // Do not use a variable whether text is already highlighted, do a check for the existing class // because this is safe for many tiny element windows at one page. - if (content.indexOf(_span_class) !== -1) { + if (content.indexOf(spanClass) !== -1) { return content; } content = content.replace(new RegExp('{\\s*mlang\\s+([^}]+?)\\s*}', 'ig'), function(match, p1) { - return _span_multilang_begin.replace(new RegExp('%lang', 'g'), p1); + return spanMultilangBegin.replace(new RegExp('%lang', 'g'), p1); }); - content = content.replace(new RegExp('{\\s*mlang\\s*}', 'ig'), _span_multilang_end); + content = content.replace(new RegExp('{\\s*mlang\\s*}', 'ig'), spanMultilangEnd); return content; }; @@ -65,8 +66,8 @@ const _add_visual_styling = function(ed) { * Also make sure we lowercase the multilang 'tags' * @param {tinymce.Editor} ed */ -const _remove_visual_styling = function(ed) { - ['begin', 'end'].forEach(function (t) { +const removeVisualStyling = function(ed) { + ['begin', 'end'].forEach(function(t) { let nodes = ed.dom.select('span.multilang-' + t); for (let n = 0, l = nodes.length; n < l; n++) { const span = nodes[n]; @@ -81,8 +82,9 @@ const _remove_visual_styling = function(ed) { * search param. * @param {tinymce.Editor} ed * @param {string} search + * @return {Node|null} The encapsulating span tag if found. */ -const _get_highlight_node_from_select = function(ed, search) { +const getHighlightNodeFromSelect = function(ed, search) { let span; ed.dom.getParents(ed.selection.getStart(), elm => { // Are we in a span that highlights the lang tag. @@ -111,7 +113,7 @@ const _get_highlight_node_from_select = function(ed, search) { const onInit = function(ed) { if (isContentToHighlight(ed)) { ed.dom.addStyle(getHighlightCss(ed)); - ed.setContent(_add_visual_styling(ed)); + ed.setContent(addVisualStyling(ed)); } }; @@ -127,14 +129,14 @@ const onBeforeGetContent = function(ed, content) { // source code dialog view, make sure we re-add the visual styling. var onClose = function(ed) { ed.off('close', onClose); - ed.setContent(_add_visual_styling(ed)); + ed.setContent(addVisualStyling(ed)); }; ed.on('CloseWindow', () => { onClose(ed); }); if (isContentToHighlight(ed)) { - _remove_visual_styling(ed); + removeVisualStyling(ed); } } }; @@ -147,7 +149,7 @@ const onBeforeGetContent = function(ed, content) { const onPreProcess = function(ed, node) { if (typeof node.save !== 'undefined' && node.save === true) { if (isContentToHighlight(ed)) { - _remove_visual_styling(ed); + removeVisualStyling(ed); } } }; @@ -158,13 +160,13 @@ const onPreProcess = function(ed, node) { * @param {tinymce.Editor} ed * @param {Object} event */ -const onDelete = function (ed, event) { +const onDelete = function(ed, event) { if (event.isComposing || (event.keyCode !== 46 && event.keyCode !== 8) || !isContentToHighlight(ed)) { return; } // Key was pressed, to delete some content. Check if we are inside a span for the lang. - const begin = _get_highlight_node_from_select(ed, 'begin'); - const end = _get_highlight_node_from_select(ed, 'end'); + const begin = getHighlightNodeFromSelect(ed, 'begin'); + const end = getHighlightNodeFromSelect(ed, 'end'); // Only if both, start and end tag are found, then delete the nodes here and prevent the default handling // because the stuff to be deleted is already gone. if (!isNull(begin) && !isNull(end)) { @@ -190,7 +192,7 @@ const applyLanguage = function(ed, iso) { if (trim(text) === '') { let newtext; if (isContentToHighlight(ed)) { - newtext = _span_multilang_begin.replace(new RegExp('%lang', 'g'), iso) + ' ' + _span_multilang_end; + newtext = spanMultilangBegin.replace(new RegExp('%lang', 'g'), iso) + ' ' + spanMultilangEnd; } else { newtext = '{mlang ' + iso + '}' + ' ' + '{mlang}'; } @@ -206,14 +208,14 @@ const applyLanguage = function(ed, iso) { } // Syntax highlighting is on. Check if we are on a special span that encapsulates the language tags. Search // for the start span tag. - const span = _get_highlight_node_from_select(ed, 'begin'); + const span = getHighlightNodeFromSelect(ed, 'begin'); // If we have a span, then it's the opening tag, and we just replace this one with the new iso. if (!isNull(span)) { - ed.dom.setOuterHTML(span, _span_multilang_begin.replace(new RegExp('%lang', 'g'), iso)); + ed.dom.setOuterHTML(span, spanMultilangBegin.replace(new RegExp('%lang', 'g'), iso)); return; } // Not inside a lang tag, insert a new opening and closing tag with the selection inside. - const newtext = _span_multilang_begin.replace(new RegExp('%lang', 'g'), iso) + text + _span_multilang_end; + const newtext = spanMultilangBegin.replace(new RegExp('%lang', 'g'), iso) + text + spanMultilangEnd; ed.selection.setContent(newtext); }; diff --git a/classes/plugininfo.php b/classes/plugininfo.php index 7985416..68ed8bd 100644 --- a/classes/plugininfo.php +++ b/classes/plugininfo.php @@ -35,6 +35,8 @@ class plugininfo extends plugin implements plugin_with_menuitems, plugin_with_buttons, plugin_with_configuration { /** + * Check if user has sufficient rights to use the plugin. + * * @param context $context * @param array $options * @param array $fpoptions diff --git a/db/access.php b/db/access.php index 51e6314..d763108 100644 --- a/db/access.php +++ b/db/access.php @@ -17,7 +17,7 @@ /** * Plugin for Moodle 'Multilingual content' drop down menu in TinyMCE 6. * - * @package tinymce_moodlelang2 + * @package tiny_multilang2 * @author Iñaki Arenaza * @author Stephan Robotta * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea diff --git a/settings.php b/settings.php index e91278d..d58c96d 100644 --- a/settings.php +++ b/settings.php @@ -17,7 +17,7 @@ /** * Multi-language integration settings. * - * @package tinymce_multilang2 + * @package tiny_multilang2 * @author Iñaki Arenaza * @author Stephan Robotta * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea diff --git a/tests/behat/behat_editor_tiny_multilang.php b/tests/behat/behat_editor_tiny_multilang2.php similarity index 92% rename from tests/behat/behat_editor_tiny_multilang.php rename to tests/behat/behat_editor_tiny_multilang2.php index 3bae10e..4942960 100644 --- a/tests/behat/behat_editor_tiny_multilang.php +++ b/tests/behat/behat_editor_tiny_multilang2.php @@ -15,8 +15,9 @@ // along with Moodle. If not, see . /** - * TinyMCE custom steps definitions for the multilang plugin. It's - * basically all what the TinyMCE steps provide, but with this extensions: + * TinyMCE custom steps definitions for the multilang plugin. + * + * It's basically all what the TinyMCE steps provide, but with this extensions: * - New step to select the inner text of an element, e.g. the paragraph without * the paragraph tags. * - Menu items can have sub items that need to be clicked as well. In this case @@ -30,12 +31,17 @@ require_once(__DIR__ . '/../../../../tests/behat/behat_editor_tiny.php'); -class behat_editor_tiny_multilang extends behat_editor_tiny { +/** + * Extends general TinyMCE test to test the tiny_multilang2 plugin. + */ +class behat_editor_tiny_multilang2 extends behat_editor_tiny { /** * Click on a button for the specified TinyMCE editor. * + * phpcs:disable * @When /^I click on the "(?P(?:[^"]|\\")*)" submenu item for the "(?P(?:[^"]|\\")*)" TinyMCE editor$/ + * phpcs:enable * * @param string $menuitem The label of the menu item * @param string $locator The locator for the editor @@ -78,7 +84,10 @@ public function i_click_on_submenuitem_in_menu(string $menuitem, string $locator * selects the part until the next sibling that would be a formatting node such as bold, italics etc. Also, * a linebreak (
) intercepts the selection. * + * phpcs:disable * @When /^I select the inner "(?P(?:[^"]|\\")*)" element in position "(?P(?:[^"]|\\")*)" of the "(?P(?:[^"]|\\")*)" TinyMCE editor$/ + * phpcs:enable + * * @param string $textlocator The type of element to select (for example `p` or `span`) * @param int $position The zero-indexed position * @param string $locator The editor to select within diff --git a/version.php b/version.php index e16a6d0..05dc27b 100644 --- a/version.php +++ b/version.php @@ -20,7 +20,7 @@ * @package tiny_multilang2 * @author Iñaki Arenaza * @author Stephan Robotta - * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea + * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */