diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..5f2302e
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,403 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+/* jshint node: true, browser: false */
+/* eslint-env node */
+
+/**
+ * @copyright 2014 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Grunt configuration
+ */
+
+module.exports = function(grunt) {
+ var path = require('path'),
+ tasks = {},
+ cwd = process.env.PWD || process.cwd(),
+ async = require('async'),
+ DOMParser = require('xmldom').DOMParser,
+ xpath = require('xpath'),
+ semver = require('semver');
+
+ // Verify the node version is new enough.
+ var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
+ var actual = semver.valid(process.version);
+ if (!semver.satisfies(actual, expected)) {
+ grunt.fail.fatal('Node version too old. Require ' + expected + ', version installed: ' + actual);
+ }
+
+ // Windows users can't run grunt in a subdirectory, so allow them to set
+ // the root by passing --root=path/to/dir.
+ if (grunt.option('root')) {
+ var root = grunt.option('root');
+ if (grunt.file.exists(__dirname, root)) {
+ cwd = path.join(__dirname, root);
+ grunt.log.ok('Setting root to ' + cwd);
+ } else {
+ grunt.fail.fatal('Setting root to ' + root + ' failed - path does not exist');
+ }
+ }
+
+ var inAMD = path.basename(cwd) == 'amd';
+
+ // Globbing pattern for matching all AMD JS source files.
+ var amdSrc = [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js'];
+
+ /**
+ * Function to generate the destination for the uglify task
+ * (e.g. build/file.min.js). This function will be passed to
+ * the rename property of files array when building dynamically:
+ * http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
+ *
+ * @param {String} destPath the current destination
+ * @param {String} srcPath the matched src path
+ * @return {String} The rewritten destination path.
+ */
+ var uglifyRename = function(destPath, srcPath) {
+ destPath = srcPath.replace('src', 'build');
+ destPath = destPath.replace('.js', '.min.js');
+ destPath = path.resolve(cwd, destPath);
+ return destPath;
+ };
+
+ /**
+ * Find thirdpartylibs.xml and generate an array of paths contained within
+ * them (used to generate ignore files and so on).
+ *
+ * @return {array} The list of thirdparty paths.
+ */
+ var getThirdPartyPathsFromXML = function() {
+ var thirdpartyfiles = grunt.file.expand('*/**/thirdpartylibs.xml');
+ var libs = ['node_modules/', 'vendor/'];
+
+ thirdpartyfiles.forEach(function(file) {
+ var dirname = path.dirname(file);
+
+ var doc = new DOMParser().parseFromString(grunt.file.read(file));
+ var nodes = xpath.select("/libraries/library/location/text()", doc);
+
+ nodes.forEach(function(node) {
+ var lib = path.join(dirname, node.toString());
+ if (grunt.file.isDir(lib)) {
+ // Ensure trailing slash on dirs.
+ lib = lib.replace(/\/?$/, '/');
+ }
+
+ // Look for duplicate paths before adding to array.
+ if (libs.indexOf(lib) === -1) {
+ libs.push(lib);
+ }
+ });
+ });
+ return libs;
+ };
+
+ // Project configuration.
+ grunt.initConfig({
+ eslint: {
+ // Even though warnings dont stop the build we don't display warnings by default because
+ // at this moment we've got too many core warnings.
+ options: {quiet: !grunt.option('show-lint-warnings')},
+ amd: {
+ src: amdSrc,
+ // Check AMD with some slightly stricter rules.
+ rules: {
+ 'no-unused-vars': 'error',
+ 'no-implicit-globals': 'error'
+ }
+ },
+ // Check YUI module source files.
+ yui: {
+ src: ['**/yui/src/**/*.js', '!*/**/yui/src/*/meta/*.js'],
+ options: {
+ // Disable some rules which we can't safely define for YUI rollups.
+ rules: {
+ 'no-undef': 'off',
+ 'no-unused-vars': 'off',
+ 'no-unused-expressions': 'off'
+ }
+ }
+ }
+ },
+ uglify: {
+ amd: {
+ files: [{
+ expand: true,
+ src: amdSrc,
+ rename: uglifyRename
+ }],
+ options: {report: 'none'}
+ }
+ },
+ less: {
+ bootstrapbase: {
+ files: {
+ "theme/bootstrapbase/style/moodle.css": "theme/bootstrapbase/less/moodle.less",
+ "theme/bootstrapbase/style/editor.css": "theme/bootstrapbase/less/editor.less",
+ },
+ options: {
+ compress: false // We must not compress to keep the comments.
+ }
+ }
+ },
+ watch: {
+ options: {
+ nospawn: true // We need not to spawn so config can be changed dynamically.
+ },
+ amd: {
+ files: ['**/amd/src/**/*.js'],
+ tasks: ['amd']
+ },
+ bootstrapbase: {
+ files: ["theme/bootstrapbase/less/**/*.less"],
+ tasks: ["css"]
+ },
+ yui: {
+ files: ['**/yui/src/**/*.js'],
+ tasks: ['yui']
+ },
+ gherkinlint: {
+ files: ['**/tests/behat/*.feature'],
+ tasks: ['gherkinlint']
+ }
+ },
+ shifter: {
+ options: {
+ recursive: true,
+ paths: [cwd]
+ }
+ },
+ gherkinlint: {
+ options: {
+ files: ['**/tests/behat/*.feature'],
+ }
+ },
+ stylelint: {
+ less: {
+ options: {
+ syntax: 'less',
+ configOverrides: {
+ rules: {
+ // These rules have to be disabled in .stylelintrc for scss compat.
+ "at-rule-no-unknown": true,
+ "no-browser-hacks": [true, {"severity": "warning"}]
+ }
+ }
+ },
+ src: ['theme/**/*.less']
+ },
+ scss: {
+ options: {syntax: 'scss'},
+ src: ['*/**/*.scss']
+ },
+ css: {
+ src: ['*/**/*.css'],
+ options: {
+ configOverrides: {
+ rules: {
+ // These rules have to be disabled in .stylelintrc for scss compat.
+ "at-rule-no-unknown": true,
+ "no-browser-hacks": [true, {"severity": "warning"}]
+ }
+ }
+ }
+ }
+ }
+ });
+
+ /**
+ * Generate ignore files (utilising thirdpartylibs.xml data)
+ */
+ tasks.ignorefiles = function() {
+ // An array of paths to third party directories.
+ var thirdPartyPaths = getThirdPartyPathsFromXML();
+ // Generate .eslintignore.
+ var eslintIgnores = ['# Generated by "grunt ignorefiles"', '*/**/yui/src/*/meta/', '*/**/build/'].concat(thirdPartyPaths);
+ grunt.file.write('.eslintignore', eslintIgnores.join('\n'));
+ // Generate .stylelintignore.
+ var stylelintIgnores = [
+ '# Generated by "grunt ignorefiles"',
+ 'theme/bootstrapbase/style/',
+ 'theme/clean/style/custom.css',
+ 'theme/more/style/custom.css'
+ ].concat(thirdPartyPaths);
+ grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
+ };
+
+ /**
+ * Shifter task. Is configured with a path to a specific file or a directory,
+ * in the case of a specific file it will work out the right module to be built.
+ *
+ * Note that this task runs the invidiaul shifter jobs async (becase it spawns
+ * so be careful to to call done().
+ */
+ tasks.shifter = function() {
+ var done = this.async(),
+ options = grunt.config('shifter.options');
+
+ // Run the shifter processes one at a time to avoid confusing output.
+ async.eachSeries(options.paths, function(src, filedone) {
+ var args = [];
+ args.push(path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
+
+ // Always ignore the node_modules directory.
+ args.push('--excludes', 'node_modules');
+
+ // Determine the most appropriate options to run with based upon the current location.
+ if (grunt.file.isMatch('**/yui/**/*.js', src)) {
+ // When passed a JS file, build our containing module (this happen with
+ // watch).
+ grunt.log.debug('Shifter passed a specific JS file');
+ src = path.dirname(path.dirname(src));
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src', src)) {
+ // When in a src directory --walk all modules.
+ grunt.log.debug('In a src directory');
+ args.push('--walk');
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src/*', src)) {
+ // When in module, only build our module.
+ grunt.log.debug('In a module directory');
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
+ // When in module src, only build our module.
+ grunt.log.debug('In a source directory');
+ src = path.dirname(src);
+ options.recursive = false;
+ }
+
+ if (grunt.option('watch')) {
+ grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
+ }
+
+ // Add the stderr option if appropriate
+ if (grunt.option('verbose')) {
+ args.push('--lint-stderr');
+ }
+
+ if (grunt.option('no-color')) {
+ args.push('--color=false');
+ }
+
+ var execShifter = function() {
+
+ grunt.log.ok("Running shifter on " + src);
+ grunt.util.spawn({
+ cmd: "node",
+ args: args,
+ opts: {cwd: src, stdio: 'inherit', env: process.env}
+ }, function(error, result, code) {
+ if (code) {
+ grunt.fail.fatal('Shifter failed with code: ' + code);
+ } else {
+ grunt.log.ok('Shifter build complete.');
+ filedone();
+ }
+ });
+ };
+
+ // Actually run shifter.
+ if (!options.recursive) {
+ execShifter();
+ } else {
+ // Check that there are yui modules otherwise shifter ends with exit code 1.
+ if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
+ args.push('--recursive');
+ execShifter();
+ } else {
+ grunt.log.ok('No YUI modules to build.');
+ filedone();
+ }
+ }
+ }, done);
+ };
+
+ tasks.gherkinlint = function() {
+ var done = this.async(),
+ options = grunt.config('gherkinlint.options');
+
+ var args = grunt.file.expand(options.files);
+ args.unshift(path.normalize(__dirname + '/node_modules/.bin/gherkin-lint'));
+ grunt.util.spawn({
+ cmd: 'node',
+ args: args,
+ opts: {stdio: 'inherit', env: process.env}
+ }, function(error, result, code) {
+ // Propagate the exit code.
+ done(code === 0);
+ });
+ };
+
+ tasks.startup = function() {
+ // Are we in a YUI directory?
+ if (path.basename(path.resolve(cwd, '../../')) == 'yui') {
+ grunt.task.run('yui');
+ // Are we in an AMD directory?
+ } else if (inAMD) {
+ grunt.task.run('amd');
+ } else {
+ // Run them all!.
+ grunt.task.run('css');
+ grunt.task.run('js');
+ grunt.task.run('gherkinlint');
+ }
+ };
+
+ // On watch, we dynamically modify config to build only affected files. This
+ // method is slightly complicated to deal with multiple changed files at once (copied
+ // from the grunt-contrib-watch readme).
+ var changedFiles = Object.create(null);
+ var onChange = grunt.util._.debounce(function() {
+ var files = Object.keys(changedFiles);
+ grunt.config('eslint.amd.src', files);
+ grunt.config('eslint.yui.src', files);
+ grunt.config('uglify.amd.files', [{expand: true, src: files, rename: uglifyRename}]);
+ grunt.config('shifter.options.paths', files);
+ grunt.config('stylelint.less.src', files);
+ grunt.config('gherkinlint.options.files', files);
+ changedFiles = Object.create(null);
+ }, 200);
+
+ grunt.event.on('watch', function(action, filepath) {
+ changedFiles[filepath] = action;
+ onChange();
+ });
+
+ // Register NPM tasks.
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+ grunt.loadNpmTasks('grunt-contrib-less');
+ grunt.loadNpmTasks('grunt-contrib-watch');
+ grunt.loadNpmTasks('grunt-eslint');
+ grunt.loadNpmTasks('grunt-stylelint');
+
+ // Register JS tasks.
+ grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
+ grunt.registerTask('gherkinlint', 'Run gherkinlint against the current directory', tasks.gherkinlint);
+ grunt.registerTask('ignorefiles', 'Generate ignore files for linters', tasks.ignorefiles);
+ grunt.registerTask('yui', ['eslint:yui', 'shifter']);
+ grunt.registerTask('amd', ['eslint:amd', 'uglify']);
+ grunt.registerTask('js', ['amd', 'yui']);
+
+ // Register CSS taks.
+ grunt.registerTask('css', ['stylelint:scss', 'stylelint:less', 'less:bootstrapbase', 'stylelint:css']);
+
+ // Register the startup task.
+ grunt.registerTask('startup', 'Run the correct tasks for the current directory', tasks.startup);
+
+ // Register the default task.
+ grunt.registerTask('default', ['startup']);
+};
diff --git a/README.md b/README.md
index 894acea..16fc65e 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,24 @@
Atto multilanguage plugin
=========================
-![Release](https://img.shields.io/badge/release-v1.7-blue.svg) ![Supported](https://img.shields.io/badge/supported-2.9%2C%203.0%2C%203.1%2C%203.2%2C%203.3-green.svg)
+![Release](https://img.shields.io/badge/release-v1.9-blue.svg) ![Supported](https://img.shields.io/badge/supported-2.9%2C%203.0%2C%203.1%2C%203.2%2C%203.3%2C%203.4-green.svg)
This plugin will make the creation of multilingual contents on Moodle much more easier with Atto editor.
The plugin is developed to work with [Iñaki Arenaza's multilang2 filter](https://github.com/iarenaza/moodle-filter_multilang2), and the idea is based on [his plugin for TinyMCE editor](https://github.com/iarenaza/moodle-tinymce_moodlelang2).
## Current version
-The latest release is the v1.7 (build 2016121100) for Moodle 2.9, 3.0, 3.1, 3.2 and 3.3 (Checkout [v2.9.1.7](https://github.com/julenpardo/moodle-atto_multilang2/releases/tag/v2.9.1.6), [v3.0.1.7](https://github.com/julenpardo/moodle-atto_multilang2/releases/tag/v3.0.1.6), [v3.1.1.7](https://github.com/julenpardo/moodle-atto_multilang2/releases/tag/v3.1.1.6), [v3.2.1.7](https://github.com/julenpardo/moodle-atto_multilang2/releases/tag/v3.2.1.6) and [v3.3.1.7](https://github.com/julenpardo/moodle-atto_multilang2/releases/tag/v3.3.1.7) releases, respectively.
-## Changes from v1.6
- - Add missing capability string
+The latest release is v1.9 (build 2017102700) for Moodle 2.9, 3.0, 3.1, 3.2, 3.4 and 3.4 (Checkout [v2.9.1.9](https://github.com/iarenaza/moodle-atto_multilang2/releases/tag/v2.9.1.9), [v3.0.1.9](https://github.com/iarenaza/moodle-atto_multilang2/releases/tag/v3.0.1.9), [v3.1.1.9](https://github.com/iarenaza/moodle-atto_multilang2/releases/tag/v3.1.1.9), [v3.2.1.9](https://github.com/iarenaza/moodle-atto_multilang2/releases/tag/v3.2.1.9) and [v3.3.1.9](https://github.com/iarenaza/moodle-atto_multilang2/releases/tag/v3.3.1.9) releases, respectively.
+
+## Changes from v1.8
+ - Added Grunt support files. No we can check Moodle Javascript coding guidelines and minify the plugin code without installing the plugins.
+ - Minor Javascript coding guidelines fix (signalled by the minifier, but ignored by the linter!)
+
+## Changes from v1.7
+ - Multiple bug fixes, especially when there were more than one Atto editor in the same page. Closes issues #2, #18, #21.
+ - Multilang tag highlighting 's are now removed/added when switching to/from HTML view, reducing clutter when editing the HTML code.
+ - Cleaned the code to conform to PHP, PHPDoc and Javascript Moodle coding guidelines (all checks pass!)
## Requirements
As mentioned before, [filter_multilang2](https://github.com/iarenaza/moodle-filter_multilang2) is required.
@@ -19,11 +26,8 @@ As mentioned before, [filter_multilang2](https://github.com/iarenaza/moodle-filt
## Installation
- Copy repository content in *moodleroot*/lib/editor/atto/plugins. The following can be omitted:
- - moodle-javascript_style_checker/
- tests/ (if you're not going to test it with Behat)
- .gitmodules
- build.xml
- - Install it from Moodle.
- - Go to Site administration/Plugins/Text
- editors/Atto HTML editor/Atto toolbar settings, and add *multilang2*
- to the Toolbar config where you prefer. E.g. `multilang2 = multilang2`
+ - Install the plugin from Moodle.
+ - Go to "Site administration" >> "Plugins" >> "Text editors" >> "Atto HTML editor" >> "Atto toolbar settings", and add *multilang2* to the Toolbar config where you prefer. E.g. `multilang2 = multilang2` (see [Text editor - Toolbar settings](https://docs.moodle.org/en/Text_editor#Toolbar_settings) and [Text editor - Adding extra buttons](https://docs.moodle.org/en/Text_editor#Adding_extra_buttons) for instructions on how to add a plugin button to Atto toolbar.
diff --git a/db/access.php b/db/access.php
index 2e9bb23..459e75c 100644
--- a/db/access.php
+++ b/db/access.php
@@ -14,12 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+/**
+ * Atto text editor multilanguage plugin lib.
+ *
+ * @package atto_multilang2
+ * @copyright 2015 onwards Julen Pardo & Mondragon Unibertsitatea
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
defined('MOODLE_INTERNAL') || die();
-// Plugin for Moodle 'Multilingual content' drop down menu.
-// @package atto_multilang2
-// @copyright 2016 onwards Julen Pardo & Mondragon Unibertsitatea
-// @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+/*
+ * Plugin for Moodle 'Multilingual content' drop down menu.
+ * @package atto_multilang2
+ * @copyright 2016 onwards Julen Pardo & Mondragon Unibertsitatea
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
$capabilities = array(
'atto/multilang2:viewlanguagemenu' => array(
diff --git a/default-css.php b/default-css.php
index 5fdc0e5..0b5f0b0 100644
--- a/default-css.php
+++ b/default-css.php
@@ -22,12 +22,11 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-$multilang2_default_css = <<< EOF
+defined('MOODLE_INTERNAL') || die();
+$multilang2defaultcss = "
outline: 1px dotted;
padding: 0.1em;
margin: 0em 0.1em;
background-color: #ffffaa;
-
-EOF
-;
+";
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2a7d142
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "Moodle",
+ "private": true,
+ "description": "Moodle",
+ "devDependencies": {
+ "async": "1.5.2",
+ "eslint": "3.7.1",
+ "gherkin-lint": "1.1.3",
+ "grunt": "1.0.1",
+ "grunt-contrib-less": "1.3.0",
+ "grunt-contrib-uglify": "1.0.1",
+ "grunt-contrib-watch": "1.0.0",
+ "grunt-eslint": "19.0.0",
+ "grunt-stylelint": "0.6.0",
+ "semver": "5.3.0",
+ "shifter": "0.5.0",
+ "stylelint": "7.4.1",
+ "stylelint-checkstyle-formatter": "0.1.0",
+ "xmldom": "0.1.22",
+ "xpath": "0.0.23"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+}
diff --git a/settings.php b/settings.php
index ef0f539..d38e26e 100644
--- a/settings.php
+++ b/settings.php
@@ -30,4 +30,4 @@
get_string('highlight', 'atto_multilang2'), get_string('highlight_desc', 'atto_multilang2'), 1));
$settings->add(new admin_setting_configtextarea('atto_multilang2/customcss',
get_string('customcss', 'atto_multilang2'), get_string('customcss_desc', 'atto_multilang2'),
- $multilang2_default_css, PARAM_RAW));
+ $multilang2defaultcss, PARAM_RAW));
diff --git a/version.php b/version.php
index 41e089b..5050629 100644
--- a/version.php
+++ b/version.php
@@ -24,9 +24,9 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017052400; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->release = 'v3.3.1.7 (version v1.7 for Moodle 3.3) (2016121100)';
-$plugin->requires = 2017051500; // Required Moodle version.
+$plugin->version = 2017102700; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->release = 'v3.3.1.9 (version v1.9 for Moodle 3.3) (2017102700)';
+$plugin->requires = 2015051100; // Required Moodle version.
$plugin->component = 'atto_multilang2'; // Full name of the plugin (used for diagnostics).
$plugin->maturity = MATURITY_STABLE;
$plugin->dependencies = array(
diff --git a/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-coverage.js b/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-coverage.js
index 8aa9f76..3eb8e65 100644
--- a/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-coverage.js
+++ b/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-coverage.js
@@ -1,6 +1,6 @@
if (typeof __coverage__ === 'undefined') { __coverage__ = {}; }
if (!__coverage__['build/moodle-atto_multilang2-button/moodle-atto_multilang2-button.js']) {
- __coverage__['build/moodle-atto_multilang2-button/moodle-atto_multilang2-button.js'] = {"path":"build/moodle-atto_multilang2-button/moodle-atto_multilang2-button.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0,"122":0,"123":0,"124":0,"125":0,"126":0,"127":0,"128":0,"129":0,"130":0,"131":0,"132":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0],"19":[0,0],"20":[0,0],"21":[0,0]},"f":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0},"fnMap":{"1":{"name":"(anonymous_1)","line":1,"loc":{"start":{"line":1,"column":41},"end":{"line":1,"column":60}}},"2":{"name":"(anonymous_2)","line":73,"loc":{"start":{"line":73,"column":17},"end":{"line":73,"column":28}}},"3":{"name":"(anonymous_3)","line":107,"loc":{"start":{"line":107,"column":22},"end":{"line":107,"column":33}}},"4":{"name":"(anonymous_4)","line":126,"loc":{"start":{"line":126,"column":29},"end":{"line":126,"column":40}}},"5":{"name":"(anonymous_5)","line":161,"loc":{"start":{"line":161,"column":14},"end":{"line":161,"column":40}}},"6":{"name":"(anonymous_6)","line":188,"loc":{"start":{"line":188,"column":23},"end":{"line":188,"column":34}}},"7":{"name":"(anonymous_7)","line":222,"loc":{"start":{"line":222,"column":27},"end":{"line":222,"column":38}}},"8":{"name":"(anonymous_8)","line":245,"loc":{"start":{"line":245,"column":25},"end":{"line":245,"column":36}}},"9":{"name":"(anonymous_9)","line":260,"loc":{"start":{"line":260,"column":33},"end":{"line":260,"column":54}}},"10":{"name":"(anonymous_10)","line":294,"loc":{"start":{"line":294,"column":24},"end":{"line":294,"column":54}}},"11":{"name":"(anonymous_11)","line":323,"loc":{"start":{"line":323,"column":27},"end":{"line":323,"column":38}}},"12":{"name":"(anonymous_12)","line":388,"loc":{"start":{"line":388,"column":26},"end":{"line":388,"column":37}}},"13":{"name":"(anonymous_13)","line":457,"loc":{"start":{"line":457,"column":25},"end":{"line":457,"column":36}}},"14":{"name":"(anonymous_14)","line":504,"loc":{"start":{"line":504,"column":29},"end":{"line":504,"column":40}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":573,"column":61}},"2":{"start":{"line":28,"column":0},"end":{"line":52,"column":56}},"3":{"start":{"line":62,"column":0},"end":{"line":570,"column":3}},"4":{"start":{"line":74,"column":8},"end":{"line":75,"column":30}},"5":{"start":{"line":77,"column":8},"end":{"line":98,"column":9}},"6":{"start":{"line":78,"column":12},"end":{"line":78,"column":58}},"7":{"start":{"line":79,"column":12},"end":{"line":79,"column":55}},"8":{"start":{"line":81,"column":12},"end":{"line":88,"column":15}},"9":{"start":{"line":90,"column":12},"end":{"line":90,"column":91}},"10":{"start":{"line":92,"column":12},"end":{"line":92,"column":36}},"11":{"start":{"line":94,"column":12},"end":{"line":97,"column":13}},"12":{"start":{"line":95,"column":16},"end":{"line":95,"column":43}},"13":{"start":{"line":96,"column":16},"end":{"line":96,"column":43}},"14":{"start":{"line":108,"column":8},"end":{"line":109,"column":18}},"15":{"start":{"line":111,"column":8},"end":{"line":111,"column":48}},"16":{"start":{"line":112,"column":8},"end":{"line":112,"column":32}},"17":{"start":{"line":113,"column":8},"end":{"line":113,"column":30}},"18":{"start":{"line":115,"column":8},"end":{"line":115,"column":41}},"19":{"start":{"line":127,"column":8},"end":{"line":129,"column":21}},"20":{"start":{"line":131,"column":8},"end":{"line":131,"column":57}},"21":{"start":{"line":133,"column":8},"end":{"line":140,"column":9}},"22":{"start":{"line":134,"column":12},"end":{"line":139,"column":13}},"23":{"start":{"line":135,"column":16},"end":{"line":138,"column":19}},"24":{"start":{"line":142,"column":8},"end":{"line":142,"column":28}},"25":{"start":{"line":162,"column":8},"end":{"line":165,"column":20}},"26":{"start":{"line":167,"column":8},"end":{"line":167,"column":84}},"27":{"start":{"line":169,"column":8},"end":{"line":169,"column":45}},"28":{"start":{"line":170,"column":8},"end":{"line":170,"column":87}},"29":{"start":{"line":172,"column":8},"end":{"line":172,"column":71}},"30":{"start":{"line":173,"column":8},"end":{"line":173,"column":73}},"31":{"start":{"line":175,"column":8},"end":{"line":175,"column":54}},"32":{"start":{"line":177,"column":8},"end":{"line":177,"column":27}},"33":{"start":{"line":189,"column":8},"end":{"line":193,"column":19}},"34":{"start":{"line":195,"column":8},"end":{"line":210,"column":9}},"35":{"start":{"line":196,"column":12},"end":{"line":196,"column":46}},"36":{"start":{"line":198,"column":12},"end":{"line":204,"column":13}},"37":{"start":{"line":199,"column":16},"end":{"line":199,"column":58}},"38":{"start":{"line":200,"column":16},"end":{"line":202,"column":17}},"39":{"start":{"line":201,"column":20},"end":{"line":201,"column":87}},"40":{"start":{"line":203,"column":16},"end":{"line":203,"column":43}},"41":{"start":{"line":206,"column":15},"end":{"line":210,"column":9}},"42":{"start":{"line":207,"column":12},"end":{"line":209,"column":13}},"43":{"start":{"line":208,"column":16},"end":{"line":208,"column":65}},"44":{"start":{"line":212,"column":8},"end":{"line":212,"column":20}},"45":{"start":{"line":223,"column":8},"end":{"line":227,"column":22}},"46":{"start":{"line":229,"column":8},"end":{"line":229,"column":67}},"47":{"start":{"line":230,"column":8},"end":{"line":230,"column":63}},"48":{"start":{"line":232,"column":8},"end":{"line":234,"column":9}},"49":{"start":{"line":233,"column":12},"end":{"line":233,"column":70}},"50":{"start":{"line":246,"column":8},"end":{"line":246,"column":56}},"51":{"start":{"line":248,"column":8},"end":{"line":248,"column":67}},"52":{"start":{"line":261,"column":8},"end":{"line":265,"column":28}},"53":{"start":{"line":267,"column":8},"end":{"line":267,"column":69}},"54":{"start":{"line":269,"column":8},"end":{"line":279,"column":9}},"55":{"start":{"line":270,"column":12},"end":{"line":270,"column":47}},"56":{"start":{"line":271,"column":12},"end":{"line":271,"column":62}},"57":{"start":{"line":273,"column":12},"end":{"line":273,"column":70}},"58":{"start":{"line":274,"column":12},"end":{"line":274,"column":77}},"59":{"start":{"line":276,"column":12},"end":{"line":278,"column":13}},"60":{"start":{"line":277,"column":16},"end":{"line":277,"column":82}},"61":{"start":{"line":295,"column":8},"end":{"line":295,"column":31}},"62":{"start":{"line":297,"column":8},"end":{"line":297,"column":37}},"63":{"start":{"line":298,"column":8},"end":{"line":298,"column":35}},"64":{"start":{"line":300,"column":8},"end":{"line":300,"column":62}},"65":{"start":{"line":301,"column":8},"end":{"line":301,"column":39}},"66":{"start":{"line":324,"column":8},"end":{"line":332,"column":30}},"67":{"start":{"line":334,"column":8},"end":{"line":334,"column":78}},"68":{"start":{"line":336,"column":8},"end":{"line":340,"column":9}},"69":{"start":{"line":337,"column":12},"end":{"line":337,"column":33}},"70":{"start":{"line":338,"column":12},"end":{"line":338,"column":27}},"71":{"start":{"line":339,"column":12},"end":{"line":339,"column":36}},"72":{"start":{"line":342,"column":8},"end":{"line":364,"column":9}},"73":{"start":{"line":343,"column":12},"end":{"line":343,"column":58}},"74":{"start":{"line":344,"column":12},"end":{"line":344,"column":64}},"75":{"start":{"line":346,"column":12},"end":{"line":346,"column":50}},"76":{"start":{"line":348,"column":12},"end":{"line":348,"column":65}},"77":{"start":{"line":350,"column":12},"end":{"line":352,"column":13}},"78":{"start":{"line":351,"column":16},"end":{"line":351,"column":25}},"79":{"start":{"line":354,"column":12},"end":{"line":361,"column":13}},"80":{"start":{"line":355,"column":16},"end":{"line":355,"column":56}},"81":{"start":{"line":356,"column":16},"end":{"line":356,"column":73}},"82":{"start":{"line":358,"column":16},"end":{"line":358,"column":69}},"83":{"start":{"line":360,"column":16},"end":{"line":360,"column":77}},"84":{"start":{"line":363,"column":12},"end":{"line":363,"column":49}},"85":{"start":{"line":366,"column":8},"end":{"line":366,"column":27}},"86":{"start":{"line":389,"column":8},"end":{"line":399,"column":21}},"87":{"start":{"line":401,"column":8},"end":{"line":401,"column":81}},"88":{"start":{"line":402,"column":8},"end":{"line":402,"column":89}},"89":{"start":{"line":404,"column":8},"end":{"line":408,"column":9}},"90":{"start":{"line":405,"column":12},"end":{"line":405,"column":33}},"91":{"start":{"line":406,"column":12},"end":{"line":406,"column":27}},"92":{"start":{"line":407,"column":12},"end":{"line":407,"column":36}},"93":{"start":{"line":410,"column":8},"end":{"line":435,"column":9}},"94":{"start":{"line":411,"column":12},"end":{"line":411,"column":58}},"95":{"start":{"line":412,"column":12},"end":{"line":412,"column":64}},"96":{"start":{"line":414,"column":12},"end":{"line":414,"column":50}},"97":{"start":{"line":416,"column":12},"end":{"line":416,"column":73}},"98":{"start":{"line":418,"column":12},"end":{"line":420,"column":13}},"99":{"start":{"line":419,"column":16},"end":{"line":419,"column":25}},"100":{"start":{"line":422,"column":12},"end":{"line":430,"column":13}},"101":{"start":{"line":423,"column":16},"end":{"line":423,"column":64}},"102":{"start":{"line":424,"column":16},"end":{"line":424,"column":68}},"103":{"start":{"line":426,"column":16},"end":{"line":426,"column":84}},"104":{"start":{"line":427,"column":16},"end":{"line":427,"column":69}},"105":{"start":{"line":429,"column":16},"end":{"line":429,"column":77}},"106":{"start":{"line":432,"column":12},"end":{"line":432,"column":49}},"107":{"start":{"line":434,"column":12},"end":{"line":434,"column":31}},"108":{"start":{"line":458,"column":8},"end":{"line":466,"column":27}},"109":{"start":{"line":468,"column":8},"end":{"line":468,"column":51}},"110":{"start":{"line":470,"column":8},"end":{"line":470,"column":58}},"111":{"start":{"line":471,"column":8},"end":{"line":471,"column":55}},"112":{"start":{"line":473,"column":8},"end":{"line":490,"column":9}},"113":{"start":{"line":474,"column":12},"end":{"line":487,"column":13}},"114":{"start":{"line":475,"column":16},"end":{"line":475,"column":44}},"115":{"start":{"line":477,"column":16},"end":{"line":477,"column":75}},"116":{"start":{"line":479,"column":16},"end":{"line":486,"column":17}},"117":{"start":{"line":480,"column":20},"end":{"line":480,"column":52}},"118":{"start":{"line":482,"column":20},"end":{"line":482,"column":76}},"119":{"start":{"line":483,"column":20},"end":{"line":483,"column":66}},"120":{"start":{"line":485,"column":20},"end":{"line":485,"column":88}},"121":{"start":{"line":489,"column":12},"end":{"line":489,"column":49}},"122":{"start":{"line":505,"column":8},"end":{"line":512,"column":18}},"123":{"start":{"line":514,"column":8},"end":{"line":514,"column":55}},"124":{"start":{"line":515,"column":8},"end":{"line":515,"column":57}},"125":{"start":{"line":516,"column":8},"end":{"line":516,"column":61}},"126":{"start":{"line":518,"column":8},"end":{"line":527,"column":9}},"127":{"start":{"line":519,"column":12},"end":{"line":526,"column":13}},"128":{"start":{"line":520,"column":16},"end":{"line":520,"column":56}},"129":{"start":{"line":522,"column":16},"end":{"line":522,"column":73}},"130":{"start":{"line":523,"column":16},"end":{"line":523,"column":69}},"131":{"start":{"line":525,"column":16},"end":{"line":525,"column":77}},"132":{"start":{"line":529,"column":8},"end":{"line":529,"column":25}}},"branchMap":{"1":{"line":77,"type":"if","locations":[{"start":{"line":77,"column":8},"end":{"line":77,"column":8}},{"start":{"line":77,"column":8},"end":{"line":77,"column":8}}]},"2":{"line":94,"type":"if","locations":[{"start":{"line":94,"column":12},"end":{"line":94,"column":12}},{"start":{"line":94,"column":12},"end":{"line":94,"column":12}}]},"3":{"line":134,"type":"if","locations":[{"start":{"line":134,"column":12},"end":{"line":134,"column":12}},{"start":{"line":134,"column":12},"end":{"line":134,"column":12}}]},"4":{"line":167,"type":"cond-expr","locations":[{"start":{"line":167,"column":44},"end":{"line":167,"column":60}},{"start":{"line":167,"column":63},"end":{"line":167,"column":83}}]},"5":{"line":170,"type":"cond-expr","locations":[{"start":{"line":170,"column":66},"end":{"line":170,"column":74}},{"start":{"line":170,"column":77},"end":{"line":170,"column":86}}]},"6":{"line":195,"type":"if","locations":[{"start":{"line":195,"column":8},"end":{"line":195,"column":8}},{"start":{"line":195,"column":8},"end":{"line":195,"column":8}}]},"7":{"line":198,"type":"if","locations":[{"start":{"line":198,"column":12},"end":{"line":198,"column":12}},{"start":{"line":198,"column":12},"end":{"line":198,"column":12}}]},"8":{"line":206,"type":"if","locations":[{"start":{"line":206,"column":15},"end":{"line":206,"column":15}},{"start":{"line":206,"column":15},"end":{"line":206,"column":15}}]},"9":{"line":207,"type":"if","locations":[{"start":{"line":207,"column":12},"end":{"line":207,"column":12}},{"start":{"line":207,"column":12},"end":{"line":207,"column":12}}]},"10":{"line":232,"type":"if","locations":[{"start":{"line":232,"column":8},"end":{"line":232,"column":8}},{"start":{"line":232,"column":8},"end":{"line":232,"column":8}}]},"11":{"line":232,"type":"binary-expr","locations":[{"start":{"line":232,"column":12},"end":{"line":232,"column":22}},{"start":{"line":232,"column":26},"end":{"line":232,"column":35}}]},"12":{"line":269,"type":"if","locations":[{"start":{"line":269,"column":8},"end":{"line":269,"column":8}},{"start":{"line":269,"column":8},"end":{"line":269,"column":8}}]},"13":{"line":276,"type":"if","locations":[{"start":{"line":276,"column":12},"end":{"line":276,"column":12}},{"start":{"line":276,"column":12},"end":{"line":276,"column":12}}]},"14":{"line":276,"type":"binary-expr","locations":[{"start":{"line":276,"column":16},"end":{"line":276,"column":31}},{"start":{"line":276,"column":35},"end":{"line":276,"column":50}}]},"15":{"line":336,"type":"if","locations":[{"start":{"line":336,"column":8},"end":{"line":336,"column":8}},{"start":{"line":336,"column":8},"end":{"line":336,"column":8}}]},"16":{"line":350,"type":"if","locations":[{"start":{"line":350,"column":12},"end":{"line":350,"column":12}},{"start":{"line":350,"column":12},"end":{"line":350,"column":12}}]},"17":{"line":404,"type":"if","locations":[{"start":{"line":404,"column":8},"end":{"line":404,"column":8}},{"start":{"line":404,"column":8},"end":{"line":404,"column":8}}]},"18":{"line":418,"type":"if","locations":[{"start":{"line":418,"column":12},"end":{"line":418,"column":12}},{"start":{"line":418,"column":12},"end":{"line":418,"column":12}}]},"19":{"line":473,"type":"if","locations":[{"start":{"line":473,"column":8},"end":{"line":473,"column":8}},{"start":{"line":473,"column":8},"end":{"line":473,"column":8}}]},"20":{"line":479,"type":"if","locations":[{"start":{"line":479,"column":16},"end":{"line":479,"column":16}},{"start":{"line":479,"column":16},"end":{"line":479,"column":16}}]},"21":{"line":518,"type":"if","locations":[{"start":{"line":518,"column":8},"end":{"line":518,"column":8}},{"start":{"line":518,"column":8},"end":{"line":518,"column":8}}]}},"code":["(function () { YUI.add('moodle-atto_multilang2-button', function (Y, NAME) {","","// This file is part of Moodle - http://moodle.org/","//","// Moodle is free software: you can redistribute it and/or modify","// it under the terms of the GNU General Public License as published by","// the Free Software Foundation, either version 3 of the License, or","// (at your option) any later version.","//","// Moodle is distributed in the hope that it will be useful,","// but WITHOUT ANY WARRANTY; without even the implied warranty of","// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the","// GNU General Public License for more details.","//","// You should have received a copy of the GNU General Public License","// along with Moodle. If not, see .","","/**"," * @package atto_multilang2"," * @copyright 2015 onwards Julen Pardo & Mondragon Unibertsitatea"," * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later"," */","","/**"," * @module moodle-atto_multilang2-button"," */","","var CLASSES = {"," TAG: 'filter-multilang-tag'"," },",""," LANG_WILDCARD = '%lang',"," CONTENT_WILDCARD = '%content',"," ATTR_LANGUAGES = 'languages',"," ATTR_CAPABILITY = 'capability',"," ATTR_HIGHLIGHT = 'highlight',"," ATTR_CSS = 'css',"," DEFAULT_LANGUAGE = '{\"en\":\"English (en)\"}',"," DEFAULT_CAPABILITY = true,"," DEFAULT_HIGHLIGHT = true,"," DEFAULT_CSS = 'outline: 1px dotted;' +"," 'padding: 0.1em;' +"," 'margin: 0em 0.1em;' +"," 'background-color: #ffffaa;',"," TEMPLATES = {"," SPANED: ' {mlang ' + LANG_WILDCARD + '}' +"," CONTENT_WILDCARD +"," '{mlang} ',",""," NOT_SPANED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'"," },"," OPENING_SPAN = '';","","/**"," * Atto text editor multilanguage plugin."," *"," * @namespace M.atto_multilang2"," * @class button"," * @extends M.editor_atto.EditorPlugin"," */","","Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {",""," /**"," * If the {mlang} tags have to be highlighted or not. Received as parameter from lib.php."," *"," * @property _highlight"," * @type boolean"," * @private"," */"," _highlight: true,",""," initializer: function() {"," var hascapability = this.get(ATTR_CAPABILITY),"," toolbarItems = [];",""," if (hascapability) {"," toolbarItems = this._initializeToolbarItems();"," this._highlight = this.get(ATTR_HIGHLIGHT);",""," this.addToolbarMenu({"," globalItemConfig: {"," callback: this._addTags"," },"," icon: 'icon',"," iconComponent: 'atto_multilang2',"," items: toolbarItems"," });",""," this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);",""," this._addDelimiterCss();",""," if (this._highlight) {"," this._decorateTagsOnInit();"," this._setSubmitListeners();"," }"," }"," },",""," /**"," * Adds the CSS rules for the delimiters, received as parameter from lib.php."," *"," * @method _addDelimiterCss"," * @private"," */"," _addDelimiterCss: function() {"," var css = '.' + CLASSES.TAG + '{' + this.get(ATTR_CSS) + '}',"," style;",""," style = document.createElement('style');"," style.type = 'text/css';"," style.innerHTML = css;",""," document.head.appendChild(style);"," },",""," /**"," * Initializes the toolbar items, which will be the installed languages,"," * received as parameter."," *"," * @method _initializeToolbarItems"," * @private"," * @return {Array} installed language strings"," */"," _initializeToolbarItems: function() {"," var toolbarItems = [],"," languages,"," langCode;",""," languages = JSON.parse(this.get(ATTR_LANGUAGES));",""," for (langCode in languages) {"," if (languages.hasOwnProperty(langCode)) {"," toolbarItems.push({"," text: languages[langCode],"," callbackArgs: langCode"," });"," }"," }",""," return toolbarItems;"," },",""," /**"," * Retrieves the selected text, wraps it with the multilang tags,"," * and replaces the selected text in the editor with with it."," *"," * If the 'highlight' setting is checked, the {mlang} will be wrapped between"," * the tags with the class for the CSS highlight; if not, they will not"," * be wrapped."," *"," * If there is no content selected, a \" \" will be inserted; otherwhise,"," * it's impossible to place the cursor inside the {mlang} tags."," *"," * @method _addTags"," * @param {EventFacade} event"," * @param {string} langCode the language code"," * @private"," */"," _addTags: function(event, langCode) {"," var selection,"," host = this.get('host'),"," taggedContent,"," content;",""," taggedContent = (this._highlight) ? TEMPLATES.SPANED : TEMPLATES.NOT_SPANED;",""," selection = this._getSelectionHTML();"," content = (host.getSelection().toString().length === 0) ? ' ' : selection;",""," taggedContent = taggedContent.replace(LANG_WILDCARD, langCode);"," taggedContent = taggedContent.replace(CONTENT_WILDCARD, content);",""," host.insertContentAtFocusPoint(taggedContent);",""," this.markUpdated();"," },",""," /**"," * Retrieves selected text with its HTML."," * Took from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234"," *"," * @method _getSelectionHTML"," * @private"," * @return {string} selected text's html; empty if nothing selected"," */"," _getSelectionHTML: function() {"," var html = '',"," selection,"," container,"," index,"," lenght;",""," if (typeof window.getSelection !== 'undefined') {"," selection = window.getSelection();",""," if (selection.rangeCount) {"," container = document.createElement('div');"," for (index = 0, lenght = selection.rangeCount; index < lenght; ++index) {"," container.appendChild(selection.getRangeAt(index).cloneContents());"," }"," html = container.innerHTML;"," }",""," } else if (typeof document.selection !== 'undefined') {"," if (document.selection.type === 'Text') {"," html = document.selection.createRange().htmlText;"," }"," }",""," return html;"," },",""," /**"," * Listens to every change of the text cursor in the text area. If the"," * cursor is placed within a multilang tag, the whole tag is selected."," *"," * @method _checkSelectionChange"," * @private"," */"," _checkSelectionChange: function() {"," var host = this.get('host'),"," node = host.getSelectionParentNode(),"," nodeValue = Y.one(node).get('text'),"," isTextNode,"," isLangTag;",""," isTextNode = Y.one(node).toString().indexOf('#text') > - 1;"," isLangTag = (nodeValue.match(/\\{mlang/g).length === 1);",""," if (isTextNode && isLangTag) {"," host.setSelection(host.getSelectionFromNode(Y.one(node)));"," }"," },",""," /**"," * Retrieves the inputs of type submit, and, for each element, calls the function"," * that sets the submit listener. Is not made in this function because there is"," * not any (apparent) way to access class scope from YUI closure."," *"," * @method _setSubmitListeners"," * @private"," */"," _setSubmitListeners: function() {"," var submitButtons = Y.all('input[type=submit]');",""," submitButtons.each(this._addListenerToSubmitButtons, this);"," },",""," /**"," * Adds the clean tags submit listener of each input[type=\"submit\"], but only if"," * it's not 'cancel' type, and if its parent form is of 'mform' class, because there"," * may be any other submit type (such us administrator's search button)."," *"," * @method _addListenerToSubmitButtons"," * @param {Node} buttonNode"," * @private"," */"," _addListenerToSubmitButtons: function(buttonNode) {"," var buttonObject,"," className,"," parentFormClassName,"," notCancelButton,"," notSearchButton;",""," buttonObject = document.getElementById(buttonNode.get('id'));",""," if (buttonObject !== null) {"," className = buttonObject.className;"," parentFormClassName = buttonObject.form.className;",""," notCancelButton = className.match(/btn-cancel/g) === null;"," notSearchButton = parentFormClassName.match(/mform/g).length > 0;",""," if (notCancelButton && notSearchButton) {"," buttonNode.on('click', this._cleanTagsOnSubmit, this, buttonNode);"," }"," }"," },",""," /**"," * When submit button clicked, this function is invoked. It has to stop the submission,"," * in order to process the textarea to clean the tags."," *"," * Once the textarea is cleaned, detaches this submit listener, i.e., it sets as default,"," * an then simulates the click, to submit the form."," *"," * @method _cleanTagsOnSubmit"," * @param {EventFacade} event"," * @param {Node} submitButton"," * @private"," */"," _cleanTagsOnSubmit: function(event, submitButton) {"," event.preventDefault();",""," this._cleanTagsWithNoYuiId();"," this._cleanTagsWithYuiId();",""," submitButton.detach('click', this._cleanTagsOnSubmit);"," submitButton.simulate('click');"," },",""," /**"," * Cleans the tags around the {mlang} tags when the form is submitted,"," * that do not have \"id\" attribute."," * The cleanup with \"id\" attribute and without it is made separately, to avoid an evil"," * regular expression."," *"," * There may be more than one atto editor textarea in the page. So, we have to retrieve"," * the textareas by the class name. If there is only one, the object will be only the"," * reference, but, if there are more, we will have an array. So, the easiest way is to"," * check if what we have is an array, and if it not, create it manually, and iterate it"," * later."," *"," * issue #15: the textareas are now retrieved passing to YUI selector the whole element,"," * instead of the id string, due to problems with special characters."," * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217"," *"," * @method _cleanTagsWithNoYuiId"," * @private"," */"," _cleanTagsWithNoYuiId: function() {"," var textareas = Y.all('.editor_atto_content'),"," textarea,"," textareaIndex,"," innerHTML,"," spanedmlangtags,"," spanedmlangtag,"," index,"," cleanmlangtag,"," regularExpression;",""," regularExpression = new RegExp(OPENING_SPAN + '.*?' + '', 'g');",""," if (!textareas instanceof Array) {"," textarea = textareas;"," textareas = [];"," textareas[0] = textarea;"," }",""," for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {"," textarea = textareas._nodes[textareaIndex].id;"," textarea = Y.one(document.getElementById(textarea));",""," innerHTML = textarea.get('innerHTML');",""," spanedmlangtags = innerHTML.match(regularExpression);",""," if (spanedmlangtags === null) {"," continue;"," }"," "," for (index = 0; index < spanedmlangtags.length; index++) {"," spanedmlangtag = spanedmlangtags[index];"," cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');",""," cleanmlangtag = cleanmlangtag.replace('', '');",""," innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);"," }",""," textarea.set('innerHTML', innerHTML);"," }",""," this.markUpdated();"," },",""," /**"," * Cleans the tags around the {mlang} tags when the form is submitted,"," * that have \"id\" attribute, generated by YUI, when the cursor is placed on the tags."," * The cleanup with \"id\" attribute and without it is made separately, to avoid an evil"," * regular expression."," *"," * There may be more than one atto editor textarea in the page. So, we have to retrieve"," * the textareas by the class name. If there is only one, the object will be only the"," * reference, but, if there are more, we will have an array. So, the easiest way is to"," * check if what we have is an array, and if it not, create it manually, and iterate it"," * later."," *"," * issue #15: the textareas are now retrieved passing to YUI selector the whole element,"," * instead of the id string, due to problems with special characters."," * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217"," *"," * @method anTagsWithYuiId"," * @private"," */"," _cleanTagsWithYuiId: function() {"," var textareas = Y.all('.editor_atto_content'),"," textarea,"," textareaIndex,"," innerHTML,"," spanedmlangtag,"," index,"," cleanmlangtag,"," regularExpression,"," openingspanwithyui,"," spanedmlangtagsdwithyui,"," mlangtag;",""," openingspanwithyui = OPENING_SPAN.replace('', 'g');",""," if (!textareas instanceof Array) {"," textarea = textareas;"," textareas = [];"," textareas[0] = textarea;"," }"," "," for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {"," textarea = textareas._nodes[textareaIndex].id;"," textarea = Y.one(document.getElementById(textarea));",""," innerHTML = textarea.get('innerHTML');",""," spanedmlangtagsdwithyui = innerHTML.match(regularExpression);",""," if (spanedmlangtagsdwithyui === null) {"," continue;"," }"," "," for (index = 0; index < spanedmlangtagsdwithyui.length; index++) {"," spanedmlangtag = spanedmlangtagsdwithyui[index];"," mlangtag = spanedmlangtag.match(/\\{mlang.*?\\}/g)[0];",""," cleanmlangtag = spanedmlangtag.replace(regularExpression, mlangtag);"," cleanmlangtag = cleanmlangtag.replace('', '');",""," innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);"," }",""," textarea.set('innerHTML', innerHTML);",""," this.markUpdated();"," }"," },",""," /**"," * Adds the tags to the {mlang} tags when the editor is loaded."," * In this case, we DON'T HAVE TO CALL TO markUpdated(). Why? Honestly,"," * I don't know. But, if we call it after setting the HTML, the {mlang}"," * tags flicker with the decoration, and returns to their original state."," *"," * Instead of taking the HTML directly from the textarea, we have to"," * retrieve it, first, without the tags that can be stored"," * in database, due to a bug in version 2015120501 that stores the"," * {mlang} tags in database, with the tags."," * More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8"," *"," * Every different {mlang} tag has to be replaced only once, otherwise,"," * nested s will be created in every repeated replacement. So, we"," * have to have a track of which replacements have been made."," *"," * @method _decorateTagsOnInit"," * @private"," */"," _decorateTagsOnInit: function() {"," var textarea = Y.all('.editor_atto_content'),"," innerHTML,"," regularExpression,"," mlangtags,"," mlangtag,"," index,"," decoratedmlangtag,"," replacementsmade = [],"," notreplacedyet;",""," innerHTML = this._getHTMLwithCleanedTags();",""," regularExpression = new RegExp('{mlang.*?}', 'g');"," mlangtags = innerHTML.match(regularExpression);",""," if (mlangtags !== null) {"," for (index = 0; index < mlangtags.length; index++) {"," mlangtag = mlangtags[index];",""," notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;",""," if (notreplacedyet) {"," replacementsmade.push(mlangtag);",""," decoratedmlangtag = OPENING_SPAN + mlangtag + '';"," regularExpression = new RegExp(mlangtag, 'g');",""," innerHTML = innerHTML.replace(regularExpression, decoratedmlangtag);"," }"," }",""," textarea.set('innerHTML', innerHTML);"," }",""," },",""," /**"," * This function returns the HTML as it is in the textarea, but cleaning every"," * tag around the {mlang} tags. This is necessary for decorating tags on"," * init, because it could happen that in database are stored the {mlang} tags with"," * their tags, due to a bug in version 2015120501."," * More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8"," *"," * @method _getHTMLwithCleanedTags"," * @return {string} HTML in textarea, without any around {mlang} tags"," */"," _getHTMLwithCleanedTags: function() {"," var host = this.get('host'),"," innerHTML = host.getCleanHTML(),"," regexString,"," regularExpression,"," spanedmlangtags,"," spanedmlangtag,"," cleanmlangtag,"," index;",""," regexString = OPENING_SPAN + '.*?' + '';"," regularExpression = new RegExp(regexString, 'g');"," spanedmlangtags = innerHTML.match(regularExpression);",""," if (spanedmlangtags !== null) {"," for (index = 0; index < spanedmlangtags.length; index++) {"," spanedmlangtag = spanedmlangtags[index];",""," cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');"," cleanmlangtag = cleanmlangtag.replace('', '');",""," innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);"," }"," }",""," return innerHTML;"," }","","}, {"," ATTRS: {"," /**"," * The list of installed languages."," *"," * @attribute languages"," * @type array"," * @default {\"en\":\"English (en)\"}"," */"," languages: DEFAULT_LANGUAGE,",""," /**"," * If the current user has the capability to use the plugin."," *"," * @attribute capability"," * @type boolean"," * @default true"," */"," capability: DEFAULT_CAPABILITY,",""," /**"," * If the {mlang} tags have to be highlighted or not."," *"," * @property highlight"," * @type boolean"," * @default true"," */"," highlight: DEFAULT_HIGHLIGHT,",""," /**"," * The CSS for delimiters."," *"," * @property css"," * @type string"," * @default DEFAULT_CSS"," */"," css: DEFAULT_CSS"," }","});","","","}, '@VERSION@', {\"requires\": [\"moodle-editor_atto-plugin\"]});","","}());"]};
+ __coverage__['build/moodle-atto_multilang2-button/moodle-atto_multilang2-button.js'] = {"path":"build/moodle-atto_multilang2-button/moodle-atto_multilang2-button.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0,0,0,0],"16":[0,0],"17":[0,0],"18":[0,0,0,0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0],"23":[0,0]},"f":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0},"fnMap":{"1":{"name":"(anonymous_1)","line":1,"loc":{"start":{"line":1,"column":41},"end":{"line":1,"column":60}}},"2":{"name":"(anonymous_2)","line":74,"loc":{"start":{"line":74,"column":17},"end":{"line":74,"column":28}}},"3":{"name":"(anonymous_3)","line":130,"loc":{"start":{"line":130,"column":29},"end":{"line":130,"column":40}}},"4":{"name":"(anonymous_4)","line":154,"loc":{"start":{"line":154,"column":25},"end":{"line":154,"column":36}}},"5":{"name":"(anonymous_5)","line":176,"loc":{"start":{"line":176,"column":25},"end":{"line":176,"column":36}}},"6":{"name":"(anonymous_6)","line":180,"loc":{"start":{"line":180,"column":31},"end":{"line":180,"column":42}}},"7":{"name":"(anonymous_7)","line":182,"loc":{"start":{"line":182,"column":19},"end":{"line":182,"column":30}}},"8":{"name":"(anonymous_8)","line":202,"loc":{"start":{"line":202,"column":29},"end":{"line":202,"column":40}}},"9":{"name":"(anonymous_9)","line":206,"loc":{"start":{"line":206,"column":35},"end":{"line":206,"column":46}}},"10":{"name":"(anonymous_10)","line":208,"loc":{"start":{"line":208,"column":19},"end":{"line":208,"column":30}}},"11":{"name":"(anonymous_11)","line":234,"loc":{"start":{"line":234,"column":14},"end":{"line":234,"column":40}}},"12":{"name":"(anonymous_12)","line":261,"loc":{"start":{"line":261,"column":23},"end":{"line":261,"column":34}}},"13":{"name":"(anonymous_13)","line":295,"loc":{"start":{"line":295,"column":27},"end":{"line":295,"column":38}}},"14":{"name":"(anonymous_14)","line":323,"loc":{"start":{"line":323,"column":21},"end":{"line":323,"column":32}}},"15":{"name":"(anonymous_15)","line":346,"loc":{"start":{"line":346,"column":25},"end":{"line":346,"column":36}}},"16":{"name":"(anonymous_16)","line":392,"loc":{"start":{"line":392,"column":29},"end":{"line":392,"column":47}}},"17":{"name":"(anonymous_17)","line":404,"loc":{"start":{"line":404,"column":25},"end":{"line":404,"column":40}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":460,"column":61}},"2":{"start":{"line":37,"column":0},"end":{"line":60,"column":6}},"3":{"start":{"line":63,"column":0},"end":{"line":457,"column":3}},"4":{"start":{"line":75,"column":8},"end":{"line":78,"column":17}},"5":{"start":{"line":80,"column":8},"end":{"line":119,"column":9}},"6":{"start":{"line":81,"column":12},"end":{"line":81,"column":58}},"7":{"start":{"line":83,"column":12},"end":{"line":90,"column":15}},"8":{"start":{"line":92,"column":12},"end":{"line":92,"column":54}},"9":{"start":{"line":94,"column":12},"end":{"line":94,"column":55}},"10":{"start":{"line":95,"column":12},"end":{"line":118,"column":13}},"11":{"start":{"line":96,"column":16},"end":{"line":96,"column":54}},"12":{"start":{"line":100,"column":16},"end":{"line":100,"column":40}},"13":{"start":{"line":101,"column":16},"end":{"line":101,"column":54}},"14":{"start":{"line":102,"column":16},"end":{"line":104,"column":17}},"15":{"start":{"line":103,"column":20},"end":{"line":103,"column":66}},"16":{"start":{"line":108,"column":16},"end":{"line":108,"column":95}},"17":{"start":{"line":111,"column":16},"end":{"line":111,"column":85}},"18":{"start":{"line":112,"column":16},"end":{"line":112,"column":85}},"19":{"start":{"line":116,"column":16},"end":{"line":116,"column":43}},"20":{"start":{"line":117,"column":16},"end":{"line":117,"column":47}},"21":{"start":{"line":131,"column":8},"end":{"line":133,"column":21}},"22":{"start":{"line":135,"column":8},"end":{"line":135,"column":57}},"23":{"start":{"line":136,"column":8},"end":{"line":143,"column":9}},"24":{"start":{"line":137,"column":12},"end":{"line":142,"column":13}},"25":{"start":{"line":138,"column":16},"end":{"line":141,"column":19}},"26":{"start":{"line":145,"column":8},"end":{"line":145,"column":28}},"27":{"start":{"line":155,"column":8},"end":{"line":156,"column":18}},"28":{"start":{"line":158,"column":8},"end":{"line":158,"column":48}},"29":{"start":{"line":159,"column":8},"end":{"line":159,"column":32}},"30":{"start":{"line":160,"column":8},"end":{"line":160,"column":30}},"31":{"start":{"line":162,"column":8},"end":{"line":162,"column":41}},"32":{"start":{"line":177,"column":8},"end":{"line":178,"column":35}},"33":{"start":{"line":180,"column":8},"end":{"line":188,"column":13}},"34":{"start":{"line":181,"column":12},"end":{"line":181,"column":54}},"35":{"start":{"line":182,"column":12},"end":{"line":187,"column":14}},"36":{"start":{"line":183,"column":16},"end":{"line":185,"column":17}},"37":{"start":{"line":184,"column":20},"end":{"line":184,"column":126}},"38":{"start":{"line":186,"column":16},"end":{"line":186,"column":62}},"39":{"start":{"line":203,"column":8},"end":{"line":204,"column":35}},"40":{"start":{"line":206,"column":8},"end":{"line":215,"column":13}},"41":{"start":{"line":207,"column":12},"end":{"line":207,"column":62}},"42":{"start":{"line":208,"column":12},"end":{"line":214,"column":14}},"43":{"start":{"line":209,"column":16},"end":{"line":209,"column":69}},"44":{"start":{"line":210,"column":16},"end":{"line":212,"column":17}},"45":{"start":{"line":211,"column":20},"end":{"line":211,"column":58}},"46":{"start":{"line":213,"column":16},"end":{"line":213,"column":27}},"47":{"start":{"line":235,"column":8},"end":{"line":238,"column":20}},"48":{"start":{"line":240,"column":8},"end":{"line":240,"column":42}},"49":{"start":{"line":242,"column":8},"end":{"line":242,"column":45}},"50":{"start":{"line":243,"column":8},"end":{"line":243,"column":87}},"51":{"start":{"line":245,"column":8},"end":{"line":245,"column":71}},"52":{"start":{"line":246,"column":8},"end":{"line":246,"column":73}},"53":{"start":{"line":248,"column":8},"end":{"line":248,"column":54}},"54":{"start":{"line":250,"column":8},"end":{"line":250,"column":27}},"55":{"start":{"line":262,"column":8},"end":{"line":266,"column":19}},"56":{"start":{"line":268,"column":8},"end":{"line":283,"column":9}},"57":{"start":{"line":269,"column":12},"end":{"line":269,"column":46}},"58":{"start":{"line":271,"column":12},"end":{"line":277,"column":13}},"59":{"start":{"line":272,"column":16},"end":{"line":272,"column":58}},"60":{"start":{"line":273,"column":16},"end":{"line":275,"column":17}},"61":{"start":{"line":274,"column":20},"end":{"line":274,"column":87}},"62":{"start":{"line":276,"column":16},"end":{"line":276,"column":43}},"63":{"start":{"line":279,"column":15},"end":{"line":283,"column":9}},"64":{"start":{"line":280,"column":12},"end":{"line":282,"column":13}},"65":{"start":{"line":281,"column":16},"end":{"line":281,"column":65}},"66":{"start":{"line":285,"column":8},"end":{"line":285,"column":20}},"67":{"start":{"line":296,"column":8},"end":{"line":300,"column":22}},"68":{"start":{"line":303,"column":8},"end":{"line":306,"column":9}},"69":{"start":{"line":305,"column":12},"end":{"line":305,"column":19}},"70":{"start":{"line":308,"column":8},"end":{"line":308,"column":50}},"71":{"start":{"line":309,"column":8},"end":{"line":309,"column":105}},"72":{"start":{"line":310,"column":8},"end":{"line":314,"column":9}},"73":{"start":{"line":312,"column":12},"end":{"line":312,"column":63}},"74":{"start":{"line":313,"column":12},"end":{"line":313,"column":41}},"75":{"start":{"line":324,"column":8},"end":{"line":327,"column":9}},"76":{"start":{"line":325,"column":12},"end":{"line":325,"column":85}},"77":{"start":{"line":326,"column":12},"end":{"line":326,"column":31}},"78":{"start":{"line":347,"column":8},"end":{"line":354,"column":27}},"79":{"start":{"line":355,"column":8},"end":{"line":377,"column":9}},"80":{"start":{"line":356,"column":12},"end":{"line":356,"column":77}},"81":{"start":{"line":358,"column":12},"end":{"line":358,"column":62}},"82":{"start":{"line":359,"column":12},"end":{"line":359,"column":60}},"83":{"start":{"line":360,"column":12},"end":{"line":374,"column":13}},"84":{"start":{"line":361,"column":16},"end":{"line":371,"column":17}},"85":{"start":{"line":362,"column":20},"end":{"line":362,"column":48}},"86":{"start":{"line":364,"column":20},"end":{"line":364,"column":79}},"87":{"start":{"line":365,"column":20},"end":{"line":370,"column":21}},"88":{"start":{"line":366,"column":24},"end":{"line":366,"column":56}},"89":{"start":{"line":367,"column":24},"end":{"line":367,"column":85}},"90":{"start":{"line":368,"column":24},"end":{"line":368,"column":70}},"91":{"start":{"line":369,"column":24},"end":{"line":369,"column":96}},"92":{"start":{"line":373,"column":16},"end":{"line":373,"column":48}},"93":{"start":{"line":376,"column":12},"end":{"line":376,"column":31}},"94":{"start":{"line":394,"column":8},"end":{"line":396,"column":21}},"95":{"start":{"line":398,"column":8},"end":{"line":398,"column":35}},"96":{"start":{"line":399,"column":8},"end":{"line":399,"column":52}},"97":{"start":{"line":402,"column":8},"end":{"line":402,"column":56}},"98":{"start":{"line":404,"column":8},"end":{"line":414,"column":11}},"99":{"start":{"line":405,"column":12},"end":{"line":413,"column":13}},"100":{"start":{"line":407,"column":16},"end":{"line":409,"column":17}},"101":{"start":{"line":408,"column":20},"end":{"line":408,"column":72}},"102":{"start":{"line":412,"column":16},"end":{"line":412,"column":50}},"103":{"start":{"line":416,"column":8},"end":{"line":416,"column":32}}},"branchMap":{"1":{"line":80,"type":"if","locations":[{"start":{"line":80,"column":8},"end":{"line":80,"column":8}},{"start":{"line":80,"column":8},"end":{"line":80,"column":8}}]},"2":{"line":95,"type":"if","locations":[{"start":{"line":95,"column":12},"end":{"line":95,"column":12}},{"start":{"line":95,"column":12},"end":{"line":95,"column":12}}]},"3":{"line":102,"type":"if","locations":[{"start":{"line":102,"column":16},"end":{"line":102,"column":16}},{"start":{"line":102,"column":16},"end":{"line":102,"column":16}}]},"4":{"line":137,"type":"if","locations":[{"start":{"line":137,"column":12},"end":{"line":137,"column":12}},{"start":{"line":137,"column":12},"end":{"line":137,"column":12}}]},"5":{"line":183,"type":"if","locations":[{"start":{"line":183,"column":16},"end":{"line":183,"column":16}},{"start":{"line":183,"column":16},"end":{"line":183,"column":16}}]},"6":{"line":183,"type":"binary-expr","locations":[{"start":{"line":183,"column":20},"end":{"line":183,"column":46}},{"start":{"line":183,"column":51},"end":{"line":183,"column":109}}]},"7":{"line":210,"type":"if","locations":[{"start":{"line":210,"column":16},"end":{"line":210,"column":16}},{"start":{"line":210,"column":16},"end":{"line":210,"column":16}}]},"8":{"line":210,"type":"binary-expr","locations":[{"start":{"line":210,"column":20},"end":{"line":210,"column":46}},{"start":{"line":210,"column":51},"end":{"line":210,"column":113}}]},"9":{"line":243,"type":"cond-expr","locations":[{"start":{"line":243,"column":66},"end":{"line":243,"column":74}},{"start":{"line":243,"column":77},"end":{"line":243,"column":86}}]},"10":{"line":268,"type":"if","locations":[{"start":{"line":268,"column":8},"end":{"line":268,"column":8}},{"start":{"line":268,"column":8},"end":{"line":268,"column":8}}]},"11":{"line":271,"type":"if","locations":[{"start":{"line":271,"column":12},"end":{"line":271,"column":12}},{"start":{"line":271,"column":12},"end":{"line":271,"column":12}}]},"12":{"line":279,"type":"if","locations":[{"start":{"line":279,"column":15},"end":{"line":279,"column":15}},{"start":{"line":279,"column":15},"end":{"line":279,"column":15}}]},"13":{"line":280,"type":"if","locations":[{"start":{"line":280,"column":12},"end":{"line":280,"column":12}},{"start":{"line":280,"column":12},"end":{"line":280,"column":12}}]},"14":{"line":303,"type":"if","locations":[{"start":{"line":303,"column":8},"end":{"line":303,"column":8}},{"start":{"line":303,"column":8},"end":{"line":303,"column":8}}]},"15":{"line":303,"type":"binary-expr","locations":[{"start":{"line":303,"column":13},"end":{"line":303,"column":40}},{"start":{"line":303,"column":46},"end":{"line":303,"column":59}},{"start":{"line":303,"column":65},"end":{"line":303,"column":79}},{"start":{"line":304,"column":17},"end":{"line":304,"column":55}},{"start":{"line":304,"column":61},"end":{"line":304,"column":85}}]},"16":{"line":309,"type":"cond-expr","locations":[{"start":{"line":309,"column":62},"end":{"line":309,"column":99}},{"start":{"line":309,"column":102},"end":{"line":309,"column":104}}]},"17":{"line":310,"type":"if","locations":[{"start":{"line":310,"column":8},"end":{"line":310,"column":8}},{"start":{"line":310,"column":8},"end":{"line":310,"column":8}}]},"18":{"line":310,"type":"binary-expr","locations":[{"start":{"line":310,"column":13},"end":{"line":310,"column":50}},{"start":{"line":310,"column":56},"end":{"line":310,"column":79}},{"start":{"line":310,"column":85},"end":{"line":310,"column":103}},{"start":{"line":311,"column":17},"end":{"line":311,"column":42}},{"start":{"line":311,"column":48},"end":{"line":311,"column":87}}]},"19":{"line":324,"type":"if","locations":[{"start":{"line":324,"column":8},"end":{"line":324,"column":8}},{"start":{"line":324,"column":8},"end":{"line":324,"column":8}}]},"20":{"line":355,"type":"if","locations":[{"start":{"line":355,"column":8},"end":{"line":355,"column":8}},{"start":{"line":355,"column":8},"end":{"line":355,"column":8}}]},"21":{"line":360,"type":"if","locations":[{"start":{"line":360,"column":12},"end":{"line":360,"column":12}},{"start":{"line":360,"column":12},"end":{"line":360,"column":12}}]},"22":{"line":365,"type":"if","locations":[{"start":{"line":365,"column":20},"end":{"line":365,"column":20}},{"start":{"line":365,"column":20},"end":{"line":365,"column":20}}]},"23":{"line":405,"type":"if","locations":[{"start":{"line":405,"column":12},"end":{"line":405,"column":12}},{"start":{"line":405,"column":12},"end":{"line":405,"column":12}}]}},"code":["(function () { YUI.add('moodle-atto_multilang2-button', function (Y, NAME) {","","// This file is part of Moodle - http://moodle.org/","//","// Moodle is free software: you can redistribute it and/or modify","// it under the terms of the GNU General Public License as published by","// the Free Software Foundation, either version 3 of the License, or","// (at your option) any later version.","//","// Moodle is distributed in the hope that it will be useful,","// but WITHOUT ANY WARRANTY; without even the implied warranty of","// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the","// GNU General Public License for more details.","//","// You should have received a copy of the GNU General Public License","// along with Moodle. If not, see .","","/**"," * @package atto_multilang2"," * @copyright 2015 onwards Julen Pardo & Mondragon Unibertsitatea"," * @copyright 2017 onwards Iñaki Arenaza & Mondragon Unibertsitatea"," * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later"," */","","/**"," * @module moodle-atto_multilang2-button"," */","","/**"," * Atto text editor multilanguage plugin."," *"," * @namespace M.atto_multilang2"," * @class button"," * @extends M.editor_atto.EditorPlugin."," */","","var CLASSES = {"," TAG: 'filter-multilang-tag'"," },",""," LANG_WILDCARD = '%lang',"," CONTENT_WILDCARD = '%content',"," ATTR_LANGUAGES = 'languages',"," ATTR_CAPABILITY = 'capability',"," ATTR_HIGHLIGHT = 'highlight',"," ATTR_CSS = 'css',"," DEFAULT_LANGUAGE = '{\"en\":\"English (en)\"}',"," DEFAULT_CAPABILITY = true,"," DEFAULT_HIGHLIGHT = true,"," DEFAULT_CSS = 'outline: 1px dotted;' +"," 'padding: 0.1em;' +"," 'margin: 0em 0.1em;' +"," 'background-color: #ffffaa;',"," OPENING_SPAN = '',"," CLOSING_SPAN = '',"," TEMPLATES = {"," SPANNED: ' ' + OPENING_SPAN + '{mlang ' + LANG_WILDCARD + '}' + CLOSING_SPAN +"," CONTENT_WILDCARD + OPENING_SPAN + '{mlang}' + CLOSING_SPAN + ' ',"," NOT_SPANNED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'"," };","","","Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {",""," /**"," * If the {mlang} tags have to be highlighted or not. Received as parameter from lib.php."," *"," * @property _highlight"," * @type boolean"," * @private"," */"," _highlight: true,",""," initializer: function() {"," var hascapability = this.get(ATTR_CAPABILITY),"," toolbarItems,"," host,"," form;",""," if (hascapability) {"," toolbarItems = this._initializeToolbarItems();",""," this.addToolbarMenu({"," globalItemConfig: {"," callback: this._addTags"," },"," icon: 'icon',"," iconComponent: 'atto_multilang2',"," items: toolbarItems"," });",""," this._tagTemplate = TEMPLATES.NOT_SPANNED;",""," this._highlight = this.get(ATTR_HIGHLIGHT);"," if (this._highlight) {"," this._tagTemplate = TEMPLATES.SPANNED;",""," // Attach a submit listener to the form, so we can remove"," // the highlighting html before sending content to Moodle."," host = this.get('host');"," form = host.textarea.ancestor('form');"," if (form) {"," form.on('submit', this._cleanMlangTags, this);"," }",""," // Listen to every change of the text cursor in the text area, to see if"," // the cursor is placed within a multilang tag."," this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);",""," // Highlight the multilang tags once everything is loaded."," this.get('host').on('pluginsloaded', this._addHighlightingCss, this);"," this.get('host').on('pluginsloaded', this._highlightMlangTags, this);",""," // Hook into host.updateOriginal() and host.updateFromTextArea()"," // so we can add/remove highlighting when we switch to/from HTML view."," this._hookUpdateOriginal();"," this._hookUpdateFromTextArea();"," }"," }"," },",""," /**"," * Initializes the toolbar items, which will be the installed languages,"," * received as parameter."," *"," * @method _initializeToolbarItems"," * @private"," * @return {Array} installed language strings"," */"," _initializeToolbarItems: function() {"," var toolbarItems = [],"," languages,"," langCode;",""," languages = JSON.parse(this.get(ATTR_LANGUAGES));"," for (langCode in languages) {"," if (languages.hasOwnProperty(langCode)) {"," toolbarItems.push({"," text: languages[langCode],"," callbackArgs: langCode"," });"," }"," }",""," return toolbarItems;"," },",""," /**"," * Adds the CSS rules for the delimiters, received as parameter from lib.php."," *"," * @method _addHighlightingCss"," * @private"," */"," _addHighlightingCss: function() {"," var css = '.' + CLASSES.TAG + ' {' + this.get(ATTR_CSS) + '}',"," style;",""," style = document.createElement('style');"," style.type = 'text/css';"," style.innerHTML = css;",""," document.head.appendChild(style);"," },",""," /**"," * Hook the host.updateOriginal() method to allow us to remove the highlighting html when"," * switching to HTML view. As the HTML view plugin doesn't provide a hook or fire an event"," * to notify about the switch to HTML view, we need to hijack host.updateOriginal and look"," * for the caller. Once we've cleaned up the highlighting, we need to execute the original"," * host.updateOriginal() method."," * Inspired by https://stackoverflow.com/a/16580937"," *"," * @method _hookUpdateOriginal"," * @private"," */"," _hookUpdateOriginal: function() {"," var host = this.get('host'),"," multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _removeTags().",""," host.updateOriginal = (function() {"," var _updateOriginal = host.updateOriginal;"," return function() {"," if (multilangplugin._highlight && (this.updateOriginal.caller === host.plugins.html._showHTML)) {"," multilangplugin.editor.setHTML(multilangplugin._getHTMLwithCleanedTags(multilangplugin.editor.getHTML()));"," }"," return _updateOriginal.apply(this, arguments);"," };"," })();"," },",""," /**"," * Hook the host.updateFromTextAreal() method to allow us to re-add the highlighting"," * html when switching from HTML view. As the HTML view plugin doesn't provide a hook"," * or fire an event to notify about the switch from HTML view, we need to hijack"," * host.updateFromTextArea and look for the caller. Once we've executed the original"," * host.updateFromTextArea() method, we re-added the highlighting."," * Inspired by https://stackoverflow.com/a/16580937"," *"," * @method _hookUpdateFromTextArea"," * @private"," */"," _hookUpdateFromTextArea: function() {"," var host = this.get('host'),"," multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _highlightMlangTags().",""," host.updateFromTextArea = (function() {"," var _updateFromTextArea = host.updateFromTextArea;"," return function() {"," var ret = _updateFromTextArea.apply(this, arguments);"," if (multilangplugin._highlight && (this.updateFromTextArea.caller === host.plugins.html._showHTML)) {"," multilangplugin._highlightMlangTags();"," }"," return ret;"," };"," })();"," },",""," /**"," * Retrieves the selected text, wraps it with the multilang tags,"," * and replaces the selected text in the editor with with it."," *"," * If the 'highlight' setting is checked, the {mlang} will be wrapped between"," * the tags with the class for the CSS highlight; if not, they will not"," * be wrapped."," *"," * If there is no content selected, a \" \" will be inserted; otherwhise,"," * it's impossible to place the cursor inside the {mlang} tags."," *"," * @method _addTags"," * @param {EventFacade} event"," * @param {string} langCode the language code"," * @private"," */"," _addTags: function(event, langCode) {"," var selection,"," host = this.get('host'),"," taggedContent,"," content;",""," taggedContent = this._tagTemplate;",""," selection = this._getSelectionHTML();"," content = (host.getSelection().toString().length === 0) ? ' ' : selection;",""," taggedContent = taggedContent.replace(LANG_WILDCARD, langCode);"," taggedContent = taggedContent.replace(CONTENT_WILDCARD, content);",""," host.insertContentAtFocusPoint(taggedContent);",""," this.markUpdated();"," },",""," /**"," * Retrieves selected text with its HTML."," * Taken from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234"," *"," * @method _getSelectionHTML"," * @private"," * @return {string} selected text's html; empty if nothing selected"," */"," _getSelectionHTML: function() {"," var html = '',"," selection,"," container,"," index,"," length;",""," if (typeof window.getSelection !== 'undefined') {"," selection = window.getSelection();",""," if (selection.rangeCount) {"," container = document.createElement('div');"," for (index = 0, length = selection.rangeCount; index < length; ++index) {"," container.appendChild(selection.getRangeAt(index).cloneContents());"," }"," html = container.innerHTML;"," }",""," } else if (typeof document.selection !== 'undefined') {"," if (document.selection.type === 'Text') {"," html = document.selection.createRange().htmlText;"," }"," }",""," return html;"," },",""," /**"," * Listens to every change of the text cursor in the text area. If the"," * cursor is placed within a highlighted multilang tag, the whole tag is selected."," *"," * @method _checkSelectionChange"," * @private"," */"," _checkSelectionChange: function() {"," var host = this.get('host'),"," node = host.getSelectionParentNode(),"," parentNodeName,"," parentClass,"," selection;",""," // If the event fires without a parent node for the selection, ignore the whole thing."," if ((typeof node === 'undefined') || (node === null) || (node === false) ||"," (typeof node.parentNode === 'undefined') || (node.parentNode === null)) {"," return;"," }",""," parentNodeName = node.parentNode.nodeName;"," parentClass = node.parentNode.hasAttribute('class') ? node.parentNode.getAttribute('class') : '';"," if ((typeof parentNodeName !== 'undefined') && (parentNodeName !== null) && (parentClass !== '') &&"," (parentNodeName === 'SPAN') && (parentClass.indexOf(CLASSES.TAG) !== -1)) {"," selection = host.getSelectionFromNode(Y.one(node));"," host.setSelection(selection);"," }"," },",""," /**"," * When submitting the form, this function is invoked to clean the highlighting html code."," *"," * @method _cleanMlangTags"," * @private"," */"," _cleanMlangTags: function() {"," if (this._highlight) {"," this.editor.setHTML(this._getHTMLwithCleanedTags(this.editor.getHTML()));"," this.markUpdated();"," }"," },",""," /**"," * Adds the tags to the {mlang} tags if highlighting is enable."," *"," * Instead of taking the HTML directly from the textarea, we have to"," * retrieve it, first, without the tags that can be stored"," * in database, due to a bug in version 2015120501 that stores the"," * {mlang} tags in database, with the tags."," * More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8"," *"," * Every different {mlang} tag has to be replaced only once, otherwise,"," * nested s will be created in every repeated replacement. So, we"," * need to track which replacements have been made."," *"," * @method _highlightMlangTags"," * @private"," */"," _highlightMlangTags: function() {"," var editorHTML,"," regularExpression,"," mlangtags,"," mlangtag,"," index,"," highlightedmlangtag,"," replacementsmade = [],"," notreplacedyet;"," if (this._highlight) {"," editorHTML = this._getHTMLwithCleanedTags(this.editor.getHTML());",""," regularExpression = new RegExp('{mlang.*?}', 'g');"," mlangtags = editorHTML.match(regularExpression);"," if (mlangtags !== null) {"," for (index = 0; index < mlangtags.length; index++) {"," mlangtag = mlangtags[index];",""," notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;"," if (notreplacedyet) {"," replacementsmade.push(mlangtag);"," highlightedmlangtag = OPENING_SPAN + mlangtag + CLOSING_SPAN;"," regularExpression = new RegExp(mlangtag, 'g');"," editorHTML = editorHTML.replace(regularExpression, highlightedmlangtag);"," }"," }",""," this.editor.setHTML(editorHTML);"," }",""," this.markUpdated();"," }"," },",""," /**"," * This function returns the HTML passed in as parameter, but cleaning every multilang"," * tag around the {mlang} tags. This is necessary for decorating tags on"," * init, because it could happen that in database are stored the {mlang} tags with"," * their tags, due to a bug in version 2015120501."," * More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8"," * Implementation based on code from EditorClean._clearSpans()"," *"," * @method _getHTMLwithCleanedTags"," * @param {string} content The to be cleaned."," * @return {string} HTML in editor, without any around {mlang} tags."," */"," _getHTMLwithCleanedTags: function(content) {"," // This is better to run detached from the DOM, so the browser doesn't try to update on each change."," var holder = document.createElement('div'),"," spans,"," spansarr;",""," holder.innerHTML = content;"," spans = holder.getElementsByTagName('span');",""," // Since we will be removing elements from the list, we should copy it to an array, making it static."," spansarr = Array.prototype.slice.call(spans, 0);",""," spansarr.forEach(function(span) {"," if (span.className.indexOf(CLASSES.TAG) !== -1) {"," // Move each child (if they exist) to the parent in place of this span."," while (span.firstChild) {"," span.parentNode.insertBefore(span.firstChild, span);"," }",""," // Remove the now empty span."," span.parentNode.removeChild(span);"," }"," });",""," return holder.innerHTML;"," }","","}, {"," ATTRS: {"," /**"," * The list of installed languages."," *"," * @attribute languages"," * @type array"," * @default {\"en\":\"English (en)\"}"," */"," languages: DEFAULT_LANGUAGE,",""," /**"," * If the current user has the capability to use the plugin."," *"," * @attribute capability"," * @type boolean"," * @default true"," */"," capability: DEFAULT_CAPABILITY,",""," /**"," * If the {mlang} tags have to be highlighted or not."," *"," * @property highlight"," * @type boolean"," * @default true"," */"," highlight: DEFAULT_HIGHLIGHT,",""," /**"," * The CSS for delimiters."," *"," * @property css"," * @type string"," * @default DEFAULT_CSS"," */"," css: DEFAULT_CSS"," }","});","","","}, '@VERSION@', {\"requires\": [\"moodle-editor_atto-plugin\"]});","","}());"]};
}
var __cov_xlcucJyua6odXhLRctX6xg = __coverage__['build/moodle-atto_multilang2-button/moodle-atto_multilang2-button.js'];
-__cov_xlcucJyua6odXhLRctX6xg.s['1']++;YUI.add('moodle-atto_multilang2-button',function(Y,NAME){__cov_xlcucJyua6odXhLRctX6xg.f['1']++;__cov_xlcucJyua6odXhLRctX6xg.s['2']++;var CLASSES={TAG:'filter-multilang-tag'},LANG_WILDCARD='%lang',CONTENT_WILDCARD='%content',ATTR_LANGUAGES='languages',ATTR_CAPABILITY='capability',ATTR_HIGHLIGHT='highlight',ATTR_CSS='css',DEFAULT_LANGUAGE='{"en":"English (en)"}',DEFAULT_CAPABILITY=true,DEFAULT_HIGHLIGHT=true,DEFAULT_CSS='outline: 1px dotted;'+'padding: 0.1em;'+'margin: 0em 0.1em;'+'background-color: #ffffaa;',TEMPLATES={SPANED:' {mlang '+LANG_WILDCARD+'}'+CONTENT_WILDCARD+'{mlang} ',NOT_SPANED:'{mlang '+LANG_WILDCARD+'}'+CONTENT_WILDCARD+'{mlang}'},OPENING_SPAN='';__cov_xlcucJyua6odXhLRctX6xg.s['3']++;Y.namespace('M.atto_multilang2').Button=Y.Base.create('button',Y.M.editor_atto.EditorPlugin,[],{_highlight:true,initializer:function(){__cov_xlcucJyua6odXhLRctX6xg.f['2']++;__cov_xlcucJyua6odXhLRctX6xg.s['4']++;var hascapability=this.get(ATTR_CAPABILITY),toolbarItems=[];__cov_xlcucJyua6odXhLRctX6xg.s['5']++;if(hascapability){__cov_xlcucJyua6odXhLRctX6xg.b['1'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['6']++;toolbarItems=this._initializeToolbarItems();__cov_xlcucJyua6odXhLRctX6xg.s['7']++;this._highlight=this.get(ATTR_HIGHLIGHT);__cov_xlcucJyua6odXhLRctX6xg.s['8']++;this.addToolbarMenu({globalItemConfig:{callback:this._addTags},icon:'icon',iconComponent:'atto_multilang2',items:toolbarItems});__cov_xlcucJyua6odXhLRctX6xg.s['9']++;this.get('host').on('atto:selectionchanged',this._checkSelectionChange,this);__cov_xlcucJyua6odXhLRctX6xg.s['10']++;this._addDelimiterCss();__cov_xlcucJyua6odXhLRctX6xg.s['11']++;if(this._highlight){__cov_xlcucJyua6odXhLRctX6xg.b['2'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['12']++;this._decorateTagsOnInit();__cov_xlcucJyua6odXhLRctX6xg.s['13']++;this._setSubmitListeners();}else{__cov_xlcucJyua6odXhLRctX6xg.b['2'][1]++;}}else{__cov_xlcucJyua6odXhLRctX6xg.b['1'][1]++;}},_addDelimiterCss:function(){__cov_xlcucJyua6odXhLRctX6xg.f['3']++;__cov_xlcucJyua6odXhLRctX6xg.s['14']++;var css='.'+CLASSES.TAG+'{'+this.get(ATTR_CSS)+'}',style;__cov_xlcucJyua6odXhLRctX6xg.s['15']++;style=document.createElement('style');__cov_xlcucJyua6odXhLRctX6xg.s['16']++;style.type='text/css';__cov_xlcucJyua6odXhLRctX6xg.s['17']++;style.innerHTML=css;__cov_xlcucJyua6odXhLRctX6xg.s['18']++;document.head.appendChild(style);},_initializeToolbarItems:function(){__cov_xlcucJyua6odXhLRctX6xg.f['4']++;__cov_xlcucJyua6odXhLRctX6xg.s['19']++;var toolbarItems=[],languages,langCode;__cov_xlcucJyua6odXhLRctX6xg.s['20']++;languages=JSON.parse(this.get(ATTR_LANGUAGES));__cov_xlcucJyua6odXhLRctX6xg.s['21']++;for(langCode in languages){__cov_xlcucJyua6odXhLRctX6xg.s['22']++;if(languages.hasOwnProperty(langCode)){__cov_xlcucJyua6odXhLRctX6xg.b['3'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['23']++;toolbarItems.push({text:languages[langCode],callbackArgs:langCode});}else{__cov_xlcucJyua6odXhLRctX6xg.b['3'][1]++;}}__cov_xlcucJyua6odXhLRctX6xg.s['24']++;return toolbarItems;},_addTags:function(event,langCode){__cov_xlcucJyua6odXhLRctX6xg.f['5']++;__cov_xlcucJyua6odXhLRctX6xg.s['25']++;var selection,host=this.get('host'),taggedContent,content;__cov_xlcucJyua6odXhLRctX6xg.s['26']++;taggedContent=this._highlight?(__cov_xlcucJyua6odXhLRctX6xg.b['4'][0]++,TEMPLATES.SPANED):(__cov_xlcucJyua6odXhLRctX6xg.b['4'][1]++,TEMPLATES.NOT_SPANED);__cov_xlcucJyua6odXhLRctX6xg.s['27']++;selection=this._getSelectionHTML();__cov_xlcucJyua6odXhLRctX6xg.s['28']++;content=host.getSelection().toString().length===0?(__cov_xlcucJyua6odXhLRctX6xg.b['5'][0]++,' '):(__cov_xlcucJyua6odXhLRctX6xg.b['5'][1]++,selection);__cov_xlcucJyua6odXhLRctX6xg.s['29']++;taggedContent=taggedContent.replace(LANG_WILDCARD,langCode);__cov_xlcucJyua6odXhLRctX6xg.s['30']++;taggedContent=taggedContent.replace(CONTENT_WILDCARD,content);__cov_xlcucJyua6odXhLRctX6xg.s['31']++;host.insertContentAtFocusPoint(taggedContent);__cov_xlcucJyua6odXhLRctX6xg.s['32']++;this.markUpdated();},_getSelectionHTML:function(){__cov_xlcucJyua6odXhLRctX6xg.f['6']++;__cov_xlcucJyua6odXhLRctX6xg.s['33']++;var html='',selection,container,index,lenght;__cov_xlcucJyua6odXhLRctX6xg.s['34']++;if(typeof window.getSelection!=='undefined'){__cov_xlcucJyua6odXhLRctX6xg.b['6'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['35']++;selection=window.getSelection();__cov_xlcucJyua6odXhLRctX6xg.s['36']++;if(selection.rangeCount){__cov_xlcucJyua6odXhLRctX6xg.b['7'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['37']++;container=document.createElement('div');__cov_xlcucJyua6odXhLRctX6xg.s['38']++;for(index=0,lenght=selection.rangeCount;index-1;__cov_xlcucJyua6odXhLRctX6xg.s['47']++;isLangTag=nodeValue.match(/\{mlang/g).length===1;__cov_xlcucJyua6odXhLRctX6xg.s['48']++;if((__cov_xlcucJyua6odXhLRctX6xg.b['11'][0]++,isTextNode)&&(__cov_xlcucJyua6odXhLRctX6xg.b['11'][1]++,isLangTag)){__cov_xlcucJyua6odXhLRctX6xg.b['10'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['49']++;host.setSelection(host.getSelectionFromNode(Y.one(node)));}else{__cov_xlcucJyua6odXhLRctX6xg.b['10'][1]++;}},_setSubmitListeners:function(){__cov_xlcucJyua6odXhLRctX6xg.f['8']++;__cov_xlcucJyua6odXhLRctX6xg.s['50']++;var submitButtons=Y.all('input[type=submit]');__cov_xlcucJyua6odXhLRctX6xg.s['51']++;submitButtons.each(this._addListenerToSubmitButtons,this);},_addListenerToSubmitButtons:function(buttonNode){__cov_xlcucJyua6odXhLRctX6xg.f['9']++;__cov_xlcucJyua6odXhLRctX6xg.s['52']++;var buttonObject,className,parentFormClassName,notCancelButton,notSearchButton;__cov_xlcucJyua6odXhLRctX6xg.s['53']++;buttonObject=document.getElementById(buttonNode.get('id'));__cov_xlcucJyua6odXhLRctX6xg.s['54']++;if(buttonObject!==null){__cov_xlcucJyua6odXhLRctX6xg.b['12'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['55']++;className=buttonObject.className;__cov_xlcucJyua6odXhLRctX6xg.s['56']++;parentFormClassName=buttonObject.form.className;__cov_xlcucJyua6odXhLRctX6xg.s['57']++;notCancelButton=className.match(/btn-cancel/g)===null;__cov_xlcucJyua6odXhLRctX6xg.s['58']++;notSearchButton=parentFormClassName.match(/mform/g).length>0;__cov_xlcucJyua6odXhLRctX6xg.s['59']++;if((__cov_xlcucJyua6odXhLRctX6xg.b['14'][0]++,notCancelButton)&&(__cov_xlcucJyua6odXhLRctX6xg.b['14'][1]++,notSearchButton)){__cov_xlcucJyua6odXhLRctX6xg.b['13'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['60']++;buttonNode.on('click',this._cleanTagsOnSubmit,this,buttonNode);}else{__cov_xlcucJyua6odXhLRctX6xg.b['13'][1]++;}}else{__cov_xlcucJyua6odXhLRctX6xg.b['12'][1]++;}},_cleanTagsOnSubmit:function(event,submitButton){__cov_xlcucJyua6odXhLRctX6xg.f['10']++;__cov_xlcucJyua6odXhLRctX6xg.s['61']++;event.preventDefault();__cov_xlcucJyua6odXhLRctX6xg.s['62']++;this._cleanTagsWithNoYuiId();__cov_xlcucJyua6odXhLRctX6xg.s['63']++;this._cleanTagsWithYuiId();__cov_xlcucJyua6odXhLRctX6xg.s['64']++;submitButton.detach('click',this._cleanTagsOnSubmit);__cov_xlcucJyua6odXhLRctX6xg.s['65']++;submitButton.simulate('click');},_cleanTagsWithNoYuiId:function(){__cov_xlcucJyua6odXhLRctX6xg.f['11']++;__cov_xlcucJyua6odXhLRctX6xg.s['66']++;var textareas=Y.all('.editor_atto_content'),textarea,textareaIndex,innerHTML,spanedmlangtags,spanedmlangtag,index,cleanmlangtag,regularExpression;__cov_xlcucJyua6odXhLRctX6xg.s['67']++;regularExpression=new RegExp(OPENING_SPAN+'.*?'+'','g');__cov_xlcucJyua6odXhLRctX6xg.s['68']++;if(!textareas instanceof Array){__cov_xlcucJyua6odXhLRctX6xg.b['15'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['69']++;textarea=textareas;__cov_xlcucJyua6odXhLRctX6xg.s['70']++;textareas=[];__cov_xlcucJyua6odXhLRctX6xg.s['71']++;textareas[0]=textarea;}else{__cov_xlcucJyua6odXhLRctX6xg.b['15'][1]++;}__cov_xlcucJyua6odXhLRctX6xg.s['72']++;for(textareaIndex=0;textareaIndex','');__cov_xlcucJyua6odXhLRctX6xg.s['83']++;innerHTML=innerHTML.replace(spanedmlangtag,cleanmlangtag);}__cov_xlcucJyua6odXhLRctX6xg.s['84']++;textarea.set('innerHTML',innerHTML);}__cov_xlcucJyua6odXhLRctX6xg.s['85']++;this.markUpdated();},_cleanTagsWithYuiId:function(){__cov_xlcucJyua6odXhLRctX6xg.f['12']++;__cov_xlcucJyua6odXhLRctX6xg.s['86']++;var textareas=Y.all('.editor_atto_content'),textarea,textareaIndex,innerHTML,spanedmlangtag,index,cleanmlangtag,regularExpression,openingspanwithyui,spanedmlangtagsdwithyui,mlangtag;__cov_xlcucJyua6odXhLRctX6xg.s['87']++;openingspanwithyui=OPENING_SPAN.replace('','g');__cov_xlcucJyua6odXhLRctX6xg.s['89']++;if(!textareas instanceof Array){__cov_xlcucJyua6odXhLRctX6xg.b['17'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['90']++;textarea=textareas;__cov_xlcucJyua6odXhLRctX6xg.s['91']++;textareas=[];__cov_xlcucJyua6odXhLRctX6xg.s['92']++;textareas[0]=textarea;}else{__cov_xlcucJyua6odXhLRctX6xg.b['17'][1]++;}__cov_xlcucJyua6odXhLRctX6xg.s['93']++;for(textareaIndex=0;textareaIndex','');__cov_xlcucJyua6odXhLRctX6xg.s['105']++;innerHTML=innerHTML.replace(spanedmlangtag,cleanmlangtag);}__cov_xlcucJyua6odXhLRctX6xg.s['106']++;textarea.set('innerHTML',innerHTML);__cov_xlcucJyua6odXhLRctX6xg.s['107']++;this.markUpdated();}},_decorateTagsOnInit:function(){__cov_xlcucJyua6odXhLRctX6xg.f['13']++;__cov_xlcucJyua6odXhLRctX6xg.s['108']++;var textarea=Y.all('.editor_atto_content'),innerHTML,regularExpression,mlangtags,mlangtag,index,decoratedmlangtag,replacementsmade=[],notreplacedyet;__cov_xlcucJyua6odXhLRctX6xg.s['109']++;innerHTML=this._getHTMLwithCleanedTags();__cov_xlcucJyua6odXhLRctX6xg.s['110']++;regularExpression=new RegExp('{mlang.*?}','g');__cov_xlcucJyua6odXhLRctX6xg.s['111']++;mlangtags=innerHTML.match(regularExpression);__cov_xlcucJyua6odXhLRctX6xg.s['112']++;if(mlangtags!==null){__cov_xlcucJyua6odXhLRctX6xg.b['19'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['113']++;for(index=0;index';__cov_xlcucJyua6odXhLRctX6xg.s['119']++;regularExpression=new RegExp(mlangtag,'g');__cov_xlcucJyua6odXhLRctX6xg.s['120']++;innerHTML=innerHTML.replace(regularExpression,decoratedmlangtag);}else{__cov_xlcucJyua6odXhLRctX6xg.b['20'][1]++;}}__cov_xlcucJyua6odXhLRctX6xg.s['121']++;textarea.set('innerHTML',innerHTML);}else{__cov_xlcucJyua6odXhLRctX6xg.b['19'][1]++;}},_getHTMLwithCleanedTags:function(){__cov_xlcucJyua6odXhLRctX6xg.f['14']++;__cov_xlcucJyua6odXhLRctX6xg.s['122']++;var host=this.get('host'),innerHTML=host.getCleanHTML(),regexString,regularExpression,spanedmlangtags,spanedmlangtag,cleanmlangtag,index;__cov_xlcucJyua6odXhLRctX6xg.s['123']++;regexString=OPENING_SPAN+'.*?'+'';__cov_xlcucJyua6odXhLRctX6xg.s['124']++;regularExpression=new RegExp(regexString,'g');__cov_xlcucJyua6odXhLRctX6xg.s['125']++;spanedmlangtags=innerHTML.match(regularExpression);__cov_xlcucJyua6odXhLRctX6xg.s['126']++;if(spanedmlangtags!==null){__cov_xlcucJyua6odXhLRctX6xg.b['21'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['127']++;for(index=0;index','');__cov_xlcucJyua6odXhLRctX6xg.s['131']++;innerHTML=innerHTML.replace(spanedmlangtag,cleanmlangtag);}}else{__cov_xlcucJyua6odXhLRctX6xg.b['21'][1]++;}__cov_xlcucJyua6odXhLRctX6xg.s['132']++;return innerHTML;}},{ATTRS:{languages:DEFAULT_LANGUAGE,capability:DEFAULT_CAPABILITY,highlight:DEFAULT_HIGHLIGHT,css:DEFAULT_CSS}});},'@VERSION@',{'requires':['moodle-editor_atto-plugin']});
+__cov_xlcucJyua6odXhLRctX6xg.s['1']++;YUI.add('moodle-atto_multilang2-button',function(Y,NAME){__cov_xlcucJyua6odXhLRctX6xg.f['1']++;__cov_xlcucJyua6odXhLRctX6xg.s['2']++;var CLASSES={TAG:'filter-multilang-tag'},LANG_WILDCARD='%lang',CONTENT_WILDCARD='%content',ATTR_LANGUAGES='languages',ATTR_CAPABILITY='capability',ATTR_HIGHLIGHT='highlight',ATTR_CSS='css',DEFAULT_LANGUAGE='{"en":"English (en)"}',DEFAULT_CAPABILITY=true,DEFAULT_HIGHLIGHT=true,DEFAULT_CSS='outline: 1px dotted;'+'padding: 0.1em;'+'margin: 0em 0.1em;'+'background-color: #ffffaa;',OPENING_SPAN='',CLOSING_SPAN='',TEMPLATES={SPANNED:' '+OPENING_SPAN+'{mlang '+LANG_WILDCARD+'}'+CLOSING_SPAN+CONTENT_WILDCARD+OPENING_SPAN+'{mlang}'+CLOSING_SPAN+' ',NOT_SPANNED:'{mlang '+LANG_WILDCARD+'}'+CONTENT_WILDCARD+'{mlang}'};__cov_xlcucJyua6odXhLRctX6xg.s['3']++;Y.namespace('M.atto_multilang2').Button=Y.Base.create('button',Y.M.editor_atto.EditorPlugin,[],{_highlight:true,initializer:function(){__cov_xlcucJyua6odXhLRctX6xg.f['2']++;__cov_xlcucJyua6odXhLRctX6xg.s['4']++;var hascapability=this.get(ATTR_CAPABILITY),toolbarItems,host,form;__cov_xlcucJyua6odXhLRctX6xg.s['5']++;if(hascapability){__cov_xlcucJyua6odXhLRctX6xg.b['1'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['6']++;toolbarItems=this._initializeToolbarItems();__cov_xlcucJyua6odXhLRctX6xg.s['7']++;this.addToolbarMenu({globalItemConfig:{callback:this._addTags},icon:'icon',iconComponent:'atto_multilang2',items:toolbarItems});__cov_xlcucJyua6odXhLRctX6xg.s['8']++;this._tagTemplate=TEMPLATES.NOT_SPANNED;__cov_xlcucJyua6odXhLRctX6xg.s['9']++;this._highlight=this.get(ATTR_HIGHLIGHT);__cov_xlcucJyua6odXhLRctX6xg.s['10']++;if(this._highlight){__cov_xlcucJyua6odXhLRctX6xg.b['2'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['11']++;this._tagTemplate=TEMPLATES.SPANNED;__cov_xlcucJyua6odXhLRctX6xg.s['12']++;host=this.get('host');__cov_xlcucJyua6odXhLRctX6xg.s['13']++;form=host.textarea.ancestor('form');__cov_xlcucJyua6odXhLRctX6xg.s['14']++;if(form){__cov_xlcucJyua6odXhLRctX6xg.b['3'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['15']++;form.on('submit',this._cleanMlangTags,this);}else{__cov_xlcucJyua6odXhLRctX6xg.b['3'][1]++;}__cov_xlcucJyua6odXhLRctX6xg.s['16']++;this.get('host').on('atto:selectionchanged',this._checkSelectionChange,this);__cov_xlcucJyua6odXhLRctX6xg.s['17']++;this.get('host').on('pluginsloaded',this._addHighlightingCss,this);__cov_xlcucJyua6odXhLRctX6xg.s['18']++;this.get('host').on('pluginsloaded',this._highlightMlangTags,this);__cov_xlcucJyua6odXhLRctX6xg.s['19']++;this._hookUpdateOriginal();__cov_xlcucJyua6odXhLRctX6xg.s['20']++;this._hookUpdateFromTextArea();}else{__cov_xlcucJyua6odXhLRctX6xg.b['2'][1]++;}}else{__cov_xlcucJyua6odXhLRctX6xg.b['1'][1]++;}},_initializeToolbarItems:function(){__cov_xlcucJyua6odXhLRctX6xg.f['3']++;__cov_xlcucJyua6odXhLRctX6xg.s['21']++;var toolbarItems=[],languages,langCode;__cov_xlcucJyua6odXhLRctX6xg.s['22']++;languages=JSON.parse(this.get(ATTR_LANGUAGES));__cov_xlcucJyua6odXhLRctX6xg.s['23']++;for(langCode in languages){__cov_xlcucJyua6odXhLRctX6xg.s['24']++;if(languages.hasOwnProperty(langCode)){__cov_xlcucJyua6odXhLRctX6xg.b['4'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['25']++;toolbarItems.push({text:languages[langCode],callbackArgs:langCode});}else{__cov_xlcucJyua6odXhLRctX6xg.b['4'][1]++;}}__cov_xlcucJyua6odXhLRctX6xg.s['26']++;return toolbarItems;},_addHighlightingCss:function(){__cov_xlcucJyua6odXhLRctX6xg.f['4']++;__cov_xlcucJyua6odXhLRctX6xg.s['27']++;var css='.'+CLASSES.TAG+' {'+this.get(ATTR_CSS)+'}',style;__cov_xlcucJyua6odXhLRctX6xg.s['28']++;style=document.createElement('style');__cov_xlcucJyua6odXhLRctX6xg.s['29']++;style.type='text/css';__cov_xlcucJyua6odXhLRctX6xg.s['30']++;style.innerHTML=css;__cov_xlcucJyua6odXhLRctX6xg.s['31']++;document.head.appendChild(style);},_hookUpdateOriginal:function(){__cov_xlcucJyua6odXhLRctX6xg.f['5']++;__cov_xlcucJyua6odXhLRctX6xg.s['32']++;var host=this.get('host'),multilangplugin=this;__cov_xlcucJyua6odXhLRctX6xg.s['33']++;host.updateOriginal=function(){__cov_xlcucJyua6odXhLRctX6xg.f['6']++;__cov_xlcucJyua6odXhLRctX6xg.s['34']++;var _updateOriginal=host.updateOriginal;__cov_xlcucJyua6odXhLRctX6xg.s['35']++;return function(){__cov_xlcucJyua6odXhLRctX6xg.f['7']++;__cov_xlcucJyua6odXhLRctX6xg.s['36']++;if((__cov_xlcucJyua6odXhLRctX6xg.b['6'][0]++,multilangplugin._highlight)&&(__cov_xlcucJyua6odXhLRctX6xg.b['6'][1]++,this.updateOriginal.caller===host.plugins.html._showHTML)){__cov_xlcucJyua6odXhLRctX6xg.b['5'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['37']++;multilangplugin.editor.setHTML(multilangplugin._getHTMLwithCleanedTags(multilangplugin.editor.getHTML()));}else{__cov_xlcucJyua6odXhLRctX6xg.b['5'][1]++;}__cov_xlcucJyua6odXhLRctX6xg.s['38']++;return _updateOriginal.apply(this,arguments);};}();},_hookUpdateFromTextArea:function(){__cov_xlcucJyua6odXhLRctX6xg.f['8']++;__cov_xlcucJyua6odXhLRctX6xg.s['39']++;var host=this.get('host'),multilangplugin=this;__cov_xlcucJyua6odXhLRctX6xg.s['40']++;host.updateFromTextArea=function(){__cov_xlcucJyua6odXhLRctX6xg.f['9']++;__cov_xlcucJyua6odXhLRctX6xg.s['41']++;var _updateFromTextArea=host.updateFromTextArea;__cov_xlcucJyua6odXhLRctX6xg.s['42']++;return function(){__cov_xlcucJyua6odXhLRctX6xg.f['10']++;__cov_xlcucJyua6odXhLRctX6xg.s['43']++;var ret=_updateFromTextArea.apply(this,arguments);__cov_xlcucJyua6odXhLRctX6xg.s['44']++;if((__cov_xlcucJyua6odXhLRctX6xg.b['8'][0]++,multilangplugin._highlight)&&(__cov_xlcucJyua6odXhLRctX6xg.b['8'][1]++,this.updateFromTextArea.caller===host.plugins.html._showHTML)){__cov_xlcucJyua6odXhLRctX6xg.b['7'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['45']++;multilangplugin._highlightMlangTags();}else{__cov_xlcucJyua6odXhLRctX6xg.b['7'][1]++;}__cov_xlcucJyua6odXhLRctX6xg.s['46']++;return ret;};}();},_addTags:function(event,langCode){__cov_xlcucJyua6odXhLRctX6xg.f['11']++;__cov_xlcucJyua6odXhLRctX6xg.s['47']++;var selection,host=this.get('host'),taggedContent,content;__cov_xlcucJyua6odXhLRctX6xg.s['48']++;taggedContent=this._tagTemplate;__cov_xlcucJyua6odXhLRctX6xg.s['49']++;selection=this._getSelectionHTML();__cov_xlcucJyua6odXhLRctX6xg.s['50']++;content=host.getSelection().toString().length===0?(__cov_xlcucJyua6odXhLRctX6xg.b['9'][0]++,' '):(__cov_xlcucJyua6odXhLRctX6xg.b['9'][1]++,selection);__cov_xlcucJyua6odXhLRctX6xg.s['51']++;taggedContent=taggedContent.replace(LANG_WILDCARD,langCode);__cov_xlcucJyua6odXhLRctX6xg.s['52']++;taggedContent=taggedContent.replace(CONTENT_WILDCARD,content);__cov_xlcucJyua6odXhLRctX6xg.s['53']++;host.insertContentAtFocusPoint(taggedContent);__cov_xlcucJyua6odXhLRctX6xg.s['54']++;this.markUpdated();},_getSelectionHTML:function(){__cov_xlcucJyua6odXhLRctX6xg.f['12']++;__cov_xlcucJyua6odXhLRctX6xg.s['55']++;var html='',selection,container,index,length;__cov_xlcucJyua6odXhLRctX6xg.s['56']++;if(typeof window.getSelection!=='undefined'){__cov_xlcucJyua6odXhLRctX6xg.b['10'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['57']++;selection=window.getSelection();__cov_xlcucJyua6odXhLRctX6xg.s['58']++;if(selection.rangeCount){__cov_xlcucJyua6odXhLRctX6xg.b['11'][0]++;__cov_xlcucJyua6odXhLRctX6xg.s['59']++;container=document.createElement('div');__cov_xlcucJyua6odXhLRctX6xg.s['60']++;for(index=0,length=selection.rangeCount;index',
+ CLOSING_SPAN = '',
TEMPLATES = {
- SPANED: ' {mlang ' + LANG_WILDCARD + '}' +
- CONTENT_WILDCARD +
- '{mlang} ',
+ SPANNED: ' ' + OPENING_SPAN + '{mlang ' + LANG_WILDCARD + '}' + CLOSING_SPAN +
+ CONTENT_WILDCARD + OPENING_SPAN + '{mlang}' + CLOSING_SPAN + ' ',
+ NOT_SPANNED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'
+ };
- NOT_SPANED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'
- },
- OPENING_SPAN = '';
-
-/**
- * Atto text editor multilanguage plugin.
- *
- * @namespace M.atto_multilang2
- * @class button
- * @extends M.editor_atto.EditorPlugin
- */
Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
@@ -72,11 +73,12 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
initializer: function() {
var hascapability = this.get(ATTR_CAPABILITY),
- toolbarItems = [];
+ toolbarItems,
+ host,
+ form;
if (hascapability) {
toolbarItems = this._initializeToolbarItems();
- this._highlight = this.get(ATTR_HIGHLIGHT);
this.addToolbarMenu({
globalItemConfig: {
@@ -87,32 +89,34 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
items: toolbarItems
});
- this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);
-
- this._addDelimiterCss();
+ this._tagTemplate = TEMPLATES.NOT_SPANNED;
+ this._highlight = this.get(ATTR_HIGHLIGHT);
if (this._highlight) {
- this._decorateTagsOnInit();
- this._setSubmitListeners();
- }
- }
- },
+ this._tagTemplate = TEMPLATES.SPANNED;
+
+ // Attach a submit listener to the form, so we can remove
+ // the highlighting html before sending content to Moodle.
+ host = this.get('host');
+ form = host.textarea.ancestor('form');
+ if (form) {
+ form.on('submit', this._cleanMlangTags, this);
+ }
- /**
- * Adds the CSS rules for the delimiters, received as parameter from lib.php.
- *
- * @method _addDelimiterCss
- * @private
- */
- _addDelimiterCss: function() {
- var css = '.' + CLASSES.TAG + '{' + this.get(ATTR_CSS) + '}',
- style;
+ // Listen to every change of the text cursor in the text area, to see if
+ // the cursor is placed within a multilang tag.
+ this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);
- style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = css;
+ // Highlight the multilang tags once everything is loaded.
+ this.get('host').on('pluginsloaded', this._addHighlightingCss, this);
+ this.get('host').on('pluginsloaded', this._highlightMlangTags, this);
- document.head.appendChild(style);
+ // Hook into host.updateOriginal() and host.updateFromTextArea()
+ // so we can add/remove highlighting when we switch to/from HTML view.
+ this._hookUpdateOriginal();
+ this._hookUpdateFromTextArea();
+ }
+ }
},
/**
@@ -129,7 +133,6 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
langCode;
languages = JSON.parse(this.get(ATTR_LANGUAGES));
-
for (langCode in languages) {
if (languages.hasOwnProperty(langCode)) {
toolbarItems.push({
@@ -142,6 +145,76 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
return toolbarItems;
},
+ /**
+ * Adds the CSS rules for the delimiters, received as parameter from lib.php.
+ *
+ * @method _addHighlightingCss
+ * @private
+ */
+ _addHighlightingCss: function() {
+ var css = '.' + CLASSES.TAG + ' {' + this.get(ATTR_CSS) + '}',
+ style;
+
+ style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = css;
+
+ document.head.appendChild(style);
+ },
+
+ /**
+ * Hook the host.updateOriginal() method to allow us to remove the highlighting html when
+ * switching to HTML view. As the HTML view plugin doesn't provide a hook or fire an event
+ * to notify about the switch to HTML view, we need to hijack host.updateOriginal and look
+ * for the caller. Once we've cleaned up the highlighting, we need to execute the original
+ * host.updateOriginal() method.
+ * Inspired by https://stackoverflow.com/a/16580937
+ *
+ * @method _hookUpdateOriginal
+ * @private
+ */
+ _hookUpdateOriginal: function() {
+ var host = this.get('host'),
+ multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _removeTags().
+
+ host.updateOriginal = (function() {
+ var _updateOriginal = host.updateOriginal;
+ return function() {
+ if (multilangplugin._highlight && (this.updateOriginal.caller === host.plugins.html._showHTML)) {
+ multilangplugin.editor.setHTML(multilangplugin._getHTMLwithCleanedTags(multilangplugin.editor.getHTML()));
+ }
+ return _updateOriginal.apply(this, arguments);
+ };
+ })();
+ },
+
+ /**
+ * Hook the host.updateFromTextAreal() method to allow us to re-add the highlighting
+ * html when switching from HTML view. As the HTML view plugin doesn't provide a hook
+ * or fire an event to notify about the switch from HTML view, we need to hijack
+ * host.updateFromTextArea and look for the caller. Once we've executed the original
+ * host.updateFromTextArea() method, we re-added the highlighting.
+ * Inspired by https://stackoverflow.com/a/16580937
+ *
+ * @method _hookUpdateFromTextArea
+ * @private
+ */
+ _hookUpdateFromTextArea: function() {
+ var host = this.get('host'),
+ multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _highlightMlangTags().
+
+ host.updateFromTextArea = (function() {
+ var _updateFromTextArea = host.updateFromTextArea;
+ return function() {
+ var ret = _updateFromTextArea.apply(this, arguments);
+ if (multilangplugin._highlight && (this.updateFromTextArea.caller === host.plugins.html._showHTML)) {
+ multilangplugin._highlightMlangTags();
+ }
+ return ret;
+ };
+ })();
+ },
+
/**
* Retrieves the selected text, wraps it with the multilang tags,
* and replaces the selected text in the editor with with it.
@@ -164,7 +237,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
taggedContent,
content;
- taggedContent = (this._highlight) ? TEMPLATES.SPANED : TEMPLATES.NOT_SPANED;
+ taggedContent = this._tagTemplate;
selection = this._getSelectionHTML();
content = (host.getSelection().toString().length === 0) ? ' ' : selection;
@@ -179,7 +252,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
/**
* Retrieves selected text with its HTML.
- * Took from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234
+ * Taken from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234
*
* @method _getSelectionHTML
* @private
@@ -190,14 +263,14 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
selection,
container,
index,
- lenght;
+ length;
if (typeof window.getSelection !== 'undefined') {
selection = window.getSelection();
if (selection.rangeCount) {
container = document.createElement('div');
- for (index = 0, lenght = selection.rangeCount; index < lenght; ++index) {
+ for (index = 0, length = selection.rangeCount; index < length; ++index) {
container.appendChild(selection.getRangeAt(index).cloneContents());
}
html = container.innerHTML;
@@ -214,7 +287,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
/**
* Listens to every change of the text cursor in the text area. If the
- * cursor is placed within a multilang tag, the whole tag is selected.
+ * cursor is placed within a highlighted multilang tag, the whole tag is selected.
*
* @method _checkSelectionChange
* @private
@@ -222,224 +295,40 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
_checkSelectionChange: function() {
var host = this.get('host'),
node = host.getSelectionParentNode(),
- nodeValue = Y.one(node).get('text'),
- isTextNode,
- isLangTag;
-
- isTextNode = Y.one(node).toString().indexOf('#text') > - 1;
- isLangTag = (nodeValue.match(/\{mlang/g).length === 1);
-
- if (isTextNode && isLangTag) {
- host.setSelection(host.getSelectionFromNode(Y.one(node)));
+ parentNodeName,
+ parentClass,
+ selection;
+
+ // If the event fires without a parent node for the selection, ignore the whole thing.
+ if ((typeof node === 'undefined') || (node === null) || (node === false) ||
+ (typeof node.parentNode === 'undefined') || (node.parentNode === null)) {
+ return;
}
- },
-
- /**
- * Retrieves the inputs of type submit, and, for each element, calls the function
- * that sets the submit listener. Is not made in this function because there is
- * not any (apparent) way to access class scope from YUI closure.
- *
- * @method _setSubmitListeners
- * @private
- */
- _setSubmitListeners: function() {
- var submitButtons = Y.all('input[type=submit]');
-
- submitButtons.each(this._addListenerToSubmitButtons, this);
- },
- /**
- * Adds the clean tags submit listener of each input[type="submit"], but only if
- * it's not 'cancel' type, and if its parent form is of 'mform' class, because there
- * may be any other submit type (such us administrator's search button).
- *
- * @method _addListenerToSubmitButtons
- * @param {Node} buttonNode
- * @private
- */
- _addListenerToSubmitButtons: function(buttonNode) {
- var buttonObject,
- className,
- parentFormClassName,
- notCancelButton,
- notSearchButton;
-
- buttonObject = document.getElementById(buttonNode.get('id'));
-
- if (buttonObject !== null) {
- className = buttonObject.className;
- parentFormClassName = buttonObject.form.className;
-
- notCancelButton = className.match(/btn-cancel/g) === null;
- notSearchButton = parentFormClassName.match(/mform/g).length > 0;
-
- if (notCancelButton && notSearchButton) {
- buttonNode.on('click', this._cleanTagsOnSubmit, this, buttonNode);
- }
+ parentNodeName = node.parentNode.nodeName;
+ parentClass = node.parentNode.hasAttribute('class') ? node.parentNode.getAttribute('class') : '';
+ if ((typeof parentNodeName !== 'undefined') && (parentNodeName !== null) && (parentClass !== '') &&
+ (parentNodeName === 'SPAN') && (parentClass.indexOf(CLASSES.TAG) !== -1)) {
+ selection = host.getSelectionFromNode(Y.one(node));
+ host.setSelection(selection);
}
},
/**
- * When submit button clicked, this function is invoked. It has to stop the submission,
- * in order to process the textarea to clean the tags.
- *
- * Once the textarea is cleaned, detaches this submit listener, i.e., it sets as default,
- * an then simulates the click, to submit the form.
- *
- * @method _cleanTagsOnSubmit
- * @param {EventFacade} event
- * @param {Node} submitButton
- * @private
- */
- _cleanTagsOnSubmit: function(event, submitButton) {
- event.preventDefault();
-
- this._cleanTagsWithNoYuiId();
- this._cleanTagsWithYuiId();
-
- submitButton.detach('click', this._cleanTagsOnSubmit);
- submitButton.simulate('click');
- },
-
- /**
- * Cleans the tags around the {mlang} tags when the form is submitted,
- * that do not have "id" attribute.
- * The cleanup with "id" attribute and without it is made separately, to avoid an evil
- * regular expression.
- *
- * There may be more than one atto editor textarea in the page. So, we have to retrieve
- * the textareas by the class name. If there is only one, the object will be only the
- * reference, but, if there are more, we will have an array. So, the easiest way is to
- * check if what we have is an array, and if it not, create it manually, and iterate it
- * later.
- *
- * issue #15: the textareas are now retrieved passing to YUI selector the whole element,
- * instead of the id string, due to problems with special characters.
- * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217
- *
- * @method _cleanTagsWithNoYuiId
- * @private
- */
- _cleanTagsWithNoYuiId: function() {
- var textareas = Y.all('.editor_atto_content'),
- textarea,
- textareaIndex,
- innerHTML,
- spanedmlangtags,
- spanedmlangtag,
- index,
- cleanmlangtag,
- regularExpression;
-
- regularExpression = new RegExp(OPENING_SPAN + '.*?' + '', 'g');
-
- if (!textareas instanceof Array) {
- textarea = textareas;
- textareas = [];
- textareas[0] = textarea;
- }
-
- for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {
- textarea = textareas._nodes[textareaIndex].id;
- textarea = Y.one(document.getElementById(textarea));
-
- innerHTML = textarea.get('innerHTML');
-
- spanedmlangtags = innerHTML.match(regularExpression);
-
- if (spanedmlangtags === null) {
- continue;
- }
-
- for (index = 0; index < spanedmlangtags.length; index++) {
- spanedmlangtag = spanedmlangtags[index];
- cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');
-
- cleanmlangtag = cleanmlangtag.replace('', '');
-
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
- }
-
- textarea.set('innerHTML', innerHTML);
- }
-
- this.markUpdated();
- },
-
- /**
- * Cleans the tags around the {mlang} tags when the form is submitted,
- * that have "id" attribute, generated by YUI, when the cursor is placed on the tags.
- * The cleanup with "id" attribute and without it is made separately, to avoid an evil
- * regular expression.
+ * When submitting the form, this function is invoked to clean the highlighting html code.
*
- * There may be more than one atto editor textarea in the page. So, we have to retrieve
- * the textareas by the class name. If there is only one, the object will be only the
- * reference, but, if there are more, we will have an array. So, the easiest way is to
- * check if what we have is an array, and if it not, create it manually, and iterate it
- * later.
- *
- * issue #15: the textareas are now retrieved passing to YUI selector the whole element,
- * instead of the id string, due to problems with special characters.
- * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217
- *
- * @method anTagsWithYuiId
+ * @method _cleanMlangTags
* @private
*/
- _cleanTagsWithYuiId: function() {
- var textareas = Y.all('.editor_atto_content'),
- textarea,
- textareaIndex,
- innerHTML,
- spanedmlangtag,
- index,
- cleanmlangtag,
- regularExpression,
- openingspanwithyui,
- spanedmlangtagsdwithyui,
- mlangtag;
-
- openingspanwithyui = OPENING_SPAN.replace('', 'g');
-
- if (!textareas instanceof Array) {
- textarea = textareas;
- textareas = [];
- textareas[0] = textarea;
- }
-
- for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {
- textarea = textareas._nodes[textareaIndex].id;
- textarea = Y.one(document.getElementById(textarea));
-
- innerHTML = textarea.get('innerHTML');
-
- spanedmlangtagsdwithyui = innerHTML.match(regularExpression);
-
- if (spanedmlangtagsdwithyui === null) {
- continue;
- }
-
- for (index = 0; index < spanedmlangtagsdwithyui.length; index++) {
- spanedmlangtag = spanedmlangtagsdwithyui[index];
- mlangtag = spanedmlangtag.match(/\{mlang.*?\}/g)[0];
-
- cleanmlangtag = spanedmlangtag.replace(regularExpression, mlangtag);
- cleanmlangtag = cleanmlangtag.replace('', '');
-
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
- }
-
- textarea.set('innerHTML', innerHTML);
-
+ _cleanMlangTags: function() {
+ if (this._highlight) {
+ this.editor.setHTML(this._getHTMLwithCleanedTags(this.editor.getHTML()));
this.markUpdated();
}
},
/**
- * Adds the tags to the {mlang} tags when the editor is loaded.
- * In this case, we DON'T HAVE TO CALL TO markUpdated(). Why? Honestly,
- * I don't know. But, if we call it after setting the HTML, the {mlang}
- * tags flicker with the decoration, and returns to their original state.
+ * Adds the tags to the {mlang} tags if highlighting is enable.
*
* Instead of taking the HTML directly from the textarea, we have to
* retrieve it, first, without the tags that can be stored
@@ -449,84 +338,82 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
*
* Every different {mlang} tag has to be replaced only once, otherwise,
* nested s will be created in every repeated replacement. So, we
- * have to have a track of which replacements have been made.
+ * need to track which replacements have been made.
*
- * @method _decorateTagsOnInit
+ * @method _highlightMlangTags
* @private
*/
- _decorateTagsOnInit: function() {
- var textarea = Y.all('.editor_atto_content'),
- innerHTML,
+ _highlightMlangTags: function() {
+ var editorHTML,
regularExpression,
mlangtags,
mlangtag,
index,
- decoratedmlangtag,
+ highlightedmlangtag,
replacementsmade = [],
notreplacedyet;
-
- innerHTML = this._getHTMLwithCleanedTags();
-
- regularExpression = new RegExp('{mlang.*?}', 'g');
- mlangtags = innerHTML.match(regularExpression);
-
- if (mlangtags !== null) {
- for (index = 0; index < mlangtags.length; index++) {
- mlangtag = mlangtags[index];
-
- notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;
-
- if (notreplacedyet) {
- replacementsmade.push(mlangtag);
-
- decoratedmlangtag = OPENING_SPAN + mlangtag + '';
- regularExpression = new RegExp(mlangtag, 'g');
-
- innerHTML = innerHTML.replace(regularExpression, decoratedmlangtag);
+ if (this._highlight) {
+ editorHTML = this._getHTMLwithCleanedTags(this.editor.getHTML());
+
+ regularExpression = new RegExp('{mlang.*?}', 'g');
+ mlangtags = editorHTML.match(regularExpression);
+ if (mlangtags !== null) {
+ for (index = 0; index < mlangtags.length; index++) {
+ mlangtag = mlangtags[index];
+
+ notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;
+ if (notreplacedyet) {
+ replacementsmade.push(mlangtag);
+ highlightedmlangtag = OPENING_SPAN + mlangtag + CLOSING_SPAN;
+ regularExpression = new RegExp(mlangtag, 'g');
+ editorHTML = editorHTML.replace(regularExpression, highlightedmlangtag);
+ }
}
+
+ this.editor.setHTML(editorHTML);
}
- textarea.set('innerHTML', innerHTML);
+ this.markUpdated();
}
-
},
/**
- * This function returns the HTML as it is in the textarea, but cleaning every
+ * This function returns the HTML passed in as parameter, but cleaning every multilang
* tag around the {mlang} tags. This is necessary for decorating tags on
* init, because it could happen that in database are stored the {mlang} tags with
* their tags, due to a bug in version 2015120501.
* More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8
+ * Implementation based on code from EditorClean._clearSpans()
*
* @method _getHTMLwithCleanedTags
- * @return {string} HTML in textarea, without any around {mlang} tags
+ * @param {string} content The to be cleaned.
+ * @return {string} HTML in editor, without any around {mlang} tags.
*/
- _getHTMLwithCleanedTags: function() {
- var host = this.get('host'),
- innerHTML = host.getCleanHTML(),
- regexString,
- regularExpression,
- spanedmlangtags,
- spanedmlangtag,
- cleanmlangtag,
- index;
-
- regexString = OPENING_SPAN + '.*?' + '';
- regularExpression = new RegExp(regexString, 'g');
- spanedmlangtags = innerHTML.match(regularExpression);
-
- if (spanedmlangtags !== null) {
- for (index = 0; index < spanedmlangtags.length; index++) {
- spanedmlangtag = spanedmlangtags[index];
-
- cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');
- cleanmlangtag = cleanmlangtag.replace('', '');
+ _getHTMLwithCleanedTags: function(content) {
+ // This is better to run detached from the DOM, so the browser doesn't try to update on each change.
+ var holder = document.createElement('div'),
+ spans,
+ spansarr;
+
+ holder.innerHTML = content;
+ spans = holder.getElementsByTagName('span');
+
+ // Since we will be removing elements from the list, we should copy it to an array, making it static.
+ spansarr = Array.prototype.slice.call(spans, 0);
+
+ spansarr.forEach(function(span) {
+ if (span.className.indexOf(CLASSES.TAG) !== -1) {
+ // Move each child (if they exist) to the parent in place of this span.
+ while (span.firstChild) {
+ span.parentNode.insertBefore(span.firstChild, span);
+ }
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
+ // Remove the now empty span.
+ span.parentNode.removeChild(span);
}
- }
+ });
- return innerHTML;
+ return holder.innerHTML;
}
}, {
diff --git a/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-min.js b/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-min.js
index f0d313e..5b98511 100644
--- a/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-min.js
+++ b/yui/build/moodle-atto_multilang2-button/moodle-atto_multilang2-button-min.js
@@ -1 +1 @@
-YUI.add("moodle-atto_multilang2-button",function(e,t){var n={TAG:"filter-multilang-tag"},r="%lang",i="%content",s="languages",o="capability",u="highlight",a="css",f='{"en":"English (en)"}',l=!0,c=!0,h="outline: 1px dotted;padding: 0.1em;margin: 0em 0.1em;background-color: #ffffaa;",p={SPANED:' {mlang '+r+"}"+i+'{mlang} ',NOT_SPANED:"{mlang "+r+"}"+i+"{mlang}"},d='';e.namespace("M.atto_multilang2").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_highlight:!0,initializer:function(){var e=this.get(o),t=[];e&&(t=this._initializeToolbarItems(),this._highlight=this.get(u),this.addToolbarMenu({globalItemConfig:{callback:this._addTags},icon:"icon",iconComponent:"atto_multilang2",items:t}),this.get("host").on("atto:selectionchanged",this._checkSelectionChange,this),this._addDelimiterCss(),this._highlight&&(this._decorateTagsOnInit(),this._setSubmitListeners()))},_addDelimiterCss:function(){var e="."+n.TAG+"{"+this.get(a)+"}",t;t=document.createElement("style"),t.type="text/css",t.innerHTML=e,document.head.appendChild(t)},_initializeToolbarItems:function(){var e=[],t,n;t=JSON.parse(this.get(s));for(n in t)t.hasOwnProperty(n)&&e.push({text:t[n],callbackArgs:n});return e},_addTags:function(e,t){var n,s=this.get("host"),o,u;o=this._highlight?p.SPANED:p.NOT_SPANED,n=this._getSelectionHTML(),u=s.getSelection().toString().length===0?" ":n,o=o.replace(r,t),o=o.replace(i,u),s.insertContentAtFocusPoint(o),this.markUpdated()},_getSelectionHTML:function(){var e="",t,n,r,i;if(typeof window.getSelection!="undefined"){t=window.getSelection();if(t.rangeCount){n=document.createElement("div");for(r=0,i=t.rangeCount;r-1,s=r.match(/\{mlang/g).length===1,i&&s&&t.setSelection(t.getSelectionFromNode(e.one(n)))},_setSubmitListeners:function(){var t=e.all("input[type=submit]");t.each(this._addListenerToSubmitButtons,this)},_addListenerToSubmitButtons:function(e){var t,n,r,i,s;t=document.getElementById(e.get("id")),t!==null&&(n=t.className,r=t.form.className,i=n.match(/btn-cancel/g)===null,s=r.match(/mform/g).length>0,i&&s&&e.on("click",this._cleanTagsOnSubmit,this,e))},_cleanTagsOnSubmit:function(e,t){e.preventDefault(),this._cleanTagsWithNoYuiId(),this._cleanTagsWithYuiId(),t.detach("click",this._cleanTagsOnSubmit),t.simulate("click")},_cleanTagsWithNoYuiId:function(){var t=e.all(".editor_atto_content"),n,r,i,s,o,u,a,f;f=new RegExp(d+".*?"+"","g"),!t instanceof Array&&(n=t,t=[],t[0]=n);for(r=0;r",""),i=i.replace(o,a);n.set("innerHTML",i)}this.markUpdated()},_cleanTagsWithYuiId:function(){var t=e.all(".editor_atto_content"),n,r,i,s,o,u,a,f,l,c;f=d.replace("","g"),!t instanceof Array&&(n=t,t=[],t[0]=n);for(r=0;r",""),i=i.replace(s,u);n.set("innerHTML",i),this.markUpdated()}},_decorateTagsOnInit:function(){var t=e.all(".editor_atto_content"),n,r,i,s,o,u,a=[],f;n=this._getHTMLwithCleanedTags(),r=new RegExp("{mlang.*?}","g"),i=n.match(r);if(i!==null){for(o=0;o",r=new RegExp(s,"g"),n=n.replace(r,u));t.set("innerHTML",n)}},_getHTMLwithCleanedTags:function(){var e=this.get("host"),t=e.getCleanHTML(),n,r,i,s,o,u;n=d+".*?"+"",r=new RegExp(n,"g"),i=t.match(r);if(i!==null)for(u=0;u",""),t=t.replace(s,o);return t}},{ATTRS:{languages:f,capability:l,highlight:c,css:h}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
+YUI.add("moodle-atto_multilang2-button",function(e,t){var n={TAG:"filter-multilang-tag"},r="%lang",i="%content",s="languages",o="capability",u="highlight",a="css",f='{"en":"English (en)"}',l=!0,c=!0,h="outline: 1px dotted;padding: 0.1em;margin: 0em 0.1em;background-color: #ffffaa;",p='',d="",v={SPANNED:" "+p+"{mlang "+r+"}"+d+i+p+"{mlang}"+d+" ",NOT_SPANNED:"{mlang "+r+"}"+i+"{mlang}"};e.namespace("M.atto_multilang2").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_highlight:!0,initializer:function(){var e=this.get(o),t,n,r;e&&(t=this._initializeToolbarItems(),this.addToolbarMenu({globalItemConfig:{callback:this._addTags},icon:"icon",iconComponent:"atto_multilang2",items:t}),this._tagTemplate=v.NOT_SPANNED,this._highlight=this.get(u),this._highlight&&(this._tagTemplate=v.SPANNED,n=this.get("host"),r=n.textarea.ancestor("form"),r&&r.on("submit",this._cleanMlangTags,this),this.get("host").on("atto:selectionchanged",this._checkSelectionChange,this),this.get("host").on("pluginsloaded",this._addHighlightingCss,this),this.get("host").on("pluginsloaded",this._highlightMlangTags,this),this._hookUpdateOriginal(),this._hookUpdateFromTextArea()))},_initializeToolbarItems:function(){var e=[],t,n;t=JSON.parse(this.get(s));for(n in t)t.hasOwnProperty(n)&&e.push({text:t[n],callbackArgs:n});return e},_addHighlightingCss:function(){var e="."+n.TAG+" {"+this.get(a)+"}",t;t=document.createElement("style"),t.type="text/css",t.innerHTML=e,document.head.appendChild(t)},_hookUpdateOriginal:function(){var e=this.get("host"),t=this;e.updateOriginal=function(){var n=e.updateOriginal;return function(){return t._highlight&&this.updateOriginal.caller===e.plugins.html._showHTML&&t.editor.setHTML(t._getHTMLwithCleanedTags(t.editor.getHTML())),n.apply(this,arguments)}}()},_hookUpdateFromTextArea:function(){var e=this.get("host"),t=this;e.updateFromTextArea=function(){var n=e.updateFromTextArea;return function(){var r=n.apply(this,arguments);return t._highlight&&this.updateFromTextArea.caller===e.plugins.html._showHTML&&t._highlightMlangTags(),r}}()},_addTags:function(e,t){var n,s=this.get("host"),o,u;o=this._tagTemplate,n=this._getSelectionHTML(),u=s.getSelection().toString().length===0?" ":n,o=o.replace(r,t),o=o.replace(i,u),s.insertContentAtFocusPoint(o),this.markUpdated()},_getSelectionHTML:function(){var e="",t,n,r,i;if(typeof window.getSelection!="undefined"){t=window.getSelection();if(t.rangeCount){n=document.createElement("div");for(r=0,i=t.rangeCount;r',
+ CLOSING_SPAN = '',
TEMPLATES = {
- SPANED: ' {mlang ' + LANG_WILDCARD + '}' +
- CONTENT_WILDCARD +
- '{mlang} ',
+ SPANNED: ' ' + OPENING_SPAN + '{mlang ' + LANG_WILDCARD + '}' + CLOSING_SPAN +
+ CONTENT_WILDCARD + OPENING_SPAN + '{mlang}' + CLOSING_SPAN + ' ',
+ NOT_SPANNED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'
+ };
- NOT_SPANED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'
- },
- OPENING_SPAN = '';
-
-/**
- * Atto text editor multilanguage plugin.
- *
- * @namespace M.atto_multilang2
- * @class button
- * @extends M.editor_atto.EditorPlugin
- */
Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
@@ -72,11 +73,12 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
initializer: function() {
var hascapability = this.get(ATTR_CAPABILITY),
- toolbarItems = [];
+ toolbarItems,
+ host,
+ form;
if (hascapability) {
toolbarItems = this._initializeToolbarItems();
- this._highlight = this.get(ATTR_HIGHLIGHT);
this.addToolbarMenu({
globalItemConfig: {
@@ -87,32 +89,34 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
items: toolbarItems
});
- this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);
-
- this._addDelimiterCss();
+ this._tagTemplate = TEMPLATES.NOT_SPANNED;
+ this._highlight = this.get(ATTR_HIGHLIGHT);
if (this._highlight) {
- this._decorateTagsOnInit();
- this._setSubmitListeners();
- }
- }
- },
+ this._tagTemplate = TEMPLATES.SPANNED;
+
+ // Attach a submit listener to the form, so we can remove
+ // the highlighting html before sending content to Moodle.
+ host = this.get('host');
+ form = host.textarea.ancestor('form');
+ if (form) {
+ form.on('submit', this._cleanMlangTags, this);
+ }
- /**
- * Adds the CSS rules for the delimiters, received as parameter from lib.php.
- *
- * @method _addDelimiterCss
- * @private
- */
- _addDelimiterCss: function() {
- var css = '.' + CLASSES.TAG + '{' + this.get(ATTR_CSS) + '}',
- style;
+ // Listen to every change of the text cursor in the text area, to see if
+ // the cursor is placed within a multilang tag.
+ this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);
- style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = css;
+ // Highlight the multilang tags once everything is loaded.
+ this.get('host').on('pluginsloaded', this._addHighlightingCss, this);
+ this.get('host').on('pluginsloaded', this._highlightMlangTags, this);
- document.head.appendChild(style);
+ // Hook into host.updateOriginal() and host.updateFromTextArea()
+ // so we can add/remove highlighting when we switch to/from HTML view.
+ this._hookUpdateOriginal();
+ this._hookUpdateFromTextArea();
+ }
+ }
},
/**
@@ -129,7 +133,6 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
langCode;
languages = JSON.parse(this.get(ATTR_LANGUAGES));
-
for (langCode in languages) {
if (languages.hasOwnProperty(langCode)) {
toolbarItems.push({
@@ -142,6 +145,76 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
return toolbarItems;
},
+ /**
+ * Adds the CSS rules for the delimiters, received as parameter from lib.php.
+ *
+ * @method _addHighlightingCss
+ * @private
+ */
+ _addHighlightingCss: function() {
+ var css = '.' + CLASSES.TAG + ' {' + this.get(ATTR_CSS) + '}',
+ style;
+
+ style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = css;
+
+ document.head.appendChild(style);
+ },
+
+ /**
+ * Hook the host.updateOriginal() method to allow us to remove the highlighting html when
+ * switching to HTML view. As the HTML view plugin doesn't provide a hook or fire an event
+ * to notify about the switch to HTML view, we need to hijack host.updateOriginal and look
+ * for the caller. Once we've cleaned up the highlighting, we need to execute the original
+ * host.updateOriginal() method.
+ * Inspired by https://stackoverflow.com/a/16580937
+ *
+ * @method _hookUpdateOriginal
+ * @private
+ */
+ _hookUpdateOriginal: function() {
+ var host = this.get('host'),
+ multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _removeTags().
+
+ host.updateOriginal = (function() {
+ var _updateOriginal = host.updateOriginal;
+ return function() {
+ if (multilangplugin._highlight && (this.updateOriginal.caller === host.plugins.html._showHTML)) {
+ multilangplugin.editor.setHTML(multilangplugin._getHTMLwithCleanedTags(multilangplugin.editor.getHTML()));
+ }
+ return _updateOriginal.apply(this, arguments);
+ };
+ })();
+ },
+
+ /**
+ * Hook the host.updateFromTextAreal() method to allow us to re-add the highlighting
+ * html when switching from HTML view. As the HTML view plugin doesn't provide a hook
+ * or fire an event to notify about the switch from HTML view, we need to hijack
+ * host.updateFromTextArea and look for the caller. Once we've executed the original
+ * host.updateFromTextArea() method, we re-added the highlighting.
+ * Inspired by https://stackoverflow.com/a/16580937
+ *
+ * @method _hookUpdateFromTextArea
+ * @private
+ */
+ _hookUpdateFromTextArea: function() {
+ var host = this.get('host'),
+ multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _highlightMlangTags().
+
+ host.updateFromTextArea = (function() {
+ var _updateFromTextArea = host.updateFromTextArea;
+ return function() {
+ var ret = _updateFromTextArea.apply(this, arguments);
+ if (multilangplugin._highlight && (this.updateFromTextArea.caller === host.plugins.html._showHTML)) {
+ multilangplugin._highlightMlangTags();
+ }
+ return ret;
+ };
+ })();
+ },
+
/**
* Retrieves the selected text, wraps it with the multilang tags,
* and replaces the selected text in the editor with with it.
@@ -164,7 +237,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
taggedContent,
content;
- taggedContent = (this._highlight) ? TEMPLATES.SPANED : TEMPLATES.NOT_SPANED;
+ taggedContent = this._tagTemplate;
selection = this._getSelectionHTML();
content = (host.getSelection().toString().length === 0) ? ' ' : selection;
@@ -179,7 +252,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
/**
* Retrieves selected text with its HTML.
- * Took from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234
+ * Taken from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234
*
* @method _getSelectionHTML
* @private
@@ -190,14 +263,14 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
selection,
container,
index,
- lenght;
+ length;
if (typeof window.getSelection !== 'undefined') {
selection = window.getSelection();
if (selection.rangeCount) {
container = document.createElement('div');
- for (index = 0, lenght = selection.rangeCount; index < lenght; ++index) {
+ for (index = 0, length = selection.rangeCount; index < length; ++index) {
container.appendChild(selection.getRangeAt(index).cloneContents());
}
html = container.innerHTML;
@@ -214,7 +287,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
/**
* Listens to every change of the text cursor in the text area. If the
- * cursor is placed within a multilang tag, the whole tag is selected.
+ * cursor is placed within a highlighted multilang tag, the whole tag is selected.
*
* @method _checkSelectionChange
* @private
@@ -222,224 +295,40 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
_checkSelectionChange: function() {
var host = this.get('host'),
node = host.getSelectionParentNode(),
- nodeValue = Y.one(node).get('text'),
- isTextNode,
- isLangTag;
-
- isTextNode = Y.one(node).toString().indexOf('#text') > - 1;
- isLangTag = (nodeValue.match(/\{mlang/g).length === 1);
-
- if (isTextNode && isLangTag) {
- host.setSelection(host.getSelectionFromNode(Y.one(node)));
+ parentNodeName,
+ parentClass,
+ selection;
+
+ // If the event fires without a parent node for the selection, ignore the whole thing.
+ if ((typeof node === 'undefined') || (node === null) || (node === false) ||
+ (typeof node.parentNode === 'undefined') || (node.parentNode === null)) {
+ return;
}
- },
-
- /**
- * Retrieves the inputs of type submit, and, for each element, calls the function
- * that sets the submit listener. Is not made in this function because there is
- * not any (apparent) way to access class scope from YUI closure.
- *
- * @method _setSubmitListeners
- * @private
- */
- _setSubmitListeners: function() {
- var submitButtons = Y.all('input[type=submit]');
-
- submitButtons.each(this._addListenerToSubmitButtons, this);
- },
- /**
- * Adds the clean tags submit listener of each input[type="submit"], but only if
- * it's not 'cancel' type, and if its parent form is of 'mform' class, because there
- * may be any other submit type (such us administrator's search button).
- *
- * @method _addListenerToSubmitButtons
- * @param {Node} buttonNode
- * @private
- */
- _addListenerToSubmitButtons: function(buttonNode) {
- var buttonObject,
- className,
- parentFormClassName,
- notCancelButton,
- notSearchButton;
-
- buttonObject = document.getElementById(buttonNode.get('id'));
-
- if (buttonObject !== null) {
- className = buttonObject.className;
- parentFormClassName = buttonObject.form.className;
-
- notCancelButton = className.match(/btn-cancel/g) === null;
- notSearchButton = parentFormClassName.match(/mform/g).length > 0;
-
- if (notCancelButton && notSearchButton) {
- buttonNode.on('click', this._cleanTagsOnSubmit, this, buttonNode);
- }
+ parentNodeName = node.parentNode.nodeName;
+ parentClass = node.parentNode.hasAttribute('class') ? node.parentNode.getAttribute('class') : '';
+ if ((typeof parentNodeName !== 'undefined') && (parentNodeName !== null) && (parentClass !== '') &&
+ (parentNodeName === 'SPAN') && (parentClass.indexOf(CLASSES.TAG) !== -1)) {
+ selection = host.getSelectionFromNode(Y.one(node));
+ host.setSelection(selection);
}
},
/**
- * When submit button clicked, this function is invoked. It has to stop the submission,
- * in order to process the textarea to clean the tags.
- *
- * Once the textarea is cleaned, detaches this submit listener, i.e., it sets as default,
- * an then simulates the click, to submit the form.
- *
- * @method _cleanTagsOnSubmit
- * @param {EventFacade} event
- * @param {Node} submitButton
- * @private
- */
- _cleanTagsOnSubmit: function(event, submitButton) {
- event.preventDefault();
-
- this._cleanTagsWithNoYuiId();
- this._cleanTagsWithYuiId();
-
- submitButton.detach('click', this._cleanTagsOnSubmit);
- submitButton.simulate('click');
- },
-
- /**
- * Cleans the tags around the {mlang} tags when the form is submitted,
- * that do not have "id" attribute.
- * The cleanup with "id" attribute and without it is made separately, to avoid an evil
- * regular expression.
- *
- * There may be more than one atto editor textarea in the page. So, we have to retrieve
- * the textareas by the class name. If there is only one, the object will be only the
- * reference, but, if there are more, we will have an array. So, the easiest way is to
- * check if what we have is an array, and if it not, create it manually, and iterate it
- * later.
- *
- * issue #15: the textareas are now retrieved passing to YUI selector the whole element,
- * instead of the id string, due to problems with special characters.
- * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217
- *
- * @method _cleanTagsWithNoYuiId
- * @private
- */
- _cleanTagsWithNoYuiId: function() {
- var textareas = Y.all('.editor_atto_content'),
- textarea,
- textareaIndex,
- innerHTML,
- spanedmlangtags,
- spanedmlangtag,
- index,
- cleanmlangtag,
- regularExpression;
-
- regularExpression = new RegExp(OPENING_SPAN + '.*?' + '', 'g');
-
- if (!textareas instanceof Array) {
- textarea = textareas;
- textareas = [];
- textareas[0] = textarea;
- }
-
- for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {
- textarea = textareas._nodes[textareaIndex].id;
- textarea = Y.one(document.getElementById(textarea));
-
- innerHTML = textarea.get('innerHTML');
-
- spanedmlangtags = innerHTML.match(regularExpression);
-
- if (spanedmlangtags === null) {
- continue;
- }
-
- for (index = 0; index < spanedmlangtags.length; index++) {
- spanedmlangtag = spanedmlangtags[index];
- cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');
-
- cleanmlangtag = cleanmlangtag.replace('', '');
-
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
- }
-
- textarea.set('innerHTML', innerHTML);
- }
-
- this.markUpdated();
- },
-
- /**
- * Cleans the tags around the {mlang} tags when the form is submitted,
- * that have "id" attribute, generated by YUI, when the cursor is placed on the tags.
- * The cleanup with "id" attribute and without it is made separately, to avoid an evil
- * regular expression.
+ * When submitting the form, this function is invoked to clean the highlighting html code.
*
- * There may be more than one atto editor textarea in the page. So, we have to retrieve
- * the textareas by the class name. If there is only one, the object will be only the
- * reference, but, if there are more, we will have an array. So, the easiest way is to
- * check if what we have is an array, and if it not, create it manually, and iterate it
- * later.
- *
- * issue #15: the textareas are now retrieved passing to YUI selector the whole element,
- * instead of the id string, due to problems with special characters.
- * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217
- *
- * @method anTagsWithYuiId
+ * @method _cleanMlangTags
* @private
*/
- _cleanTagsWithYuiId: function() {
- var textareas = Y.all('.editor_atto_content'),
- textarea,
- textareaIndex,
- innerHTML,
- spanedmlangtag,
- index,
- cleanmlangtag,
- regularExpression,
- openingspanwithyui,
- spanedmlangtagsdwithyui,
- mlangtag;
-
- openingspanwithyui = OPENING_SPAN.replace('', 'g');
-
- if (!textareas instanceof Array) {
- textarea = textareas;
- textareas = [];
- textareas[0] = textarea;
- }
-
- for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {
- textarea = textareas._nodes[textareaIndex].id;
- textarea = Y.one(document.getElementById(textarea));
-
- innerHTML = textarea.get('innerHTML');
-
- spanedmlangtagsdwithyui = innerHTML.match(regularExpression);
-
- if (spanedmlangtagsdwithyui === null) {
- continue;
- }
-
- for (index = 0; index < spanedmlangtagsdwithyui.length; index++) {
- spanedmlangtag = spanedmlangtagsdwithyui[index];
- mlangtag = spanedmlangtag.match(/\{mlang.*?\}/g)[0];
-
- cleanmlangtag = spanedmlangtag.replace(regularExpression, mlangtag);
- cleanmlangtag = cleanmlangtag.replace('', '');
-
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
- }
-
- textarea.set('innerHTML', innerHTML);
-
+ _cleanMlangTags: function() {
+ if (this._highlight) {
+ this.editor.setHTML(this._getHTMLwithCleanedTags(this.editor.getHTML()));
this.markUpdated();
}
},
/**
- * Adds the tags to the {mlang} tags when the editor is loaded.
- * In this case, we DON'T HAVE TO CALL TO markUpdated(). Why? Honestly,
- * I don't know. But, if we call it after setting the HTML, the {mlang}
- * tags flicker with the decoration, and returns to their original state.
+ * Adds the tags to the {mlang} tags if highlighting is enable.
*
* Instead of taking the HTML directly from the textarea, we have to
* retrieve it, first, without the tags that can be stored
@@ -449,84 +338,82 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
*
* Every different {mlang} tag has to be replaced only once, otherwise,
* nested s will be created in every repeated replacement. So, we
- * have to have a track of which replacements have been made.
+ * need to track which replacements have been made.
*
- * @method _decorateTagsOnInit
+ * @method _highlightMlangTags
* @private
*/
- _decorateTagsOnInit: function() {
- var textarea = Y.all('.editor_atto_content'),
- innerHTML,
+ _highlightMlangTags: function() {
+ var editorHTML,
regularExpression,
mlangtags,
mlangtag,
index,
- decoratedmlangtag,
+ highlightedmlangtag,
replacementsmade = [],
notreplacedyet;
-
- innerHTML = this._getHTMLwithCleanedTags();
-
- regularExpression = new RegExp('{mlang.*?}', 'g');
- mlangtags = innerHTML.match(regularExpression);
-
- if (mlangtags !== null) {
- for (index = 0; index < mlangtags.length; index++) {
- mlangtag = mlangtags[index];
-
- notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;
-
- if (notreplacedyet) {
- replacementsmade.push(mlangtag);
-
- decoratedmlangtag = OPENING_SPAN + mlangtag + '';
- regularExpression = new RegExp(mlangtag, 'g');
-
- innerHTML = innerHTML.replace(regularExpression, decoratedmlangtag);
+ if (this._highlight) {
+ editorHTML = this._getHTMLwithCleanedTags(this.editor.getHTML());
+
+ regularExpression = new RegExp('{mlang.*?}', 'g');
+ mlangtags = editorHTML.match(regularExpression);
+ if (mlangtags !== null) {
+ for (index = 0; index < mlangtags.length; index++) {
+ mlangtag = mlangtags[index];
+
+ notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;
+ if (notreplacedyet) {
+ replacementsmade.push(mlangtag);
+ highlightedmlangtag = OPENING_SPAN + mlangtag + CLOSING_SPAN;
+ regularExpression = new RegExp(mlangtag, 'g');
+ editorHTML = editorHTML.replace(regularExpression, highlightedmlangtag);
+ }
}
+
+ this.editor.setHTML(editorHTML);
}
- textarea.set('innerHTML', innerHTML);
+ this.markUpdated();
}
-
},
/**
- * This function returns the HTML as it is in the textarea, but cleaning every
+ * This function returns the HTML passed in as parameter, but cleaning every multilang
* tag around the {mlang} tags. This is necessary for decorating tags on
* init, because it could happen that in database are stored the {mlang} tags with
* their tags, due to a bug in version 2015120501.
* More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8
+ * Implementation based on code from EditorClean._clearSpans()
*
* @method _getHTMLwithCleanedTags
- * @return {string} HTML in textarea, without any around {mlang} tags
+ * @param {string} content The to be cleaned.
+ * @return {string} HTML in editor, without any around {mlang} tags.
*/
- _getHTMLwithCleanedTags: function() {
- var host = this.get('host'),
- innerHTML = host.getCleanHTML(),
- regexString,
- regularExpression,
- spanedmlangtags,
- spanedmlangtag,
- cleanmlangtag,
- index;
-
- regexString = OPENING_SPAN + '.*?' + '';
- regularExpression = new RegExp(regexString, 'g');
- spanedmlangtags = innerHTML.match(regularExpression);
-
- if (spanedmlangtags !== null) {
- for (index = 0; index < spanedmlangtags.length; index++) {
- spanedmlangtag = spanedmlangtags[index];
-
- cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');
- cleanmlangtag = cleanmlangtag.replace('', '');
+ _getHTMLwithCleanedTags: function(content) {
+ // This is better to run detached from the DOM, so the browser doesn't try to update on each change.
+ var holder = document.createElement('div'),
+ spans,
+ spansarr;
+
+ holder.innerHTML = content;
+ spans = holder.getElementsByTagName('span');
+
+ // Since we will be removing elements from the list, we should copy it to an array, making it static.
+ spansarr = Array.prototype.slice.call(spans, 0);
+
+ spansarr.forEach(function(span) {
+ if (span.className.indexOf(CLASSES.TAG) !== -1) {
+ // Move each child (if they exist) to the parent in place of this span.
+ while (span.firstChild) {
+ span.parentNode.insertBefore(span.firstChild, span);
+ }
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
+ // Remove the now empty span.
+ span.parentNode.removeChild(span);
}
- }
+ });
- return innerHTML;
+ return holder.innerHTML;
}
}, {
diff --git a/yui/src/button/js/button.js b/yui/src/button/js/button.js
index dca595a..634724f 100644
--- a/yui/src/button/js/button.js
+++ b/yui/src/button/js/button.js
@@ -16,6 +16,7 @@
/**
* @package atto_multilang2
* @copyright 2015 onwards Julen Pardo & Mondragon Unibertsitatea
+ * @copyright 2017 onwards Iñaki Arenaza & Mondragon Unibertsitatea
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -23,6 +24,14 @@
* @module moodle-atto_multilang2-button
*/
+/**
+ * Atto text editor multilanguage plugin.
+ *
+ * @namespace M.atto_multilang2
+ * @class button
+ * @extends M.editor_atto.EditorPlugin.
+ */
+
var CLASSES = {
TAG: 'filter-multilang-tag'
},
@@ -36,26 +45,18 @@ var CLASSES = {
DEFAULT_LANGUAGE = '{"en":"English (en)"}',
DEFAULT_CAPABILITY = true,
DEFAULT_HIGHLIGHT = true,
- DEFAULT_CSS = 'outline: 1px dotted;' +
- 'padding: 0.1em;' +
- 'margin: 0em 0.1em;' +
- 'background-color: #ffffaa;',
+ DEFAULT_CSS = 'outline: 1px dotted;' +
+ 'padding: 0.1em;' +
+ 'margin: 0em 0.1em;' +
+ 'background-color: #ffffaa;',
+ OPENING_SPAN = '',
+ CLOSING_SPAN = '',
TEMPLATES = {
- SPANED: ' {mlang ' + LANG_WILDCARD + '}' +
- CONTENT_WILDCARD +
- '{mlang} ',
+ SPANNED: ' ' + OPENING_SPAN + '{mlang ' + LANG_WILDCARD + '}' + CLOSING_SPAN +
+ CONTENT_WILDCARD + OPENING_SPAN + '{mlang}' + CLOSING_SPAN + ' ',
+ NOT_SPANNED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'
+ };
- NOT_SPANED: '{mlang ' + LANG_WILDCARD + '}' + CONTENT_WILDCARD + '{mlang}'
- },
- OPENING_SPAN = '';
-
-/**
- * Atto text editor multilanguage plugin.
- *
- * @namespace M.atto_multilang2
- * @class button
- * @extends M.editor_atto.EditorPlugin
- */
Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
@@ -70,11 +71,12 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
initializer: function() {
var hascapability = this.get(ATTR_CAPABILITY),
- toolbarItems = [];
+ toolbarItems,
+ host,
+ form;
if (hascapability) {
toolbarItems = this._initializeToolbarItems();
- this._highlight = this.get(ATTR_HIGHLIGHT);
this.addToolbarMenu({
globalItemConfig: {
@@ -85,32 +87,34 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
items: toolbarItems
});
- this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);
-
- this._addDelimiterCss();
+ this._tagTemplate = TEMPLATES.NOT_SPANNED;
+ this._highlight = this.get(ATTR_HIGHLIGHT);
if (this._highlight) {
- this._decorateTagsOnInit();
- this._setSubmitListeners();
- }
- }
- },
+ this._tagTemplate = TEMPLATES.SPANNED;
+
+ // Attach a submit listener to the form, so we can remove
+ // the highlighting html before sending content to Moodle.
+ host = this.get('host');
+ form = host.textarea.ancestor('form');
+ if (form) {
+ form.on('submit', this._cleanMlangTags, this);
+ }
- /**
- * Adds the CSS rules for the delimiters, received as parameter from lib.php.
- *
- * @method _addDelimiterCss
- * @private
- */
- _addDelimiterCss: function() {
- var css = '.' + CLASSES.TAG + '{' + this.get(ATTR_CSS) + '}',
- style;
+ // Listen to every change of the text cursor in the text area, to see if
+ // the cursor is placed within a multilang tag.
+ this.get('host').on('atto:selectionchanged', this._checkSelectionChange, this);
- style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = css;
+ // Highlight the multilang tags once everything is loaded.
+ this.get('host').on('pluginsloaded', this._addHighlightingCss, this);
+ this.get('host').on('pluginsloaded', this._highlightMlangTags, this);
- document.head.appendChild(style);
+ // Hook into host.updateOriginal() and host.updateFromTextArea()
+ // so we can add/remove highlighting when we switch to/from HTML view.
+ this._hookUpdateOriginal();
+ this._hookUpdateFromTextArea();
+ }
+ }
},
/**
@@ -127,7 +131,6 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
langCode;
languages = JSON.parse(this.get(ATTR_LANGUAGES));
-
for (langCode in languages) {
if (languages.hasOwnProperty(langCode)) {
toolbarItems.push({
@@ -140,6 +143,76 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
return toolbarItems;
},
+ /**
+ * Adds the CSS rules for the delimiters, received as parameter from lib.php.
+ *
+ * @method _addHighlightingCss
+ * @private
+ */
+ _addHighlightingCss: function() {
+ var css = '.' + CLASSES.TAG + ' {' + this.get(ATTR_CSS) + '}',
+ style;
+
+ style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = css;
+
+ document.head.appendChild(style);
+ },
+
+ /**
+ * Hook the host.updateOriginal() method to allow us to remove the highlighting html when
+ * switching to HTML view. As the HTML view plugin doesn't provide a hook or fire an event
+ * to notify about the switch to HTML view, we need to hijack host.updateOriginal and look
+ * for the caller. Once we've cleaned up the highlighting, we need to execute the original
+ * host.updateOriginal() method.
+ * Inspired by https://stackoverflow.com/a/16580937
+ *
+ * @method _hookUpdateOriginal
+ * @private
+ */
+ _hookUpdateOriginal: function() {
+ var host = this.get('host'),
+ multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _removeTags().
+
+ host.updateOriginal = (function() {
+ var _updateOriginal = host.updateOriginal;
+ return function() {
+ if (multilangplugin._highlight && (this.updateOriginal.caller === host.plugins.html._showHTML)) {
+ multilangplugin.editor.setHTML(multilangplugin._getHTMLwithCleanedTags(multilangplugin.editor.getHTML()));
+ }
+ return _updateOriginal.apply(this, arguments);
+ };
+ })();
+ },
+
+ /**
+ * Hook the host.updateFromTextAreal() method to allow us to re-add the highlighting
+ * html when switching from HTML view. As the HTML view plugin doesn't provide a hook
+ * or fire an event to notify about the switch from HTML view, we need to hijack
+ * host.updateFromTextArea and look for the caller. Once we've executed the original
+ * host.updateFromTextArea() method, we re-added the highlighting.
+ * Inspired by https://stackoverflow.com/a/16580937
+ *
+ * @method _hookUpdateFromTextArea
+ * @private
+ */
+ _hookUpdateFromTextArea: function() {
+ var host = this.get('host'),
+ multilangplugin = this; // Capture the plugin in the closure below, so we can invoke _highlightMlangTags().
+
+ host.updateFromTextArea = (function() {
+ var _updateFromTextArea = host.updateFromTextArea;
+ return function() {
+ var ret = _updateFromTextArea.apply(this, arguments);
+ if (multilangplugin._highlight && (this.updateFromTextArea.caller === host.plugins.html._showHTML)) {
+ multilangplugin._highlightMlangTags();
+ }
+ return ret;
+ };
+ })();
+ },
+
/**
* Retrieves the selected text, wraps it with the multilang tags,
* and replaces the selected text in the editor with with it.
@@ -162,7 +235,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
taggedContent,
content;
- taggedContent = (this._highlight) ? TEMPLATES.SPANED : TEMPLATES.NOT_SPANED;
+ taggedContent = this._tagTemplate;
selection = this._getSelectionHTML();
content = (host.getSelection().toString().length === 0) ? ' ' : selection;
@@ -177,7 +250,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
/**
* Retrieves selected text with its HTML.
- * Took from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234
+ * Taken from: http://stackoverflow.com/questions/4176923/html-of-selected-text/4177234#4177234
*
* @method _getSelectionHTML
* @private
@@ -188,14 +261,14 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
selection,
container,
index,
- lenght;
+ length;
if (typeof window.getSelection !== 'undefined') {
selection = window.getSelection();
if (selection.rangeCount) {
container = document.createElement('div');
- for (index = 0, lenght = selection.rangeCount; index < lenght; ++index) {
+ for (index = 0, length = selection.rangeCount; index < length; ++index) {
container.appendChild(selection.getRangeAt(index).cloneContents());
}
html = container.innerHTML;
@@ -212,7 +285,7 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
/**
* Listens to every change of the text cursor in the text area. If the
- * cursor is placed within a multilang tag, the whole tag is selected.
+ * cursor is placed within a highlighted multilang tag, the whole tag is selected.
*
* @method _checkSelectionChange
* @private
@@ -220,224 +293,40 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
_checkSelectionChange: function() {
var host = this.get('host'),
node = host.getSelectionParentNode(),
- nodeValue = Y.one(node).get('text'),
- isTextNode,
- isLangTag;
-
- isTextNode = Y.one(node).toString().indexOf('#text') > - 1;
- isLangTag = (nodeValue.match(/\{mlang/g).length === 1);
-
- if (isTextNode && isLangTag) {
- host.setSelection(host.getSelectionFromNode(Y.one(node)));
+ parentNodeName,
+ parentClass,
+ selection;
+
+ // If the event fires without a parent node for the selection, ignore the whole thing.
+ if ((typeof node === 'undefined') || (node === null) || (node === false) ||
+ (typeof node.parentNode === 'undefined') || (node.parentNode === null)) {
+ return;
}
- },
-
- /**
- * Retrieves the inputs of type submit, and, for each element, calls the function
- * that sets the submit listener. Is not made in this function because there is
- * not any (apparent) way to access class scope from YUI closure.
- *
- * @method _setSubmitListeners
- * @private
- */
- _setSubmitListeners: function() {
- var submitButtons = Y.all('input[type=submit]');
-
- submitButtons.each(this._addListenerToSubmitButtons, this);
- },
- /**
- * Adds the clean tags submit listener of each input[type="submit"], but only if
- * it's not 'cancel' type, and if its parent form is of 'mform' class, because there
- * may be any other submit type (such us administrator's search button).
- *
- * @method _addListenerToSubmitButtons
- * @param {Node} buttonNode
- * @private
- */
- _addListenerToSubmitButtons: function(buttonNode) {
- var buttonObject,
- className,
- parentFormClassName,
- notCancelButton,
- notSearchButton;
-
- buttonObject = document.getElementById(buttonNode.get('id'));
-
- if (buttonObject !== null) {
- className = buttonObject.className;
- parentFormClassName = buttonObject.form.className;
-
- notCancelButton = className.match(/btn-cancel/g) === null;
- notSearchButton = parentFormClassName.match(/mform/g).length > 0;
-
- if (notCancelButton && notSearchButton) {
- buttonNode.on('click', this._cleanTagsOnSubmit, this, buttonNode);
- }
+ parentNodeName = node.parentNode.nodeName;
+ parentClass = node.parentNode.hasAttribute('class') ? node.parentNode.getAttribute('class') : '';
+ if ((typeof parentNodeName !== 'undefined') && (parentNodeName !== null) && (parentClass !== '') &&
+ (parentNodeName === 'SPAN') && (parentClass.indexOf(CLASSES.TAG) !== -1)) {
+ selection = host.getSelectionFromNode(Y.one(node));
+ host.setSelection(selection);
}
},
/**
- * When submit button clicked, this function is invoked. It has to stop the submission,
- * in order to process the textarea to clean the tags.
- *
- * Once the textarea is cleaned, detaches this submit listener, i.e., it sets as default,
- * an then simulates the click, to submit the form.
- *
- * @method _cleanTagsOnSubmit
- * @param {EventFacade} event
- * @param {Node} submitButton
- * @private
- */
- _cleanTagsOnSubmit: function(event, submitButton) {
- event.preventDefault();
-
- this._cleanTagsWithNoYuiId();
- this._cleanTagsWithYuiId();
-
- submitButton.detach('click', this._cleanTagsOnSubmit);
- submitButton.simulate('click');
- },
-
- /**
- * Cleans the tags around the {mlang} tags when the form is submitted,
- * that do not have "id" attribute.
- * The cleanup with "id" attribute and without it is made separately, to avoid an evil
- * regular expression.
- *
- * There may be more than one atto editor textarea in the page. So, we have to retrieve
- * the textareas by the class name. If there is only one, the object will be only the
- * reference, but, if there are more, we will have an array. So, the easiest way is to
- * check if what we have is an array, and if it not, create it manually, and iterate it
- * later.
- *
- * issue #15: the textareas are now retrieved passing to YUI selector the whole element,
- * instead of the id string, due to problems with special characters.
- * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217
- *
- * @method _cleanTagsWithNoYuiId
- * @private
- */
- _cleanTagsWithNoYuiId: function() {
- var textareas = Y.all('.editor_atto_content'),
- textarea,
- textareaIndex,
- innerHTML,
- spanedmlangtags,
- spanedmlangtag,
- index,
- cleanmlangtag,
- regularExpression;
-
- regularExpression = new RegExp(OPENING_SPAN + '.*?' + '', 'g');
-
- if (!textareas instanceof Array) {
- textarea = textareas;
- textareas = [];
- textareas[0] = textarea;
- }
-
- for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {
- textarea = textareas._nodes[textareaIndex].id;
- textarea = Y.one(document.getElementById(textarea));
-
- innerHTML = textarea.get('innerHTML');
-
- spanedmlangtags = innerHTML.match(regularExpression);
-
- if (spanedmlangtags === null) {
- continue;
- }
-
- for (index = 0; index < spanedmlangtags.length; index++) {
- spanedmlangtag = spanedmlangtags[index];
- cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');
-
- cleanmlangtag = cleanmlangtag.replace('', '');
-
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
- }
-
- textarea.set('innerHTML', innerHTML);
- }
-
- this.markUpdated();
- },
-
- /**
- * Cleans the tags around the {mlang} tags when the form is submitted,
- * that have "id" attribute, generated by YUI, when the cursor is placed on the tags.
- * The cleanup with "id" attribute and without it is made separately, to avoid an evil
- * regular expression.
+ * When submitting the form, this function is invoked to clean the highlighting html code.
*
- * There may be more than one atto editor textarea in the page. So, we have to retrieve
- * the textareas by the class name. If there is only one, the object will be only the
- * reference, but, if there are more, we will have an array. So, the easiest way is to
- * check if what we have is an array, and if it not, create it manually, and iterate it
- * later.
- *
- * issue #15: the textareas are now retrieved passing to YUI selector the whole element,
- * instead of the id string, due to problems with special characters.
- * See discussion: https://moodle.org/mod/forum/discuss.php?d=332217
- *
- * @method anTagsWithYuiId
+ * @method _cleanMlangTags
* @private
*/
- _cleanTagsWithYuiId: function() {
- var textareas = Y.all('.editor_atto_content'),
- textarea,
- textareaIndex,
- innerHTML,
- spanedmlangtag,
- index,
- cleanmlangtag,
- regularExpression,
- openingspanwithyui,
- spanedmlangtagsdwithyui,
- mlangtag;
-
- openingspanwithyui = OPENING_SPAN.replace('', 'g');
-
- if (!textareas instanceof Array) {
- textarea = textareas;
- textareas = [];
- textareas[0] = textarea;
- }
-
- for (textareaIndex = 0; textareaIndex < textareas._nodes.length; textareaIndex++) {
- textarea = textareas._nodes[textareaIndex].id;
- textarea = Y.one(document.getElementById(textarea));
-
- innerHTML = textarea.get('innerHTML');
-
- spanedmlangtagsdwithyui = innerHTML.match(regularExpression);
-
- if (spanedmlangtagsdwithyui === null) {
- continue;
- }
-
- for (index = 0; index < spanedmlangtagsdwithyui.length; index++) {
- spanedmlangtag = spanedmlangtagsdwithyui[index];
- mlangtag = spanedmlangtag.match(/\{mlang.*?\}/g)[0];
-
- cleanmlangtag = spanedmlangtag.replace(regularExpression, mlangtag);
- cleanmlangtag = cleanmlangtag.replace('', '');
-
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
- }
-
- textarea.set('innerHTML', innerHTML);
-
+ _cleanMlangTags: function() {
+ if (this._highlight) {
+ this.editor.setHTML(this._getHTMLwithCleanedTags(this.editor.getHTML()));
this.markUpdated();
}
},
/**
- * Adds the tags to the {mlang} tags when the editor is loaded.
- * In this case, we DON'T HAVE TO CALL TO markUpdated(). Why? Honestly,
- * I don't know. But, if we call it after setting the HTML, the {mlang}
- * tags flicker with the decoration, and returns to their original state.
+ * Adds the tags to the {mlang} tags if highlighting is enable.
*
* Instead of taking the HTML directly from the textarea, we have to
* retrieve it, first, without the tags that can be stored
@@ -447,84 +336,82 @@ Y.namespace('M.atto_multilang2').Button = Y.Base.create('button', Y.M.editor_att
*
* Every different {mlang} tag has to be replaced only once, otherwise,
* nested s will be created in every repeated replacement. So, we
- * have to have a track of which replacements have been made.
+ * need to track which replacements have been made.
*
- * @method _decorateTagsOnInit
+ * @method _highlightMlangTags
* @private
*/
- _decorateTagsOnInit: function() {
- var textarea = Y.all('.editor_atto_content'),
- innerHTML,
+ _highlightMlangTags: function() {
+ var editorHTML,
regularExpression,
mlangtags,
mlangtag,
index,
- decoratedmlangtag,
+ highlightedmlangtag,
replacementsmade = [],
notreplacedyet;
-
- innerHTML = this._getHTMLwithCleanedTags();
-
- regularExpression = new RegExp('{mlang.*?}', 'g');
- mlangtags = innerHTML.match(regularExpression);
-
- if (mlangtags !== null) {
- for (index = 0; index < mlangtags.length; index++) {
- mlangtag = mlangtags[index];
-
- notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;
-
- if (notreplacedyet) {
- replacementsmade.push(mlangtag);
-
- decoratedmlangtag = OPENING_SPAN + mlangtag + '';
- regularExpression = new RegExp(mlangtag, 'g');
-
- innerHTML = innerHTML.replace(regularExpression, decoratedmlangtag);
+ if (this._highlight) {
+ editorHTML = this._getHTMLwithCleanedTags(this.editor.getHTML());
+
+ regularExpression = new RegExp('{mlang.*?}', 'g');
+ mlangtags = editorHTML.match(regularExpression);
+ if (mlangtags !== null) {
+ for (index = 0; index < mlangtags.length; index++) {
+ mlangtag = mlangtags[index];
+
+ notreplacedyet = replacementsmade.indexOf(mlangtag) === -1;
+ if (notreplacedyet) {
+ replacementsmade.push(mlangtag);
+ highlightedmlangtag = OPENING_SPAN + mlangtag + CLOSING_SPAN;
+ regularExpression = new RegExp(mlangtag, 'g');
+ editorHTML = editorHTML.replace(regularExpression, highlightedmlangtag);
+ }
}
+
+ this.editor.setHTML(editorHTML);
}
- textarea.set('innerHTML', innerHTML);
+ this.markUpdated();
}
-
},
/**
- * This function returns the HTML as it is in the textarea, but cleaning every
+ * This function returns the HTML passed in as parameter, but cleaning every multilang
* tag around the {mlang} tags. This is necessary for decorating tags on
* init, because it could happen that in database are stored the {mlang} tags with
* their tags, due to a bug in version 2015120501.
* More info about this bug: https://github.com/julenpardo/moodle-atto_multilang2/issues/8
+ * Implementation based on code from EditorClean._clearSpans()
*
* @method _getHTMLwithCleanedTags
- * @return {string} HTML in textarea, without any around {mlang} tags
+ * @param {string} content The to be cleaned.
+ * @return {string} HTML in editor, without any around {mlang} tags.
*/
- _getHTMLwithCleanedTags: function() {
- var host = this.get('host'),
- innerHTML = host.getCleanHTML(),
- regexString,
- regularExpression,
- spanedmlangtags,
- spanedmlangtag,
- cleanmlangtag,
- index;
-
- regexString = OPENING_SPAN + '.*?' + '';
- regularExpression = new RegExp(regexString, 'g');
- spanedmlangtags = innerHTML.match(regularExpression);
-
- if (spanedmlangtags !== null) {
- for (index = 0; index < spanedmlangtags.length; index++) {
- spanedmlangtag = spanedmlangtags[index];
-
- cleanmlangtag = spanedmlangtag.replace(OPENING_SPAN, '');
- cleanmlangtag = cleanmlangtag.replace('', '');
+ _getHTMLwithCleanedTags: function(content) {
+ // This is better to run detached from the DOM, so the browser doesn't try to update on each change.
+ var holder = document.createElement('div'),
+ spans,
+ spansarr;
+
+ holder.innerHTML = content;
+ spans = holder.getElementsByTagName('span');
+
+ // Since we will be removing elements from the list, we should copy it to an array, making it static.
+ spansarr = Array.prototype.slice.call(spans, 0);
+
+ spansarr.forEach(function(span) {
+ if (span.className.indexOf(CLASSES.TAG) !== -1) {
+ // Move each child (if they exist) to the parent in place of this span.
+ while (span.firstChild) {
+ span.parentNode.insertBefore(span.firstChild, span);
+ }
- innerHTML = innerHTML.replace(spanedmlangtag, cleanmlangtag);
+ // Remove the now empty span.
+ span.parentNode.removeChild(span);
}
- }
+ });
- return innerHTML;
+ return holder.innerHTML;
}
}, {