MDL-44164 atto: Refactor the world

This commit is contained in:
Andrew Nicols
2014-03-26 10:01:48 +08:00
committed by Damyon Wiese
parent 8951d61411
commit 6246779510
202 changed files with 17098 additions and 13524 deletions
+2 -2
View File
@@ -113,7 +113,7 @@ class atto_texteditor extends texteditor {
$extra = component_callback('atto_' . $plugin, 'params_for_js', array($elementid, $options, $fpoptions));
if ($extra) {
$jsplugin['params'] = $extra;
$jsplugin = array_merge($jsplugin, $extra);
}
// We always need the plugin name.
$PAGE->requires->string_for_js('pluginname', 'atto_' . $plugin);
@@ -123,7 +123,7 @@ class atto_texteditor extends texteditor {
}
$PAGE->requires->yui_module($modules,
'M.editor_atto.init',
'Y.M.editor_atto.Editor.init',
array($this->get_init_params($elementid, $options, $fpoptions, $jsplugins)));
}
@@ -15,106 +15,192 @@ YUI.add('moodle-atto_accessibilitychecker-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor accessibilitychecker plugin.
*
* This plugin adds some functions to do things that screen readers do not do well.
* Specifically, listing the active styles for the selected text,
* listing the images in the page, listing the links in the page.
*
/*
* @package atto_accessibilitychecker
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
/**
* @module moodle-atto_accessibilitychecker-button
*/
/**
* Accessibility Checking tool for the Atto editor.
*
* @namespace M.atto_accessibilitychecker
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENT = 'atto_accessibilitychecker';
Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The window used to display the accessibility ui.
* The warnings which are displayed.
*
* @property dialogue
* @type M.core.dialogue
* @default null
* @property _displayedWarnings
* @type Object
* @private
*/
dialogue : null,
_displayedWarnings: {},
/**
* Array of nodes that have an accessibility problem
*
* @property displayedwarnings
* @type Array
* @default null
*/
displayedwarnings: [],
/**
* Display the ui dialogue.
*
* @method init
* @param Event e
* @param string elementid
*/
display_ui : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
var dialogue;
if (!M.atto_accessibilitychecker.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_accessibilitychecker'));
dialogue.render();
} else {
dialogue = M.atto_accessibilitychecker.dialogue;
// Clear the array of previously displayed warnings.
M.atto_accessibilitychecker.displayedwarnings = [];
}
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
dialogue.centerDialogue();
// Add ability to select problem areas in the editor.
Y.all('.accessibilitywarnings li').on('click', function(e) {
e.preventDefault();
var index = e.target.getAttribute("data-index");
var node = M.atto_accessibilitychecker.displayedwarnings[index];
M.atto_accessibilitychecker.dialogue.hide();
if (node) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(node));
}
initializer: function() {
this.addButton({
icon: 'e/accessibility_checker',
callback: this._displayDialogue
});
dialogue.show();
M.atto_accessibilitychecker.dialogue = dialogue;
},
/**
* Add this button to the form.
* Display the Accessibility Checker tool.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/visual_blocks', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'accessibilitychecker', iconurl, params.group, this.display_ui);
_displayDialogue: function() {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENT),
width: '800px',
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var content = Y.Node.create('<div style="word-wrap: break-word;"></div>');
content.append(this._getWarnings());
// Add ability to select problem areas in the editor.
content.delegate('click', function(e) {
e.preventDefault();
var host = this.get('host'),
index = e.target.getAttribute("data-index"),
node = this._displayedWarnings[index],
dialogue = this.getDialogue();
if (node) {
// Clear the dialogue's focusAfterHide to ensure we focus
// on the selection.
dialogue.set('focusAfterHide', null);
// Hide the dialogue.
dialogue.hide();
// Then set the selection.
host.setSelection(host.getSelectionFromNode(node));
} else {
// Hide the dialogue.
dialogue.hide();
}
}, 'a', this);
return content;
},
/**
* Find all problems with the content editable region.
*
* @method _getWarnings
* @return {Node} A complete list of all warnings and problems.
* @private
*/
_getWarnings: function() {
var problemNodes,
list = Y.Node.create('<div></div>');
// Images with no alt text or dodgy alt text.
problemNodes = [];
this.editor.all('img').each(function (img) {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
problemNodes.push(img);
}
}
}, this);
this._addWarnings(list, M.util.get_string('imagesmissingalt', COMPONENT), problemNodes, true);
problemNodes = [];
this.editor.all('*').each(function (node) {
var foreground,
background,
ratio,
lum1,
lum2;
// Check for non-empty text.
if (Y.Lang.trim(node.get('text')) !== '') {
foreground = node.getComputedStyle('color');
background = node.getComputedStyle('backgroundColor');
lum1 = this._getLuminanceFromCssColor(foreground);
lum2 = this._getLuminanceFromCssColor(background);
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
if (lum1 > lum2) {
ratio = (lum1 + 0.05) / (lum2 + 0.05);
} else {
ratio = (lum2 + 0.05) / (lum1 + 0.05);
}
if (ratio <= 4.5) {
Y.log('Contrast ratio is too low: ' + ratio +
' Colour 1: ' + foreground +
' Colour 2: ' + background +
' Luminance 1: ' + lum1 +
' Luminance 2: ' + lum2);
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < problemNodes.length; i++) {
if (node.ancestors('*').indexOf(problemNodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (problemNodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
problemNodes[i] = node;
found = true;
break;
}
}
if (!found) {
problemNodes.push(node);
}
}
}
}, this);
this._addWarnings(list, M.util.get_string('needsmorecontrast', COMPONENT), problemNodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', COMPONENT) + '</p>');
}
// Append the list of current styles.
return list;
},
/**
* Generate the HTML that lists the found warnings.
*
* @method add_warnings
* @param Y.Node list - node to append the html to.
* @param String description - description of this failure.
* @param Y.Node[] nodes - list of failing nodes.
* @param boolean imagewarnings - true if the warnings are related to images, false if text.
* @method _addWarnings
* @param {Node} A Node to append the html to.
* @param {String} description Description of this failure.
* @param {array} nodes An array of failing nodes.
* @param {boolean} imagewarnings true if the warnings are related to images, false if text.
*/
add_warnings : function(list, description, nodes, imagewarnings) {
_addWarnings: function(list, description, nodes, imagewarnings) {
var warning, fails, i, key, src, textfield;
if (nodes.length > 0) {
@@ -128,12 +214,12 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
fails.append(Y.Node.create('<li><a data-index="'+key+'" href="#"><img data-index="'+key+'" src="' + src + '" /> '+src+'</a></li>'));
} else {
key = 'text_'+i;
key = 'text_' + i;
textfield = ('innerText' in nodes[i])? 'innerText' : 'textContent';
fails.append(Y.Node.create('<li><a href="#" data-index="'+key+'">' + nodes[i].get(textfield) + '</a></li>'));
}
M.atto_accessibilitychecker.displayedwarnings[key] = nodes[i];
this._displayedWarnings[key] = nodes[i];
}
warning.append(fails);
@@ -142,13 +228,14 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
},
/**
* Convert a css color to a luminance value.
* Convert a CSS color to a luminance value.
*
* @method get_luminance_from_css_color
* @param {String} colortext
* @return {Integer}
* @method _getLuminanceFromCssColor
* @param {String} colortext The Hex value for the colour
* @return {Number} The luminance value.
* @private
*/
get_luminance_from_css_color : function(colortext) {
_getLuminanceFromCssColor: function(colortext) {
var color;
if (colortext === 'transparent') {
@@ -172,110 +259,8 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
b1 = part1(color[2]);
return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1;
},
/**
* List the accessibility warnings for the current editor
*
* @method list_warnings
* @param string elementid
* @return String
*/
list_warnings : function(elementid) {
var list = Y.Node.create('<div></div>');
var editable = M.editor_atto.get_editable_node(elementid);
var problemnodes = [];
// Images with no alt text or dodgy alt text.
var alt;
editable.all('img').each(function (img) {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
problemnodes.push(img);
}
}
}, this);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), problemnodes, true);
// Contrast ratios.
problemnodes = [];
var foreground, background, lum1, lum2, ratio;
editable.all('*').each(function (node) {
// Check for non-empty text.
if (Y.Lang.trim(node.get('text')) !== '') {
foreground = node.getComputedStyle('color');
background = node.getComputedStyle('backgroundColor');
lum1 = this.get_luminance_from_css_color(foreground);
lum2 = this.get_luminance_from_css_color(background);
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
if (lum1 > lum2) {
ratio = (lum1 + 0.05) / (lum2 + 0.05);
} else {
ratio = (lum2 + 0.05) / (lum1 + 0.05);
}
if (ratio <= 4.5) {
Y.log('Contrast ratio is too low: ' + ratio +
' Colour 1: ' + foreground +
' Colour 2: ' + background +
' Luminance 1: ' + lum1 +
' Luminance 2: ' + lum2);
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < problemnodes.length; i++) {
if (node.ancestors('*').indexOf(problemnodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (problemnodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
problemnodes[i] = node;
found = true;
break;
}
}
if (!found) {
problemnodes.push(node);
}
}
}
}, this);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), problemnodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', 'atto_accessibilitychecker') + '</p>');
}
// Append the list of current styles.
return list;
},
/**
* Return the HTML of the form to show in the dialogue.
*
* @method get_report
* @param string elementid
* @return string
*/
get_report : function(elementid) {
// Current styles.
var html = '<div style="word-wrap: break-word;"></div>';
var content = Y.Node.create(html);
content.append(this.list_warnings(elementid));
return content;
}
};
});
}, '@VERSION@', {"requires": ["node", "escape", "color-base"]});
}, '@VERSION@', {"requires": ["color-base", "moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){M.atto_accessibilitychecker=M.atto_accessibilitychecker||{dialogue:null,displayedwarnings:[],display_ui:function(t,n){t.preventDefault(),M.editor_atto.is_active(n)||M.editor_atto.focus(n);var r;M.atto_accessibilitychecker.dialogue?(r=M.atto_accessibilitychecker.dialogue,M.atto_accessibilitychecker.displayedwarnings=[]):(r=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0,width:"800px"}),r.set("headerContent",M.util.get_string("pluginname","atto_accessibilitychecker")),r.render()),r.set("bodyContent",M.atto_accessibilitychecker.get_report(n)),r.centerDialogue(),e.all(".accessibilitywarnings li").on("click",function(e){e.preventDefault();var t=e.target.getAttribute("data-index"),n=M.atto_accessibilitychecker.displayedwarnings[t];M.atto_accessibilitychecker.dialogue.hide(),n&&M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(n))}),r.show(),M.atto_accessibilitychecker.dialogue=r},init:function(e){var t=M.util.image_url("e/visual_blocks","core");M.editor_atto.add_toolbar_button(e.elementid,"accessibilitychecker",t,e.group,this.display_ui)},add_warnings:function(t,n,r,i){var s,o,u,a,f,l;if(r.length>0){s=e.Node.create("<p>"+n+"</p>"),o=e.Node.create('<ol class="accessibilitywarnings"></ol>'),u=0;for(u=0;u<r.length;u++)i?(a="image_"+u,f=r[u].getAttribute("src"),o.append(e.Node.create('<li><a data-index="'+a+'" href="#"><img data-index="'+a+'" src="'+f+'" /> '+f+"</a></li>"))):(a="text_"+u,l="innerText"in r[u]?"innerText":"textContent",o.append(e.Node.create('<li><a href="#" data-index="'+a+'">'+r[u].get(l)+"</a></li>"))),M.atto_accessibilitychecker.displayedwarnings[a]=r[u];s.append(o),t.append(s)}},get_luminance_from_css_color:function(t){var n;t==="transparent"&&(t="#ffffff"),n=e.Color.toArray(e.Color.toRGB(t));var r=function(e){return e=parseInt(e,10)/255,e<=.03928?e/=12.92:e=Math.pow((e+.055)/1.055,2.4),e},i=r(n[0]),s=r(n[1]),o=r(n[2]);return.2126*i+.7152*s+.0722*o},list_warnings:function(t){var n=e.Node.create("<div></div>"),r=M.editor_atto.get_editable_node(t),i=[],s;r.all("img").each(function(e){s=e.getAttribute("alt"),(typeof s=="undefined"||s==="")&&e.getAttribute("role")!=="presentation"&&i.push(e)},this),this.add_warnings(n,M.util.get_string("imagesmissingalt","atto_accessibilitychecker"),i,!0),i=[];var o,u,a,f,l;return r.all("*").each(function(t){if(e.Lang.trim(t.get("text"))!==""){o=t.getComputedStyle("color"),u=t.getComputedStyle("backgroundColor"),a=this.get_luminance_from_css_color(o),f=this.get_luminance_from_css_color(u),a>f?l=(a+.05)/(f+.05):l=(f+.05)/(a+.05);if(l<=4.5){var n=0,r=!1;for(n=0;n<i.length;n++){if(t.ancestors("*").indexOf(i[n])!==-1){r=!0;break}if(i[n].ancestors("*").indexOf(t)!==-1){i[n]=t,r=!0;break}}r||i.push(t)}}},this),this.add_warnings(n,M.util.get_string("needsmorecontrast","atto_accessibilitychecker"),i,!1),n.hasChildNodes()||n.append("<p>"+M.util.get_string("nowarnings","atto_accessibilitychecker")+"</p>"),n},get_report:function(t){var n='<div style="word-wrap: break-word;"></div>',r=e.Node.create(n);return r.append(this.list_warnings(t)),r}}},"@VERSION@",{requires:["node","escape","color-base"]});
YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){var n="atto_accessibilitychecker";e.namespace("M.atto_accessibilitychecker").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_displayedWarnings:{},initializer:function(){this.addButton({icon:"e/accessibility_checker",callback:this._displayDialogue})},_displayDialogue:function(){var e=this.getDialogue({headerContent:M.util.get_string("pluginname",n),width:"800px",focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()},_getDialogueContent:function(){var t=e.Node.create('<div style="word-wrap: break-word;"></div>');return t.append(this._getWarnings()),t.delegate("click",function(e){e.preventDefault();var t=this.get("host"),n=e.target.getAttribute("data-index"),r=this._displayedWarnings[n],i=this.getDialogue();r?(i.set("focusAfterHide",null),i.hide(),t.setSelection(t.getSelectionFromNode(r))):i.hide()},"a",this),t},_getWarnings:function(){var t,r=e.Node.create("<div></div>");return t=[],this.editor.all("img").each(function(e){alt=e.getAttribute("alt"),(typeof alt=="undefined"||alt==="")&&e.getAttribute("role")!=="presentation"&&t.push(e)},this),this._addWarnings(r,M.util.get_string("imagesmissingalt",n),t,!0),t=[],this.editor.all("*").each(function(n){var r,i,s,o,u;if(e.Lang.trim(n.get("text"))!==""){r=n.getComputedStyle("color"),i=n.getComputedStyle("backgroundColor"),o=this._getLuminanceFromCssColor(r),u=this._getLuminanceFromCssColor(i),o>u?s=(o+.05)/(u+.05):s=(u+.05)/(o+.05);if(s<=4.5){var a=0,f=!1;for(a=0;a<t.length;a++){if(n.ancestors("*").indexOf(t[a])!==-1){f=!0;break}if(t[a].ancestors("*").indexOf(n)!==-1){t[a]=n,f=!0;break}}f||t.push(n)}}},this),this._addWarnings(r,M.util.get_string("needsmorecontrast",n),t,!1),r.hasChildNodes()||r.append("<p>"+M.util.get_string("nowarnings",n)+"</p>"),r},_addWarnings:function(t,n,r,i){var s,o,u,a,f,l;if(r.length>0){s=e.Node.create("<p>"+n+"</p>"),o=e.Node.create('<ol class="accessibilitywarnings"></ol>'),u=0;for(u=0;u<r.length;u++)i?(a="image_"+u,f=r[u].getAttribute("src"),o.append(e.Node.create('<li><a data-index="'+a+'" href="#"><img data-index="'+a+'" src="'+f+'" /> '+f+"</a></li>"))):(a="text_"+u,l="innerText"in r[u]?"innerText":"textContent",o.append(e.Node.create('<li><a href="#" data-index="'+a+'">'+r[u].get(l)+"</a></li>"))),this._displayedWarnings[a]=r[u];s.append(o),t.append(s)}},_getLuminanceFromCssColor:function(t){var n;t==="transparent"&&(t="#ffffff"),n=e.Color.toArray(e.Color.toRGB(t));var r=function(e){return e=parseInt(e,10)/255,e<=.03928?e/=12.92:e=Math.pow((e+.055)/1.055,2.4),e},i=r(n[0]),s=r(n[1]),o=r(n[2]);return.2126*i+.7152*s+.0722*o}})},"@VERSION@",{requires:["color-base","moodle-editor_atto-plugin"]});
@@ -15,106 +15,187 @@ YUI.add('moodle-atto_accessibilitychecker-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor accessibilitychecker plugin.
*
* This plugin adds some functions to do things that screen readers do not do well.
* Specifically, listing the active styles for the selected text,
* listing the images in the page, listing the links in the page.
*
/*
* @package atto_accessibilitychecker
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
/**
* @module moodle-atto_accessibilitychecker-button
*/
/**
* Accessibility Checking tool for the Atto editor.
*
* @namespace M.atto_accessibilitychecker
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENT = 'atto_accessibilitychecker';
Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The window used to display the accessibility ui.
* The warnings which are displayed.
*
* @property dialogue
* @type M.core.dialogue
* @default null
* @property _displayedWarnings
* @type Object
* @private
*/
dialogue : null,
_displayedWarnings: {},
/**
* Array of nodes that have an accessibility problem
*
* @property displayedwarnings
* @type Array
* @default null
*/
displayedwarnings: [],
/**
* Display the ui dialogue.
*
* @method init
* @param Event e
* @param string elementid
*/
display_ui : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
var dialogue;
if (!M.atto_accessibilitychecker.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_accessibilitychecker'));
dialogue.render();
} else {
dialogue = M.atto_accessibilitychecker.dialogue;
// Clear the array of previously displayed warnings.
M.atto_accessibilitychecker.displayedwarnings = [];
}
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
dialogue.centerDialogue();
// Add ability to select problem areas in the editor.
Y.all('.accessibilitywarnings li').on('click', function(e) {
e.preventDefault();
var index = e.target.getAttribute("data-index");
var node = M.atto_accessibilitychecker.displayedwarnings[index];
M.atto_accessibilitychecker.dialogue.hide();
if (node) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(node));
}
initializer: function() {
this.addButton({
icon: 'e/accessibility_checker',
callback: this._displayDialogue
});
dialogue.show();
M.atto_accessibilitychecker.dialogue = dialogue;
},
/**
* Add this button to the form.
* Display the Accessibility Checker tool.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/visual_blocks', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'accessibilitychecker', iconurl, params.group, this.display_ui);
_displayDialogue: function() {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENT),
width: '800px',
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var content = Y.Node.create('<div style="word-wrap: break-word;"></div>');
content.append(this._getWarnings());
// Add ability to select problem areas in the editor.
content.delegate('click', function(e) {
e.preventDefault();
var host = this.get('host'),
index = e.target.getAttribute("data-index"),
node = this._displayedWarnings[index],
dialogue = this.getDialogue();
if (node) {
// Clear the dialogue's focusAfterHide to ensure we focus
// on the selection.
dialogue.set('focusAfterHide', null);
// Hide the dialogue.
dialogue.hide();
// Then set the selection.
host.setSelection(host.getSelectionFromNode(node));
} else {
// Hide the dialogue.
dialogue.hide();
}
}, 'a', this);
return content;
},
/**
* Find all problems with the content editable region.
*
* @method _getWarnings
* @return {Node} A complete list of all warnings and problems.
* @private
*/
_getWarnings: function() {
var problemNodes,
list = Y.Node.create('<div></div>');
// Images with no alt text or dodgy alt text.
problemNodes = [];
this.editor.all('img').each(function (img) {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
problemNodes.push(img);
}
}
}, this);
this._addWarnings(list, M.util.get_string('imagesmissingalt', COMPONENT), problemNodes, true);
problemNodes = [];
this.editor.all('*').each(function (node) {
var foreground,
background,
ratio,
lum1,
lum2;
// Check for non-empty text.
if (Y.Lang.trim(node.get('text')) !== '') {
foreground = node.getComputedStyle('color');
background = node.getComputedStyle('backgroundColor');
lum1 = this._getLuminanceFromCssColor(foreground);
lum2 = this._getLuminanceFromCssColor(background);
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
if (lum1 > lum2) {
ratio = (lum1 + 0.05) / (lum2 + 0.05);
} else {
ratio = (lum2 + 0.05) / (lum1 + 0.05);
}
if (ratio <= 4.5) {
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < problemNodes.length; i++) {
if (node.ancestors('*').indexOf(problemNodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (problemNodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
problemNodes[i] = node;
found = true;
break;
}
}
if (!found) {
problemNodes.push(node);
}
}
}
}, this);
this._addWarnings(list, M.util.get_string('needsmorecontrast', COMPONENT), problemNodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', COMPONENT) + '</p>');
}
// Append the list of current styles.
return list;
},
/**
* Generate the HTML that lists the found warnings.
*
* @method add_warnings
* @param Y.Node list - node to append the html to.
* @param String description - description of this failure.
* @param Y.Node[] nodes - list of failing nodes.
* @param boolean imagewarnings - true if the warnings are related to images, false if text.
* @method _addWarnings
* @param {Node} A Node to append the html to.
* @param {String} description Description of this failure.
* @param {array} nodes An array of failing nodes.
* @param {boolean} imagewarnings true if the warnings are related to images, false if text.
*/
add_warnings : function(list, description, nodes, imagewarnings) {
_addWarnings: function(list, description, nodes, imagewarnings) {
var warning, fails, i, key, src, textfield;
if (nodes.length > 0) {
@@ -128,12 +209,12 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
fails.append(Y.Node.create('<li><a data-index="'+key+'" href="#"><img data-index="'+key+'" src="' + src + '" /> '+src+'</a></li>'));
} else {
key = 'text_'+i;
key = 'text_' + i;
textfield = ('innerText' in nodes[i])? 'innerText' : 'textContent';
fails.append(Y.Node.create('<li><a href="#" data-index="'+key+'">' + nodes[i].get(textfield) + '</a></li>'));
}
M.atto_accessibilitychecker.displayedwarnings[key] = nodes[i];
this._displayedWarnings[key] = nodes[i];
}
warning.append(fails);
@@ -142,13 +223,14 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
},
/**
* Convert a css color to a luminance value.
* Convert a CSS color to a luminance value.
*
* @method get_luminance_from_css_color
* @param {String} colortext
* @return {Integer}
* @method _getLuminanceFromCssColor
* @param {String} colortext The Hex value for the colour
* @return {Number} The luminance value.
* @private
*/
get_luminance_from_css_color : function(colortext) {
_getLuminanceFromCssColor: function(colortext) {
var color;
if (colortext === 'transparent') {
@@ -172,105 +254,8 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
b1 = part1(color[2]);
return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1;
},
/**
* List the accessibility warnings for the current editor
*
* @method list_warnings
* @param string elementid
* @return String
*/
list_warnings : function(elementid) {
var list = Y.Node.create('<div></div>');
var editable = M.editor_atto.get_editable_node(elementid);
var problemnodes = [];
// Images with no alt text or dodgy alt text.
var alt;
editable.all('img').each(function (img) {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
problemnodes.push(img);
}
}
}, this);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), problemnodes, true);
// Contrast ratios.
problemnodes = [];
var foreground, background, lum1, lum2, ratio;
editable.all('*').each(function (node) {
// Check for non-empty text.
if (Y.Lang.trim(node.get('text')) !== '') {
foreground = node.getComputedStyle('color');
background = node.getComputedStyle('backgroundColor');
lum1 = this.get_luminance_from_css_color(foreground);
lum2 = this.get_luminance_from_css_color(background);
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
if (lum1 > lum2) {
ratio = (lum1 + 0.05) / (lum2 + 0.05);
} else {
ratio = (lum2 + 0.05) / (lum1 + 0.05);
}
if (ratio <= 4.5) {
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < problemnodes.length; i++) {
if (node.ancestors('*').indexOf(problemnodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (problemnodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
problemnodes[i] = node;
found = true;
break;
}
}
if (!found) {
problemnodes.push(node);
}
}
}
}, this);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), problemnodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', 'atto_accessibilitychecker') + '</p>');
}
// Append the list of current styles.
return list;
},
/**
* Return the HTML of the form to show in the dialogue.
*
* @method get_report
* @param string elementid
* @return string
*/
get_report : function(elementid) {
// Current styles.
var html = '<div style="word-wrap: break-word;"></div>';
var content = Y.Node.create(html);
content.append(this.list_warnings(elementid));
return content;
}
};
});
}, '@VERSION@', {"requires": ["node", "escape", "color-base"]});
}, '@VERSION@', {"requires": ["color-base", "moodle-editor_atto-plugin"]});
@@ -13,106 +13,192 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor accessibilitychecker plugin.
*
* This plugin adds some functions to do things that screen readers do not do well.
* Specifically, listing the active styles for the selected text,
* listing the images in the page, listing the links in the page.
*
/*
* @package atto_accessibilitychecker
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
/**
* @module moodle-atto_accessibilitychecker-button
*/
/**
* Accessibility Checking tool for the Atto editor.
*
* @namespace M.atto_accessibilitychecker
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENT = 'atto_accessibilitychecker';
Y.namespace('M.atto_accessibilitychecker').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The window used to display the accessibility ui.
* The warnings which are displayed.
*
* @property dialogue
* @type M.core.dialogue
* @default null
* @property _displayedWarnings
* @type Object
* @private
*/
dialogue : null,
_displayedWarnings: {},
/**
* Array of nodes that have an accessibility problem
*
* @property displayedwarnings
* @type Array
* @default null
*/
displayedwarnings: [],
/**
* Display the ui dialogue.
*
* @method init
* @param Event e
* @param string elementid
*/
display_ui : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
var dialogue;
if (!M.atto_accessibilitychecker.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_accessibilitychecker'));
dialogue.render();
} else {
dialogue = M.atto_accessibilitychecker.dialogue;
// Clear the array of previously displayed warnings.
M.atto_accessibilitychecker.displayedwarnings = [];
}
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
dialogue.centerDialogue();
// Add ability to select problem areas in the editor.
Y.all('.accessibilitywarnings li').on('click', function(e) {
e.preventDefault();
var index = e.target.getAttribute("data-index");
var node = M.atto_accessibilitychecker.displayedwarnings[index];
M.atto_accessibilitychecker.dialogue.hide();
if (node) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(node));
}
initializer: function() {
this.addButton({
icon: 'e/accessibility_checker',
callback: this._displayDialogue
});
dialogue.show();
M.atto_accessibilitychecker.dialogue = dialogue;
},
/**
* Add this button to the form.
* Display the Accessibility Checker tool.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/visual_blocks', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'accessibilitychecker', iconurl, params.group, this.display_ui);
_displayDialogue: function() {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENT),
width: '800px',
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var content = Y.Node.create('<div style="word-wrap: break-word;"></div>');
content.append(this._getWarnings());
// Add ability to select problem areas in the editor.
content.delegate('click', function(e) {
e.preventDefault();
var host = this.get('host'),
index = e.target.getAttribute("data-index"),
node = this._displayedWarnings[index],
dialogue = this.getDialogue();
if (node) {
// Clear the dialogue's focusAfterHide to ensure we focus
// on the selection.
dialogue.set('focusAfterHide', null);
// Hide the dialogue.
dialogue.hide();
// Then set the selection.
host.setSelection(host.getSelectionFromNode(node));
} else {
// Hide the dialogue.
dialogue.hide();
}
}, 'a', this);
return content;
},
/**
* Find all problems with the content editable region.
*
* @method _getWarnings
* @return {Node} A complete list of all warnings and problems.
* @private
*/
_getWarnings: function() {
var problemNodes,
list = Y.Node.create('<div></div>');
// Images with no alt text or dodgy alt text.
problemNodes = [];
this.editor.all('img').each(function (img) {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
problemNodes.push(img);
}
}
}, this);
this._addWarnings(list, M.util.get_string('imagesmissingalt', COMPONENT), problemNodes, true);
problemNodes = [];
this.editor.all('*').each(function (node) {
var foreground,
background,
ratio,
lum1,
lum2;
// Check for non-empty text.
if (Y.Lang.trim(node.get('text')) !== '') {
foreground = node.getComputedStyle('color');
background = node.getComputedStyle('backgroundColor');
lum1 = this._getLuminanceFromCssColor(foreground);
lum2 = this._getLuminanceFromCssColor(background);
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
if (lum1 > lum2) {
ratio = (lum1 + 0.05) / (lum2 + 0.05);
} else {
ratio = (lum2 + 0.05) / (lum1 + 0.05);
}
if (ratio <= 4.5) {
Y.log('Contrast ratio is too low: ' + ratio +
' Colour 1: ' + foreground +
' Colour 2: ' + background +
' Luminance 1: ' + lum1 +
' Luminance 2: ' + lum2);
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < problemNodes.length; i++) {
if (node.ancestors('*').indexOf(problemNodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (problemNodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
problemNodes[i] = node;
found = true;
break;
}
}
if (!found) {
problemNodes.push(node);
}
}
}
}, this);
this._addWarnings(list, M.util.get_string('needsmorecontrast', COMPONENT), problemNodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', COMPONENT) + '</p>');
}
// Append the list of current styles.
return list;
},
/**
* Generate the HTML that lists the found warnings.
*
* @method add_warnings
* @param Y.Node list - node to append the html to.
* @param String description - description of this failure.
* @param Y.Node[] nodes - list of failing nodes.
* @param boolean imagewarnings - true if the warnings are related to images, false if text.
* @method _addWarnings
* @param {Node} A Node to append the html to.
* @param {String} description Description of this failure.
* @param {array} nodes An array of failing nodes.
* @param {boolean} imagewarnings true if the warnings are related to images, false if text.
*/
add_warnings : function(list, description, nodes, imagewarnings) {
_addWarnings: function(list, description, nodes, imagewarnings) {
var warning, fails, i, key, src, textfield;
if (nodes.length > 0) {
@@ -126,12 +212,12 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
fails.append(Y.Node.create('<li><a data-index="'+key+'" href="#"><img data-index="'+key+'" src="' + src + '" /> '+src+'</a></li>'));
} else {
key = 'text_'+i;
key = 'text_' + i;
textfield = ('innerText' in nodes[i])? 'innerText' : 'textContent';
fails.append(Y.Node.create('<li><a href="#" data-index="'+key+'">' + nodes[i].get(textfield) + '</a></li>'));
}
M.atto_accessibilitychecker.displayedwarnings[key] = nodes[i];
this._displayedWarnings[key] = nodes[i];
}
warning.append(fails);
@@ -140,13 +226,14 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
},
/**
* Convert a css color to a luminance value.
* Convert a CSS color to a luminance value.
*
* @method get_luminance_from_css_color
* @param {String} colortext
* @return {Integer}
* @method _getLuminanceFromCssColor
* @param {String} colortext The Hex value for the colour
* @return {Number} The luminance value.
* @private
*/
get_luminance_from_css_color : function(colortext) {
_getLuminanceFromCssColor: function(colortext) {
var color;
if (colortext === 'transparent') {
@@ -170,107 +257,5 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
b1 = part1(color[2]);
return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1;
},
/**
* List the accessibility warnings for the current editor
*
* @method list_warnings
* @param string elementid
* @return String
*/
list_warnings : function(elementid) {
var list = Y.Node.create('<div></div>');
var editable = M.editor_atto.get_editable_node(elementid);
var problemnodes = [];
// Images with no alt text or dodgy alt text.
var alt;
editable.all('img').each(function (img) {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
problemnodes.push(img);
}
}
}, this);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), problemnodes, true);
// Contrast ratios.
problemnodes = [];
var foreground, background, lum1, lum2, ratio;
editable.all('*').each(function (node) {
// Check for non-empty text.
if (Y.Lang.trim(node.get('text')) !== '') {
foreground = node.getComputedStyle('color');
background = node.getComputedStyle('backgroundColor');
lum1 = this.get_luminance_from_css_color(foreground);
lum2 = this.get_luminance_from_css_color(background);
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
if (lum1 > lum2) {
ratio = (lum1 + 0.05) / (lum2 + 0.05);
} else {
ratio = (lum2 + 0.05) / (lum1 + 0.05);
}
if (ratio <= 4.5) {
Y.log('Contrast ratio is too low: ' + ratio +
' Colour 1: ' + foreground +
' Colour 2: ' + background +
' Luminance 1: ' + lum1 +
' Luminance 2: ' + lum2);
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < problemnodes.length; i++) {
if (node.ancestors('*').indexOf(problemnodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (problemnodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
problemnodes[i] = node;
found = true;
break;
}
}
if (!found) {
problemnodes.push(node);
}
}
}
}, this);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), problemnodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', 'atto_accessibilitychecker') + '</p>');
}
// Append the list of current styles.
return list;
},
/**
* Return the HTML of the form to show in the dialogue.
*
* @method get_report
* @param string elementid
* @return string
*/
get_report : function(elementid) {
// Current styles.
var html = '<div style="word-wrap: break-word;"></div>';
var content = Y.Node.create(html);
content.append(this.list_warnings(elementid));
return content;
}
};
});
@@ -1,5 +1,8 @@
{
"moodle-atto_accessibilitychecker-button": {
"requires": ["node", "escape", "color-base"]
}
"moodle-atto_accessibilitychecker-button": {
"requires": [
"color-base",
"moodle-editor_atto-plugin"
]
}
}
@@ -15,30 +15,16 @@ YUI.add('moodle-atto_accessibilityhelper-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS classes and IDs.
*
* @type {Object}
*/
var CSS = {
STYLESLABEL: 'atto_accessibilityhelper_styleslabel',
LISTSTYLES: 'atto_accessibilityhelper_liststyles',
LINKSLABEL: 'atto_accessibilityhelper_linkslabel',
LISTLINKS: 'atto_accessibilityhelper_listlinks',
IMAGESLABEL: 'atto_accessibilityhelper_imageslabel',
LISTIMAGES: 'atto_accessibilityhelper_listimages'
};
/**
* Selectors.
*
* @type {Object}
/*
* @package atto_accessibilityhelper
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var SELECTORS = {
LISTSTYLES: '#atto_accessibilityhelper_liststyles',
LISTLINKS: '#atto_accessibilityhelper_listlinks',
LISTIMAGES: '#atto_accessibilityhelper_listimages'
};
/**
* @module moodle-atto_accessibilityhelper-button
*/
/**
* Atto text editor accessibilityhelper plugin.
*
@@ -46,99 +32,209 @@ var SELECTORS = {
* Specifically, listing the active styles for the selected text,
* listing the images in the page, listing the links in the page.
*
* @package editor-atto
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @namespace M.atto_accessibilityhelper
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
/**
* The window used to display the accessibility ui.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
var COMPONENT = 'atto_accessibilityhelper',
TEMPLATE = '' +
// The list of styles.
'<div><p id="{{elementid}}_{{CSS.STYLESLABEL}}">' +
'{{get_string "liststyles" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.STYLESLABEL}}" />' +
'</p></div>' +
'<span class="listStyles"></span>' +
'<p id="{{elementid}}_{{CSS.LINKSLABEL}}">' +
'{{get_string "listlinks" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.LINKSLABEL}}"/>' +
'</p>' +
'<span class="listLinks"></span>' +
'<p id="{{elementid}}_{{CSS.IMAGESLABEL}}">' +
'{{get_string "listimages" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.IMAGESLABEL}}"/>' +
'</p>' +
'<span class="listImages"></span>',
CSS = {
STYLESLABEL: COMPONENT + '_styleslabel',
LINKSLABEL: COMPONENT + '_linkslabel',
IMAGESLABEL: COMPONENT + '_imageslabel'
};
Y.namespace('M.atto_accessibilityhelper').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* Display the ui dialogue.
* The warnings which are displayed.
*
* @method init
* @param Event e
* @param string elementid
* @property _displayedWarnings
* @type Object
* @private
*/
display_ui : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
var dialogue;
if (!M.atto_accessibilityhelper.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_accessibilityhelper'));
dialogue.render();
} else {
dialogue = M.atto_accessibilityhelper.dialogue;
_displayedWarnings: {},
initializer: function() {
this.addButton({
icon: 'e/screenreader_helper',
callback: this._displayDialogue
});
},
/**
* Display the Accessibility Helper tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENT),
width: '800px',
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
CSS: CSS,
component: COMPONENT
}));
// Add the data.
content.one('.listStyles')
.empty()
.appendChild(this._listStyles());
content.one('.listLinks')
.empty()
.appendChild(this._listLinks());
content.one('.listImages')
.empty()
.appendChild(this._listImages());
// Add ability to select problem areas in the editor.
content.delegate('click', function(e) {
e.preventDefault();
var host = this.get('host'),
index = e.target.getAttribute("data-index"),
node = this._displayedWarnings[index],
dialogue = this.getDialogue();
if (node) {
// Clear the dialogue's focusAfterHide to ensure we focus
// on the selection.
dialogue.set('focusAfterHide', null);
host.setSelection(host.getSelectionFromNode(node));
}
// Hide the dialogue.
dialogue.hide();
}, 'a', this);
return content;
},
/**
* List the styles present for the selection.
*
* @method _listStyles
* @return {String} The list of styles in use.
* @private
*/
_listStyles: function() {
// Clear the status node.
var list = [],
host = this.get('host'),
current = host.getSelectionParentNode(),
tagname;
if (current) {
current = Y.one(current);
}
dialogue.set('bodyContent', M.atto_accessibilityhelper.get_content(elementid));
dialogue.centerDialogue();
while (current && (current !== this.editor)) {
tagname = current.get('tagName');
if (typeof tagname !== 'undefined') {
list.push(Y.Escape.html(tagname));
}
current = current.ancestor();
}
if (list.length === 0) {
list.push(M.util.get_string('nostyles', COMPONENT));
}
dialogue.show();
M.atto_accessibilityhelper.dialogue = dialogue;
list.reverse();
// Append the list of current styles.
return list.join(', ');
},
/**
* Add this button to the form.
* List the links for the current editor
*
* @method init
* @param {Object} params
* @method _listLinks
* @return {string}
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/visual_aid', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'accessibilityhelper', iconurl, params.group, this.display_ui);
_listLinks: function() {
var list = Y.Node.create('<ol />'),
listitem,
selectlink;
this.editor.all('a').each(function(link) {
selectlink = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectlink', COMPONENT) + '">' +
Y.Escape.html(link.get('text')) +
'</a>');
selectlink.setData('sourcelink', link);
selectlink.on('click', this._linkSelected, this);
listitem = Y.Node.create('<li></li>');
listitem.appendChild(selectlink);
list.appendChild(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('nolinks', COMPONENT) + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* Event handler for selecting an image.
* List the images used in the editor.
*
* @method image_selected
* @param Event e
* @param string elementid
* @method _listImages
* @return {Node} A Node containing all of the images present in the editor.
* @private
*/
image_selected : function(e, elementid) {
e.preventDefault();
M.atto_accessibilityhelper.dialogue.hide();
var image = e.target.getData('sourceimage');
var selection = M.editor_atto.get_selection_from_node(image);
M.editor_atto.selections[elementid] = selection;
M.editor_atto.focus(elementid);
},
/**
* List the images for the current editor
*
* @method list_images
* @param string elementid
* @return String
*/
list_images : function(elementid) {
var list = Y.Node.create('<ol/>');
var editable = M.editor_atto.get_editable_node(elementid),
listitem, selectimage;
editable.all('img').each(function(image) {
_listImages: function() {
var list = Y.Node.create('<ol/>'),
listitem,
selectimage;
this.editor.all('img').each(function(image) {
// Get the alt or title or img url of the image.
var imgalt = image.getAttribute('alt');
if (imgalt === '') {
@@ -149,156 +245,67 @@ M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
}
selectimage = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectimage', 'atto_accessibilityhelper') + '">' +
Y.Escape.html(imgalt) +
'</a>');
M.util.get_string('selectimage', COMPONENT) + '">' +
Y.Escape.html(imgalt) +
'</a>');
selectimage.setData('sourceimage', image);
selectimage.on('click', this.image_selected, this, elementid);
selectimage.on('click', this._imageSelected, this);
listitem = Y.Node.create('<li></li>');
listitem.append(selectimage);
list.append(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('noimages', 'atto_accessibilityhelper') + '</li>');
list.append('<li>' + M.util.get_string('noimages', COMPONENT) + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* Event handler for selecting an image.
*
* @method _imageSelected
* @param {EventFacade} e
* @private
*/
_imageSelected: function(e) {
e.preventDefault();
this.getDialogue({
focusAfterNode: null
}).hide();
var host = this.get('host'),
target = e.target.getData('sourceimage');
this.editor.focus();
host.setSelection(host.getSelectionFromNode(target));
},
/**
* Event handler for selecting a link.
*
* @method link_selected
* @param Event e
* @param string elementid
* @method _linkSelected
* @param {EventFacade} e
* @private
*/
link_selected : function(e, elementid) {
_linkSelected: function(e) {
e.preventDefault();
M.atto_accessibilityhelper.dialogue.hide();
this.getDialogue({
focusAfterNode: null
}).hide();
var link = e.target.getData('sourcelink');
var selection = M.editor_atto.get_selection_from_node(link);
var host = this.get('host'),
target = e.target.getData('sourcelink');
M.editor_atto.selections[elementid] = selection;
M.editor_atto.focus(elementid);
},
/**
* List the links for the current editor
*
* @method list_links
* @param string elementid
* @return String
*/
list_links : function(elementid) {
var list = Y.Node.create('<ol/>');
var editable = M.editor_atto.get_editable_node(elementid),
listitem, selectlink;
editable.all('a').each(function(link) {
selectlink = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectlink', 'atto_accessibilityhelper') + '">' +
Y.Escape.html(link.get('text')) +
'</a>');
selectlink.setData('sourcelink', link);
selectlink.on('click', this.link_selected, this, elementid);
listitem = Y.Node.create('<li></li>');
listitem.append(selectlink);
list.append(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('nolinks', 'atto_accessibilityhelper') + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* List the styles for the current selection.
*
* @method list_styles
* @param string elementid
* @return String
*/
list_styles : function(elementid) {
// Clear the status node.
var list = [];
var current = M.editor_atto.get_selection_parent_node();
var editable = M.editor_atto.get_editable_node(elementid);
var tagname;
if (current) {
current = Y.one(current);
}
while (current && (current !== editable)) {
tagname = current.get('tagName');
if (typeof tagname !== 'undefined') {
list.push(Y.Escape.html(tagname));
}
current = current.ancestor();
}
if (list.length === 0) {
list.push(M.util.get_string('nostyles', 'atto_accessibilityhelper'));
}
list.reverse();
// Append the list of current styles.
return list.join(', ');
},
/**
* Return the HTML of the form to show in the dialogue.
*
* @method get_content
* @param string elementid
* @return string
*/
get_content : function(elementid) {
// Current styles.
var html = '<div><p id="' + CSS.STYLESLABEL + '">' +
M.util.get_string('liststyles', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTSTYLES + '" ' +
'aria-labelledby="' + CSS.STYLESLABEL + '"/></p></div>';
var content = Y.Node.create(html);
content.one(SELECTORS.LISTSTYLES).append(this.list_styles(elementid));
// Current links.
html = '<p id="' + CSS.LINKSLABEL + '">' +
M.util.get_string('listlinks', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTLINKS + '" ' +
'aria-labelledby="' + CSS.LINKSLABEL + '"/></p>';
content.append(html);
content.one(SELECTORS.LISTLINKS).append(this.list_links(elementid));
// Current images.
html = '<p id="' + CSS.IMAGESLABEL + '">' +
M.util.get_string('listimages', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTIMAGES + '" ' +
'aria-labelledby="' + CSS.IMAGESLABEL + '"/></p>';
content.append(html);
content.one(SELECTORS.LISTIMAGES).append(this.list_images(elementid));
return content;
this.editor.focus();
host.setSelection(host.getSelectionFromNode(target));
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_accessibilityhelper-button",function(e,t){var n={STYLESLABEL:"atto_accessibilityhelper_styleslabel",LISTSTYLES:"atto_accessibilityhelper_liststyles",LINKSLABEL:"atto_accessibilityhelper_linkslabel",LISTLINKS:"atto_accessibilityhelper_listlinks",IMAGESLABEL:"atto_accessibilityhelper_imageslabel",LISTIMAGES:"atto_accessibilityhelper_listimages"},r={LISTSTYLES:"#atto_accessibilityhelper_liststyles",LISTLINKS:"#atto_accessibilityhelper_listlinks",LISTIMAGES:"#atto_accessibilityhelper_listimages"};M.atto_accessibilityhelper=M.atto_accessibilityhelper||{dialogue:null,display_ui:function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t);var n;M.atto_accessibilityhelper.dialogue?n=M.atto_accessibilityhelper.dialogue:(n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0}),n.set("headerContent",M.util.get_string("pluginname","atto_accessibilityhelper")),n.render()),n.set("bodyContent",M.atto_accessibilityhelper.get_content(t)),n.centerDialogue(),n.show(),M.atto_accessibilityhelper.dialogue=n},init:function(e){var t=M.util.image_url("e/visual_aid","core");M.editor_atto.add_toolbar_button(e.elementid,"accessibilityhelper",t,e.group,this.display_ui)},image_selected:function(e,t){e.preventDefault(),M.atto_accessibilityhelper.dialogue.hide();var n=e.target.getData("sourceimage"),r=M.editor_atto.get_selection_from_node(n);M.editor_atto.selections[t]=r,M.editor_atto.focus(t)},list_images:function(t){var n=e.Node.create("<ol/>"),r=M.editor_atto.get_editable_node(t),i,s;return r.all("img").each(function(r){var o=r.getAttribute("alt");o===""&&(o=r.getAttribute("title"),o===""&&(o=r.getAttribute("src"))),s=e.Node.create('<a href="#" title="'+M.util.get_string("selectimage","atto_accessibilityhelper")+'">'+e.Escape.html(o)+"</a>"),s.setData("sourceimage",r),s.on("click",this.image_selected,this,t),i=e.Node.create("<li></li>"),i.append(s),n.append(i)},this),n.hasChildNodes()||n.append("<li>"+M.util.get_string("noimages","atto_accessibilityhelper")+"</li>"),n},link_selected:function(e,t){e.preventDefault(),M.atto_accessibilityhelper.dialogue.hide();var n=e.target.getData("sourcelink"),r=M.editor_atto.get_selection_from_node(n);M.editor_atto.selections[t]=r,M.editor_atto.focus(t)},list_links:function(t){var n=e.Node.create("<ol/>"),r=M.editor_atto.get_editable_node(t),i,s;return r.all("a").each(function(r){s=e.Node.create('<a href="#" title="'+M.util.get_string("selectlink","atto_accessibilityhelper")+'">'+e.Escape.html(r.get("text"))+"</a>"),s.setData("sourcelink",r),s.on("click",this.link_selected,this,t),i=e.Node.create("<li></li>"),i.append(s),n.append(i)},this),n.hasChildNodes()||n.append("<li>"+M.util.get_string("nolinks","atto_accessibilityhelper")+"</li>"),n},list_styles:function(t){var n=[],r=M.editor_atto.get_selection_parent_node(),i=M.editor_atto.get_editable_node(t),s;r&&(r=e.one(r));while(r&&r!==i)s=r.get("tagName"),typeof s!="undefined"&&n.push(e.Escape.html(s)),r=r.ancestor();return n.length===0&&n.push(M.util.get_string("nostyles","atto_accessibilityhelper")),n.reverse(),n.join(", ")},get_content:function(t){var i='<div><p id="'+n.STYLESLABEL+'">'+M.util.get_string("liststyles","atto_accessibilityhelper")+"<br/>"+'<span id="'+n.LISTSTYLES+'" '+'aria-labelledby="'+n.STYLESLABEL+'"/></p></div>',s=e.Node.create(i);return s.one(r.LISTSTYLES).append(this.list_styles(t)),i='<p id="'+n.LINKSLABEL+'">'+M.util.get_string("listlinks","atto_accessibilityhelper")+"<br/>"+'<span id="'+n.LISTLINKS+'" '+'aria-labelledby="'+n.LINKSLABEL+'"/></p>',s.append(i),s.one(r.LISTLINKS).append(this.list_links(t)),i='<p id="'+n.IMAGESLABEL+'">'+M.util.get_string("listimages","atto_accessibilityhelper")+"<br/>"+'<span id="'+n.LISTIMAGES+'" '+'aria-labelledby="'+n.IMAGESLABEL+'"/></p>',s.append(i),s.one(r.LISTIMAGES).append(this.list_images(t)),s}}},"@VERSION@",{requires:["node","escape"]});
YUI.add("moodle-atto_accessibilityhelper-button",function(e,t){var n="atto_accessibilityhelper",r='<div><p id="{{elementid}}_{{CSS.STYLESLABEL}}">{{get_string "liststyles" component}}<br/><span aria-labelledby="{{elementid}}_{{CSS.STYLESLABEL}}" /></p></div><span class="listStyles"></span><p id="{{elementid}}_{{CSS.LINKSLABEL}}">{{get_string "listlinks" component}}<br/><span aria-labelledby="{{elementid}}_{{CSS.LINKSLABEL}}"/></p><span class="listLinks"></span><p id="{{elementid}}_{{CSS.IMAGESLABEL}}">{{get_string "listimages" component}}<br/><span aria-labelledby="{{elementid}}_{{CSS.IMAGESLABEL}}"/></p><span class="listImages"></span>',i={STYLESLABEL:n+"_styleslabel",LINKSLABEL:n+"_linkslabel",IMAGESLABEL:n+"_imageslabel"};e.namespace("M.atto_accessibilityhelper").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_displayedWarnings:{},initializer:function(){this.addButton({icon:"e/screenreader_helper",callback:this._displayDialogue})},_displayDialogue:function(){var e=this.getDialogue({headerContent:M.util.get_string("pluginname",n),width:"800px",focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()},_getDialogueContent:function(){var t=e.Handlebars.compile(r),s=e.Node.create(t({CSS:i,component:n}));return s.one(".listStyles").empty().appendChild(this._listStyles()),s.one(".listLinks").empty().appendChild(this._listLinks()),s.one(".listImages").empty().appendChild(this._listImages()),s.delegate("click",function(e){e.preventDefault();var t=this.get("host"),n=e.target.getAttribute("data-index"),r=this._displayedWarnings[n],i=this.getDialogue();r&&(i.set("focusAfterHide",null),t.setSelection(t.getSelectionFromNode(r))),i.hide()},"a",this),s},_listStyles:function(){var t=[],r=this.get("host"),i=r.getSelectionParentNode(),s;i&&(i=e.one(i));while(i&&i!==this.editor)s=i.get("tagName"),typeof s!="undefined"&&t.push(e.Escape.html(s)),i=i.ancestor();return t.length===0&&t.push(M.util.get_string("nostyles",n)),t.reverse(),t.join(", ")},_listLinks:function(){var t=e.Node.create("<ol />"),r,i;return this.editor.all("a").each(function(s){i=e.Node.create('<a href="#" title="'+M.util.get_string("selectlink",n)+'">'+e.Escape.html(s.get("text"))+"</a>"),i.setData("sourcelink",s),i.on("click",this._linkSelected,this),r=e.Node.create("<li></li>"),r.appendChild(i),t.appendChild(r)},this),t.hasChildNodes()||t.append("<li>"+M.util.get_string("nolinks",n)+"</li>"),t},_listImages:function(){var t=e.Node.create("<ol/>"),r,i;return this.editor.all("img").each(function(s){var o=s.getAttribute("alt");o===""&&(o=s.getAttribute("title"),o===""&&(o=s.getAttribute("src"))),i=e.Node.create('<a href="#" title="'+M.util.get_string("selectimage",n)+'">'+e.Escape.html(o)+"</a>"),i.setData("sourceimage",s),i.on("click",this._imageSelected,this),r=e.Node.create("<li></li>"),r.append(i),t.append(r)},this),t.hasChildNodes()||t.append("<li>"+M.util.get_string("noimages",n)+"</li>"),t},_imageSelected:function(e){e.preventDefault(),this.getDialogue({focusAfterNode:null}).hide();var t=this.get("host"),n=e.target.getData("sourceimage");this.editor.focus(),t.setSelection(t.getSelectionFromNode(n))},_linkSelected:function(e){e.preventDefault(),this.getDialogue({focusAfterNode:null}).hide();var t=this.get("host"),n=e.target.getData("sourcelink");this.editor.focus(),t.setSelection(t.getSelectionFromNode(n))}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,30 +15,16 @@ YUI.add('moodle-atto_accessibilityhelper-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS classes and IDs.
*
* @type {Object}
*/
var CSS = {
STYLESLABEL: 'atto_accessibilityhelper_styleslabel',
LISTSTYLES: 'atto_accessibilityhelper_liststyles',
LINKSLABEL: 'atto_accessibilityhelper_linkslabel',
LISTLINKS: 'atto_accessibilityhelper_listlinks',
IMAGESLABEL: 'atto_accessibilityhelper_imageslabel',
LISTIMAGES: 'atto_accessibilityhelper_listimages'
};
/**
* Selectors.
*
* @type {Object}
/*
* @package atto_accessibilityhelper
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var SELECTORS = {
LISTSTYLES: '#atto_accessibilityhelper_liststyles',
LISTLINKS: '#atto_accessibilityhelper_listlinks',
LISTIMAGES: '#atto_accessibilityhelper_listimages'
};
/**
* @module moodle-atto_accessibilityhelper-button
*/
/**
* Atto text editor accessibilityhelper plugin.
*
@@ -46,99 +32,209 @@ var SELECTORS = {
* Specifically, listing the active styles for the selected text,
* listing the images in the page, listing the links in the page.
*
* @package editor-atto
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @namespace M.atto_accessibilityhelper
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
/**
* The window used to display the accessibility ui.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
var COMPONENT = 'atto_accessibilityhelper',
TEMPLATE = '' +
// The list of styles.
'<div><p id="{{elementid}}_{{CSS.STYLESLABEL}}">' +
'{{get_string "liststyles" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.STYLESLABEL}}" />' +
'</p></div>' +
'<span class="listStyles"></span>' +
'<p id="{{elementid}}_{{CSS.LINKSLABEL}}">' +
'{{get_string "listlinks" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.LINKSLABEL}}"/>' +
'</p>' +
'<span class="listLinks"></span>' +
'<p id="{{elementid}}_{{CSS.IMAGESLABEL}}">' +
'{{get_string "listimages" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.IMAGESLABEL}}"/>' +
'</p>' +
'<span class="listImages"></span>',
CSS = {
STYLESLABEL: COMPONENT + '_styleslabel',
LINKSLABEL: COMPONENT + '_linkslabel',
IMAGESLABEL: COMPONENT + '_imageslabel'
};
Y.namespace('M.atto_accessibilityhelper').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* Display the ui dialogue.
* The warnings which are displayed.
*
* @method init
* @param Event e
* @param string elementid
* @property _displayedWarnings
* @type Object
* @private
*/
display_ui : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
var dialogue;
if (!M.atto_accessibilityhelper.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_accessibilityhelper'));
dialogue.render();
} else {
dialogue = M.atto_accessibilityhelper.dialogue;
_displayedWarnings: {},
initializer: function() {
this.addButton({
icon: 'e/screenreader_helper',
callback: this._displayDialogue
});
},
/**
* Display the Accessibility Helper tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENT),
width: '800px',
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
CSS: CSS,
component: COMPONENT
}));
// Add the data.
content.one('.listStyles')
.empty()
.appendChild(this._listStyles());
content.one('.listLinks')
.empty()
.appendChild(this._listLinks());
content.one('.listImages')
.empty()
.appendChild(this._listImages());
// Add ability to select problem areas in the editor.
content.delegate('click', function(e) {
e.preventDefault();
var host = this.get('host'),
index = e.target.getAttribute("data-index"),
node = this._displayedWarnings[index],
dialogue = this.getDialogue();
if (node) {
// Clear the dialogue's focusAfterHide to ensure we focus
// on the selection.
dialogue.set('focusAfterHide', null);
host.setSelection(host.getSelectionFromNode(node));
}
// Hide the dialogue.
dialogue.hide();
}, 'a', this);
return content;
},
/**
* List the styles present for the selection.
*
* @method _listStyles
* @return {String} The list of styles in use.
* @private
*/
_listStyles: function() {
// Clear the status node.
var list = [],
host = this.get('host'),
current = host.getSelectionParentNode(),
tagname;
if (current) {
current = Y.one(current);
}
dialogue.set('bodyContent', M.atto_accessibilityhelper.get_content(elementid));
dialogue.centerDialogue();
while (current && (current !== this.editor)) {
tagname = current.get('tagName');
if (typeof tagname !== 'undefined') {
list.push(Y.Escape.html(tagname));
}
current = current.ancestor();
}
if (list.length === 0) {
list.push(M.util.get_string('nostyles', COMPONENT));
}
dialogue.show();
M.atto_accessibilityhelper.dialogue = dialogue;
list.reverse();
// Append the list of current styles.
return list.join(', ');
},
/**
* Add this button to the form.
* List the links for the current editor
*
* @method init
* @param {Object} params
* @method _listLinks
* @return {string}
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/visual_aid', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'accessibilityhelper', iconurl, params.group, this.display_ui);
_listLinks: function() {
var list = Y.Node.create('<ol />'),
listitem,
selectlink;
this.editor.all('a').each(function(link) {
selectlink = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectlink', COMPONENT) + '">' +
Y.Escape.html(link.get('text')) +
'</a>');
selectlink.setData('sourcelink', link);
selectlink.on('click', this._linkSelected, this);
listitem = Y.Node.create('<li></li>');
listitem.appendChild(selectlink);
list.appendChild(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('nolinks', COMPONENT) + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* Event handler for selecting an image.
* List the images used in the editor.
*
* @method image_selected
* @param Event e
* @param string elementid
* @method _listImages
* @return {Node} A Node containing all of the images present in the editor.
* @private
*/
image_selected : function(e, elementid) {
e.preventDefault();
M.atto_accessibilityhelper.dialogue.hide();
var image = e.target.getData('sourceimage');
var selection = M.editor_atto.get_selection_from_node(image);
M.editor_atto.selections[elementid] = selection;
M.editor_atto.focus(elementid);
},
/**
* List the images for the current editor
*
* @method list_images
* @param string elementid
* @return String
*/
list_images : function(elementid) {
var list = Y.Node.create('<ol/>');
var editable = M.editor_atto.get_editable_node(elementid),
listitem, selectimage;
editable.all('img').each(function(image) {
_listImages: function() {
var list = Y.Node.create('<ol/>'),
listitem,
selectimage;
this.editor.all('img').each(function(image) {
// Get the alt or title or img url of the image.
var imgalt = image.getAttribute('alt');
if (imgalt === '') {
@@ -149,156 +245,67 @@ M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
}
selectimage = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectimage', 'atto_accessibilityhelper') + '">' +
Y.Escape.html(imgalt) +
'</a>');
M.util.get_string('selectimage', COMPONENT) + '">' +
Y.Escape.html(imgalt) +
'</a>');
selectimage.setData('sourceimage', image);
selectimage.on('click', this.image_selected, this, elementid);
selectimage.on('click', this._imageSelected, this);
listitem = Y.Node.create('<li></li>');
listitem.append(selectimage);
list.append(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('noimages', 'atto_accessibilityhelper') + '</li>');
list.append('<li>' + M.util.get_string('noimages', COMPONENT) + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* Event handler for selecting an image.
*
* @method _imageSelected
* @param {EventFacade} e
* @private
*/
_imageSelected: function(e) {
e.preventDefault();
this.getDialogue({
focusAfterNode: null
}).hide();
var host = this.get('host'),
target = e.target.getData('sourceimage');
this.editor.focus();
host.setSelection(host.getSelectionFromNode(target));
},
/**
* Event handler for selecting a link.
*
* @method link_selected
* @param Event e
* @param string elementid
* @method _linkSelected
* @param {EventFacade} e
* @private
*/
link_selected : function(e, elementid) {
_linkSelected: function(e) {
e.preventDefault();
M.atto_accessibilityhelper.dialogue.hide();
this.getDialogue({
focusAfterNode: null
}).hide();
var link = e.target.getData('sourcelink');
var selection = M.editor_atto.get_selection_from_node(link);
var host = this.get('host'),
target = e.target.getData('sourcelink');
M.editor_atto.selections[elementid] = selection;
M.editor_atto.focus(elementid);
},
/**
* List the links for the current editor
*
* @method list_links
* @param string elementid
* @return String
*/
list_links : function(elementid) {
var list = Y.Node.create('<ol/>');
var editable = M.editor_atto.get_editable_node(elementid),
listitem, selectlink;
editable.all('a').each(function(link) {
selectlink = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectlink', 'atto_accessibilityhelper') + '">' +
Y.Escape.html(link.get('text')) +
'</a>');
selectlink.setData('sourcelink', link);
selectlink.on('click', this.link_selected, this, elementid);
listitem = Y.Node.create('<li></li>');
listitem.append(selectlink);
list.append(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('nolinks', 'atto_accessibilityhelper') + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* List the styles for the current selection.
*
* @method list_styles
* @param string elementid
* @return String
*/
list_styles : function(elementid) {
// Clear the status node.
var list = [];
var current = M.editor_atto.get_selection_parent_node();
var editable = M.editor_atto.get_editable_node(elementid);
var tagname;
if (current) {
current = Y.one(current);
}
while (current && (current !== editable)) {
tagname = current.get('tagName');
if (typeof tagname !== 'undefined') {
list.push(Y.Escape.html(tagname));
}
current = current.ancestor();
}
if (list.length === 0) {
list.push(M.util.get_string('nostyles', 'atto_accessibilityhelper'));
}
list.reverse();
// Append the list of current styles.
return list.join(', ');
},
/**
* Return the HTML of the form to show in the dialogue.
*
* @method get_content
* @param string elementid
* @return string
*/
get_content : function(elementid) {
// Current styles.
var html = '<div><p id="' + CSS.STYLESLABEL + '">' +
M.util.get_string('liststyles', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTSTYLES + '" ' +
'aria-labelledby="' + CSS.STYLESLABEL + '"/></p></div>';
var content = Y.Node.create(html);
content.one(SELECTORS.LISTSTYLES).append(this.list_styles(elementid));
// Current links.
html = '<p id="' + CSS.LINKSLABEL + '">' +
M.util.get_string('listlinks', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTLINKS + '" ' +
'aria-labelledby="' + CSS.LINKSLABEL + '"/></p>';
content.append(html);
content.one(SELECTORS.LISTLINKS).append(this.list_links(elementid));
// Current images.
html = '<p id="' + CSS.IMAGESLABEL + '">' +
M.util.get_string('listimages', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTIMAGES + '" ' +
'aria-labelledby="' + CSS.IMAGESLABEL + '"/></p>';
content.append(html);
content.one(SELECTORS.LISTIMAGES).append(this.list_images(elementid));
return content;
this.editor.focus();
host.setSelection(host.getSelectionFromNode(target));
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -13,30 +13,16 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS classes and IDs.
*
* @type {Object}
*/
var CSS = {
STYLESLABEL: 'atto_accessibilityhelper_styleslabel',
LISTSTYLES: 'atto_accessibilityhelper_liststyles',
LINKSLABEL: 'atto_accessibilityhelper_linkslabel',
LISTLINKS: 'atto_accessibilityhelper_listlinks',
IMAGESLABEL: 'atto_accessibilityhelper_imageslabel',
LISTIMAGES: 'atto_accessibilityhelper_listimages'
};
/**
* Selectors.
*
* @type {Object}
/*
* @package atto_accessibilityhelper
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var SELECTORS = {
LISTSTYLES: '#atto_accessibilityhelper_liststyles',
LISTLINKS: '#atto_accessibilityhelper_listlinks',
LISTIMAGES: '#atto_accessibilityhelper_listimages'
};
/**
* @module moodle-atto_accessibilityhelper-button
*/
/**
* Atto text editor accessibilityhelper plugin.
*
@@ -44,99 +30,209 @@ var SELECTORS = {
* Specifically, listing the active styles for the selected text,
* listing the images in the page, listing the links in the page.
*
* @package editor-atto
* @copyright 2014 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @namespace M.atto_accessibilityhelper
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
/**
* The window used to display the accessibility ui.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
var COMPONENT = 'atto_accessibilityhelper',
TEMPLATE = '' +
// The list of styles.
'<div><p id="{{elementid}}_{{CSS.STYLESLABEL}}">' +
'{{get_string "liststyles" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.STYLESLABEL}}" />' +
'</p></div>' +
'<span class="listStyles"></span>' +
'<p id="{{elementid}}_{{CSS.LINKSLABEL}}">' +
'{{get_string "listlinks" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.LINKSLABEL}}"/>' +
'</p>' +
'<span class="listLinks"></span>' +
'<p id="{{elementid}}_{{CSS.IMAGESLABEL}}">' +
'{{get_string "listimages" component}}<br/>' +
'<span aria-labelledby="{{elementid}}_{{CSS.IMAGESLABEL}}"/>' +
'</p>' +
'<span class="listImages"></span>',
CSS = {
STYLESLABEL: COMPONENT + '_styleslabel',
LINKSLABEL: COMPONENT + '_linkslabel',
IMAGESLABEL: COMPONENT + '_imageslabel'
};
Y.namespace('M.atto_accessibilityhelper').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* Display the ui dialogue.
* The warnings which are displayed.
*
* @method init
* @param Event e
* @param string elementid
* @property _displayedWarnings
* @type Object
* @private
*/
display_ui : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
var dialogue;
if (!M.atto_accessibilityhelper.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_accessibilityhelper'));
dialogue.render();
} else {
dialogue = M.atto_accessibilityhelper.dialogue;
_displayedWarnings: {},
initializer: function() {
this.addButton({
icon: 'e/screenreader_helper',
callback: this._displayDialogue
});
},
/**
* Display the Accessibility Helper tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENT),
width: '800px',
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
CSS: CSS,
component: COMPONENT
}));
// Add the data.
content.one('.listStyles')
.empty()
.appendChild(this._listStyles());
content.one('.listLinks')
.empty()
.appendChild(this._listLinks());
content.one('.listImages')
.empty()
.appendChild(this._listImages());
// Add ability to select problem areas in the editor.
content.delegate('click', function(e) {
e.preventDefault();
var host = this.get('host'),
index = e.target.getAttribute("data-index"),
node = this._displayedWarnings[index],
dialogue = this.getDialogue();
if (node) {
// Clear the dialogue's focusAfterHide to ensure we focus
// on the selection.
dialogue.set('focusAfterHide', null);
host.setSelection(host.getSelectionFromNode(node));
}
// Hide the dialogue.
dialogue.hide();
}, 'a', this);
return content;
},
/**
* List the styles present for the selection.
*
* @method _listStyles
* @return {String} The list of styles in use.
* @private
*/
_listStyles: function() {
// Clear the status node.
var list = [],
host = this.get('host'),
current = host.getSelectionParentNode(),
tagname;
if (current) {
current = Y.one(current);
}
dialogue.set('bodyContent', M.atto_accessibilityhelper.get_content(elementid));
dialogue.centerDialogue();
while (current && (current !== this.editor)) {
tagname = current.get('tagName');
if (typeof tagname !== 'undefined') {
list.push(Y.Escape.html(tagname));
}
current = current.ancestor();
}
if (list.length === 0) {
list.push(M.util.get_string('nostyles', COMPONENT));
}
dialogue.show();
M.atto_accessibilityhelper.dialogue = dialogue;
list.reverse();
// Append the list of current styles.
return list.join(', ');
},
/**
* Add this button to the form.
* List the links for the current editor
*
* @method init
* @param {Object} params
* @method _listLinks
* @return {string}
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/visual_aid', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'accessibilityhelper', iconurl, params.group, this.display_ui);
_listLinks: function() {
var list = Y.Node.create('<ol />'),
listitem,
selectlink;
this.editor.all('a').each(function(link) {
selectlink = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectlink', COMPONENT) + '">' +
Y.Escape.html(link.get('text')) +
'</a>');
selectlink.setData('sourcelink', link);
selectlink.on('click', this._linkSelected, this);
listitem = Y.Node.create('<li></li>');
listitem.appendChild(selectlink);
list.appendChild(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('nolinks', COMPONENT) + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* Event handler for selecting an image.
* List the images used in the editor.
*
* @method image_selected
* @param Event e
* @param string elementid
* @method _listImages
* @return {Node} A Node containing all of the images present in the editor.
* @private
*/
image_selected : function(e, elementid) {
e.preventDefault();
M.atto_accessibilityhelper.dialogue.hide();
var image = e.target.getData('sourceimage');
var selection = M.editor_atto.get_selection_from_node(image);
M.editor_atto.selections[elementid] = selection;
M.editor_atto.focus(elementid);
},
/**
* List the images for the current editor
*
* @method list_images
* @param string elementid
* @return String
*/
list_images : function(elementid) {
var list = Y.Node.create('<ol/>');
var editable = M.editor_atto.get_editable_node(elementid),
listitem, selectimage;
editable.all('img').each(function(image) {
_listImages: function() {
var list = Y.Node.create('<ol/>'),
listitem,
selectimage;
this.editor.all('img').each(function(image) {
// Get the alt or title or img url of the image.
var imgalt = image.getAttribute('alt');
if (imgalt === '') {
@@ -147,153 +243,64 @@ M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
}
selectimage = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectimage', 'atto_accessibilityhelper') + '">' +
Y.Escape.html(imgalt) +
'</a>');
M.util.get_string('selectimage', COMPONENT) + '">' +
Y.Escape.html(imgalt) +
'</a>');
selectimage.setData('sourceimage', image);
selectimage.on('click', this.image_selected, this, elementid);
selectimage.on('click', this._imageSelected, this);
listitem = Y.Node.create('<li></li>');
listitem.append(selectimage);
list.append(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('noimages', 'atto_accessibilityhelper') + '</li>');
list.append('<li>' + M.util.get_string('noimages', COMPONENT) + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* Event handler for selecting an image.
*
* @method _imageSelected
* @param {EventFacade} e
* @private
*/
_imageSelected: function(e) {
e.preventDefault();
this.getDialogue({
focusAfterNode: null
}).hide();
var host = this.get('host'),
target = e.target.getData('sourceimage');
this.editor.focus();
host.setSelection(host.getSelectionFromNode(target));
},
/**
* Event handler for selecting a link.
*
* @method link_selected
* @param Event e
* @param string elementid
* @method _linkSelected
* @param {EventFacade} e
* @private
*/
link_selected : function(e, elementid) {
_linkSelected: function(e) {
e.preventDefault();
M.atto_accessibilityhelper.dialogue.hide();
this.getDialogue({
focusAfterNode: null
}).hide();
var link = e.target.getData('sourcelink');
var selection = M.editor_atto.get_selection_from_node(link);
var host = this.get('host'),
target = e.target.getData('sourcelink');
M.editor_atto.selections[elementid] = selection;
M.editor_atto.focus(elementid);
},
/**
* List the links for the current editor
*
* @method list_links
* @param string elementid
* @return String
*/
list_links : function(elementid) {
var list = Y.Node.create('<ol/>');
var editable = M.editor_atto.get_editable_node(elementid),
listitem, selectlink;
editable.all('a').each(function(link) {
selectlink = Y.Node.create('<a href="#" title="' +
M.util.get_string('selectlink', 'atto_accessibilityhelper') + '">' +
Y.Escape.html(link.get('text')) +
'</a>');
selectlink.setData('sourcelink', link);
selectlink.on('click', this.link_selected, this, elementid);
listitem = Y.Node.create('<li></li>');
listitem.append(selectlink);
list.append(listitem);
}, this);
if (!list.hasChildNodes()) {
list.append('<li>' + M.util.get_string('nolinks', 'atto_accessibilityhelper') + '</li>');
}
// Append the list of current styles.
return list;
},
/**
* List the styles for the current selection.
*
* @method list_styles
* @param string elementid
* @return String
*/
list_styles : function(elementid) {
// Clear the status node.
var list = [];
var current = M.editor_atto.get_selection_parent_node();
var editable = M.editor_atto.get_editable_node(elementid);
var tagname;
if (current) {
current = Y.one(current);
}
while (current && (current !== editable)) {
tagname = current.get('tagName');
if (typeof tagname !== 'undefined') {
list.push(Y.Escape.html(tagname));
}
current = current.ancestor();
}
if (list.length === 0) {
list.push(M.util.get_string('nostyles', 'atto_accessibilityhelper'));
}
list.reverse();
// Append the list of current styles.
return list.join(', ');
},
/**
* Return the HTML of the form to show in the dialogue.
*
* @method get_content
* @param string elementid
* @return string
*/
get_content : function(elementid) {
// Current styles.
var html = '<div><p id="' + CSS.STYLESLABEL + '">' +
M.util.get_string('liststyles', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTSTYLES + '" ' +
'aria-labelledby="' + CSS.STYLESLABEL + '"/></p></div>';
var content = Y.Node.create(html);
content.one(SELECTORS.LISTSTYLES).append(this.list_styles(elementid));
// Current links.
html = '<p id="' + CSS.LINKSLABEL + '">' +
M.util.get_string('listlinks', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTLINKS + '" ' +
'aria-labelledby="' + CSS.LINKSLABEL + '"/></p>';
content.append(html);
content.one(SELECTORS.LISTLINKS).append(this.list_links(elementid));
// Current images.
html = '<p id="' + CSS.IMAGESLABEL + '">' +
M.util.get_string('listimages', 'atto_accessibilityhelper') +
'<br/>' +
'<span id="' + CSS.LISTIMAGES + '" ' +
'aria-labelledby="' + CSS.IMAGESLABEL + '"/></p>';
content.append(html);
content.one(SELECTORS.LISTIMAGES).append(this.list_images(elementid));
return content;
this.editor.focus();
host.setSelection(host.getSelectionFromNode(target));
}
};
});
@@ -1,5 +1,7 @@
{
"moodle-atto_accessibilityhelper-button": {
"requires": ["node", "escape"]
}
"moodle-atto_accessibilityhelper-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,84 +15,83 @@ YUI.add('moodle-atto_align-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor align plugin.
*
/*
* @package atto_align
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var LEFT = 'left',
RIGHT = 'right',
CENTER = 'center';
/**
* @module moodle-atto_align-button
*/
M.atto_align = M.atto_align || {
/**
* Atto text editor align plugin.
*
* @namespace M.atto_align
* @class button
* @extends M.editor_atto.EditorPlugin
*/
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
var iconurl, leftAlign, rightAlign, centerAlign;
Y.namespace('M.atto_align').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var alignment;
leftAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, LEFT);
};
alignment = 'justifyLeft';
this.addButton({
icon: 'e/align_left',
title: 'leftalign',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
centerAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, CENTER);
};
alignment = 'justifyCenter';
this.addButton({
icon: 'e/align_center',
title: 'center',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
rightAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, RIGHT);
};
iconurl = M.util.image_url('e/align_left', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, leftAlign,
'left', M.util.get_string('leftalign', 'atto_align'));
iconurl = M.util.image_url('e/align_center', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, centerAlign,
'center', M.util.get_string('center', 'atto_align'));
iconurl = M.util.image_url('e/align_right', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, rightAlign,
'right', M.util.get_string('rightalign', 'atto_align'));
alignment = 'justifyRight';
this.addButton({
icon: 'e/align_right',
title: 'rightalign',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
},
/**
* Changes the text alignment.
* Change the alignment to the specified justification.
*
* @param {String} elementid The editor ID.
* @param {String} alignment The alignment to change to.
* @return {Void}
* @method _changeStyle
* @param {EventFacade} e
* @param {string} justification The execCommand for the new justification.
* @private
*/
changeAlignment: function(elementid, alignment) {
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
_changeStyle: function(e, justification) {
var host = this.get('host');
// We temporarily re-enable CSS styling to try to have the most consistency.
// Though, IE, as always, is stubborn and will do its own thing...
M.editor_atto.enable_css_styling();
if (alignment === RIGHT) {
document.execCommand('justifyRight', false, null);
} else if (alignment === CENTER) {
document.execCommand('justifyCenter', false, null);
} else {
document.execCommand('justifyLeft', false, null);
}
M.editor_atto.disable_css_styling();
host.enableCssStyling();
document.execCommand(justification, false, null);
// Re-disable the CSS styling after making the change.
host.disableCssStyling();
// Mark the text as having been updated.
this.markUpdated();
this.editor.focus();
}
};
});
}, '@VERSION@', {"requires": []});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_align-button",function(e,t){var n="left",r="right",i="center";M.atto_align=M.atto_align||{init:function(e){var t,s,o,u;s=function(e,t){e.preventDefault(),M.atto_align.changeAlignment(t,n)},u=function(e,t){e.preventDefault(),M.atto_align.changeAlignment(t,i)},o=function(e,t){e.preventDefault(),M.atto_align.changeAlignment(t,r)},t=M.util.image_url("e/align_left","core"),M.editor_atto.add_toolbar_button(e.elementid,"align",t,e.group,s,"left",M.util.get_string("leftalign","atto_align")),t=M.util.image_url("e/align_center","core"),M.editor_atto.add_toolbar_button(e.elementid,"align",t,e.group,u,"center",M.util.get_string("center","atto_align")),t=M.util.image_url("e/align_right","core"),M.editor_atto.add_toolbar_button(e.elementid,"align",t,e.group,o,"right",M.util.get_string("rightalign","atto_align"))},changeAlignment:function(e,t){M.editor_atto.is_active(e)||M.editor_atto.focus(e),M.editor_atto.enable_css_styling(),t===r?document.execCommand("justifyRight",!1,null):t===i?document.execCommand("justifyCenter",!1,null):document.execCommand("justifyLeft",!1,null),M.editor_atto.disable_css_styling()}}},"@VERSION@",{requires:[]});
YUI.add("moodle-atto_align-button",function(e,t){e.namespace("M.atto_align").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var e;e="justifyLeft",this.addButton({icon:"e/align_left",title:"leftalign",buttonName:e,callback:this._changeStyle,callbackArgs:e}),e="justifyCenter",this.addButton({icon:"e/align_center",title:"center",buttonName:e,callback:this._changeStyle,callbackArgs:e}),e="justifyRight",this.addButton({icon:"e/align_right",title:"rightalign",buttonName:e,callback:this._changeStyle,callbackArgs:e})},_changeStyle:function(e,t){var n=this.get("host");n.enableCssStyling(),document.execCommand(t,!1,null),n.disableCssStyling(),this.markUpdated(),this.editor.focus()}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,84 +15,83 @@ YUI.add('moodle-atto_align-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor align plugin.
*
/*
* @package atto_align
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var LEFT = 'left',
RIGHT = 'right',
CENTER = 'center';
/**
* @module moodle-atto_align-button
*/
M.atto_align = M.atto_align || {
/**
* Atto text editor align plugin.
*
* @namespace M.atto_align
* @class button
* @extends M.editor_atto.EditorPlugin
*/
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
var iconurl, leftAlign, rightAlign, centerAlign;
Y.namespace('M.atto_align').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var alignment;
leftAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, LEFT);
};
alignment = 'justifyLeft';
this.addButton({
icon: 'e/align_left',
title: 'leftalign',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
centerAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, CENTER);
};
alignment = 'justifyCenter';
this.addButton({
icon: 'e/align_center',
title: 'center',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
rightAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, RIGHT);
};
iconurl = M.util.image_url('e/align_left', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, leftAlign,
'left', M.util.get_string('leftalign', 'atto_align'));
iconurl = M.util.image_url('e/align_center', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, centerAlign,
'center', M.util.get_string('center', 'atto_align'));
iconurl = M.util.image_url('e/align_right', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, rightAlign,
'right', M.util.get_string('rightalign', 'atto_align'));
alignment = 'justifyRight';
this.addButton({
icon: 'e/align_right',
title: 'rightalign',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
},
/**
* Changes the text alignment.
* Change the alignment to the specified justification.
*
* @param {String} elementid The editor ID.
* @param {String} alignment The alignment to change to.
* @return {Void}
* @method _changeStyle
* @param {EventFacade} e
* @param {string} justification The execCommand for the new justification.
* @private
*/
changeAlignment: function(elementid, alignment) {
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
_changeStyle: function(e, justification) {
var host = this.get('host');
// We temporarily re-enable CSS styling to try to have the most consistency.
// Though, IE, as always, is stubborn and will do its own thing...
M.editor_atto.enable_css_styling();
if (alignment === RIGHT) {
document.execCommand('justifyRight', false, null);
} else if (alignment === CENTER) {
document.execCommand('justifyCenter', false, null);
} else {
document.execCommand('justifyLeft', false, null);
}
M.editor_atto.disable_css_styling();
host.enableCssStyling();
document.execCommand(justification, false, null);
// Re-disable the CSS styling after making the change.
host.disableCssStyling();
// Mark the text as having been updated.
this.markUpdated();
this.editor.focus();
}
};
});
}, '@VERSION@', {"requires": []});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+58 -59
View File
@@ -13,81 +13,80 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor align plugin.
*
/*
* @package atto_align
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var LEFT = 'left',
RIGHT = 'right',
CENTER = 'center';
/**
* @module moodle-atto_align-button
*/
M.atto_align = M.atto_align || {
/**
* Atto text editor align plugin.
*
* @namespace M.atto_align
* @class button
* @extends M.editor_atto.EditorPlugin
*/
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
var iconurl, leftAlign, rightAlign, centerAlign;
Y.namespace('M.atto_align').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var alignment;
leftAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, LEFT);
};
alignment = 'justifyLeft';
this.addButton({
icon: 'e/align_left',
title: 'leftalign',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
centerAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, CENTER);
};
alignment = 'justifyCenter';
this.addButton({
icon: 'e/align_center',
title: 'center',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
rightAlign = function(e, elementid) {
e.preventDefault();
M.atto_align.changeAlignment(elementid, RIGHT);
};
iconurl = M.util.image_url('e/align_left', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, leftAlign,
'left', M.util.get_string('leftalign', 'atto_align'));
iconurl = M.util.image_url('e/align_center', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, centerAlign,
'center', M.util.get_string('center', 'atto_align'));
iconurl = M.util.image_url('e/align_right', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'align', iconurl, params.group, rightAlign,
'right', M.util.get_string('rightalign', 'atto_align'));
alignment = 'justifyRight';
this.addButton({
icon: 'e/align_right',
title: 'rightalign',
buttonName: alignment,
callback: this._changeStyle,
callbackArgs: alignment
});
},
/**
* Changes the text alignment.
* Change the alignment to the specified justification.
*
* @param {String} elementid The editor ID.
* @param {String} alignment The alignment to change to.
* @return {Void}
* @method _changeStyle
* @param {EventFacade} e
* @param {string} justification The execCommand for the new justification.
* @private
*/
changeAlignment: function(elementid, alignment) {
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
_changeStyle: function(e, justification) {
var host = this.get('host');
// We temporarily re-enable CSS styling to try to have the most consistency.
// Though, IE, as always, is stubborn and will do its own thing...
M.editor_atto.enable_css_styling();
if (alignment === RIGHT) {
document.execCommand('justifyRight', false, null);
} else if (alignment === CENTER) {
document.execCommand('justifyCenter', false, null);
} else {
document.execCommand('justifyLeft', false, null);
}
M.editor_atto.disable_css_styling();
}
host.enableCssStyling();
};
document.execCommand(justification, false, null);
// Re-disable the CSS styling after making the change.
host.disableCssStyling();
// Mark the text as having been updated.
this.markUpdated();
this.editor.focus();
}
});
@@ -1,5 +1,7 @@
{
"moodle-atto_align-button": {
"requires": []
}
"moodle-atto_align-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,123 +15,129 @@ YUI.add('moodle-atto_backcolor-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor background color plugin.
*
* @package editor-atto
/*
* @package atto_backcolor
* @copyright 2014 Rossiani Wijaya <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_backcolor = M.atto_backcolor || {
init : function(params) {
var plugin = 'backcolor';
var rgb_white = '#FFFFFF',
rgb_red = '#EF4540',
rgb_yellow = '#FFCF35',
rgb_green = '#98CA3E',
rgb_blue = '#7D9FD3',
rgb_black = '#333333';
/**
* @module moodle-atto_backcolor-button
*/
var click_white = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_white);
};
var click_red = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_red);
};
var click_yellow = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_yellow);
};
var click_green = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_green);
};
var click_blue = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_blue);
};
var click_black = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_black);
};
/**
* Atto text editor backcolor plugin.
*
* @namespace M.atto_backcolor
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var buttoncss = 'width: 20px; height: 20px; border: 1px solid #CCC; background-color: ';
var white = '<div style="' + buttoncss + rgb_white + '"></div>';
var red = '<div style="' + buttoncss + rgb_red + '"></div>';
var yellow = '<div style="' + buttoncss + rgb_yellow + '"></div>';
var green = '<div style="' + buttoncss + rgb_green + '"></div>';
var blue = '<div style="' + buttoncss + rgb_blue + '"></div>';
var black = '<div style="' + buttoncss + rgb_black + '"></div>';
var doc = document,
BackColor = 'BackColor',
colors = [
{
name: 'white',
color: '#FFFFFF'
}, {
name: 'red',
color: '#EF4540'
}, {
name: 'yellow',
color: '#FFCF35'
}, {
name: 'green',
color: '#98CA3E'
}, {
name: 'blue',
color: '#7D9FD3'
}, {
name: 'black',
color: '#333333'
}
];
var iconurl = M.util.image_url('e/text_highlight', 'core');
Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var items = [];
Y.Array.each(colors, function(color) {
items.push({
text: '<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ' +
color.color +
'"></div>',
callbackArgs: color.color
});
});
M.editor_atto.add_toolbar_menu(params.elementid,
plugin,
iconurl,
params.group,
[
{'text' : white, 'handler' : click_white},
{'text' : red, 'handler' : click_red},
{'text' : yellow, 'handler' : click_yellow},
{'text' : green, 'handler' : click_green},
{'text' : blue, 'handler' : click_blue},
{'text' : black, 'handler' : click_black}
],
false,
false,
'4');
this.addToolbarMenu({
icon: 'e/text_highlight',
overlayWidth: '4',
globalItemConfig: {
callback: this._changeStyle
},
items: items
});
},
/**
* Handle to change the background color.
* @param event e - The event that triggered this.
* @param string elementid - the elemen id of menu icon.
* @param string color - The color for the background.
* Change the background color to the specified color.
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} color The new background color
* @private
*/
change_color : function(e, elementid, color) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
_changeStyle: function(e, color) {
if (window.getSelection) {
// Test for IE9 and non-IE browsers.
try {
if (!document.execCommand("BackColor", false, color)) {
M.atto_backcolor.set_back_color(color);
if (!doc.execCommand(BackColor, false, color)) {
this._fallbackChangeStyle(color);
}
} catch (ex) {
M.atto_backcolor.set_back_color(color);
this._fallbackChangeStyle(color);
}
} else if (document.selection && document.selection.createRange) {
} else if (doc.selection && doc.selection.createRange) {
// Test for IE8 or less.
range = document.selection.createRange();
range.execCommand("BackColor", false, color);
range = doc.selection.createRange();
range.execCommand(BackColor, false, color);
}
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
// Mark as updated
this.markUpdated();
},
/**
* Change the background color.
* This function is an alternative use for IE broswers.
* @param string color - The color for the background.
*
* This function is an alternative use for IE browsers.
*
* @method _fallbackChangeStyle
* @param {string} color The color for the background.
* @chainable
* @private
*/
set_back_color : function (color) {
var selection = window.getSelection();
var range = null;
_fallbackChangeStyle: function (color) {
var selection = window.getSelection(),
range;
if (selection.rangeCount && selection.getRangeAt) {
range = selection.getRangeAt(0);
}
document.designMode = "on";
doc.designMode = "on";
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
if (!doc.execCommand("HiliteColor", false, color)) {
doc.execCommand(BackColor, false, color);
}
document.designMode = "off";
doc.designMode = "off";
return this;
}
};
});
}, '@VERSION@');
@@ -1 +1 @@
YUI.add("moodle-atto_backcolor-button",function(e,t){M.atto_backcolor=M.atto_backcolor||{init:function(e){var t="backcolor",n="#FFFFFF",r="#EF4540",i="#FFCF35",s="#98CA3E",o="#7D9FD3",u="#333333",a=function(e,t){M.atto_backcolor.change_color(e,t,n)},f=function(e,t){M.atto_backcolor.change_color(e,t,r)},l=function(e,t){M.atto_backcolor.change_color(e,t,i)},c=function(e,t){M.atto_backcolor.change_color(e,t,s)},h=function(e,t){M.atto_backcolor.change_color(e,t,o)},p=function(e,t){M.atto_backcolor.change_color(e,t,u)},d="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ",v='<div style="'+d+n+'"></div>',m='<div style="'+d+r+'"></div>',g='<div style="'+d+i+'"></div>',y='<div style="'+d+s+'"></div>',b='<div style="'+d+o+'"></div>',w='<div style="'+d+u+'"></div>',E=M.util.image_url("e/text_highlight","core");M.editor_atto.add_toolbar_menu(e.elementid,t,E,e.group,[{text:v,handler:a},{text:m,handler:f},{text:g,handler:l},{text:y,handler:c},{text:b,handler:h},{text:w,handler:p}],!1,!1,"4")},change_color:function(e,t,n){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t);if(window.getSelection)try{document.execCommand("BackColor",!1,n)||M.atto_backcolor.set_back_color(n)}catch(r){M.atto_backcolor.set_back_color(n)}else document.selection&&document.selection.createRange&&(range=document.selection.createRange(),range.execCommand("BackColor",!1,n));M.editor_atto.text_updated(t)},set_back_color:function(e){var t=window.getSelection(),n=null;t.rangeCount&&t.getRangeAt&&(n=t.getRangeAt(0)),document.designMode="on",n&&(t.removeAllRanges(),t.addRange(n)),document.execCommand("HiliteColor",!1,e)||document.execCommand("BackColor",!1,e),document.designMode="off"}}},"@VERSION@");
YUI.add("moodle-atto_backcolor-button",function(e,t){var n=document,r="BackColor",i=[{name:"white",color:"#FFFFFF"},{name:"red",color:"#EF4540"},{name:"yellow",color:"#FFCF35"},{name:"green",color:"#98CA3E"},{name:"blue",color:"#7D9FD3"},{name:"black",color:"#333333"}];e.namespace("M.atto_backcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(i,function(e){t.push({text:'<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: '+e.color+'"></div>',callbackArgs:e.color})}),this.addToolbarMenu({icon:"e/text_highlight",overlayWidth:"4",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(e,t){if(window.getSelection)try{n.execCommand(r,!1,t)||this._fallbackChangeStyle(t)}catch(i){this._fallbackChangeStyle(t)}else n.selection&&n.selection.createRange&&(range=n.selection.createRange(),range.execCommand(r,!1,t));this.markUpdated()},_fallbackChangeStyle:function(e){var t=window.getSelection(),i;return t.rangeCount&&t.getRangeAt&&(i=t.getRangeAt(0)),n.designMode="on",i&&(t.removeAllRanges(),t.addRange(i)),n.execCommand("HiliteColor",!1,e)||n.execCommand(r,!1,e),n.designMode="off",this}})},"@VERSION@");
@@ -15,123 +15,129 @@ YUI.add('moodle-atto_backcolor-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor background color plugin.
*
* @package editor-atto
/*
* @package atto_backcolor
* @copyright 2014 Rossiani Wijaya <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_backcolor = M.atto_backcolor || {
init : function(params) {
var plugin = 'backcolor';
var rgb_white = '#FFFFFF',
rgb_red = '#EF4540',
rgb_yellow = '#FFCF35',
rgb_green = '#98CA3E',
rgb_blue = '#7D9FD3',
rgb_black = '#333333';
/**
* @module moodle-atto_backcolor-button
*/
var click_white = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_white);
};
var click_red = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_red);
};
var click_yellow = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_yellow);
};
var click_green = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_green);
};
var click_blue = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_blue);
};
var click_black = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_black);
};
/**
* Atto text editor backcolor plugin.
*
* @namespace M.atto_backcolor
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var buttoncss = 'width: 20px; height: 20px; border: 1px solid #CCC; background-color: ';
var white = '<div style="' + buttoncss + rgb_white + '"></div>';
var red = '<div style="' + buttoncss + rgb_red + '"></div>';
var yellow = '<div style="' + buttoncss + rgb_yellow + '"></div>';
var green = '<div style="' + buttoncss + rgb_green + '"></div>';
var blue = '<div style="' + buttoncss + rgb_blue + '"></div>';
var black = '<div style="' + buttoncss + rgb_black + '"></div>';
var doc = document,
BackColor = 'BackColor',
colors = [
{
name: 'white',
color: '#FFFFFF'
}, {
name: 'red',
color: '#EF4540'
}, {
name: 'yellow',
color: '#FFCF35'
}, {
name: 'green',
color: '#98CA3E'
}, {
name: 'blue',
color: '#7D9FD3'
}, {
name: 'black',
color: '#333333'
}
];
var iconurl = M.util.image_url('e/text_highlight', 'core');
Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var items = [];
Y.Array.each(colors, function(color) {
items.push({
text: '<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ' +
color.color +
'"></div>',
callbackArgs: color.color
});
});
M.editor_atto.add_toolbar_menu(params.elementid,
plugin,
iconurl,
params.group,
[
{'text' : white, 'handler' : click_white},
{'text' : red, 'handler' : click_red},
{'text' : yellow, 'handler' : click_yellow},
{'text' : green, 'handler' : click_green},
{'text' : blue, 'handler' : click_blue},
{'text' : black, 'handler' : click_black}
],
false,
false,
'4');
this.addToolbarMenu({
icon: 'e/text_highlight',
overlayWidth: '4',
globalItemConfig: {
callback: this._changeStyle
},
items: items
});
},
/**
* Handle to change the background color.
* @param event e - The event that triggered this.
* @param string elementid - the elemen id of menu icon.
* @param string color - The color for the background.
* Change the background color to the specified color.
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} color The new background color
* @private
*/
change_color : function(e, elementid, color) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
_changeStyle: function(e, color) {
if (window.getSelection) {
// Test for IE9 and non-IE browsers.
try {
if (!document.execCommand("BackColor", false, color)) {
M.atto_backcolor.set_back_color(color);
if (!doc.execCommand(BackColor, false, color)) {
this._fallbackChangeStyle(color);
}
} catch (ex) {
M.atto_backcolor.set_back_color(color);
this._fallbackChangeStyle(color);
}
} else if (document.selection && document.selection.createRange) {
} else if (doc.selection && doc.selection.createRange) {
// Test for IE8 or less.
range = document.selection.createRange();
range.execCommand("BackColor", false, color);
range = doc.selection.createRange();
range.execCommand(BackColor, false, color);
}
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
// Mark as updated
this.markUpdated();
},
/**
* Change the background color.
* This function is an alternative use for IE broswers.
* @param string color - The color for the background.
*
* This function is an alternative use for IE browsers.
*
* @method _fallbackChangeStyle
* @param {string} color The color for the background.
* @chainable
* @private
*/
set_back_color : function (color) {
var selection = window.getSelection();
var range = null;
_fallbackChangeStyle: function (color) {
var selection = window.getSelection(),
range;
if (selection.rangeCount && selection.getRangeAt) {
range = selection.getRangeAt(0);
}
document.designMode = "on";
doc.designMode = "on";
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
if (!doc.execCommand("HiliteColor", false, color)) {
doc.execCommand(BackColor, false, color);
}
document.designMode = "off";
doc.designMode = "off";
return this;
}
};
});
}, '@VERSION@');
+89 -83
View File
@@ -13,120 +13,126 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor background color plugin.
*
* @package editor-atto
/*
* @package atto_backcolor
* @copyright 2014 Rossiani Wijaya <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_backcolor = M.atto_backcolor || {
init : function(params) {
var plugin = 'backcolor';
var rgb_white = '#FFFFFF',
rgb_red = '#EF4540',
rgb_yellow = '#FFCF35',
rgb_green = '#98CA3E',
rgb_blue = '#7D9FD3',
rgb_black = '#333333';
/**
* @module moodle-atto_backcolor-button
*/
var click_white = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_white);
};
var click_red = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_red);
};
var click_yellow = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_yellow);
};
var click_green = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_green);
};
var click_blue = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_blue);
};
var click_black = function(e, elementid) {
M.atto_backcolor.change_color(e, elementid, rgb_black);
};
/**
* Atto text editor backcolor plugin.
*
* @namespace M.atto_backcolor
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var buttoncss = 'width: 20px; height: 20px; border: 1px solid #CCC; background-color: ';
var white = '<div style="' + buttoncss + rgb_white + '"></div>';
var red = '<div style="' + buttoncss + rgb_red + '"></div>';
var yellow = '<div style="' + buttoncss + rgb_yellow + '"></div>';
var green = '<div style="' + buttoncss + rgb_green + '"></div>';
var blue = '<div style="' + buttoncss + rgb_blue + '"></div>';
var black = '<div style="' + buttoncss + rgb_black + '"></div>';
var doc = document,
BackColor = 'BackColor',
colors = [
{
name: 'white',
color: '#FFFFFF'
}, {
name: 'red',
color: '#EF4540'
}, {
name: 'yellow',
color: '#FFCF35'
}, {
name: 'green',
color: '#98CA3E'
}, {
name: 'blue',
color: '#7D9FD3'
}, {
name: 'black',
color: '#333333'
}
];
var iconurl = M.util.image_url('e/text_highlight', 'core');
Y.namespace('M.atto_backcolor').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var items = [];
Y.Array.each(colors, function(color) {
items.push({
text: '<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ' +
color.color +
'"></div>',
callbackArgs: color.color
});
});
M.editor_atto.add_toolbar_menu(params.elementid,
plugin,
iconurl,
params.group,
[
{'text' : white, 'handler' : click_white},
{'text' : red, 'handler' : click_red},
{'text' : yellow, 'handler' : click_yellow},
{'text' : green, 'handler' : click_green},
{'text' : blue, 'handler' : click_blue},
{'text' : black, 'handler' : click_black}
],
false,
false,
'4');
this.addToolbarMenu({
icon: 'e/text_highlight',
overlayWidth: '4',
globalItemConfig: {
callback: this._changeStyle
},
items: items
});
},
/**
* Handle to change the background color.
* @param event e - The event that triggered this.
* @param string elementid - the elemen id of menu icon.
* @param string color - The color for the background.
* Change the background color to the specified color.
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} color The new background color
* @private
*/
change_color : function(e, elementid, color) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
_changeStyle: function(e, color) {
if (window.getSelection) {
// Test for IE9 and non-IE browsers.
try {
if (!document.execCommand("BackColor", false, color)) {
M.atto_backcolor.set_back_color(color);
if (!doc.execCommand(BackColor, false, color)) {
this._fallbackChangeStyle(color);
}
} catch (ex) {
M.atto_backcolor.set_back_color(color);
this._fallbackChangeStyle(color);
}
} else if (document.selection && document.selection.createRange) {
} else if (doc.selection && doc.selection.createRange) {
// Test for IE8 or less.
range = document.selection.createRange();
range.execCommand("BackColor", false, color);
range = doc.selection.createRange();
range.execCommand(BackColor, false, color);
}
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
// Mark as updated
this.markUpdated();
},
/**
* Change the background color.
* This function is an alternative use for IE broswers.
* @param string color - The color for the background.
*
* This function is an alternative use for IE browsers.
*
* @method _fallbackChangeStyle
* @param {string} color The color for the background.
* @chainable
* @private
*/
set_back_color : function (color) {
var selection = window.getSelection();
var range = null;
_fallbackChangeStyle: function (color) {
var selection = window.getSelection(),
range;
if (selection.rangeCount && selection.getRangeAt) {
range = selection.getRangeAt(0);
}
document.designMode = "on";
doc.designMode = "on";
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
if (!doc.execCommand("HiliteColor", false, color)) {
doc.execCommand(BackColor, false, color);
}
document.designMode = "off";
doc.designMode = "off";
return this;
}
};
});
@@ -15,50 +15,37 @@ YUI.add('moodle-atto_bold-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_bold
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_bold-button
*/
var SELECTORS = {
TAGS : 'b,strong'
};
/**
* Atto text editor bold plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_bold
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_bold = M.atto_bold || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('bold', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/bold', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'bold', iconurl, params.group, click);
M.editor_atto.add_button_shortcut({action: 'bold', keys: 66});
Y.namespace('M.atto_bold').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'bold',
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'bold');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'bold');
}
// Key code for the keyboard shortcut which triggers this button:
keys: '66',
// Watch the following tags and add/remove highlighting as appropriate:
tags: 'b, strong'
});
}
};
});
}, '@VERSION@', {"requires": ["node", "moodle-editor_atto-editor-shortcut"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_bold-button",function(e,t){var n={TAGS:"b,strong"};M.atto_bold=M.atto_bold||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("bold",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/bold","core");M.editor_atto.add_toolbar_button(e.elementid,"bold",r,e.group,t),M.editor_atto.add_button_shortcut({action:"bold",keys:66}),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"bold"):M.editor_atto.remove_widget_highlight(e.elementid,"bold")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
YUI.add("moodle-atto_bold-button",function(e,t){e.namespace("M.atto_bold").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addBasicButton({exec:"bold",keys:"66",tags:"b, strong"})}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,50 +15,37 @@ YUI.add('moodle-atto_bold-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_bold
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_bold-button
*/
var SELECTORS = {
TAGS : 'b,strong'
};
/**
* Atto text editor bold plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_bold
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_bold = M.atto_bold || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('bold', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/bold', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'bold', iconurl, params.group, click);
M.editor_atto.add_button_shortcut({action: 'bold', keys: 66});
Y.namespace('M.atto_bold').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'bold',
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'bold');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'bold');
}
// Key code for the keyboard shortcut which triggers this button:
keys: '66',
// Watch the following tags and add/remove highlighting as appropriate:
tags: 'b, strong'
});
}
};
});
}, '@VERSION@', {"requires": ["node", "moodle-editor_atto-editor-shortcut"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+20 -33
View File
@@ -13,47 +13,34 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_bold
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_bold-button
*/
var SELECTORS = {
TAGS : 'b,strong'
};
/**
* Atto text editor bold plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_bold
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_bold = M.atto_bold || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('bold', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/bold', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'bold', iconurl, params.group, click);
M.editor_atto.add_button_shortcut({action: 'bold', keys: 66});
Y.namespace('M.atto_bold').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'bold',
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'bold');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'bold');
}
// Key code for the keyboard shortcut which triggers this button:
keys: '66',
// Watch the following tags and add/remove highlighting as appropriate:
tags: 'b, strong'
});
}
};
});
@@ -1,8 +1,7 @@
{
"moodle-atto_bold-button": {
"requires": [
"node",
"moodle-editor_atto-editor-shortcut"
]
}
"moodle-atto_bold-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,32 +15,24 @@ YUI.add('moodle-atto_charmap-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor charmap plugin.
*
/*
* @package atto_charmap
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* CSS classes and IDs.
* Atto text editor character map plugin
*
* @type {Object}
* @module moodle-atto_charmap-button
*/
var CSS = {
var COMPONENTNAME = 'atto_charmap',
CSS = {
BUTTON: 'atto_charmap_character',
CHARMAP: 'atto_charmap_selector'
},
/**
* Selectors.
*
* @type {Object}
*/
SELECTORS = {
BUTTON: '.atto_charmap_character'
},
/**
/*
* Map of special characters, kindly borrowed from TinyMCE.
*
* Each entries contains in order:
@@ -49,6 +41,7 @@ var CSS = {
* - {Boolean} Whether or not to include it in the list
* - {String} The language string key
*
* @property CHARMAP
* @type {Array}
*/
CHARMAP = [
@@ -314,128 +307,111 @@ var CSS = {
['&shy;', '&#173;', false,'softhyphen']
];
M.atto_charmap = M.atto_charmap || {
/**
* Atto text editor charmap plugin.
*
* @namespace M.atto_charmap
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_charmap').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* Keeps track of the selection made by the user.
*
* @type {Mixed}
*/
selection: null,
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
var display_chooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_charmap.selection = M.editor_atto.get_selection();
if (M.atto_charmap.selection === false) {
return;
}
// Stores what editor we are working on.
M.atto_charmap.currentElementId = elementid;
// Initialising the dialogue.
var dialogue;
if (!M.atto_charmap.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
// Setting up the content of the dialogue.
dialogue.set('bodyContent', M.atto_charmap.getDialogueContent());
dialogue.set('headerContent', M.util.get_string('insertcharacter', 'atto_charmap'));
dialogue.render();
dialogue.centerDialogue();
M.atto_charmap.dialogue = dialogue;
} else {
dialogue = M.atto_charmap.dialogue;
}
dialogue.show();
};
var iconurl = M.util.image_url('e/special_character', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'charmap', iconurl, params.group, display_chooser);
initializer: function() {
this.addButton({
icon: 'e/special_character',
callback: this._displayDialogue
});
},
/**
* Generates the content of the dialogue.
* Display the Character Map selector.
*
* @return {Node} Node containing the dialogue content
* @method _displayDialogue
* @private
*/
getDialogueContent: function() {
var content,
html = '<div class="' + CSS.CHARMAP + '">',
i;
for (i = 0; i < CHARMAP.length; i++) {
if (!CHARMAP[i][2]) {
continue;
}
html += '<button class="' + CSS.BUTTON + '" ' +
'aria-label="' + Y.Escape.html(M.util.get_string(CHARMAP[i][3], 'atto_charmap')) + '" ' +
'title="' + Y.Escape.html(M.util.get_string(CHARMAP[i][3], 'atto_charmap')) + '" ' +
'data-character="' + CHARMAP[i][0] + '" ' +
'>' +
CHARMAP[i][0] +
'</button>';
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
html += '</div>';
content = Y.Node.create(html);
Y.delegate('click', M.atto_charmap.insertChar, content, SELECTORS.BUTTON, this);
var dialogue = this.getDialogue({
headerContent: M.util.get_string('insertcharacter', COMPONENTNAME),
focusAfterHide: true
}, true);
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(
'<div class="{{CSS.CHARMAP}}">' +
'{{#each CHARMAP}}' +
'{{#if this.[2]}}' +
'<button class="{{../../CSS.BUTTON}}" ' +
'aria-label="{{get_string this.[3] ../../component}}" ' +
'title="{{get_string this.[3] ../../component}}" ' +
'data-character="{{this.[0]}}" ' +
'>{{{this.[0]}}}</button>' +
'{{/if}}' +
'{{/each}}' +
'</div>'
);
var content = Y.Node.create(template({
component: COMPONENTNAME,
CSS: CSS,
CHARMAP: CHARMAP
}));
content.delegate('click', this._insertChar, '.' + CSS.BUTTON, this);
return content;
},
/**
* Insert the picked character in Atto.
* Insert the picked character into the editor.
*
* @param {Event} e The event
* @return {Void}
* @method _insertChar
* @param {EventFacade} e
* @private
*/
insertChar: function(e) {
_insertChar: function(e) {
var character = e.target.getData('character');
e.preventDefault();
e.stopPropagation();
M.atto_charmap.dialogue.hide();
// Hide the dialogue.
this.getDialogue({
focusAfterHide: null
}).hide();
M.editor_atto.set_selection(M.atto_charmap.selection);
var host = this.get('host');
M.editor_atto.insert_html_at_focus_point(character);
// Focus on the last point.
host.setSelection(this._currentSelection);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(M.atto_charmap.currentElementId);
// And add the character.
host.insertContentAtFocusPoint(character);
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
File diff suppressed because one or more lines are too long
@@ -15,32 +15,24 @@ YUI.add('moodle-atto_charmap-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor charmap plugin.
*
/*
* @package atto_charmap
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* CSS classes and IDs.
* Atto text editor character map plugin
*
* @type {Object}
* @module moodle-atto_charmap-button
*/
var CSS = {
var COMPONENTNAME = 'atto_charmap',
CSS = {
BUTTON: 'atto_charmap_character',
CHARMAP: 'atto_charmap_selector'
},
/**
* Selectors.
*
* @type {Object}
*/
SELECTORS = {
BUTTON: '.atto_charmap_character'
},
/**
/*
* Map of special characters, kindly borrowed from TinyMCE.
*
* Each entries contains in order:
@@ -49,6 +41,7 @@ var CSS = {
* - {Boolean} Whether or not to include it in the list
* - {String} The language string key
*
* @property CHARMAP
* @type {Array}
*/
CHARMAP = [
@@ -314,128 +307,111 @@ var CSS = {
['&shy;', '&#173;', false,'softhyphen']
];
M.atto_charmap = M.atto_charmap || {
/**
* Atto text editor charmap plugin.
*
* @namespace M.atto_charmap
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_charmap').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* Keeps track of the selection made by the user.
*
* @type {Mixed}
*/
selection: null,
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
var display_chooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_charmap.selection = M.editor_atto.get_selection();
if (M.atto_charmap.selection === false) {
return;
}
// Stores what editor we are working on.
M.atto_charmap.currentElementId = elementid;
// Initialising the dialogue.
var dialogue;
if (!M.atto_charmap.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
// Setting up the content of the dialogue.
dialogue.set('bodyContent', M.atto_charmap.getDialogueContent());
dialogue.set('headerContent', M.util.get_string('insertcharacter', 'atto_charmap'));
dialogue.render();
dialogue.centerDialogue();
M.atto_charmap.dialogue = dialogue;
} else {
dialogue = M.atto_charmap.dialogue;
}
dialogue.show();
};
var iconurl = M.util.image_url('e/special_character', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'charmap', iconurl, params.group, display_chooser);
initializer: function() {
this.addButton({
icon: 'e/special_character',
callback: this._displayDialogue
});
},
/**
* Generates the content of the dialogue.
* Display the Character Map selector.
*
* @return {Node} Node containing the dialogue content
* @method _displayDialogue
* @private
*/
getDialogueContent: function() {
var content,
html = '<div class="' + CSS.CHARMAP + '">',
i;
for (i = 0; i < CHARMAP.length; i++) {
if (!CHARMAP[i][2]) {
continue;
}
html += '<button class="' + CSS.BUTTON + '" ' +
'aria-label="' + Y.Escape.html(M.util.get_string(CHARMAP[i][3], 'atto_charmap')) + '" ' +
'title="' + Y.Escape.html(M.util.get_string(CHARMAP[i][3], 'atto_charmap')) + '" ' +
'data-character="' + CHARMAP[i][0] + '" ' +
'>' +
CHARMAP[i][0] +
'</button>';
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
html += '</div>';
content = Y.Node.create(html);
Y.delegate('click', M.atto_charmap.insertChar, content, SELECTORS.BUTTON, this);
var dialogue = this.getDialogue({
headerContent: M.util.get_string('insertcharacter', COMPONENTNAME),
focusAfterHide: true
}, true);
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(
'<div class="{{CSS.CHARMAP}}">' +
'{{#each CHARMAP}}' +
'{{#if this.[2]}}' +
'<button class="{{../../CSS.BUTTON}}" ' +
'aria-label="{{get_string this.[3] ../../component}}" ' +
'title="{{get_string this.[3] ../../component}}" ' +
'data-character="{{this.[0]}}" ' +
'>{{{this.[0]}}}</button>' +
'{{/if}}' +
'{{/each}}' +
'</div>'
);
var content = Y.Node.create(template({
component: COMPONENTNAME,
CSS: CSS,
CHARMAP: CHARMAP
}));
content.delegate('click', this._insertChar, '.' + CSS.BUTTON, this);
return content;
},
/**
* Insert the picked character in Atto.
* Insert the picked character into the editor.
*
* @param {Event} e The event
* @return {Void}
* @method _insertChar
* @param {EventFacade} e
* @private
*/
insertChar: function(e) {
_insertChar: function(e) {
var character = e.target.getData('character');
e.preventDefault();
e.stopPropagation();
M.atto_charmap.dialogue.hide();
// Hide the dialogue.
this.getDialogue({
focusAfterHide: null
}).hide();
M.editor_atto.set_selection(M.atto_charmap.selection);
var host = this.get('host');
M.editor_atto.insert_html_at_focus_point(character);
// Focus on the last point.
host.setSelection(this._currentSelection);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(M.atto_charmap.currentElementId);
// And add the character.
host.insertContentAtFocusPoint(character);
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+89 -113
View File
@@ -13,32 +13,24 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor charmap plugin.
*
/*
* @package atto_charmap
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* CSS classes and IDs.
* Atto text editor character map plugin
*
* @type {Object}
* @module moodle-atto_charmap-button
*/
var CSS = {
var COMPONENTNAME = 'atto_charmap',
CSS = {
BUTTON: 'atto_charmap_character',
CHARMAP: 'atto_charmap_selector'
},
/**
* Selectors.
*
* @type {Object}
*/
SELECTORS = {
BUTTON: '.atto_charmap_character'
},
/**
/*
* Map of special characters, kindly borrowed from TinyMCE.
*
* Each entries contains in order:
@@ -47,6 +39,7 @@ var CSS = {
* - {Boolean} Whether or not to include it in the list
* - {String} The language string key
*
* @property CHARMAP
* @type {Array}
*/
CHARMAP = [
@@ -312,125 +305,108 @@ var CSS = {
['&shy;', '&#173;', false,'softhyphen']
];
M.atto_charmap = M.atto_charmap || {
/**
* Atto text editor charmap plugin.
*
* @namespace M.atto_charmap
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_charmap').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* Keeps track of the selection made by the user.
*
* @type {Mixed}
*/
selection: null,
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
var display_chooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_charmap.selection = M.editor_atto.get_selection();
if (M.atto_charmap.selection === false) {
return;
}
// Stores what editor we are working on.
M.atto_charmap.currentElementId = elementid;
// Initialising the dialogue.
var dialogue;
if (!M.atto_charmap.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
// Setting up the content of the dialogue.
dialogue.set('bodyContent', M.atto_charmap.getDialogueContent());
dialogue.set('headerContent', M.util.get_string('insertcharacter', 'atto_charmap'));
dialogue.render();
dialogue.centerDialogue();
M.atto_charmap.dialogue = dialogue;
} else {
dialogue = M.atto_charmap.dialogue;
}
dialogue.show();
};
var iconurl = M.util.image_url('e/special_character', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'charmap', iconurl, params.group, display_chooser);
initializer: function() {
this.addButton({
icon: 'e/special_character',
callback: this._displayDialogue
});
},
/**
* Generates the content of the dialogue.
* Display the Character Map selector.
*
* @return {Node} Node containing the dialogue content
* @method _displayDialogue
* @private
*/
getDialogueContent: function() {
var content,
html = '<div class="' + CSS.CHARMAP + '">',
i;
for (i = 0; i < CHARMAP.length; i++) {
if (!CHARMAP[i][2]) {
continue;
}
html += '<button class="' + CSS.BUTTON + '" ' +
'aria-label="' + Y.Escape.html(M.util.get_string(CHARMAP[i][3], 'atto_charmap')) + '" ' +
'title="' + Y.Escape.html(M.util.get_string(CHARMAP[i][3], 'atto_charmap')) + '" ' +
'data-character="' + CHARMAP[i][0] + '" ' +
'>' +
CHARMAP[i][0] +
'</button>';
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
html += '</div>';
content = Y.Node.create(html);
Y.delegate('click', M.atto_charmap.insertChar, content, SELECTORS.BUTTON, this);
var dialogue = this.getDialogue({
headerContent: M.util.get_string('insertcharacter', COMPONENTNAME),
focusAfterHide: true
}, true);
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool.
*
* @method _getDialogueContent
* @private
* @return {Node} The content to place in the dialogue.
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(
'<div class="{{CSS.CHARMAP}}">' +
'{{#each CHARMAP}}' +
'{{#if this.[2]}}' +
'<button class="{{../../CSS.BUTTON}}" ' +
'aria-label="{{get_string this.[3] ../../component}}" ' +
'title="{{get_string this.[3] ../../component}}" ' +
'data-character="{{this.[0]}}" ' +
'>{{{this.[0]}}}</button>' +
'{{/if}}' +
'{{/each}}' +
'</div>'
);
var content = Y.Node.create(template({
component: COMPONENTNAME,
CSS: CSS,
CHARMAP: CHARMAP
}));
content.delegate('click', this._insertChar, '.' + CSS.BUTTON, this);
return content;
},
/**
* Insert the picked character in Atto.
* Insert the picked character into the editor.
*
* @param {Event} e The event
* @return {Void}
* @method _insertChar
* @param {EventFacade} e
* @private
*/
insertChar: function(e) {
_insertChar: function(e) {
var character = e.target.getData('character');
e.preventDefault();
e.stopPropagation();
M.atto_charmap.dialogue.hide();
// Hide the dialogue.
this.getDialogue({
focusAfterHide: null
}).hide();
M.editor_atto.set_selection(M.atto_charmap.selection);
var host = this.get('host');
M.editor_atto.insert_html_at_focus_point(character);
// Focus on the last point.
host.setSelection(this._currentSelection);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(M.atto_charmap.currentElementId);
// And add the character.
host.insertContentAtFocusPoint(character);
}
};
});
@@ -1,8 +1,7 @@
{
"moodle-atto_charmap-button": {
"requires": [
"node",
"escape"
]
}
"moodle-atto_charmap-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,29 +15,32 @@ YUI.add('moodle-atto_clear-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor clear plugin.
*
* @package editor-atto
/*
* @package atto_clear
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_clear = M.atto_clear || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('removeFormat', false);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/clear_formatting', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'clear', iconurl, params.group, click);
/**
* @module moodle-atto_clear-button
*/
/**
* Atto text editor clear plugin.
*
* @namespace M.atto_clear
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_clear').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'removeFormat',
icon: 'e/clear_formatting'
});
}
};
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_clear-button",function(e,t){M.atto_clear=M.atto_clear||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("removeFormat",!1),M.editor_atto.text_updated(t)},n=M.util.image_url("e/clear_formatting","core");M.editor_atto.add_toolbar_button(e.elementid,"clear",n,e.group,t)}}},"@VERSION@",{requires:["node"]});
YUI.add("moodle-atto_clear-button",function(e,t){e.namespace("M.atto_clear").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addBasicButton({exec:"removeFormat",icon:"e/clear_formatting"})}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,29 +15,32 @@ YUI.add('moodle-atto_clear-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor clear plugin.
*
* @package editor-atto
/*
* @package atto_clear
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_clear = M.atto_clear || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('removeFormat', false);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/clear_formatting', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'clear', iconurl, params.group, click);
/**
* @module moodle-atto_clear-button
*/
/**
* Atto text editor clear plugin.
*
* @namespace M.atto_clear
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_clear').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'removeFormat',
icon: 'e/clear_formatting'
});
}
};
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+21 -18
View File
@@ -13,26 +13,29 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor clear plugin.
*
* @package editor-atto
/*
* @package atto_clear
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_clear = M.atto_clear || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('removeFormat', false);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/clear_formatting', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'clear', iconurl, params.group, click);
/**
* @module moodle-atto_clear-button
*/
/**
* Atto text editor clear plugin.
*
* @namespace M.atto_clear
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_clear').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'removeFormat',
icon: 'e/clear_formatting'
});
}
};
});
@@ -1,5 +1,7 @@
{
"moodle-atto_clear-button": {
"requires": ["node"]
}
"moodle-atto_clear-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -24,7 +24,7 @@
$string['pluginname'] = 'Show/hide advanced buttons';
$string['showmore'] = 'Show more buttons';
$string['showless'] = 'Show less buttons';
$string['showfewer'] = 'Show fewer buttons';
$string['settings'] = 'Collapse toolbar settings';
$string['showgroups'] = 'Show (n) groups when collapsed.';
$string['showgroups_desc'] = 'When the toolbar is collapsed (it is by default) only this many groups will be displayed at once.';
+1 -1
View File
@@ -30,7 +30,7 @@ defined('MOODLE_INTERNAL') || die();
function atto_collapse_strings_for_js() {
global $PAGE;
$PAGE->requires->strings_for_js(array('showmore', 'showless'), 'atto_collapse');
$PAGE->requires->strings_for_js(array('showmore', 'showfewer'), 'atto_collapse');
}
/**
@@ -15,99 +15,101 @@ YUI.add('moodle-atto_collapse-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS Selectors
*
* @type {Object}
*/
var SELECTORS = {
GROUPS: '.atto_group',
BUTTON: '.atto_collapse_button'
};
/**
* Atto text editor collapse plugin.
*
/*
* @package atto_collapse
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_collapse = M.atto_collapse || {
/**
* How many groups to show when collapsed.
*
* @property showgroups
* @type {Integer}
* @default 3
*/
showgroups : 3,
/**
* @module moodle-atto_collapse-button
*/
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
M.atto_collapse.toggle(elementid);
};
/**
* Atto text editor collapse plugin.
*
* @namespace M.atto_collapse
* @class button
* @extends M.editor_atto.EditorPlugin
*/
this.showgroups = params.showgroups;
var PLUGINNAME = 'atto_collapse',
ATTRSHOWGROUPS = 'showgroups',
COLLAPSE = 'collapse',
COLLAPSED = 'collapsed',
GROUPS = '.atto_group';
var iconurl = M.util.image_url('icon', 'atto_collapse');
Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var button = this.addButton({
icon: M.util.image_url('icon', PLUGINNAME),
callback: this._toggle
});
// Add the button to the toolbar.
M.editor_atto.add_toolbar_button(params.elementid, 'collapse', iconurl, params.group, click);
// Perform a toggle after all plugins have been loaded for the first time.
this.get('host').on('pluginsloaded', function(e, button) {
this._setVisibility(button);
// Set the toolbar to break after the initial those displayed by default.
var firstGroup = this.toolbar.all(GROUPS).item(this.get(ATTRSHOWGROUPS));
firstGroup.insert('<div class="toolbarbreak"></div>', 'before');
}, this, button);
},
/**
* Either hide or show the extra groups in the toolbar.
* Toggle the visibility of the extra groups in the toolbar.
*
* @param {String} elementid
*
* @return {Void}
* @method _toggle
* @param {EventFacade} e
* @private
*/
toggle : function(elementid) {
var toolbar = M.editor_atto.get_toolbar_node(elementid);
var button = toolbar.one(SELECTORS.BUTTON);
var groups = toolbar.all(SELECTORS.GROUPS).slice(this.showgroups);
_toggle: function(e) {
e.preventDefault();
var button = this.buttons[COLLAPSE];
if (button.getData('collapsed')) {
button.set('title', M.util.get_string('showmore', 'atto_collapse'));
groups.show();
button.setData('collapsed', false);
if (button.getData(COLLAPSED)) {
this._setVisibility(button, true);
} else {
button.set('title', M.util.get_string('showless', 'atto_collapse'));
groups.hide();
button.setData('collapsed', true);
this._setVisibility(button);
}
},
/**
* After init function called after all plugins init() has been run.
* Set the visibility of the toolbar groups.
*
* @param {Object} params
*
* @return {Void}
* @method _setVisibility
* @param {Node} button The collapse button
* @param {Booelan} visibility Whether the groups should be made visible
* @private
*/
after_init : function(params) {
var toolbar = M.editor_atto.get_toolbar_node(params.elementid);
var button = toolbar.one(SELECTORS.BUTTON);
var firstgroup = toolbar.all(SELECTORS.GROUPS).item(this.showgroups);
_setVisibility: function(button, visibility) {
var groups = this.toolbar.all(GROUPS).slice(this.get(ATTRSHOWGROUPS));
if (visibility) {
button.set('title', M.util.get_string('showfewer', PLUGINNAME));
groups.show();
button.setData(COLLAPSED, false);
} else {
button.set('title', M.util.get_string('showmore', PLUGINNAME));
groups.hide();
button.setData(COLLAPSED, true);
}
// Set the toolbar to break after the initial those displayed by default.
firstgroup.insert('<div class="toolbarbreak"></div>', 'before');
// Set the state to "not collapsed" (which is the state when the page loads).
button.setData('collapsed', false);
// Call toggle to change the state when the page loads to "collapsed".
M.atto_collapse.toggle(params.elementid);
}
};
}, {
ATTRS: {
/**
* How many groups to show when collapsed.
*
* @attribute showgroups
* @type Number
* @default 3
*/
showgroups: {
value: 3
}
}
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_collapse-button",function(e,t){var n={GROUPS:".atto_group",BUTTON:".atto_collapse_button"};M.atto_collapse=M.atto_collapse||{showgroups:3,init:function(e){var t=function(e,t){e.preventDefault(),M.atto_collapse.toggle(t)};this.showgroups=e.showgroups;var n=M.util.image_url("icon","atto_collapse");M.editor_atto.add_toolbar_button(e.elementid,"collapse",n,e.group,t)},toggle:function(e){var t=M.editor_atto.get_toolbar_node(e),r=t.one(n.BUTTON),i=t.all(n.GROUPS).slice(this.showgroups);r.getData("collapsed")?(r.set("title",M.util.get_string("showmore","atto_collapse")),i.show(),r.setData("collapsed",!1)):(r.set("title",M.util.get_string("showless","atto_collapse")),i.hide(),r.setData("collapsed",!0))},after_init:function(e){var t=M.editor_atto.get_toolbar_node(e.elementid),r=t.one(n.BUTTON),i=t.all(n.GROUPS).item(this.showgroups);i.insert('<div class="toolbarbreak"></div>',"before"),r.setData("collapsed",!1),M.atto_collapse.toggle(e.elementid)}}},"@VERSION@",{requires:["node"]});
YUI.add("moodle-atto_collapse-button",function(e,t){var n="atto_collapse",r="showgroups",i="collapse",s="collapsed",o=".atto_group";e.namespace("M.atto_collapse").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var e=this.addButton({icon:M.util.image_url("icon",n),callback:this._toggle});this.get("host").on("pluginsloaded",function(e,t){this._setVisibility(t);var n=this.toolbar.all(o).item(this.get(r));n.insert('<div class="toolbarbreak"></div>',"before")},this,e)},_toggle:function(e){e.preventDefault();var t=this.buttons[i];t.getData(s)?this._setVisibility(t,!0):this._setVisibility(t)},_setVisibility:function(e,t){var i=this.toolbar.all(o).slice(this.get(r));t?(e.set("title",M.util.get_string("showfewer",n)),i.show(),e.setData(s,!1)):(e.set("title",M.util.get_string("showmore",n)),i.hide(),e.setData(s,!0))}},{ATTRS:{showgroups:{value:3}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,99 +15,101 @@ YUI.add('moodle-atto_collapse-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS Selectors
*
* @type {Object}
*/
var SELECTORS = {
GROUPS: '.atto_group',
BUTTON: '.atto_collapse_button'
};
/**
* Atto text editor collapse plugin.
*
/*
* @package atto_collapse
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_collapse = M.atto_collapse || {
/**
* How many groups to show when collapsed.
*
* @property showgroups
* @type {Integer}
* @default 3
*/
showgroups : 3,
/**
* @module moodle-atto_collapse-button
*/
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
M.atto_collapse.toggle(elementid);
};
/**
* Atto text editor collapse plugin.
*
* @namespace M.atto_collapse
* @class button
* @extends M.editor_atto.EditorPlugin
*/
this.showgroups = params.showgroups;
var PLUGINNAME = 'atto_collapse',
ATTRSHOWGROUPS = 'showgroups',
COLLAPSE = 'collapse',
COLLAPSED = 'collapsed',
GROUPS = '.atto_group';
var iconurl = M.util.image_url('icon', 'atto_collapse');
Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var button = this.addButton({
icon: M.util.image_url('icon', PLUGINNAME),
callback: this._toggle
});
// Add the button to the toolbar.
M.editor_atto.add_toolbar_button(params.elementid, 'collapse', iconurl, params.group, click);
// Perform a toggle after all plugins have been loaded for the first time.
this.get('host').on('pluginsloaded', function(e, button) {
this._setVisibility(button);
// Set the toolbar to break after the initial those displayed by default.
var firstGroup = this.toolbar.all(GROUPS).item(this.get(ATTRSHOWGROUPS));
firstGroup.insert('<div class="toolbarbreak"></div>', 'before');
}, this, button);
},
/**
* Either hide or show the extra groups in the toolbar.
* Toggle the visibility of the extra groups in the toolbar.
*
* @param {String} elementid
*
* @return {Void}
* @method _toggle
* @param {EventFacade} e
* @private
*/
toggle : function(elementid) {
var toolbar = M.editor_atto.get_toolbar_node(elementid);
var button = toolbar.one(SELECTORS.BUTTON);
var groups = toolbar.all(SELECTORS.GROUPS).slice(this.showgroups);
_toggle: function(e) {
e.preventDefault();
var button = this.buttons[COLLAPSE];
if (button.getData('collapsed')) {
button.set('title', M.util.get_string('showmore', 'atto_collapse'));
groups.show();
button.setData('collapsed', false);
if (button.getData(COLLAPSED)) {
this._setVisibility(button, true);
} else {
button.set('title', M.util.get_string('showless', 'atto_collapse'));
groups.hide();
button.setData('collapsed', true);
this._setVisibility(button);
}
},
/**
* After init function called after all plugins init() has been run.
* Set the visibility of the toolbar groups.
*
* @param {Object} params
*
* @return {Void}
* @method _setVisibility
* @param {Node} button The collapse button
* @param {Booelan} visibility Whether the groups should be made visible
* @private
*/
after_init : function(params) {
var toolbar = M.editor_atto.get_toolbar_node(params.elementid);
var button = toolbar.one(SELECTORS.BUTTON);
var firstgroup = toolbar.all(SELECTORS.GROUPS).item(this.showgroups);
_setVisibility: function(button, visibility) {
var groups = this.toolbar.all(GROUPS).slice(this.get(ATTRSHOWGROUPS));
if (visibility) {
button.set('title', M.util.get_string('showfewer', PLUGINNAME));
groups.show();
button.setData(COLLAPSED, false);
} else {
button.set('title', M.util.get_string('showmore', PLUGINNAME));
groups.hide();
button.setData(COLLAPSED, true);
}
// Set the toolbar to break after the initial those displayed by default.
firstgroup.insert('<div class="toolbarbreak"></div>', 'before');
// Set the state to "not collapsed" (which is the state when the page loads).
button.setData('collapsed', false);
// Call toggle to change the state when the page loads to "collapsed".
M.atto_collapse.toggle(params.elementid);
}
};
}, {
ATTRS: {
/**
* How many groups to show when collapsed.
*
* @attribute showgroups
* @type Number
* @default 3
*/
showgroups: {
value: 3
}
}
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+71 -69
View File
@@ -13,96 +13,98 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS Selectors
*
* @type {Object}
*/
var SELECTORS = {
GROUPS: '.atto_group',
BUTTON: '.atto_collapse_button'
};
/**
* Atto text editor collapse plugin.
*
/*
* @package atto_collapse
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_collapse = M.atto_collapse || {
/**
* How many groups to show when collapsed.
*
* @property showgroups
* @type {Integer}
* @default 3
*/
showgroups : 3,
/**
* @module moodle-atto_collapse-button
*/
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
M.atto_collapse.toggle(elementid);
};
/**
* Atto text editor collapse plugin.
*
* @namespace M.atto_collapse
* @class button
* @extends M.editor_atto.EditorPlugin
*/
this.showgroups = params.showgroups;
var PLUGINNAME = 'atto_collapse',
ATTRSHOWGROUPS = 'showgroups',
COLLAPSE = 'collapse',
COLLAPSED = 'collapsed',
GROUPS = '.atto_group';
var iconurl = M.util.image_url('icon', 'atto_collapse');
Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var button = this.addButton({
icon: M.util.image_url('icon', PLUGINNAME),
callback: this._toggle
});
// Add the button to the toolbar.
M.editor_atto.add_toolbar_button(params.elementid, 'collapse', iconurl, params.group, click);
// Perform a toggle after all plugins have been loaded for the first time.
this.get('host').on('pluginsloaded', function(e, button) {
this._setVisibility(button);
// Set the toolbar to break after the initial those displayed by default.
var firstGroup = this.toolbar.all(GROUPS).item(this.get(ATTRSHOWGROUPS));
firstGroup.insert('<div class="toolbarbreak"></div>', 'before');
}, this, button);
},
/**
* Either hide or show the extra groups in the toolbar.
* Toggle the visibility of the extra groups in the toolbar.
*
* @param {String} elementid
*
* @return {Void}
* @method _toggle
* @param {EventFacade} e
* @private
*/
toggle : function(elementid) {
var toolbar = M.editor_atto.get_toolbar_node(elementid);
var button = toolbar.one(SELECTORS.BUTTON);
var groups = toolbar.all(SELECTORS.GROUPS).slice(this.showgroups);
_toggle: function(e) {
e.preventDefault();
var button = this.buttons[COLLAPSE];
if (button.getData('collapsed')) {
button.set('title', M.util.get_string('showmore', 'atto_collapse'));
groups.show();
button.setData('collapsed', false);
if (button.getData(COLLAPSED)) {
this._setVisibility(button, true);
} else {
button.set('title', M.util.get_string('showless', 'atto_collapse'));
groups.hide();
button.setData('collapsed', true);
this._setVisibility(button);
}
},
/**
* After init function called after all plugins init() has been run.
* Set the visibility of the toolbar groups.
*
* @param {Object} params
*
* @return {Void}
* @method _setVisibility
* @param {Node} button The collapse button
* @param {Booelan} visibility Whether the groups should be made visible
* @private
*/
after_init : function(params) {
var toolbar = M.editor_atto.get_toolbar_node(params.elementid);
var button = toolbar.one(SELECTORS.BUTTON);
var firstgroup = toolbar.all(SELECTORS.GROUPS).item(this.showgroups);
_setVisibility: function(button, visibility) {
var groups = this.toolbar.all(GROUPS).slice(this.get(ATTRSHOWGROUPS));
if (visibility) {
button.set('title', M.util.get_string('showfewer', PLUGINNAME));
groups.show();
button.setData(COLLAPSED, false);
} else {
button.set('title', M.util.get_string('showmore', PLUGINNAME));
groups.hide();
button.setData(COLLAPSED, true);
}
// Set the toolbar to break after the initial those displayed by default.
firstgroup.insert('<div class="toolbarbreak"></div>', 'before');
// Set the state to "not collapsed" (which is the state when the page loads).
button.setData('collapsed', false);
// Call toggle to change the state when the page loads to "collapsed".
M.atto_collapse.toggle(params.elementid);
}
};
}, {
ATTRS: {
/**
* How many groups to show when collapsed.
*
* @attribute showgroups
* @type Number
* @default 3
*/
showgroups: {
value: 3
}
}
});
@@ -1,5 +1,7 @@
{
"moodle-atto_collapse-button": {
"requires": ["node"]
}
"moodle-atto_collapse-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,176 +15,156 @@ YUI.add('moodle-atto_emoticon-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor emoticon plugin.
*
/*
* @package atto_emoticon
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* CSS classes and IDs.
*
* @type {Object}
*/
var CSS = {
* @module moodle-atto_emoticon-button
*/
var COMPONENTNAME = 'atto_emoticon',
CSS = {
EMOTE: 'atto_emoticon_emote',
MAP: 'atto_emoticon_map'
},
/**
* Selectors.
*
* @type {Object}
*/
SELECTORS = {
EMOTE: '.atto_emoticon_emote'
};
},
TEMPLATE = '' +
'<div class="{{CSS.MAP}}">' +
'<ul>' +
'{{#each emoticons}}' +
'<li><div>' +
'<a href="#" class="{{../CSS.EMOTE}}" data-text="{{text}}">' +
'<img ' +
'src="{{image_url imagename imagecomponent}}" ' +
'alt="{{get_string altidentifier altcomponent}}"' +
'/>' +
'</a>' +
'</div>' +
'<div>{{text}}</div>' +
'<div>{{get_string altidentifier altcomponent}}</div>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
M.atto_emoticon = M.atto_emoticon || {
/**
* Atto text editor emoticon plugin.
*
* @namespace M.atto_emoticon
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_emoticon').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* List of emoticons.
*
* This must be populated from the result of the PHP function emoticon_manager::get_emoticons().
*
* @type {Array}
*/
emoticons: null,
/**
* Keeps track of the selection made by the user.
*
* @type {Mixed}
*/
selection: null,
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
M.atto_emoticon.emoticons = params.emoticons;
var displayChooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
// Stores the selection.
M.atto_emoticon.selection = M.editor_atto.get_selection();
if (M.atto_emoticon.selection === false) {
return;
}
// Stores what editor we are working on.
M.atto_emoticon.currentElementId = elementid;
// Initialising the dialogue.
var dialogue;
if (!M.atto_emoticon.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
// Setting up the content of the dialogue.
dialogue.set('bodyContent', M.atto_emoticon.getDialogueContent());
dialogue.set('headerContent', M.util.get_string('insertemoticon', 'atto_emoticon'));
dialogue.render();
dialogue.centerDialogue();
M.atto_emoticon.dialogue = dialogue;
} else {
dialogue = M.atto_emoticon.dialogue;
}
dialogue.show();
};
var iconurl = M.util.image_url('e/emoticons', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'emoticon', iconurl, params.group, displayChooser);
initializer: function() {
this.addButton({
icon: 'e/emoticons',
callback: this._displayDialogue
});
},
/**
* Generates the content of the dialogue.
* Display the Emoticon chooser.
*
* @return {Node} Node containing the dialogue content
* @method _displayDialogue
* @private
*/
getDialogueContent: function() {
var content,
emote,
emotealt,
html = '',
i;
html += '<div class="' + CSS.MAP + '">';
html += '<ul>';
for (i = 0; i < M.atto_emoticon.emoticons.length; i++ ) {
emote = M.atto_emoticon.emoticons[i];
emotealt = Y.Escape.html(M.util.get_string(emote.altidentifier, emote.altcomponent));
html += '<li>';
html += '<div><a href="#" class="' + CSS.EMOTE + '" data-index="' + i + '">';
html += '<img src="' + M.util.image_url(emote.imagename, emote.imagecomponent) + '" alt="' + emotealt + '" />';
html += '</a></div>';
html += '<div>' + Y.Escape.html(emote.text) + '</div>';
html += '<div>' + emotealt + '</div>';
html += '</li>';
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
html += '</ul>';
html += '</div>';
content = Y.Node.create(html);
content.delegate('click', M.atto_emoticon.insertEmote, SELECTORS.EMOTE, this);
content.delegate('key', M.atto_emoticon.insertEmote, '32', SELECTORS.EMOTE, this);
var dialogue = this.getDialogue({
headerContent: M.util.get_string('insertemoticon', COMPONENTNAME),
focusAfterHide: true
}, true);
return content;
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Insert the picked emote in Atto.
* Insert the emoticon.
*
* @param {Event} e The event
* @return {Void}
* @method _insertEmote
* @param {EventFacade} e
* @private
*/
insertEmote: function(e) {
_insertEmote: function(e) {
var target = e.target.ancestor(SELECTORS.EMOTE, true),
emote = M.atto_emoticon.emoticons[target.getData('index')],
html = '';
host = this.get('host');
e.preventDefault();
e.stopPropagation();
M.atto_emoticon.dialogue.hide();
html = ' ' + emote.text + ' ';
M.editor_atto.set_selection(M.atto_emoticon.selection);
// Hide the dialogue.
this.getDialogue({
focusAfterHide: null
}).hide();
M.editor_atto.insert_html_at_focus_point(html);
// Build the Emoticon text.
var html = ' ' + target.getData('text') + ' ';
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(M.atto_emoticon.currentElementId);
// Focus on the previous selection.
host.setSelection(this._currentSelection);
// And add the character.
host.insertContentAtFocusPoint(html);
this.markUpdated();
},
/**
* Generates the content of the dialogue, attaching event listeners to
* the content.
*
* @method _getDialogueContent
* @return {Node} Node containing the dialogue content
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
emoticons: this.get('emoticons'),
CSS: CSS
}));
content.delegate('click', this._insertEmote, SELECTORS.EMOTE, this);
content.delegate('key', this._insertEmote, '32', SELECTORS.EMOTE, this);
return content;
}
};
}, {
ATTRS: {
/**
* The list of emoticons to display.
*
* @attribute emoticons
* @type array
* @default {}
*/
emoticons: {
value: {}
}
}
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_emoticon-button",function(e,t){var n={EMOTE:"atto_emoticon_emote",MAP:"atto_emoticon_map"},r={EMOTE:".atto_emoticon_emote"};M.atto_emoticon=M.atto_emoticon||{currentElementId:null,dialogue:null,emoticons:null,selection:null,init:function(e){M.atto_emoticon.emoticons=e.emoticons;var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),M.atto_emoticon.selection=M.editor_atto.get_selection();if(M.atto_emoticon.selection===!1)return;M.atto_emoticon.currentElementId=t;var n;M.atto_emoticon.dialogue?n=M.atto_emoticon.dialogue:(n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0}),n.set("bodyContent",M.atto_emoticon.getDialogueContent()),n.set("headerContent",M.util.get_string("insertemoticon","atto_emoticon")),n.render(),n.centerDialogue(),M.atto_emoticon.dialogue=n),n.show()},n=M.util.image_url("e/emoticons","core");M.editor_atto.add_toolbar_button(e.elementid,"emoticon",n,e.group,t)},getDialogueContent:function(){var t,i,s,o="",u;o+='<div class="'+n.MAP+'">',o+="<ul>";for(u=0;u<M.atto_emoticon.emoticons.length;u++)i=M.atto_emoticon.emoticons[u],s=e.Escape.html(M.util.get_string(i.altidentifier,i.altcomponent)),o+="<li>",o+='<div><a href="#" class="'+n.EMOTE+'" data-index="'+u+'">',o+='<img src="'+M.util.image_url(i.imagename,i.imagecomponent)+'" alt="'+s+'" />',o+="</a></div>",o+="<div>"+e.Escape.html(i.text)+"</div>",o+="<div>"+s+"</div>",o+="</li>";return o+="</ul>",o+="</div>",t=e.Node.create(o),t.delegate("click",M.atto_emoticon.insertEmote,r.EMOTE,this),t.delegate("key",M.atto_emoticon.insertEmote,"32",r.EMOTE,this),t},insertEmote:function(e){var t=e.target.ancestor(r.EMOTE,!0),n=M.atto_emoticon.emoticons[t.getData("index")],i="";e.preventDefault(),e.stopPropagation(),M.atto_emoticon.dialogue.hide(),i=" "+n.text+" ",M.editor_atto.set_selection(M.atto_emoticon.selection),M.editor_atto.insert_html_at_focus_point(i),M.editor_atto.text_updated(M.atto_emoticon.currentElementId)}}},"@VERSION@",{requires:["node"]});
YUI.add("moodle-atto_emoticon-button",function(e,t){var n="atto_emoticon",r={EMOTE:"atto_emoticon_emote",MAP:"atto_emoticon_map"},i={EMOTE:".atto_emoticon_emote"},s='<div class="{{CSS.MAP}}"><ul>{{#each emoticons}}<li><div><a href="#" class="{{../CSS.EMOTE}}" data-text="{{text}}"><img src="{{image_url imagename imagecomponent}}" alt="{{get_string altidentifier altcomponent}}"/></a></div><div>{{text}}</div><div>{{get_string altidentifier altcomponent}}</div></li>{{/each}}</ul></div>';e.namespace("M.atto_emoticon").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,initializer:function(){this.addButton({icon:"e/emoticons",callback:this._displayDialogue})},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1)return;var e=this.getDialogue({headerContent:M.util.get_string("insertemoticon",n),focusAfterHide:!0},!0);e.set("bodyContent",this._getDialogueContent()).show()},_insertEmote:function(e){var t=e.target.ancestor(i.EMOTE,!0),n=this.get("host");e.preventDefault(),this.getDialogue({focusAfterHide:null}).hide();var r=" "+t.getData("text")+" ";n.setSelection(this._currentSelection),n.insertContentAtFocusPoint(r),this.markUpdated()},_getDialogueContent:function(){var t=e.Handlebars.compile(s),n=e.Node.create(t({emoticons:this.get("emoticons"),CSS:r}));return n.delegate("click",this._insertEmote,i.EMOTE,this),n.delegate("key",this._insertEmote,"32",i.EMOTE,this),n}},{ATTRS:{emoticons:{value:{}}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,176 +15,156 @@ YUI.add('moodle-atto_emoticon-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor emoticon plugin.
*
/*
* @package atto_emoticon
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* CSS classes and IDs.
*
* @type {Object}
*/
var CSS = {
* @module moodle-atto_emoticon-button
*/
var COMPONENTNAME = 'atto_emoticon',
CSS = {
EMOTE: 'atto_emoticon_emote',
MAP: 'atto_emoticon_map'
},
/**
* Selectors.
*
* @type {Object}
*/
SELECTORS = {
EMOTE: '.atto_emoticon_emote'
};
},
TEMPLATE = '' +
'<div class="{{CSS.MAP}}">' +
'<ul>' +
'{{#each emoticons}}' +
'<li><div>' +
'<a href="#" class="{{../CSS.EMOTE}}" data-text="{{text}}">' +
'<img ' +
'src="{{image_url imagename imagecomponent}}" ' +
'alt="{{get_string altidentifier altcomponent}}"' +
'/>' +
'</a>' +
'</div>' +
'<div>{{text}}</div>' +
'<div>{{get_string altidentifier altcomponent}}</div>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
M.atto_emoticon = M.atto_emoticon || {
/**
* Atto text editor emoticon plugin.
*
* @namespace M.atto_emoticon
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_emoticon').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* List of emoticons.
*
* This must be populated from the result of the PHP function emoticon_manager::get_emoticons().
*
* @type {Array}
*/
emoticons: null,
/**
* Keeps track of the selection made by the user.
*
* @type {Mixed}
*/
selection: null,
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
M.atto_emoticon.emoticons = params.emoticons;
var displayChooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
// Stores the selection.
M.atto_emoticon.selection = M.editor_atto.get_selection();
if (M.atto_emoticon.selection === false) {
return;
}
// Stores what editor we are working on.
M.atto_emoticon.currentElementId = elementid;
// Initialising the dialogue.
var dialogue;
if (!M.atto_emoticon.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
// Setting up the content of the dialogue.
dialogue.set('bodyContent', M.atto_emoticon.getDialogueContent());
dialogue.set('headerContent', M.util.get_string('insertemoticon', 'atto_emoticon'));
dialogue.render();
dialogue.centerDialogue();
M.atto_emoticon.dialogue = dialogue;
} else {
dialogue = M.atto_emoticon.dialogue;
}
dialogue.show();
};
var iconurl = M.util.image_url('e/emoticons', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'emoticon', iconurl, params.group, displayChooser);
initializer: function() {
this.addButton({
icon: 'e/emoticons',
callback: this._displayDialogue
});
},
/**
* Generates the content of the dialogue.
* Display the Emoticon chooser.
*
* @return {Node} Node containing the dialogue content
* @method _displayDialogue
* @private
*/
getDialogueContent: function() {
var content,
emote,
emotealt,
html = '',
i;
html += '<div class="' + CSS.MAP + '">';
html += '<ul>';
for (i = 0; i < M.atto_emoticon.emoticons.length; i++ ) {
emote = M.atto_emoticon.emoticons[i];
emotealt = Y.Escape.html(M.util.get_string(emote.altidentifier, emote.altcomponent));
html += '<li>';
html += '<div><a href="#" class="' + CSS.EMOTE + '" data-index="' + i + '">';
html += '<img src="' + M.util.image_url(emote.imagename, emote.imagecomponent) + '" alt="' + emotealt + '" />';
html += '</a></div>';
html += '<div>' + Y.Escape.html(emote.text) + '</div>';
html += '<div>' + emotealt + '</div>';
html += '</li>';
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
html += '</ul>';
html += '</div>';
content = Y.Node.create(html);
content.delegate('click', M.atto_emoticon.insertEmote, SELECTORS.EMOTE, this);
content.delegate('key', M.atto_emoticon.insertEmote, '32', SELECTORS.EMOTE, this);
var dialogue = this.getDialogue({
headerContent: M.util.get_string('insertemoticon', COMPONENTNAME),
focusAfterHide: true
}, true);
return content;
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Insert the picked emote in Atto.
* Insert the emoticon.
*
* @param {Event} e The event
* @return {Void}
* @method _insertEmote
* @param {EventFacade} e
* @private
*/
insertEmote: function(e) {
_insertEmote: function(e) {
var target = e.target.ancestor(SELECTORS.EMOTE, true),
emote = M.atto_emoticon.emoticons[target.getData('index')],
html = '';
host = this.get('host');
e.preventDefault();
e.stopPropagation();
M.atto_emoticon.dialogue.hide();
html = ' ' + emote.text + ' ';
M.editor_atto.set_selection(M.atto_emoticon.selection);
// Hide the dialogue.
this.getDialogue({
focusAfterHide: null
}).hide();
M.editor_atto.insert_html_at_focus_point(html);
// Build the Emoticon text.
var html = ' ' + target.getData('text') + ' ';
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(M.atto_emoticon.currentElementId);
// Focus on the previous selection.
host.setSelection(this._currentSelection);
// And add the character.
host.insertContentAtFocusPoint(html);
this.markUpdated();
},
/**
* Generates the content of the dialogue, attaching event listeners to
* the content.
*
* @method _getDialogueContent
* @return {Node} Node containing the dialogue content
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
emoticons: this.get('emoticons'),
CSS: CSS
}));
content.delegate('click', this._insertEmote, SELECTORS.EMOTE, this);
content.delegate('key', this._insertEmote, '32', SELECTORS.EMOTE, this);
return content;
}
};
}, {
ATTRS: {
/**
* The list of emoticons to display.
*
* @attribute emoticons
* @type array
* @default {}
*/
emoticons: {
value: {}
}
}
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+113 -133
View File
@@ -13,173 +13,153 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor emoticon plugin.
*
/*
* @package atto_emoticon
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* CSS classes and IDs.
*
* @type {Object}
*/
var CSS = {
* @module moodle-atto_emoticon-button
*/
var COMPONENTNAME = 'atto_emoticon',
CSS = {
EMOTE: 'atto_emoticon_emote',
MAP: 'atto_emoticon_map'
},
/**
* Selectors.
*
* @type {Object}
*/
SELECTORS = {
EMOTE: '.atto_emoticon_emote'
};
},
TEMPLATE = '' +
'<div class="{{CSS.MAP}}">' +
'<ul>' +
'{{#each emoticons}}' +
'<li><div>' +
'<a href="#" class="{{../CSS.EMOTE}}" data-text="{{text}}">' +
'<img ' +
'src="{{image_url imagename imagecomponent}}" ' +
'alt="{{get_string altidentifier altcomponent}}"' +
'/>' +
'</a>' +
'</div>' +
'<div>{{text}}</div>' +
'<div>{{get_string altidentifier altcomponent}}</div>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
M.atto_emoticon = M.atto_emoticon || {
/**
* Atto text editor emoticon plugin.
*
* @namespace M.atto_emoticon
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_emoticon').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* List of emoticons.
*
* This must be populated from the result of the PHP function emoticon_manager::get_emoticons().
*
* @type {Array}
*/
emoticons: null,
/**
* Keeps track of the selection made by the user.
*
* @type {Mixed}
*/
selection: null,
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init: function(params) {
M.atto_emoticon.emoticons = params.emoticons;
var displayChooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
// Stores the selection.
M.atto_emoticon.selection = M.editor_atto.get_selection();
if (M.atto_emoticon.selection === false) {
return;
}
// Stores what editor we are working on.
M.atto_emoticon.currentElementId = elementid;
// Initialising the dialogue.
var dialogue;
if (!M.atto_emoticon.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
// Setting up the content of the dialogue.
dialogue.set('bodyContent', M.atto_emoticon.getDialogueContent());
dialogue.set('headerContent', M.util.get_string('insertemoticon', 'atto_emoticon'));
dialogue.render();
dialogue.centerDialogue();
M.atto_emoticon.dialogue = dialogue;
} else {
dialogue = M.atto_emoticon.dialogue;
}
dialogue.show();
};
var iconurl = M.util.image_url('e/emoticons', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'emoticon', iconurl, params.group, displayChooser);
initializer: function() {
this.addButton({
icon: 'e/emoticons',
callback: this._displayDialogue
});
},
/**
* Generates the content of the dialogue.
* Display the Emoticon chooser.
*
* @return {Node} Node containing the dialogue content
* @method _displayDialogue
* @private
*/
getDialogueContent: function() {
var content,
emote,
emotealt,
html = '',
i;
html += '<div class="' + CSS.MAP + '">';
html += '<ul>';
for (i = 0; i < M.atto_emoticon.emoticons.length; i++ ) {
emote = M.atto_emoticon.emoticons[i];
emotealt = Y.Escape.html(M.util.get_string(emote.altidentifier, emote.altcomponent));
html += '<li>';
html += '<div><a href="#" class="' + CSS.EMOTE + '" data-index="' + i + '">';
html += '<img src="' + M.util.image_url(emote.imagename, emote.imagecomponent) + '" alt="' + emotealt + '" />';
html += '</a></div>';
html += '<div>' + Y.Escape.html(emote.text) + '</div>';
html += '<div>' + emotealt + '</div>';
html += '</li>';
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
html += '</ul>';
html += '</div>';
content = Y.Node.create(html);
content.delegate('click', M.atto_emoticon.insertEmote, SELECTORS.EMOTE, this);
content.delegate('key', M.atto_emoticon.insertEmote, '32', SELECTORS.EMOTE, this);
var dialogue = this.getDialogue({
headerContent: M.util.get_string('insertemoticon', COMPONENTNAME),
focusAfterHide: true
}, true);
return content;
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Insert the picked emote in Atto.
* Insert the emoticon.
*
* @param {Event} e The event
* @return {Void}
* @method _insertEmote
* @param {EventFacade} e
* @private
*/
insertEmote: function(e) {
_insertEmote: function(e) {
var target = e.target.ancestor(SELECTORS.EMOTE, true),
emote = M.atto_emoticon.emoticons[target.getData('index')],
html = '';
host = this.get('host');
e.preventDefault();
e.stopPropagation();
M.atto_emoticon.dialogue.hide();
html = ' ' + emote.text + ' ';
M.editor_atto.set_selection(M.atto_emoticon.selection);
// Hide the dialogue.
this.getDialogue({
focusAfterHide: null
}).hide();
M.editor_atto.insert_html_at_focus_point(html);
// Build the Emoticon text.
var html = ' ' + target.getData('text') + ' ';
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(M.atto_emoticon.currentElementId);
// Focus on the previous selection.
host.setSelection(this._currentSelection);
// And add the character.
host.insertContentAtFocusPoint(html);
this.markUpdated();
},
/**
* Generates the content of the dialogue, attaching event listeners to
* the content.
*
* @method _getDialogueContent
* @return {Node} Node containing the dialogue content
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
emoticons: this.get('emoticons'),
CSS: CSS
}));
content.delegate('click', this._insertEmote, SELECTORS.EMOTE, this);
content.delegate('key', this._insertEmote, '32', SELECTORS.EMOTE, this);
return content;
}
};
}, {
ATTRS: {
/**
* The list of emoticons to display.
*
* @attribute emoticons
* @type array
* @default {}
*/
emoticons: {
value: {}
}
}
});
@@ -1,5 +1,7 @@
{
"moodle-atto_emoticon-button": {
"requires": ["node"]
}
"moodle-atto_emoticon-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
+17 -4
View File
@@ -64,10 +64,23 @@ function atto_equation_params_for_js($elementid, $options, $fpoptions) {
}
// Tex example librarys.
$library = array('group1' => get_config('atto_equation', 'librarygroup1'),
'group2' => get_config('atto_equation', 'librarygroup2'),
'group3' => get_config('atto_equation', 'librarygroup3'),
'group4' => get_config('atto_equation', 'librarygroup4'));
$library = array(
'group1' => array(
'groupname' => 'librarygroup1',
'elements' => get_config('atto_equation', 'librarygroup1'),
),
'group2' => array(
'groupname' => 'librarygroup2',
'elements' => get_config('atto_equation', 'librarygroup2'),
),
'group3' => array(
'groupname' => 'librarygroup3',
'elements' => get_config('atto_equation', 'librarygroup3'),
),
'group4' => array(
'groupname' => 'librarygroup4',
'elements' => get_config('atto_equation', 'librarygroup4'),
));
return array('texfilteractive' => $texfilteractive, 'contextid'=>$context->id, 'library'=>$library);
}
+3 -3
View File
@@ -1,15 +1,15 @@
#atto_equation_library .yui3-tabview-list {
.atto_equation_library .yui3-tabview-list {
border: none;
}
#atto_equation_library .yui3-tab-selected .yui3-tab-label, .yui3-skin-sam #atto_equation_library .yui3-tab-selected .yui3-tab-label:focus, .yui3-skin-sam #atto_equation_library .yui3-tab-selected .yui3-tab-label:hover {
.atto_equation_library .yui3-tab-selected .yui3-tab-label, .yui3-skin-sam #atto_equation_library .yui3-tab-selected .yui3-tab-label:focus, .yui3-skin-sam #atto_equation_library .yui3-tab-selected .yui3-tab-label:hover {
background: none;
color: black;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
#atto_equation_library button {
.atto_equation_library button {
margin: 4px;
}
@@ -16,153 +16,179 @@ YUI.add('moodle-atto_equation-button', function (Y, NAME) {
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor equation plugin.
*
* @package editor-atto
* @package atto_equation
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_equation = M.atto_equation || {
/**
* The window used to get the equation details.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
/**
* Atto text editor equation plugin.
*/
/**
* Atto equation editor.
*
* @namespace M.atto_equation
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENTNAME = 'atto_equation',
CSS = {
EQUATION_TEXT: 'atto_equation_equation',
EQUATION_PREVIEW: 'atto_equation_preview',
SUBMIT: 'atto_equation_submit',
LIBRARY: 'atto_equation_library',
LIBRARY_GROUP_PREFIX: 'atto_equation_library'
},
SELECTORS = {
LIBRARY_GROUP_PREFIX: '.' + CSS.LIBRARY_GROUP_PREFIX,
EQUATION_TEXT: '.' + CSS.EQUATION_TEXT,
EQUATION_PREVIEW: '.' + CSS.EQUATION_PREVIEW,
SUBMIT: '.' + CSS.SUBMIT,
LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
},
TEMPLATES = {
FORM: '' +
'<form class="atto_form">' +
'{{{library}}}' +
'<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{get_string "editequation" component}}</label>' +
'<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
'<p>{{{get_string "editequation_desc" component}}}</p>' +
'<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="{{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
'</div>' +
'</form>',
LIBRARY: '' +
'<div class="{{CSS.LIBRARY}}">' +
'<ul>' +
'{{#each library}}' +
'<li><a href="#{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">{{get_string groupname ../component}}</a></li>' +
'{{/each}}' +
'</ul>' +
'<div>' +
'{{#each library}}' +
'<div id="{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">' +
'{{#split "\n" elements}}' +
'<button data-tex="{{this}}" title="{{this}}">$${{this}}$$</button>' +
'{{/split}}' +
'</div>' +
'{{/each}}' +
'</div>' +
'</div>'
};
Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The selection object returned by the browser.
*
* @property selection
* @property _currentSelection
* @type Range
* @default null
* @private
*/
selection : null,
_currentSelection: null,
/**
* A mapping of elementids to contextids.
* The cursor position in the equation textarea.
*
* @property contextids
* @type Object
* @default {}
*/
contextids : {},
/**
* A nested object containing a the configured list of tex examples.
*
* @property library
* @type Object
* @default {}
*/
library : {},
/**
* The last cursor index in the source.
*
* @property lastcursor
* @type Integer
* @property _lastCursorPos
* @type Number
* @default 0
* @private
*/
lastcursor : 0,
_lastCursorPos: 0,
/**
* Display the chooser dialogue.
* A reference to the dialogue content.
*
* @method display_chooser
* @param Event e
* @param string elementid
* @property _content
* @type Node
* @private
*/
display_chooser : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_equation.selection = M.editor_atto.get_selection();
if (M.atto_equation.selection) {
var dialogue;
if (!M.atto_equation.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
} else {
dialogue = M.atto_equation.dialogue;
}
_content: null,
dialogue.render();
dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation'));
var tabview = new Y.TabView({
srcNode: '#atto_equation_library'
initializer: function() {
if (this.get('texfilteractive')) {
// Add the button to the toolbar.
this.addButton({
icon: 'e/math',
callback: this._displayDialogue
});
tabview.render();
dialogue.show();
var equation = M.atto_equation.resolve_equation();
if (equation) {
Y.one('#atto_equation_equation').set('text', equation);
}
M.atto_equation.update_preview(false, elementid);
M.atto_equation.dialogue = dialogue;
// We need custom highlight logic for this button.
this.get('host').on('atto:selectionchanged', function() {
if (this._resolveEquation()) {
this.highlightButtons();
} else {
this.unHighlightButtons();
}
}, this);
}
},
/**
* Add this button to the form.
* Display the equation editor.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/math', 'core');
_displayDialogue: function() {
this._currentSelection = this.get('host').getSelection();
if (params.texfilteractive) {
// Save the elementid/contextid mapping.
this.contextids[params.elementid] = params.contextid;
// Save the button library.
this.library = params.library;
// Add the button to the toolbar.
M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser);
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.atto_equation.resolve_equation()) {
M.editor_atto.add_widget_highlight(e.elementid, 'equation');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
}
});
if (this._currentSelection === false) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENTNAME),
focusAfterHide: true
});
var content = this._getDialogueContent();
dialogue.set('bodyContent', content);
var library = content.one(SELECTORS.LIBRARY_GROUP_PREFIX);
var tabview = new Y.TabView({
srcNode: library
});
tabview.render();
dialogue.show();
var equation = this._resolveEquation();
if (equation) {
content.one(SELECTORS.EQUATION_TEXT).set('text', equation);
}
this._updatePreview(false);
},
/**
* If there is selected text and it is part of an equation,
* extract the equation (and set it in the form).
*
* @method resolve_equation
* @method _resolveEquation
* @private
* @return {String|Boolean} The equation or false.
*/
resolve_equation : function() {
_resolveEquation: function() {
// Find the equation in the surrounding text.
var selectednode = M.editor_atto.get_selection_parent_node(),
var selectedNode = this.get('host').getSelectionParentNode(),
text,
equation;
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
if (!selectedNode) {
return false;
}
text = Y.one(selectednode).get('text');
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
@@ -176,154 +202,156 @@ M.atto_equation = M.atto_equation || {
},
/**
* The OK button has been pressed - make the changes to the source.
* Handle insertion of a new equation, or update of an existing one.
*
* @method set_equation
* @param {Y.Event} e
* @param {String} elementid
* @method _setEquation
* @param {EventFacade} e
* @private
*/
set_equation : function(e, elementid) {
_setEquation: function(e) {
var input,
selectednode,
selectedNode,
text,
pattern,
equation,
value;
var host = this.get('host');
e.preventDefault();
M.atto_equation.dialogue.hide();
M.editor_atto.set_selection(M.atto_equation.selection);
this.getDialogue({
focusAfterHide: null
}).hide();
input = e.currentTarget.ancestor('.atto_form').one('textarea');
value = input.get('value');
if (value !== '') {
host.setSelection(this._currentSelection);
value = '$$ ' + value.trim() + ' $$';
selectednode = Y.one(M.editor_atto.get_selection_parent_node()),
text = selectednode.get('text');
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
// Replace the equation.
equation = equation.pop();
text = text.replace(equation, '$$' + value + '$$');
selectednode.set('text', text);
selectedNode.set('text', text);
} else {
// Insert the new equation.
M.editor_atto.insert_html_at_focus_point(value);
host.insertContentAtFocusPoint(value);
}
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
this.markUpdated();
}
},
/**
* Update the preview div to match the current equation.
*
* @param Event e - unused
* @param String elementid - The editor elementid.
* @method update_preview
* @param {EventFacade} e
* @method _updatePreview
* @private
*/
update_preview : function(e, elementid) {
var textarea = Y.one('#atto_equation_equation');
var equation = textarea.get('value'), url, preview;
var prefix = '';
var cursorlatex = '\\square ' ;
_updatePreview: function(e) {
var textarea = this._content.one(SELECTORS.EQUATION_TEXT),
equation = textarea.get('value'),
url,
preview,
currentPos = textarea.get('selectionStart'),
prefix = '',
cursorLatex = '\\square ',
isChar;
var currentpos = textarea.get('selectionStart');
if (!currentpos) {
currentpos = 0;
}
// Move the cursor so it does not break expressions.
//
while (equation.charAt(currentpos) === '\\' && currentpos > 0) {
currentpos -= 1;
}
var ischar = /[\w\{\}]/;
while (ischar.test(equation.charAt(currentpos)) && currentpos < equation.length) {
currentpos += 1;
}
// Save the cursor position - for insertion from the library.
this.lastcursorpos = currentpos;
equation = prefix + equation.substring(0, currentpos) + cursorlatex + equation.substring(currentpos);
if (e) {
e.preventDefault();
}
if (!currentPos) {
currentPos = 0;
}
// Move the cursor so it does not break expressions.
//
while (equation.charAt(currentPos) === '\\' && currentPos > 0) {
currentPos -= 1;
}
isChar = /[\w\{\}]/;
while (isChar.test(equation.charAt(currentPos)) && currentPos < equation.length) {
currentPos += 1;
}
// Save the cursor position - for insertion from the library.
this._lastCursorPos = currentPos;
equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
params = {
sesskey: M.cfg.sesskey,
contextid: this.contextids[elementid],
action : 'filtertext',
text : '$$ ' + equation + ' $$'
contextid: this.get('contextid'),
action: 'filtertext',
text: '$$ ' + equation + ' $$'
};
preview = Y.io(url, { sync: true,
data: params });
if (preview.status === 200) {
Y.one('#atto_equation_preview').setHTML(preview.responseText);
this._content.one(SELECTORS.EQUATION_PREVIEW).setHTML(preview.responseText);
}
},
/**
* Return the HTML of the form to show in the dialogue.
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method get_form_content
* @param string elementid
* @return string
* @method _getDialogueContent
* @return {Node}
* @private
*/
get_form_content : function(elementid) {
var content = Y.Node.create('<form class="atto_form">' +
this.get_library_html(elementid) +
'<label for="atto_equation_equation">' + M.util.get_string('editequation', 'atto_equation') +
'</label>' +
'<textarea class="fullwidth" id="atto_equation_equation" rows="8"></textarea><br/>' +
'<p>' + M.util.get_string('editequation_desc', 'atto_equation') + '</p>' +
'<label for="atto_equation_preview">' + M.util.get_string('preview', 'atto_equation') +
'</label>' +
'<div class="fullwidth" id="atto_equation_preview"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button id="atto_equation_submit">' +
M.util.get_string('saveequation', 'atto_equation') +
'</button>' +
'</div>' +
'</form>');
_getDialogueContent: function() {
var library = this._getLibraryContent(),
template = Y.Handlebars.compile(TEMPLATES.FORM);
content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid);
content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid);
content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid);
content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid);
this._content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
}));
content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid);
this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._updatePreview, this);
this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
return content;
return this._content;
},
/**
* Reponse to button presses in the tex library panels.
* Reponse to button presses in the TeX library panels.
*
* @method select_library_item
* @param Event event
* @param string elementid
* @return string
* @method _selectLibraryItem
* @param {EventFacade} e
* @return {string}
* @private
*/
select_library_item : function(event, elementid) {
var tex = event.currentTarget.getAttribute('data-tex');
_selectLibraryItem: function(e) {
var tex = e.currentTarget.getAttribute('data-tex');
event.preventDefault();
e.preventDefault();
input = event.currentTarget.ancestor('.atto_form').one('textarea');
input = e.currentTarget.ancestor('.atto_form').one('textarea');
value = input.get('value');
value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length);
value = value.substring(0, this._lastCursorPos) + tex + value.substring(this._lastCursorPos, value.length);
input.set('value', value);
input.focus();
var focusPoint = this.lastcursorpos + tex.length,
var focusPoint = this._lastCursorPos + tex.length,
realInput = input.getDOMNode();
if (typeof realInput.selectionStart === "number") {
// Modern browsers have selectionStart and selectionEnd to control the cursor position.
@@ -335,54 +363,98 @@ M.atto_equation = M.atto_equation || {
range.select();
}
// Focus must be set before updating the preview for the cursor box to be in the correct location.
M.atto_equation.update_preview(false, elementid);
this._updatePreview(false);
},
/**
* Return the HTML for rendering the library of predefined buttons.
*
* @method get_library_html
* @param string elementid
* @return string
* @method _getLibraryContent
* @return {string}
* @private
*/
get_library_html : function(elementid) {
var content = '<div id="atto_equation_library">', i = 0, group = 1;
content += '<ul>';
for (group = 1; group < 5; group++) {
content += '<li><a href="#atto_equation_library' + group + '">' + M.util.get_string('librarygroup' + group, 'atto_equation') + '</a></li>';
}
content += '</ul>';
content += '<div>';
for (group = 1; group < 5; group++) {
content += '<div id="atto_equation_library' + group + '">';
var examples = this.library['group' + group].split("\n");
for (i = 0; i < examples.length; i++) {
if (examples[i]) {
examples[i] = Y.Escape.html(examples[i]);
content += '<button data-tex="' + examples[i] + '" title="' + examples[i] + '">$$' + examples[i] + '$$</button>';
}
_getLibraryContent: function() {
var template = Y.Handlebars.compile(TEMPLATES.LIBRARY),
library = this.get('library'),
content = '';
// Helper to iterate over a newline separated string.
Y.Handlebars.registerHelper('split', function(delimiter, str, options) {
var parts,
current,
out;
if (typeof delimiter === "undefined" || typeof str === "undefined") {
Y.log('Handlebars split helper: String and delimiter are required.', 'debug', 'moodle-atto_equation-button');
return '';
}
content += '</div>';
}
content += '</div>';
content += '</div>';
out = '';
parts = str.trim().split(delimiter);
while (parts.length > 0) {
current = parts.shift();
out += options.fn(current);
}
return out;
});
content = template({
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
});
var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
var params = {
sesskey: M.cfg.sesskey,
contextid: this.contextids[elementid],
action : 'filtertext',
text : content
contextid: this.get('contextid'),
action: 'filtertext',
text: content
};
preview = Y.io(url, { sync: true, data: params, method: 'POST'});
preview = Y.io(url, {
sync: true,
data: params,
method: 'POST'
});
if (preview.status === 200) {
content = preview.responseText;
}
return content;
}
};
}, {
ATTRS: {
/**
* Whether the TeX filter is currently active.
*
* @attribute texfilteractive
* @type Boolean
*/
texfilteractive: {
value: false
},
/**
* The contextid to use when generating this preview.
*
* @attribute contextid
* @type String
*/
contextid: {
value: null
},
/**
* The content of the example library.
*
* @attribute library
* @type object
*/
library: {
value: {}
}
}
});
}, '@VERSION@', {"requires": ["node", "escape", "io", "event-valuechange", "tabview"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "io", "event-valuechange", "tabview"]});
File diff suppressed because one or more lines are too long
@@ -16,153 +16,179 @@ YUI.add('moodle-atto_equation-button', function (Y, NAME) {
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor equation plugin.
*
* @package editor-atto
* @package atto_equation
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_equation = M.atto_equation || {
/**
* The window used to get the equation details.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
/**
* Atto text editor equation plugin.
*/
/**
* Atto equation editor.
*
* @namespace M.atto_equation
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENTNAME = 'atto_equation',
CSS = {
EQUATION_TEXT: 'atto_equation_equation',
EQUATION_PREVIEW: 'atto_equation_preview',
SUBMIT: 'atto_equation_submit',
LIBRARY: 'atto_equation_library',
LIBRARY_GROUP_PREFIX: 'atto_equation_library'
},
SELECTORS = {
LIBRARY_GROUP_PREFIX: '.' + CSS.LIBRARY_GROUP_PREFIX,
EQUATION_TEXT: '.' + CSS.EQUATION_TEXT,
EQUATION_PREVIEW: '.' + CSS.EQUATION_PREVIEW,
SUBMIT: '.' + CSS.SUBMIT,
LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
},
TEMPLATES = {
FORM: '' +
'<form class="atto_form">' +
'{{{library}}}' +
'<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{get_string "editequation" component}}</label>' +
'<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
'<p>{{{get_string "editequation_desc" component}}}</p>' +
'<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="{{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
'</div>' +
'</form>',
LIBRARY: '' +
'<div class="{{CSS.LIBRARY}}">' +
'<ul>' +
'{{#each library}}' +
'<li><a href="#{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">{{get_string groupname ../component}}</a></li>' +
'{{/each}}' +
'</ul>' +
'<div>' +
'{{#each library}}' +
'<div id="{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">' +
'{{#split "\n" elements}}' +
'<button data-tex="{{this}}" title="{{this}}">$${{this}}$$</button>' +
'{{/split}}' +
'</div>' +
'{{/each}}' +
'</div>' +
'</div>'
};
Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The selection object returned by the browser.
*
* @property selection
* @property _currentSelection
* @type Range
* @default null
* @private
*/
selection : null,
_currentSelection: null,
/**
* A mapping of elementids to contextids.
* The cursor position in the equation textarea.
*
* @property contextids
* @type Object
* @default {}
*/
contextids : {},
/**
* A nested object containing a the configured list of tex examples.
*
* @property library
* @type Object
* @default {}
*/
library : {},
/**
* The last cursor index in the source.
*
* @property lastcursor
* @type Integer
* @property _lastCursorPos
* @type Number
* @default 0
* @private
*/
lastcursor : 0,
_lastCursorPos: 0,
/**
* Display the chooser dialogue.
* A reference to the dialogue content.
*
* @method display_chooser
* @param Event e
* @param string elementid
* @property _content
* @type Node
* @private
*/
display_chooser : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_equation.selection = M.editor_atto.get_selection();
if (M.atto_equation.selection) {
var dialogue;
if (!M.atto_equation.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
} else {
dialogue = M.atto_equation.dialogue;
}
_content: null,
dialogue.render();
dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation'));
var tabview = new Y.TabView({
srcNode: '#atto_equation_library'
initializer: function() {
if (this.get('texfilteractive')) {
// Add the button to the toolbar.
this.addButton({
icon: 'e/math',
callback: this._displayDialogue
});
tabview.render();
dialogue.show();
var equation = M.atto_equation.resolve_equation();
if (equation) {
Y.one('#atto_equation_equation').set('text', equation);
}
M.atto_equation.update_preview(false, elementid);
M.atto_equation.dialogue = dialogue;
// We need custom highlight logic for this button.
this.get('host').on('atto:selectionchanged', function() {
if (this._resolveEquation()) {
this.highlightButtons();
} else {
this.unHighlightButtons();
}
}, this);
}
},
/**
* Add this button to the form.
* Display the equation editor.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/math', 'core');
_displayDialogue: function() {
this._currentSelection = this.get('host').getSelection();
if (params.texfilteractive) {
// Save the elementid/contextid mapping.
this.contextids[params.elementid] = params.contextid;
// Save the button library.
this.library = params.library;
// Add the button to the toolbar.
M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser);
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.atto_equation.resolve_equation()) {
M.editor_atto.add_widget_highlight(e.elementid, 'equation');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
}
});
if (this._currentSelection === false) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENTNAME),
focusAfterHide: true
});
var content = this._getDialogueContent();
dialogue.set('bodyContent', content);
var library = content.one(SELECTORS.LIBRARY_GROUP_PREFIX);
var tabview = new Y.TabView({
srcNode: library
});
tabview.render();
dialogue.show();
var equation = this._resolveEquation();
if (equation) {
content.one(SELECTORS.EQUATION_TEXT).set('text', equation);
}
this._updatePreview(false);
},
/**
* If there is selected text and it is part of an equation,
* extract the equation (and set it in the form).
*
* @method resolve_equation
* @method _resolveEquation
* @private
* @return {String|Boolean} The equation or false.
*/
resolve_equation : function() {
_resolveEquation: function() {
// Find the equation in the surrounding text.
var selectednode = M.editor_atto.get_selection_parent_node(),
var selectedNode = this.get('host').getSelectionParentNode(),
text,
equation;
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
if (!selectedNode) {
return false;
}
text = Y.one(selectednode).get('text');
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
@@ -176,154 +202,156 @@ M.atto_equation = M.atto_equation || {
},
/**
* The OK button has been pressed - make the changes to the source.
* Handle insertion of a new equation, or update of an existing one.
*
* @method set_equation
* @param {Y.Event} e
* @param {String} elementid
* @method _setEquation
* @param {EventFacade} e
* @private
*/
set_equation : function(e, elementid) {
_setEquation: function(e) {
var input,
selectednode,
selectedNode,
text,
pattern,
equation,
value;
var host = this.get('host');
e.preventDefault();
M.atto_equation.dialogue.hide();
M.editor_atto.set_selection(M.atto_equation.selection);
this.getDialogue({
focusAfterHide: null
}).hide();
input = e.currentTarget.ancestor('.atto_form').one('textarea');
value = input.get('value');
if (value !== '') {
host.setSelection(this._currentSelection);
value = '$$ ' + value.trim() + ' $$';
selectednode = Y.one(M.editor_atto.get_selection_parent_node()),
text = selectednode.get('text');
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
// Replace the equation.
equation = equation.pop();
text = text.replace(equation, '$$' + value + '$$');
selectednode.set('text', text);
selectedNode.set('text', text);
} else {
// Insert the new equation.
M.editor_atto.insert_html_at_focus_point(value);
host.insertContentAtFocusPoint(value);
}
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
this.markUpdated();
}
},
/**
* Update the preview div to match the current equation.
*
* @param Event e - unused
* @param String elementid - The editor elementid.
* @method update_preview
* @param {EventFacade} e
* @method _updatePreview
* @private
*/
update_preview : function(e, elementid) {
var textarea = Y.one('#atto_equation_equation');
var equation = textarea.get('value'), url, preview;
var prefix = '';
var cursorlatex = '\\square ' ;
_updatePreview: function(e) {
var textarea = this._content.one(SELECTORS.EQUATION_TEXT),
equation = textarea.get('value'),
url,
preview,
currentPos = textarea.get('selectionStart'),
prefix = '',
cursorLatex = '\\square ',
isChar;
var currentpos = textarea.get('selectionStart');
if (!currentpos) {
currentpos = 0;
}
// Move the cursor so it does not break expressions.
//
while (equation.charAt(currentpos) === '\\' && currentpos > 0) {
currentpos -= 1;
}
var ischar = /[\w\{\}]/;
while (ischar.test(equation.charAt(currentpos)) && currentpos < equation.length) {
currentpos += 1;
}
// Save the cursor position - for insertion from the library.
this.lastcursorpos = currentpos;
equation = prefix + equation.substring(0, currentpos) + cursorlatex + equation.substring(currentpos);
if (e) {
e.preventDefault();
}
if (!currentPos) {
currentPos = 0;
}
// Move the cursor so it does not break expressions.
//
while (equation.charAt(currentPos) === '\\' && currentPos > 0) {
currentPos -= 1;
}
isChar = /[\w\{\}]/;
while (isChar.test(equation.charAt(currentPos)) && currentPos < equation.length) {
currentPos += 1;
}
// Save the cursor position - for insertion from the library.
this._lastCursorPos = currentPos;
equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
params = {
sesskey: M.cfg.sesskey,
contextid: this.contextids[elementid],
action : 'filtertext',
text : '$$ ' + equation + ' $$'
contextid: this.get('contextid'),
action: 'filtertext',
text: '$$ ' + equation + ' $$'
};
preview = Y.io(url, { sync: true,
data: params });
if (preview.status === 200) {
Y.one('#atto_equation_preview').setHTML(preview.responseText);
this._content.one(SELECTORS.EQUATION_PREVIEW).setHTML(preview.responseText);
}
},
/**
* Return the HTML of the form to show in the dialogue.
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method get_form_content
* @param string elementid
* @return string
* @method _getDialogueContent
* @return {Node}
* @private
*/
get_form_content : function(elementid) {
var content = Y.Node.create('<form class="atto_form">' +
this.get_library_html(elementid) +
'<label for="atto_equation_equation">' + M.util.get_string('editequation', 'atto_equation') +
'</label>' +
'<textarea class="fullwidth" id="atto_equation_equation" rows="8"></textarea><br/>' +
'<p>' + M.util.get_string('editequation_desc', 'atto_equation') + '</p>' +
'<label for="atto_equation_preview">' + M.util.get_string('preview', 'atto_equation') +
'</label>' +
'<div class="fullwidth" id="atto_equation_preview"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button id="atto_equation_submit">' +
M.util.get_string('saveequation', 'atto_equation') +
'</button>' +
'</div>' +
'</form>');
_getDialogueContent: function() {
var library = this._getLibraryContent(),
template = Y.Handlebars.compile(TEMPLATES.FORM);
content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid);
content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid);
content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid);
content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid);
this._content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
}));
content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid);
this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._updatePreview, this);
this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
return content;
return this._content;
},
/**
* Reponse to button presses in the tex library panels.
* Reponse to button presses in the TeX library panels.
*
* @method select_library_item
* @param Event event
* @param string elementid
* @return string
* @method _selectLibraryItem
* @param {EventFacade} e
* @return {string}
* @private
*/
select_library_item : function(event, elementid) {
var tex = event.currentTarget.getAttribute('data-tex');
_selectLibraryItem: function(e) {
var tex = e.currentTarget.getAttribute('data-tex');
event.preventDefault();
e.preventDefault();
input = event.currentTarget.ancestor('.atto_form').one('textarea');
input = e.currentTarget.ancestor('.atto_form').one('textarea');
value = input.get('value');
value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length);
value = value.substring(0, this._lastCursorPos) + tex + value.substring(this._lastCursorPos, value.length);
input.set('value', value);
input.focus();
var focusPoint = this.lastcursorpos + tex.length,
var focusPoint = this._lastCursorPos + tex.length,
realInput = input.getDOMNode();
if (typeof realInput.selectionStart === "number") {
// Modern browsers have selectionStart and selectionEnd to control the cursor position.
@@ -335,54 +363,97 @@ M.atto_equation = M.atto_equation || {
range.select();
}
// Focus must be set before updating the preview for the cursor box to be in the correct location.
M.atto_equation.update_preview(false, elementid);
this._updatePreview(false);
},
/**
* Return the HTML for rendering the library of predefined buttons.
*
* @method get_library_html
* @param string elementid
* @return string
* @method _getLibraryContent
* @return {string}
* @private
*/
get_library_html : function(elementid) {
var content = '<div id="atto_equation_library">', i = 0, group = 1;
content += '<ul>';
for (group = 1; group < 5; group++) {
content += '<li><a href="#atto_equation_library' + group + '">' + M.util.get_string('librarygroup' + group, 'atto_equation') + '</a></li>';
}
content += '</ul>';
content += '<div>';
for (group = 1; group < 5; group++) {
content += '<div id="atto_equation_library' + group + '">';
var examples = this.library['group' + group].split("\n");
for (i = 0; i < examples.length; i++) {
if (examples[i]) {
examples[i] = Y.Escape.html(examples[i]);
content += '<button data-tex="' + examples[i] + '" title="' + examples[i] + '">$$' + examples[i] + '$$</button>';
}
_getLibraryContent: function() {
var template = Y.Handlebars.compile(TEMPLATES.LIBRARY),
library = this.get('library'),
content = '';
// Helper to iterate over a newline separated string.
Y.Handlebars.registerHelper('split', function(delimiter, str, options) {
var parts,
current,
out;
if (typeof delimiter === "undefined" || typeof str === "undefined") {
return '';
}
content += '</div>';
}
content += '</div>';
content += '</div>';
out = '';
parts = str.trim().split(delimiter);
while (parts.length > 0) {
current = parts.shift();
out += options.fn(current);
}
return out;
});
content = template({
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
});
var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
var params = {
sesskey: M.cfg.sesskey,
contextid: this.contextids[elementid],
action : 'filtertext',
text : content
contextid: this.get('contextid'),
action: 'filtertext',
text: content
};
preview = Y.io(url, { sync: true, data: params, method: 'POST'});
preview = Y.io(url, {
sync: true,
data: params,
method: 'POST'
});
if (preview.status === 200) {
content = preview.responseText;
}
return content;
}
};
}, {
ATTRS: {
/**
* Whether the TeX filter is currently active.
*
* @attribute texfilteractive
* @type Boolean
*/
texfilteractive: {
value: false
},
/**
* The contextid to use when generating this preview.
*
* @attribute contextid
* @type String
*/
contextid: {
value: null
},
/**
* The content of the example library.
*
* @attribute library
* @type object
*/
library: {
value: {}
}
}
});
}, '@VERSION@', {"requires": ["node", "escape", "io", "event-valuechange", "tabview"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "io", "event-valuechange", "tabview"]});
+282 -210
View File
@@ -14,153 +14,179 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor equation plugin.
*
* @package editor-atto
* @package atto_equation
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_equation = M.atto_equation || {
/**
* The window used to get the equation details.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
/**
* Atto text editor equation plugin.
*/
/**
* Atto equation editor.
*
* @namespace M.atto_equation
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENTNAME = 'atto_equation',
CSS = {
EQUATION_TEXT: 'atto_equation_equation',
EQUATION_PREVIEW: 'atto_equation_preview',
SUBMIT: 'atto_equation_submit',
LIBRARY: 'atto_equation_library',
LIBRARY_GROUP_PREFIX: 'atto_equation_library'
},
SELECTORS = {
LIBRARY_GROUP_PREFIX: '.' + CSS.LIBRARY_GROUP_PREFIX,
EQUATION_TEXT: '.' + CSS.EQUATION_TEXT,
EQUATION_PREVIEW: '.' + CSS.EQUATION_PREVIEW,
SUBMIT: '.' + CSS.SUBMIT,
LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
},
TEMPLATES = {
FORM: '' +
'<form class="atto_form">' +
'{{{library}}}' +
'<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{get_string "editequation" component}}</label>' +
'<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
'<p>{{{get_string "editequation_desc" component}}}</p>' +
'<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="{{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
'</div>' +
'</form>',
LIBRARY: '' +
'<div class="{{CSS.LIBRARY}}">' +
'<ul>' +
'{{#each library}}' +
'<li><a href="#{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">{{get_string groupname ../component}}</a></li>' +
'{{/each}}' +
'</ul>' +
'<div>' +
'{{#each library}}' +
'<div id="{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">' +
'{{#split "\n" elements}}' +
'<button data-tex="{{this}}" title="{{this}}">$${{this}}$$</button>' +
'{{/split}}' +
'</div>' +
'{{/each}}' +
'</div>' +
'</div>'
};
Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The selection object returned by the browser.
*
* @property selection
* @property _currentSelection
* @type Range
* @default null
* @private
*/
selection : null,
_currentSelection: null,
/**
* A mapping of elementids to contextids.
* The cursor position in the equation textarea.
*
* @property contextids
* @type Object
* @default {}
*/
contextids : {},
/**
* A nested object containing a the configured list of tex examples.
*
* @property library
* @type Object
* @default {}
*/
library : {},
/**
* The last cursor index in the source.
*
* @property lastcursor
* @type Integer
* @property _lastCursorPos
* @type Number
* @default 0
* @private
*/
lastcursor : 0,
_lastCursorPos: 0,
/**
* Display the chooser dialogue.
* A reference to the dialogue content.
*
* @method display_chooser
* @param Event e
* @param string elementid
* @property _content
* @type Node
* @private
*/
display_chooser : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_equation.selection = M.editor_atto.get_selection();
if (M.atto_equation.selection) {
var dialogue;
if (!M.atto_equation.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
} else {
dialogue = M.atto_equation.dialogue;
}
_content: null,
dialogue.render();
dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation'));
var tabview = new Y.TabView({
srcNode: '#atto_equation_library'
initializer: function() {
if (this.get('texfilteractive')) {
// Add the button to the toolbar.
this.addButton({
icon: 'e/math',
callback: this._displayDialogue
});
tabview.render();
dialogue.show();
var equation = M.atto_equation.resolve_equation();
if (equation) {
Y.one('#atto_equation_equation').set('text', equation);
}
M.atto_equation.update_preview(false, elementid);
M.atto_equation.dialogue = dialogue;
// We need custom highlight logic for this button.
this.get('host').on('atto:selectionchanged', function() {
if (this._resolveEquation()) {
this.highlightButtons();
} else {
this.unHighlightButtons();
}
}, this);
}
},
/**
* Add this button to the form.
* Display the equation editor.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/math', 'core');
_displayDialogue: function() {
this._currentSelection = this.get('host').getSelection();
if (params.texfilteractive) {
// Save the elementid/contextid mapping.
this.contextids[params.elementid] = params.contextid;
// Save the button library.
this.library = params.library;
// Add the button to the toolbar.
M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser);
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.atto_equation.resolve_equation()) {
M.editor_atto.add_widget_highlight(e.elementid, 'equation');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
}
});
if (this._currentSelection === false) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('pluginname', COMPONENTNAME),
focusAfterHide: true
});
var content = this._getDialogueContent();
dialogue.set('bodyContent', content);
var library = content.one(SELECTORS.LIBRARY_GROUP_PREFIX);
var tabview = new Y.TabView({
srcNode: library
});
tabview.render();
dialogue.show();
var equation = this._resolveEquation();
if (equation) {
content.one(SELECTORS.EQUATION_TEXT).set('text', equation);
}
this._updatePreview(false);
},
/**
* If there is selected text and it is part of an equation,
* extract the equation (and set it in the form).
*
* @method resolve_equation
* @method _resolveEquation
* @private
* @return {String|Boolean} The equation or false.
*/
resolve_equation : function() {
_resolveEquation: function() {
// Find the equation in the surrounding text.
var selectednode = M.editor_atto.get_selection_parent_node(),
var selectedNode = this.get('host').getSelectionParentNode(),
text,
equation;
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
if (!selectedNode) {
return false;
}
text = Y.one(selectednode).get('text');
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
@@ -174,154 +200,156 @@ M.atto_equation = M.atto_equation || {
},
/**
* The OK button has been pressed - make the changes to the source.
* Handle insertion of a new equation, or update of an existing one.
*
* @method set_equation
* @param {Y.Event} e
* @param {String} elementid
* @method _setEquation
* @param {EventFacade} e
* @private
*/
set_equation : function(e, elementid) {
_setEquation: function(e) {
var input,
selectednode,
selectedNode,
text,
pattern,
equation,
value;
var host = this.get('host');
e.preventDefault();
M.atto_equation.dialogue.hide();
M.editor_atto.set_selection(M.atto_equation.selection);
this.getDialogue({
focusAfterHide: null
}).hide();
input = e.currentTarget.ancestor('.atto_form').one('textarea');
value = input.get('value');
if (value !== '') {
host.setSelection(this._currentSelection);
value = '$$ ' + value.trim() + ' $$';
selectednode = Y.one(M.editor_atto.get_selection_parent_node()),
text = selectednode.get('text');
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
// Replace the equation.
equation = equation.pop();
text = text.replace(equation, '$$' + value + '$$');
selectednode.set('text', text);
selectedNode.set('text', text);
} else {
// Insert the new equation.
M.editor_atto.insert_html_at_focus_point(value);
host.insertContentAtFocusPoint(value);
}
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
this.markUpdated();
}
},
/**
* Update the preview div to match the current equation.
*
* @param Event e - unused
* @param String elementid - The editor elementid.
* @method update_preview
* @param {EventFacade} e
* @method _updatePreview
* @private
*/
update_preview : function(e, elementid) {
var textarea = Y.one('#atto_equation_equation');
var equation = textarea.get('value'), url, preview;
var prefix = '';
var cursorlatex = '\\square ' ;
_updatePreview: function(e) {
var textarea = this._content.one(SELECTORS.EQUATION_TEXT),
equation = textarea.get('value'),
url,
preview,
currentPos = textarea.get('selectionStart'),
prefix = '',
cursorLatex = '\\square ',
isChar;
var currentpos = textarea.get('selectionStart');
if (!currentpos) {
currentpos = 0;
}
// Move the cursor so it does not break expressions.
//
while (equation.charAt(currentpos) === '\\' && currentpos > 0) {
currentpos -= 1;
}
var ischar = /[\w\{\}]/;
while (ischar.test(equation.charAt(currentpos)) && currentpos < equation.length) {
currentpos += 1;
}
// Save the cursor position - for insertion from the library.
this.lastcursorpos = currentpos;
equation = prefix + equation.substring(0, currentpos) + cursorlatex + equation.substring(currentpos);
if (e) {
e.preventDefault();
}
if (!currentPos) {
currentPos = 0;
}
// Move the cursor so it does not break expressions.
//
while (equation.charAt(currentPos) === '\\' && currentPos > 0) {
currentPos -= 1;
}
isChar = /[\w\{\}]/;
while (isChar.test(equation.charAt(currentPos)) && currentPos < equation.length) {
currentPos += 1;
}
// Save the cursor position - for insertion from the library.
this._lastCursorPos = currentPos;
equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
params = {
sesskey: M.cfg.sesskey,
contextid: this.contextids[elementid],
action : 'filtertext',
text : '$$ ' + equation + ' $$'
contextid: this.get('contextid'),
action: 'filtertext',
text: '$$ ' + equation + ' $$'
};
preview = Y.io(url, { sync: true,
data: params });
if (preview.status === 200) {
Y.one('#atto_equation_preview').setHTML(preview.responseText);
this._content.one(SELECTORS.EQUATION_PREVIEW).setHTML(preview.responseText);
}
},
/**
* Return the HTML of the form to show in the dialogue.
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method get_form_content
* @param string elementid
* @return string
* @method _getDialogueContent
* @return {Node}
* @private
*/
get_form_content : function(elementid) {
var content = Y.Node.create('<form class="atto_form">' +
this.get_library_html(elementid) +
'<label for="atto_equation_equation">' + M.util.get_string('editequation', 'atto_equation') +
'</label>' +
'<textarea class="fullwidth" id="atto_equation_equation" rows="8"></textarea><br/>' +
'<p>' + M.util.get_string('editequation_desc', 'atto_equation') + '</p>' +
'<label for="atto_equation_preview">' + M.util.get_string('preview', 'atto_equation') +
'</label>' +
'<div class="fullwidth" id="atto_equation_preview"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button id="atto_equation_submit">' +
M.util.get_string('saveequation', 'atto_equation') +
'</button>' +
'</div>' +
'</form>');
_getDialogueContent: function() {
var library = this._getLibraryContent(),
template = Y.Handlebars.compile(TEMPLATES.FORM);
content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid);
content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid);
content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid);
content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid);
this._content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
}));
content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid);
this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._updatePreview, this);
this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
return content;
return this._content;
},
/**
* Reponse to button presses in the tex library panels.
* Reponse to button presses in the TeX library panels.
*
* @method select_library_item
* @param Event event
* @param string elementid
* @return string
* @method _selectLibraryItem
* @param {EventFacade} e
* @return {string}
* @private
*/
select_library_item : function(event, elementid) {
var tex = event.currentTarget.getAttribute('data-tex');
_selectLibraryItem: function(e) {
var tex = e.currentTarget.getAttribute('data-tex');
event.preventDefault();
e.preventDefault();
input = event.currentTarget.ancestor('.atto_form').one('textarea');
input = e.currentTarget.ancestor('.atto_form').one('textarea');
value = input.get('value');
value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length);
value = value.substring(0, this._lastCursorPos) + tex + value.substring(this._lastCursorPos, value.length);
input.set('value', value);
input.focus();
var focusPoint = this.lastcursorpos + tex.length,
var focusPoint = this._lastCursorPos + tex.length,
realInput = input.getDOMNode();
if (typeof realInput.selectionStart === "number") {
// Modern browsers have selectionStart and selectionEnd to control the cursor position.
@@ -333,51 +361,95 @@ M.atto_equation = M.atto_equation || {
range.select();
}
// Focus must be set before updating the preview for the cursor box to be in the correct location.
M.atto_equation.update_preview(false, elementid);
this._updatePreview(false);
},
/**
* Return the HTML for rendering the library of predefined buttons.
*
* @method get_library_html
* @param string elementid
* @return string
* @method _getLibraryContent
* @return {string}
* @private
*/
get_library_html : function(elementid) {
var content = '<div id="atto_equation_library">', i = 0, group = 1;
content += '<ul>';
for (group = 1; group < 5; group++) {
content += '<li><a href="#atto_equation_library' + group + '">' + M.util.get_string('librarygroup' + group, 'atto_equation') + '</a></li>';
}
content += '</ul>';
content += '<div>';
for (group = 1; group < 5; group++) {
content += '<div id="atto_equation_library' + group + '">';
var examples = this.library['group' + group].split("\n");
for (i = 0; i < examples.length; i++) {
if (examples[i]) {
examples[i] = Y.Escape.html(examples[i]);
content += '<button data-tex="' + examples[i] + '" title="' + examples[i] + '">$$' + examples[i] + '$$</button>';
}
_getLibraryContent: function() {
var template = Y.Handlebars.compile(TEMPLATES.LIBRARY),
library = this.get('library'),
content = '';
// Helper to iterate over a newline separated string.
Y.Handlebars.registerHelper('split', function(delimiter, str, options) {
var parts,
current,
out;
if (typeof delimiter === "undefined" || typeof str === "undefined") {
Y.log('Handlebars split helper: String and delimiter are required.', 'debug', 'moodle-atto_equation-button');
return '';
}
content += '</div>';
}
content += '</div>';
content += '</div>';
out = '';
parts = str.trim().split(delimiter);
while (parts.length > 0) {
current = parts.shift();
out += options.fn(current);
}
return out;
});
content = template({
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
});
var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
var params = {
sesskey: M.cfg.sesskey,
contextid: this.contextids[elementid],
action : 'filtertext',
text : content
contextid: this.get('contextid'),
action: 'filtertext',
text: content
};
preview = Y.io(url, { sync: true, data: params, method: 'POST'});
preview = Y.io(url, {
sync: true,
data: params,
method: 'POST'
});
if (preview.status === 200) {
content = preview.responseText;
}
return content;
}
};
}, {
ATTRS: {
/**
* Whether the TeX filter is currently active.
*
* @attribute texfilteractive
* @type Boolean
*/
texfilteractive: {
value: false
},
/**
* The contextid to use when generating this preview.
*
* @attribute contextid
* @type String
*/
contextid: {
value: null
},
/**
* The content of the example library.
*
* @attribute library
* @type object
*/
library: {
value: {}
}
}
});
@@ -1,11 +1,10 @@
{
"moodle-atto_equation-button": {
"requires": [
"node",
"escape",
"io",
"event-valuechange",
"tabview"
]
}
"moodle-atto_equation-button": {
"requires": [
"moodle-editor_atto-plugin",
"io",
"event-valuechange",
"tabview"
]
}
}
@@ -15,88 +15,85 @@ YUI.add('moodle-atto_fontcolor-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor font color plugin.
*
* @package editor-atto
/*
* @package atto_fontcolor
* @copyright 2014 Rossiani Wijaya <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_fontcolor = M.atto_fontcolor || {
dialogue : null,
init : function(params) {
var plugin = 'fontcolor';
var rgb_white = '#FFFFFF',
rgb_red = '#EF4540',
rgb_yellow = '#FFCF35',
rgb_green = '#98CA3E',
rgb_blue = '#7D9FD3',
rgb_black = '#333333';
/**
* @module moodle-atto_align-button
*/
var click_white = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, 'transparent');
};
var click_red = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_red);
};
var click_yellow = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_yellow);
};
var click_green = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_green);
};
var click_blue = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_blue);
};
var click_black = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_black);
};
/**
* Atto text editor fontcolor plugin.
*
* @namespace M.atto_fontcolor
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var buttoncss = 'width: 20px; height: 20px; border: 1px solid #CCC; background-color: ';
var white = '<div style="' + buttoncss + rgb_white + '"></div>';
var red = '<div style="' + buttoncss + rgb_red + '"></div>';
var yellow = '<div style="' + buttoncss + rgb_yellow + '"></div>';
var green = '<div style="' + buttoncss + rgb_green + '"></div>';
var blue = '<div style="' + buttoncss + rgb_blue + '"></div>';
var black = '<div style="' + buttoncss + rgb_black + '"></div>';
var colors = [
{
name: 'white',
color: '#FFFFFF'
}, {
name: 'red',
color: '#EF4540'
}, {
name: 'yellow',
color: '#FFCF35'
}, {
name: 'green',
color: '#98CA3E'
}, {
name: 'blue',
color: '#7D9FD3'
}, {
name: 'black',
color: '#333333'
}
];
var iconurl = M.util.image_url('e/text_color', 'core');
Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var items = [];
Y.Array.each(colors, function(color) {
items.push({
text: '<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ' +
color.color +
'"></div>',
callbackArgs: color.color,
callback: this._changeStyle
});
});
M.editor_atto.add_toolbar_menu(params.elementid,
plugin,
iconurl,
params.group,
[
{'text' : white, 'handler' : click_white},
{'text' : red, 'handler' : click_red},
{'text' : yellow, 'handler' : click_yellow},
{'text' : green, 'handler' : click_green},
{'text' : blue, 'handler' : click_blue},
{'text' : black, 'handler' : click_black}
],
false,
false,
'4',
'#333333');
this.addToolbarMenu({
icon: 'e/text_color',
overlayWidth: '4',
menuColor: '#333333',
globalItemConfig: {
callback: this._changeStyle
},
items: items
});
},
/**
* Handle to change the editor font color.
* @param event e - The event that triggered this.
* @param string elementid - the elemen id of menu icon.
* @param string color - The color for the background.
* Change the font color to the specified color.
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} color The new font color
* @private
*/
change_color : function(e, elementid, color) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('foreColor', 0, color);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
_changeStyle: function(e, color) {
document.execCommand('forecolor', 0, color);
// Mark as updated
this.markUpdated();
}
};
});
}, '@VERSION@');
@@ -1 +1 @@
YUI.add("moodle-atto_fontcolor-button",function(e,t){M.atto_fontcolor=M.atto_fontcolor||{dialogue:null,init:function(e){var t="fontcolor",n="#FFFFFF",r="#EF4540",i="#FFCF35",s="#98CA3E",o="#7D9FD3",u="#333333",a=function(e,t){M.atto_fontcolor.change_color(e,t,"transparent")},f=function(e,t){M.atto_fontcolor.change_color(e,t,r)},l=function(e,t){M.atto_fontcolor.change_color(e,t,i)},c=function(e,t){M.atto_fontcolor.change_color(e,t,s)},h=function(e,t){M.atto_fontcolor.change_color(e,t,o)},p=function(e,t){M.atto_fontcolor.change_color(e,t,u)},d="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ",v='<div style="'+d+n+'"></div>',m='<div style="'+d+r+'"></div>',g='<div style="'+d+i+'"></div>',y='<div style="'+d+s+'"></div>',b='<div style="'+d+o+'"></div>',w='<div style="'+d+u+'"></div>',E=M.util.image_url("e/text_color","core");M.editor_atto.add_toolbar_menu(e.elementid,t,E,e.group,[{text:v,handler:a},{text:m,handler:f},{text:g,handler:l},{text:y,handler:c},{text:b,handler:h},{text:w,handler:p}],!1,!1,"4","#333333")},change_color:function(e,t,n){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("foreColor",0,n),M.editor_atto.text_updated(t)}}},"@VERSION@");
YUI.add("moodle-atto_fontcolor-button",function(e,t){var n=[{name:"white",color:"#FFFFFF"},{name:"red",color:"#EF4540"},{name:"yellow",color:"#FFCF35"},{name:"green",color:"#98CA3E"},{name:"blue",color:"#7D9FD3"},{name:"black",color:"#333333"}];e.namespace("M.atto_fontcolor").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=[];e.Array.each(n,function(e){t.push({text:'<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: '+e.color+'"></div>',callbackArgs:e.color,callback:this._changeStyle})}),this.addToolbarMenu({icon:"e/text_color",overlayWidth:"4",menuColor:"#333333",globalItemConfig:{callback:this._changeStyle},items:t})},_changeStyle:function(e,t){document.execCommand("forecolor",0,t),this.markUpdated()}})},"@VERSION@");
@@ -15,88 +15,85 @@ YUI.add('moodle-atto_fontcolor-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor font color plugin.
*
* @package editor-atto
/*
* @package atto_fontcolor
* @copyright 2014 Rossiani Wijaya <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_fontcolor = M.atto_fontcolor || {
dialogue : null,
init : function(params) {
var plugin = 'fontcolor';
var rgb_white = '#FFFFFF',
rgb_red = '#EF4540',
rgb_yellow = '#FFCF35',
rgb_green = '#98CA3E',
rgb_blue = '#7D9FD3',
rgb_black = '#333333';
/**
* @module moodle-atto_align-button
*/
var click_white = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, 'transparent');
};
var click_red = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_red);
};
var click_yellow = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_yellow);
};
var click_green = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_green);
};
var click_blue = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_blue);
};
var click_black = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_black);
};
/**
* Atto text editor fontcolor plugin.
*
* @namespace M.atto_fontcolor
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var buttoncss = 'width: 20px; height: 20px; border: 1px solid #CCC; background-color: ';
var white = '<div style="' + buttoncss + rgb_white + '"></div>';
var red = '<div style="' + buttoncss + rgb_red + '"></div>';
var yellow = '<div style="' + buttoncss + rgb_yellow + '"></div>';
var green = '<div style="' + buttoncss + rgb_green + '"></div>';
var blue = '<div style="' + buttoncss + rgb_blue + '"></div>';
var black = '<div style="' + buttoncss + rgb_black + '"></div>';
var colors = [
{
name: 'white',
color: '#FFFFFF'
}, {
name: 'red',
color: '#EF4540'
}, {
name: 'yellow',
color: '#FFCF35'
}, {
name: 'green',
color: '#98CA3E'
}, {
name: 'blue',
color: '#7D9FD3'
}, {
name: 'black',
color: '#333333'
}
];
var iconurl = M.util.image_url('e/text_color', 'core');
Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var items = [];
Y.Array.each(colors, function(color) {
items.push({
text: '<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ' +
color.color +
'"></div>',
callbackArgs: color.color,
callback: this._changeStyle
});
});
M.editor_atto.add_toolbar_menu(params.elementid,
plugin,
iconurl,
params.group,
[
{'text' : white, 'handler' : click_white},
{'text' : red, 'handler' : click_red},
{'text' : yellow, 'handler' : click_yellow},
{'text' : green, 'handler' : click_green},
{'text' : blue, 'handler' : click_blue},
{'text' : black, 'handler' : click_black}
],
false,
false,
'4',
'#333333');
this.addToolbarMenu({
icon: 'e/text_color',
overlayWidth: '4',
menuColor: '#333333',
globalItemConfig: {
callback: this._changeStyle
},
items: items
});
},
/**
* Handle to change the editor font color.
* @param event e - The event that triggered this.
* @param string elementid - the elemen id of menu icon.
* @param string color - The color for the background.
* Change the font color to the specified color.
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} color The new font color
* @private
*/
change_color : function(e, elementid, color) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('foreColor', 0, color);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
_changeStyle: function(e, color) {
document.execCommand('forecolor', 0, color);
// Mark as updated
this.markUpdated();
}
};
});
}, '@VERSION@');
+67 -70
View File
@@ -13,85 +13,82 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor font color plugin.
*
* @package editor-atto
/*
* @package atto_fontcolor
* @copyright 2014 Rossiani Wijaya <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_fontcolor = M.atto_fontcolor || {
dialogue : null,
init : function(params) {
var plugin = 'fontcolor';
var rgb_white = '#FFFFFF',
rgb_red = '#EF4540',
rgb_yellow = '#FFCF35',
rgb_green = '#98CA3E',
rgb_blue = '#7D9FD3',
rgb_black = '#333333';
/**
* @module moodle-atto_align-button
*/
var click_white = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, 'transparent');
};
var click_red = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_red);
};
var click_yellow = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_yellow);
};
var click_green = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_green);
};
var click_blue = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_blue);
};
var click_black = function(e, elementid) {
M.atto_fontcolor.change_color(e, elementid, rgb_black);
};
/**
* Atto text editor fontcolor plugin.
*
* @namespace M.atto_fontcolor
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var buttoncss = 'width: 20px; height: 20px; border: 1px solid #CCC; background-color: ';
var white = '<div style="' + buttoncss + rgb_white + '"></div>';
var red = '<div style="' + buttoncss + rgb_red + '"></div>';
var yellow = '<div style="' + buttoncss + rgb_yellow + '"></div>';
var green = '<div style="' + buttoncss + rgb_green + '"></div>';
var blue = '<div style="' + buttoncss + rgb_blue + '"></div>';
var black = '<div style="' + buttoncss + rgb_black + '"></div>';
var colors = [
{
name: 'white',
color: '#FFFFFF'
}, {
name: 'red',
color: '#EF4540'
}, {
name: 'yellow',
color: '#FFCF35'
}, {
name: 'green',
color: '#98CA3E'
}, {
name: 'blue',
color: '#7D9FD3'
}, {
name: 'black',
color: '#333333'
}
];
var iconurl = M.util.image_url('e/text_color', 'core');
Y.namespace('M.atto_fontcolor').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
var items = [];
Y.Array.each(colors, function(color) {
items.push({
text: '<div style="width: 20px; height: 20px; border: 1px solid #CCC; background-color: ' +
color.color +
'"></div>',
callbackArgs: color.color,
callback: this._changeStyle
});
});
M.editor_atto.add_toolbar_menu(params.elementid,
plugin,
iconurl,
params.group,
[
{'text' : white, 'handler' : click_white},
{'text' : red, 'handler' : click_red},
{'text' : yellow, 'handler' : click_yellow},
{'text' : green, 'handler' : click_green},
{'text' : blue, 'handler' : click_blue},
{'text' : black, 'handler' : click_black}
],
false,
false,
'4',
'#333333');
this.addToolbarMenu({
icon: 'e/text_color',
overlayWidth: '4',
menuColor: '#333333',
globalItemConfig: {
callback: this._changeStyle
},
items: items
});
},
/**
* Handle to change the editor font color.
* @param event e - The event that triggered this.
* @param string elementid - the elemen id of menu icon.
* @param string color - The color for the background.
* Change the font color to the specified color.
*
* @method _changeStyle
* @param {EventFacade} e
* @param {string} color The new font color
* @private
*/
change_color : function(e, elementid, color) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('foreColor', 0, color);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
_changeStyle: function(e, color) {
document.execCommand('forecolor', 0, color);
// Mark as updated
this.markUpdated();
}
};
});
@@ -15,48 +15,106 @@ YUI.add('moodle-atto_html-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor html plugin.
*
* @package editor-atto
/*
* @package atto_html
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_html = M.atto_html || {
/**
* @module moodle-atto_html-button
*/
/**
* Atto text editor HTML plugin.
*
* @namespace M.atto_html
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_html').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addButton({
icon: 'e/source_code',
callback: this._toggleHTML
});
},
/**
* Are we in html editing mode or not?
* Toggle the view between the content editable div, and the textarea,
* updating the content as it goes.
*
* @method _toggleHTML
* @private
*/
ishtml : false,
_toggleHTML: function() {
// Toggle the HTML status.
this.set('isHTML', !this.get('isHTML'));
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
var textarea = M.editor_atto.get_textarea_node(elementid),
atto = M.editor_atto.get_editable_node(elementid);
// Now make the UI changes.
this._showHTML();
},
if (M.atto_html.ishtml) {
M.editor_atto.enable_all_widgets(elementid);
// Copy the text to the contenteditable div.
M.editor_atto.update_from_textarea(elementid);
textarea.hide();
atto.show();
atto.focus();
} else {
M.editor_atto.disable_all_widgets(elementid);
M.editor_atto.enable_widget(elementid, 'html');
M.editor_atto.text_updated(elementid);
atto.hide();
textarea.show();
textarea.focus();
}
/**
* Set the current state of the textarea and contenteditable div
* according to the isHTML property.
*
* @method _showHTML
* @private
*/
_showHTML: function() {
var host = this.get('host');
if (!this.get('isHTML')) {
// Enable all plugins.
host.enablePlugins();
M.atto_html.ishtml = !M.atto_html.ishtml;
};
// Copy the text to the contenteditable div.
host.updateFromTextArea();
var iconurl = M.util.image_url('e/source_code', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'html', iconurl, params.group, click);
// Hide the textarea, and show the editor.
host.textarea.hide();
this.editor.show();
// Focus on the editor.
host.focus();
// And re-mark everything as updated.
this.markUpdated();
} else {
// Disable all plugins.
host.disablePlugins();
// And then re-enable this one.
host.enablePlugins(this.name);
// Copy the text to the contenteditable div.
host.updateOriginal();
// Hide the editor, and show the textarea.
this.editor.hide();
host.textarea.show();
// Focus on the textarea.
host.textarea.focus();
}
}
};
}, {
ATTRS: {
/**
* The current state for the HTML view. If true, the HTML source is
* shown in a textarea, otherwise the contenteditable area is
* displayed.
*
* @attribute isHTML
* @type Boolean
* @default false
*/
isHTML: {
value: false
}
}
});
}, '@VERSION@', {"requires": ["node", "event-valuechange"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "event-valuechange"]});
@@ -1 +1 @@
YUI.add("moodle-atto_html-button",function(e,t){M.atto_html=M.atto_html||{ishtml:!1,init:function(e){var t=function(e,t){e.preventDefault();var n=M.editor_atto.get_textarea_node(t),r=M.editor_atto.get_editable_node(t);M.atto_html.ishtml?(M.editor_atto.enable_all_widgets(t),M.editor_atto.update_from_textarea(t),n.hide(),r.show(),r.focus()):(M.editor_atto.disable_all_widgets(t),M.editor_atto.enable_widget(t,"html"),M.editor_atto.text_updated(t),r.hide(),n.show(),n.focus()),M.atto_html.ishtml=!M.atto_html.ishtml},n=M.util.image_url("e/source_code","core");M.editor_atto.add_toolbar_button(e.elementid,"html",n,e.group,t)}}},"@VERSION@",{requires:["node","event-valuechange"]});
YUI.add("moodle-atto_html-button",function(e,t){e.namespace("M.atto_html").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addButton({icon:"e/source_code",callback:this._toggleHTML})},_toggleHTML:function(){this.set("isHTML",!this.get("isHTML")),this._showHTML()},_showHTML:function(){var e=this.get("host");this.get("isHTML")?(e.disablePlugins(),e.enablePlugins(this.name),e.updateOriginal(),this.editor.hide(),e.textarea.show(),e.textarea.focus()):(e.enablePlugins(),e.updateFromTextArea(),e.textarea.hide(),this.editor.show(),e.focus(),this.markUpdated())}},{ATTRS:{isHTML:{value:!1}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","event-valuechange"]});
@@ -15,48 +15,106 @@ YUI.add('moodle-atto_html-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor html plugin.
*
* @package editor-atto
/*
* @package atto_html
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_html = M.atto_html || {
/**
* @module moodle-atto_html-button
*/
/**
* Atto text editor HTML plugin.
*
* @namespace M.atto_html
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_html').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addButton({
icon: 'e/source_code',
callback: this._toggleHTML
});
},
/**
* Are we in html editing mode or not?
* Toggle the view between the content editable div, and the textarea,
* updating the content as it goes.
*
* @method _toggleHTML
* @private
*/
ishtml : false,
_toggleHTML: function() {
// Toggle the HTML status.
this.set('isHTML', !this.get('isHTML'));
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
var textarea = M.editor_atto.get_textarea_node(elementid),
atto = M.editor_atto.get_editable_node(elementid);
// Now make the UI changes.
this._showHTML();
},
if (M.atto_html.ishtml) {
M.editor_atto.enable_all_widgets(elementid);
// Copy the text to the contenteditable div.
M.editor_atto.update_from_textarea(elementid);
textarea.hide();
atto.show();
atto.focus();
} else {
M.editor_atto.disable_all_widgets(elementid);
M.editor_atto.enable_widget(elementid, 'html');
M.editor_atto.text_updated(elementid);
atto.hide();
textarea.show();
textarea.focus();
}
/**
* Set the current state of the textarea and contenteditable div
* according to the isHTML property.
*
* @method _showHTML
* @private
*/
_showHTML: function() {
var host = this.get('host');
if (!this.get('isHTML')) {
// Enable all plugins.
host.enablePlugins();
M.atto_html.ishtml = !M.atto_html.ishtml;
};
// Copy the text to the contenteditable div.
host.updateFromTextArea();
var iconurl = M.util.image_url('e/source_code', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'html', iconurl, params.group, click);
// Hide the textarea, and show the editor.
host.textarea.hide();
this.editor.show();
// Focus on the editor.
host.focus();
// And re-mark everything as updated.
this.markUpdated();
} else {
// Disable all plugins.
host.disablePlugins();
// And then re-enable this one.
host.enablePlugins(this.name);
// Copy the text to the contenteditable div.
host.updateOriginal();
// Hide the editor, and show the textarea.
this.editor.hide();
host.textarea.show();
// Focus on the textarea.
host.textarea.focus();
}
}
};
}, {
ATTRS: {
/**
* The current state for the HTML view. If true, the HTML source is
* shown in a textarea, otherwise the contenteditable area is
* displayed.
*
* @attribute isHTML
* @type Boolean
* @default false
*/
isHTML: {
value: false
}
}
});
}, '@VERSION@', {"requires": ["node", "event-valuechange"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "event-valuechange"]});
+90 -32
View File
@@ -13,45 +13,103 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor html plugin.
*
* @package editor-atto
/*
* @package atto_html
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_html = M.atto_html || {
/**
* @module moodle-atto_html-button
*/
/**
* Atto text editor HTML plugin.
*
* @namespace M.atto_html
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_html').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addButton({
icon: 'e/source_code',
callback: this._toggleHTML
});
},
/**
* Are we in html editing mode or not?
* Toggle the view between the content editable div, and the textarea,
* updating the content as it goes.
*
* @method _toggleHTML
* @private
*/
ishtml : false,
_toggleHTML: function() {
// Toggle the HTML status.
this.set('isHTML', !this.get('isHTML'));
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
var textarea = M.editor_atto.get_textarea_node(elementid),
atto = M.editor_atto.get_editable_node(elementid);
// Now make the UI changes.
this._showHTML();
},
if (M.atto_html.ishtml) {
M.editor_atto.enable_all_widgets(elementid);
// Copy the text to the contenteditable div.
M.editor_atto.update_from_textarea(elementid);
textarea.hide();
atto.show();
atto.focus();
} else {
M.editor_atto.disable_all_widgets(elementid);
M.editor_atto.enable_widget(elementid, 'html');
M.editor_atto.text_updated(elementid);
atto.hide();
textarea.show();
textarea.focus();
}
/**
* Set the current state of the textarea and contenteditable div
* according to the isHTML property.
*
* @method _showHTML
* @private
*/
_showHTML: function() {
var host = this.get('host');
if (!this.get('isHTML')) {
// Enable all plugins.
host.enablePlugins();
M.atto_html.ishtml = !M.atto_html.ishtml;
};
// Copy the text to the contenteditable div.
host.updateFromTextArea();
var iconurl = M.util.image_url('e/source_code', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'html', iconurl, params.group, click);
// Hide the textarea, and show the editor.
host.textarea.hide();
this.editor.show();
// Focus on the editor.
host.focus();
// And re-mark everything as updated.
this.markUpdated();
} else {
// Disable all plugins.
host.disablePlugins();
// And then re-enable this one.
host.enablePlugins(this.name);
// Copy the text to the contenteditable div.
host.updateOriginal();
// Hide the editor, and show the textarea.
this.editor.hide();
host.textarea.show();
// Focus on the textarea.
host.textarea.focus();
}
}
};
}, {
ATTRS: {
/**
* The current state for the HTML view. If true, the HTML source is
* shown in a textarea, otherwise the contenteditable area is
* displayed.
*
* @attribute isHTML
* @type Boolean
* @default false
*/
isHTML: {
value: false
}
}
});
@@ -1,8 +1,8 @@
{
"moodle-atto_html-button": {
"requires": [
"node",
"event-valuechange"
]
}
"moodle-atto_html-button": {
"requires": [
"moodle-editor_atto-plugin",
"event-valuechange"
]
}
}
@@ -15,14 +15,29 @@ YUI.add('moodle-atto_image-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
var SELECTORS = {
TAGS: 'img'
},
CSS = {
/*
* @package atto_image
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module moodle-atto_image_alignment-button
*/
/**
* Atto image selection tool.
*
* @namespace M.atto_image
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var CSS = {
INPUTALIGNMENT: 'atto_image_alignment',
INPUTALT: 'atto_image_altentry',
INPUTHEIGHT: 'atto_image_heightentry',
INPUTSUMBIT: 'atto_image_urlentrysubmit',
INPUTSUBMIT: 'atto_image_urlentrysubmit',
INPUTURL: 'atto_image_urlentry',
INPUTWIDTH: 'atto_image_widthentry',
IMAGEALTWARNING: 'atto_image_altwarning',
@@ -30,416 +45,250 @@ var SELECTORS = {
IMAGEPRESENTATION: 'atto_image_presentation',
IMAGEPREVIEW: 'atto_image_preview'
},
ALIGNMENTS,
ALIGNMENT;
ALIGNMENTS = [
// Vertical alignment.
{
name: 'baseline',
str: 'alignment_baseline',
value: 'vertical-align'
}, {
name: 'sub',
str: 'alignment_sub',
value: 'vertical-align'
}, {
name: 'super',
str: 'alignment_super',
value: 'vertical-align'
}, {
name: 'top',
str: 'alignment_top',
value: 'vertical-align'
}, {
name: 'text-top',
str: 'alignment_texttop',
value: 'vertical-align'
}, {
name: 'middle',
str: 'alignment_middle',
value: 'vertical-align'
}, {
name: 'bottom',
str: 'alignment_bottom',
value: 'vertical-align'
}, {
name: 'text-bottom',
str: 'alignment_textbottom',
value: 'vertical-align'
},
/**
* Alignment class to aid with image alignment.
*
* @class ALIGNMENT
* @constructor
* @param {String} value
* @param {String} style
*/
ALIGNMENT = function(value, style) {
this.value = value;
this.style = style;
this.regex = new RegExp(this._regex_escape(style) + ' *: *' + this._regex_escape(value));
};
ALIGNMENT.prototype = {
/**
* The value of this alignment instance.
* @property value
* @type {String}
*/
value: null,
/**
* The style this alignment instance will use.
* @property style
* @type {String}
*/
style: null,
/**
* A regex to match this alignment instance in use.
* @property regex
* @type {RegExp}
*/
regex: null,
/**
* Tests a given style string to check if this instance is used within it.
* @method test
* @param {String} str
* @returns {Boolean}
*/
test: function(str) {
return this.regex.test(str);
},
/**
* Escapes a string for use in a RegExp definition.
* @method _regex_escape
* @private
* @param str
* @returns {String}
*/
_regex_escape: function(str) {
return str.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
},
/**
* Applys this style to a given node.
* @method apply
* @param {Node} node
*/
apply: function(node) {
var style = node.getAttribute('style');
if (style !== '' && style.substr(style.length - 1, 1) !== ';') {
style += ';';
// Floats.
{
name: 'left',
str: 'alignment_left',
value: 'float'
}, {
name: 'right',
str: 'alignment_right',
value: 'float'
}
style += this.style + ': ' + this.value + ';';
},
];
var COMPONENTNAME = 'atto_image',
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth {{CSS.INPUTURL}}" type="url" id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'<br/>' +
// Add the repository browser button.
'{{#if showFilepicker}}' +
'<button class="{{CSS.IMAGEBROWSER}}" type="button">{{get_string "browserepositories" component}}</button>' +
'{{/if}}' +
// Add the Alt box.
'<div style="display:none" role="alert" class="warning {{CSS.IMAGEALTWARNING}}">' +
'{{get_string "presentationoraltrequired" component}}' +
'</div>' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
'<input class="fullwidth {{CSS.INPUTALT}}" type="text" value="" id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' +
'<br/>' +
// Add the presentation select box.
'<input type="checkbox" class="{{CSS.IMAGEPRESENTATION}}" id="{{elementid}}_{{CSS.IMAGEPRESENTATION}}"/>' +
'<label class="sameline" for="{{elementid}}_{{CSS.IMAGEPRESENTATION}}">{{get_string "presentation" component}}</label>' +
'<br/>' +
// Add the width entry box.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTWIDTH}}">{{get_string "width" component}}</label>' +
'<input type="text" class="{{CSS.INPUTWIDTH}} id="{{elementid}}_{{CSS.INPUTWIDTH}}" size="10"/>' +
'<br/>' +
// Add the height entry box.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTHEIGHT}}">{{get_string "height" component}}</label>' +
'<input type="text" class="{{CSS.INPUTHEIGHT}}" id="{{elementid}}_{{CSS.INPUTHEIGHT}}" size="10"/>' +
'<br/>' +
// Add the alignment selector.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTALIGNMENT}}">{{get_string "alignment" component}}</label>' +
'<select class="{{CSS.INPUTALIGNMENT}}" id="{{elementid}}_{{CSS.INPUTALIGNMENT}}">' +
'{{#each alignments}}' +
'<option value="{{value}}">{{get_string str ../component}}</option>' +
'{{/each}}' +
'</select>' +
'<br/>' +
// Add the image preview.
'<label for="{{elementid}}_{{CSS.IMAGEPREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="mdl-align">' +
'<img src="#" width="200" class="{{CSS.IMAGEPREVIEW}}" id="{{elementid}}_{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' +
'<br/>' +
// Add the submit button and close the form.
'<button class="{{CSS.INPUTSUBMIT}}" type="submit">{{get_string "createimage" component}}</button>' +
'</div>' +
'</form>',
IMAGETEMPLATE = '' +
'<img src="{{url}}" alt="{{alt}}" ' +
'{{#if width}}width="{{width}}" {{/if}}' +
'{{#if height}}height="{{height}}" {{/if}}' +
'{{#if presentation}}role="presentation" {{/if}}' +
'{{#if alignment}}style="{{alignment}}" {{/if}}' +
'/>';
Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property _currentSelection
* @type Range
* @private
*/
_currentSelection: null,
/**
* Returns this alignment instance as a select option.
* @method to_select_option
* @returns {string}
* The most recently selected image.
*
* @param _selectedImage
* @type Node
* @private
*/
to_select_option: function() {
var str = M.util.get_string('alignment_'+this.value.replace('-', ''), 'atto_image');
value = this.style + ': '+ this.value;
return '<option value="' + value + '">' + str + '</option>';
}
};
_selectedImage: null,
/**
* An array containing all of the valid alignments an image can have.
* @type {ALIGNMENT[]}
*/
ALIGNMENTS = [
// Vertical alignment.
new ALIGNMENT('baseline', 'vertical-align'),
new ALIGNMENT('sub', 'vertical-align'),
new ALIGNMENT('super', 'vertical-align'),
new ALIGNMENT('top', 'vertical-align'),
new ALIGNMENT('text-top', 'vertical-align'),
new ALIGNMENT('middle', 'vertical-align'),
new ALIGNMENT('bottom', 'vertical-align'),
new ALIGNMENT('text-bottom', 'vertical-align'),
// Floats.
new ALIGNMENT('left', 'float'),
new ALIGNMENT('right', 'float')
];
/**
* A reference to the currently open form.
*
* @param _form
* @type Node
* @private
*/
_form: null,
/**
* Atto text editor image plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_image = M.atto_image || {
dialogue: null,
selection: null,
currentlyselected : {},
lastselectedimage : null,
init: function(params) {
var display_chooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_image.selection = M.editor_atto.get_selection();
if (M.atto_image.selection !== false) {
var dialogue;
if (!M.atto_image.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_image.dialogue;
}
dialogue.set('bodyContent', M.atto_image.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createimage', 'atto_image'));
dialogue.render();
dialogue.centerDialogue();
M.atto_image.dialogue = dialogue;
dialogue.show();
}
};
var iconurl = M.util.image_url('e/insert_edit_image', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'image', iconurl, params.group, display_chooser);
M.editor_atto.currentlyselected = M.editor_atto.currentlyselected || {};
M.editor_atto.currentlyselected[params.elementid] = null;
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes, false)) {
M.editor_atto.add_widget_highlight(e.elementid, 'image');
M.editor_atto.currentlyselected[e.elementid] = e.selectedNodes;
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'image');
M.editor_atto.currentlyselected[e.elementid] = null;
}
initializer: function() {
this.addButton({
icon: 'e/insert_edit_image',
callback: this._displayDialogue,
tags: 'img',
tagMatchRequiresAll: false
});
},
open_filepicker: function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
M.editor_atto.show_filepicker(elementid, 'image', M.atto_image.filepicker_callback);
/**
* Display the image editing tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createimage', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
filepicker_callback: function(params) {
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @return {Node} The content to place in the dialogue.
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
CSS: CSS,
component: COMPONENTNAME,
showFilepicker: this.get('host').canShowFilepicker('image'),
alignments: ALIGNMENTS
}));
this._form = content;
// Configure the view of the current image.
this._applyImageProperties(this._form);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
this.get('host').showFilepicker('image', this._filepickerCallback, this);
}, this);
return content;
},
/**
* Update the dialogue after an image was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
_filepickerCallback: function(params) {
if (params.url !== '') {
var input = Y.one('#' + CSS.INPUTURL);
var input = this._form.one('.' + CSS.INPUTURL),
self = this;
input.set('value', params.url);
// Auto set the width and height.
var image = new Image();
image.onload = function() {
Y.one('#' + CSS.INPUTWIDTH).set('value', this.width);
Y.one('#' + CSS.INPUTHEIGHT).set('value', this.height);
Y.one('#' + CSS.IMAGEPREVIEW).set('src', this.src);
Y.one('#' + CSS.IMAGEPREVIEW).setStyle('display', 'inline');
self._form.one('.' + CSS.INPUTWIDTH).set('value', this.width);
self._form.one('.' + CSS.INPUTHEIGHT).set('value', this.height);
self._form.one('.' + CSS.IMAGEPREVIEW).set('src', this.src);
self._form.one('.' + CSS.IMAGEPREVIEW).setStyle('display', 'inline');
// Centre the dialogue once the preview image has loaded.
self.getDialogue().centerDialogue();
};
image.src = params.url;
}
},
url_changed: function() {
var input = Y.one('#' + CSS.INPUTURL);
if (input.get('value') !== '') {
// Auto set the width and height.
var image = new Image();
image.onload = function() {
var input;
input = Y.one('#' + CSS.INPUTWIDTH);
if (input.get('value') === '') {
input.set('value', this.width);
}
input = Y.one('#' + CSS.INPUTHEIGHT);
if (input.get('value') === '') {
input.set('value', this.height);
}
input = Y.one('#' + CSS.IMAGEPREVIEW);
input.set('src', this.src);
input.setStyle('display', 'inline');
};
image.src = input.get('value');
}
},
set_image: function(e, elementid) {
var form = e.currentTarget.ancestor('.atto_form'),
url = form.one('#' + CSS.INPUTURL).get('value'),
alt = form.one('#' + CSS.INPUTALT).get('value'),
width = form.one('#' + CSS.INPUTWIDTH).get('value'),
height = form.one('#' + CSS.INPUTHEIGHT).get('value'),
alignment = form.one('#' + CSS.INPUTALIGNMENT).get('value'),
presentation = form.one('#' + CSS.IMAGEPRESENTATION).get('checked'),
imagehtml;
e.preventDefault();
if (alt === '' && !presentation) {
form.one('#' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('#' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('#' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
return;
} else {
form.one('#' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('#' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('#' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
}
M.atto_image.dialogue.hide();
M.editor_atto.focus(elementid);
if (url !== '') {
if (this.lastselectedimage) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(this.lastselectedimage));
} else {
M.editor_atto.set_selection(M.atto_image.selection);
}
imagehtml = '<img src="' + Y.Escape.html(url) + '" alt="' + Y.Escape.html(alt) + '"';
if (width) {
imagehtml += ' width="' + Y.Escape.html(width) + '"';
}
if (height) {
imagehtml += ' height="' + Y.Escape.html(height) + '"';
}
if (presentation) {
imagehtml += ' role="presentation"';
}
if (alignment) {
imagehtml += ' style="' + alignment + '"';
}
imagehtml += '/>';
M.editor_atto.insert_html_at_focus_point(imagehtml);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
}
},
/**
* Gets the properties of the currently selected image.
*
* The first image only if multiple images are selected.
*
* @method _get_selected_image_properties
* @private
* @param {string} elementid
* @returns {object}
*/
_get_selected_image_properties: function(elementid) {
var properties = {
src: null,
alt :null,
width: null,
height: null,
align: null,
display: 'inline',
presentation: false
},
images = M.editor_atto.currentlyselected[elementid],
i, image, width, height, style;
if (images) {
images = images.filter('img');
}
if (images && images.size()) {
image = images.item(0);
this.lastselectedimage = image;
style = image.getAttribute('style');
width = parseInt(image.getAttribute('width'), 10);
height = parseInt(image.getAttribute('height'), 10);
if (width > 0) {
properties.width = width;
}
if (height > 0) {
properties.height = height;
}
for (i in ALIGNMENTS) {
if (ALIGNMENTS[i].test(style)) {
properties.align = ALIGNMENTS[i];
break;
}
}
properties.src = image.getAttribute('src');
properties.alt = image.getAttribute('alt') || '';
properties.presentation = (image.get('role') === 'presentation');
return properties;
}
return false;
},
get_form_content: function(elementid) {
// String collection for quick refernce.
var str = {
alignment: M.util.get_string('alignment', 'atto_image'),
alt: M.util.get_string('enteralt', 'atto_image'),
browse: M.util.get_string('browserepositories', 'atto_image'),
create: M.util.get_string('createimage', 'atto_image'),
height: M.util.get_string('height', 'atto_image'),
presentation: M.util.get_string('presentation', 'atto_image'),
presentationrequired: M.util.get_string('presentationoraltrequired', 'atto_image'),
preview: M.util.get_string('preview', 'atto_image'),
width: M.util.get_string('width', 'atto_image'),
url: M.util.get_string('enterurl', 'atto_image')
},
html,
i;
// Start the form.
html = '<form class="atto_form">' +
'<label for="' + CSS.INPUTURL + '">' + str.url + '</label>' +
'<input class="fullwidth" type="url" value="" id="' + CSS.INPUTURL + '" size="32"/>' +
'<br/>';
if (M.editor_atto.can_show_filepicker(elementid, 'image')) {
// Add the repository browser button.
html += '<button id="' + CSS.IMAGEBROWSER + '" data-editor="' + Y.Escape.html(elementid) + '" type="button">' + str.browse + '</button>' +
'<br/>';
}
// Add the Alt box.
html += '<div style="display:none" role="alert" id="' + CSS.IMAGEALTWARNING + '" class="warning">' + str.presentationrequired + '</div>' +
'<label for="' + CSS.INPUTALT + '">' + str.alt + '</label>' +
'<input class="fullwidth" type="text" value="" id="' + CSS.INPUTALT + '" size="32"/>' +
'<br/>';
// Add the presentation select box.
html += '<input type="checkbox" id="' + CSS.IMAGEPRESENTATION + '"/>' +
'<label class="sameline" for="' + CSS.IMAGEPRESENTATION + '">' + str.presentation + '</label>' +
'<br/>';
// Add the width entry box.
html += '<label class="sameline" for="' + CSS.INPUTWIDTH + '">' + str.width + '</label>' +
'<input type="text" value="" id="' + CSS.INPUTWIDTH + '" size="10"/>' +
'<br/>';
// Add the height entry box.
html += '<label class="sameline" for="' + CSS.INPUTHEIGHT + '">' + str.height + '</label>' +
'<input type="text" value="" id="' + CSS.INPUTHEIGHT + '" size="10"/>' +
'<br/>';
// Add the alignment selector.
html += '<label class="sameline" for="' + CSS.INPUTALIGNMENT + '">' + str.alignment + '</label>' +
'<select id="' + CSS.INPUTALIGNMENT + '">';
for (i in ALIGNMENTS) {
html += ALIGNMENTS[i].to_select_option();
}
html += '</select>' +
'<br/>';
// Add the image preview.
html += '<label for="' + CSS.IMAGEPREVIEW + '">' + str.preview + '</label>' +
'<img src="#" width="200" id="' + CSS.IMAGEPREVIEW + '" alt="" style="display: none;"/>' +
'<div class="mdl-align">' +
'<br/>';
// Add the submit button and close the form.
html += '<button id="' + CSS.INPUTSUMBIT + '" type="submit">' + str.create + '</button>' +
'</div>' +
'</form>';
var content = Y.Node.create(html);
this._apply_image_properties(content, elementid);
content.one('#' + CSS.INPUTURL).on('blur', M.atto_image.url_changed, this);
content.one('#' + CSS.INPUTSUMBIT).on('click', M.atto_image.set_image, this, elementid);
if (M.editor_atto.can_show_filepicker(elementid, 'image')) {
content.one('#' + CSS.IMAGEBROWSER).on('click', M.atto_image.open_filepicker);
}
return content;
},
/**
* Applies properties of an existing image to the image dialogue for editing.
*
* @method _apply_image_properties
* @private
* @method _applyImageProperties
* @param {Node} form
* @param {string} elementid
* @private
*/
_apply_image_properties: function(form, elementid) {
var properties = this._get_selected_image_properties(elementid),
img = form.one('#' + CSS.IMAGEPREVIEW);
_applyImageProperties: function(form) {
var properties = this._getSelectedImageProperties(),
img = form.one('.' + CSS.IMAGEPREVIEW);
if (properties === false) {
img.setStyle('display', 'none');
@@ -453,23 +302,174 @@ M.atto_image = M.atto_image || {
img.setStyle('display', properties.display);
}
if (properties.width) {
form.one('#' + CSS.INPUTWIDTH).set('value', properties.width);
form.one('.' + CSS.INPUTWIDTH).set('value', properties.width);
}
if (properties.height) {
form.one('#' + CSS.INPUTHEIGHT).set('value', properties.height);
form.one('.' + CSS.INPUTHEIGHT).set('value', properties.height);
}
if (properties.alt) {
form.one('#' + CSS.INPUTALT).set('value', properties.alt);
form.one('.' + CSS.INPUTALT).set('value', properties.alt);
}
if (properties.src) {
form.one('#' + CSS.INPUTURL).set('value', properties.src);
form.one('.' + CSS.INPUTURL).set('value', properties.src);
img.setAttribute('src', properties.src);
}
if (properties.presentation) {
form.one('#' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
form.one('.' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
}
},
/**
* Gets the properties of the currently selected image.
*
* The first image only if multiple images are selected.
*
* @method _getSelectedImageProperties
* @return {object}
* @private
*/
_getSelectedImageProperties: function() {
var properties = {
src: null,
alt :null,
width: null,
height: null,
align: null,
display: 'inline',
presentation: false
},
// Get the current selection.
images = this.get('host').getSelectedNodes(),
i, width, height, style;
if (images) {
images = images.filter('img');
}
if (images && images.size()) {
image = images.item(0);
this._selectedImage = image;
style = image.getAttribute('style');
width = parseInt(image.getAttribute('width'), 10);
height = parseInt(image.getAttribute('height'), 10);
if (width > 0) {
properties.width = width;
}
if (height > 0) {
properties.height = height;
}
for (i in ALIGNMENTS) {
if (ALIGNMENTS[i].name === style) {
properties.align = ALIGNMENTS[i];
break;
}
}
properties.src = image.getAttribute('src');
properties.alt = image.getAttribute('alt') || '';
properties.presentation = (image.get('role') === 'presentation');
return properties;
}
// No image selected - clean up.
this._selectedImage = null;
return false;
},
/**
* Update the form when the URL was changed. This includes updating the
* height, width, and image preview.
*
* @method _urlChanged
* @private
*/
_urlChanged: function() {
var input = this._form.one('.' + CSS.INPUTURL),
self = this;
if (input.get('value') !== '') {
// Auto set the width and height.
var image = new Image();
image.onload = function() {
var input;
input = self._form.one('.' + CSS.INPUTWIDTH);
if (input.get('value') === '') {
input.set('value', this.width);
}
input = self._form.one('.' + CSS.INPUTHEIGHT);
if (input.get('value') === '') {
input.set('value', this.height);
}
input = self._form.one('.' + CSS.IMAGEPREVIEW);
input.set('src', this.src);
input.setStyle('display', 'inline');
};
image.src = input.get('value');
}
},
/**
* Update the image in the contenteditable.
*
* @method _setImage
* @param {EventFacade} e
* @private
*/
_setImage: function(e) {
var form = this._form,
url = form.one('.' + CSS.INPUTURL).get('value'),
alt = form.one('.' + CSS.INPUTALT).get('value'),
width = form.one('.' + CSS.INPUTWIDTH).get('value'),
height = form.one('.' + CSS.INPUTHEIGHT).get('value'),
alignment = form.one('.' + CSS.INPUTALIGNMENT).get('value'),
presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'),
imagehtml,
host = this.get('host');
e.preventDefault();
if (alt === '' && !presentation) {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
return;
} else {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
}
this.getDialogue({
focusAfterHide: null
}).hide();
// Focus on the editor in preparation for inserting the image.
host.focus();
if (url !== '') {
if (this._selectedImage) {
host.setSelection(host.getSelectionFromNode(this._selectedImage));
} else {
host.setSelection(this._currentSelection);
}
template = Y.Handlebars.compile(IMAGETEMPLATE);
imagehtml = template({
url: url,
alt: alt,
width: width,
height: height,
presentation: presentation,
alignment: alignment
});
this.get('host').insertContentAtFocusPoint(imagehtml);
this.markUpdated();
}
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
File diff suppressed because one or more lines are too long
@@ -15,14 +15,29 @@ YUI.add('moodle-atto_image-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
var SELECTORS = {
TAGS: 'img'
},
CSS = {
/*
* @package atto_image
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module moodle-atto_image_alignment-button
*/
/**
* Atto image selection tool.
*
* @namespace M.atto_image
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var CSS = {
INPUTALIGNMENT: 'atto_image_alignment',
INPUTALT: 'atto_image_altentry',
INPUTHEIGHT: 'atto_image_heightentry',
INPUTSUMBIT: 'atto_image_urlentrysubmit',
INPUTSUBMIT: 'atto_image_urlentrysubmit',
INPUTURL: 'atto_image_urlentry',
INPUTWIDTH: 'atto_image_widthentry',
IMAGEALTWARNING: 'atto_image_altwarning',
@@ -30,416 +45,250 @@ var SELECTORS = {
IMAGEPRESENTATION: 'atto_image_presentation',
IMAGEPREVIEW: 'atto_image_preview'
},
ALIGNMENTS,
ALIGNMENT;
ALIGNMENTS = [
// Vertical alignment.
{
name: 'baseline',
str: 'alignment_baseline',
value: 'vertical-align'
}, {
name: 'sub',
str: 'alignment_sub',
value: 'vertical-align'
}, {
name: 'super',
str: 'alignment_super',
value: 'vertical-align'
}, {
name: 'top',
str: 'alignment_top',
value: 'vertical-align'
}, {
name: 'text-top',
str: 'alignment_texttop',
value: 'vertical-align'
}, {
name: 'middle',
str: 'alignment_middle',
value: 'vertical-align'
}, {
name: 'bottom',
str: 'alignment_bottom',
value: 'vertical-align'
}, {
name: 'text-bottom',
str: 'alignment_textbottom',
value: 'vertical-align'
},
/**
* Alignment class to aid with image alignment.
*
* @class ALIGNMENT
* @constructor
* @param {String} value
* @param {String} style
*/
ALIGNMENT = function(value, style) {
this.value = value;
this.style = style;
this.regex = new RegExp(this._regex_escape(style) + ' *: *' + this._regex_escape(value));
};
ALIGNMENT.prototype = {
/**
* The value of this alignment instance.
* @property value
* @type {String}
*/
value: null,
/**
* The style this alignment instance will use.
* @property style
* @type {String}
*/
style: null,
/**
* A regex to match this alignment instance in use.
* @property regex
* @type {RegExp}
*/
regex: null,
/**
* Tests a given style string to check if this instance is used within it.
* @method test
* @param {String} str
* @returns {Boolean}
*/
test: function(str) {
return this.regex.test(str);
},
/**
* Escapes a string for use in a RegExp definition.
* @method _regex_escape
* @private
* @param str
* @returns {String}
*/
_regex_escape: function(str) {
return str.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
},
/**
* Applys this style to a given node.
* @method apply
* @param {Node} node
*/
apply: function(node) {
var style = node.getAttribute('style');
if (style !== '' && style.substr(style.length - 1, 1) !== ';') {
style += ';';
// Floats.
{
name: 'left',
str: 'alignment_left',
value: 'float'
}, {
name: 'right',
str: 'alignment_right',
value: 'float'
}
style += this.style + ': ' + this.value + ';';
},
];
var COMPONENTNAME = 'atto_image',
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth {{CSS.INPUTURL}}" type="url" id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'<br/>' +
// Add the repository browser button.
'{{#if showFilepicker}}' +
'<button class="{{CSS.IMAGEBROWSER}}" type="button">{{get_string "browserepositories" component}}</button>' +
'{{/if}}' +
// Add the Alt box.
'<div style="display:none" role="alert" class="warning {{CSS.IMAGEALTWARNING}}">' +
'{{get_string "presentationoraltrequired" component}}' +
'</div>' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
'<input class="fullwidth {{CSS.INPUTALT}}" type="text" value="" id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' +
'<br/>' +
// Add the presentation select box.
'<input type="checkbox" class="{{CSS.IMAGEPRESENTATION}}" id="{{elementid}}_{{CSS.IMAGEPRESENTATION}}"/>' +
'<label class="sameline" for="{{elementid}}_{{CSS.IMAGEPRESENTATION}}">{{get_string "presentation" component}}</label>' +
'<br/>' +
// Add the width entry box.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTWIDTH}}">{{get_string "width" component}}</label>' +
'<input type="text" class="{{CSS.INPUTWIDTH}} id="{{elementid}}_{{CSS.INPUTWIDTH}}" size="10"/>' +
'<br/>' +
// Add the height entry box.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTHEIGHT}}">{{get_string "height" component}}</label>' +
'<input type="text" class="{{CSS.INPUTHEIGHT}}" id="{{elementid}}_{{CSS.INPUTHEIGHT}}" size="10"/>' +
'<br/>' +
// Add the alignment selector.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTALIGNMENT}}">{{get_string "alignment" component}}</label>' +
'<select class="{{CSS.INPUTALIGNMENT}}" id="{{elementid}}_{{CSS.INPUTALIGNMENT}}">' +
'{{#each alignments}}' +
'<option value="{{value}}">{{get_string str ../component}}</option>' +
'{{/each}}' +
'</select>' +
'<br/>' +
// Add the image preview.
'<label for="{{elementid}}_{{CSS.IMAGEPREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="mdl-align">' +
'<img src="#" width="200" class="{{CSS.IMAGEPREVIEW}}" id="{{elementid}}_{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' +
'<br/>' +
// Add the submit button and close the form.
'<button class="{{CSS.INPUTSUBMIT}}" type="submit">{{get_string "createimage" component}}</button>' +
'</div>' +
'</form>',
IMAGETEMPLATE = '' +
'<img src="{{url}}" alt="{{alt}}" ' +
'{{#if width}}width="{{width}}" {{/if}}' +
'{{#if height}}height="{{height}}" {{/if}}' +
'{{#if presentation}}role="presentation" {{/if}}' +
'{{#if alignment}}style="{{alignment}}" {{/if}}' +
'/>';
Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property _currentSelection
* @type Range
* @private
*/
_currentSelection: null,
/**
* Returns this alignment instance as a select option.
* @method to_select_option
* @returns {string}
* The most recently selected image.
*
* @param _selectedImage
* @type Node
* @private
*/
to_select_option: function() {
var str = M.util.get_string('alignment_'+this.value.replace('-', ''), 'atto_image');
value = this.style + ': '+ this.value;
return '<option value="' + value + '">' + str + '</option>';
}
};
_selectedImage: null,
/**
* An array containing all of the valid alignments an image can have.
* @type {ALIGNMENT[]}
*/
ALIGNMENTS = [
// Vertical alignment.
new ALIGNMENT('baseline', 'vertical-align'),
new ALIGNMENT('sub', 'vertical-align'),
new ALIGNMENT('super', 'vertical-align'),
new ALIGNMENT('top', 'vertical-align'),
new ALIGNMENT('text-top', 'vertical-align'),
new ALIGNMENT('middle', 'vertical-align'),
new ALIGNMENT('bottom', 'vertical-align'),
new ALIGNMENT('text-bottom', 'vertical-align'),
// Floats.
new ALIGNMENT('left', 'float'),
new ALIGNMENT('right', 'float')
];
/**
* A reference to the currently open form.
*
* @param _form
* @type Node
* @private
*/
_form: null,
/**
* Atto text editor image plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_image = M.atto_image || {
dialogue: null,
selection: null,
currentlyselected : {},
lastselectedimage : null,
init: function(params) {
var display_chooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_image.selection = M.editor_atto.get_selection();
if (M.atto_image.selection !== false) {
var dialogue;
if (!M.atto_image.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_image.dialogue;
}
dialogue.set('bodyContent', M.atto_image.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createimage', 'atto_image'));
dialogue.render();
dialogue.centerDialogue();
M.atto_image.dialogue = dialogue;
dialogue.show();
}
};
var iconurl = M.util.image_url('e/insert_edit_image', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'image', iconurl, params.group, display_chooser);
M.editor_atto.currentlyselected = M.editor_atto.currentlyselected || {};
M.editor_atto.currentlyselected[params.elementid] = null;
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes, false)) {
M.editor_atto.add_widget_highlight(e.elementid, 'image');
M.editor_atto.currentlyselected[e.elementid] = e.selectedNodes;
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'image');
M.editor_atto.currentlyselected[e.elementid] = null;
}
initializer: function() {
this.addButton({
icon: 'e/insert_edit_image',
callback: this._displayDialogue,
tags: 'img',
tagMatchRequiresAll: false
});
},
open_filepicker: function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
M.editor_atto.show_filepicker(elementid, 'image', M.atto_image.filepicker_callback);
/**
* Display the image editing tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createimage', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
filepicker_callback: function(params) {
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @return {Node} The content to place in the dialogue.
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
CSS: CSS,
component: COMPONENTNAME,
showFilepicker: this.get('host').canShowFilepicker('image'),
alignments: ALIGNMENTS
}));
this._form = content;
// Configure the view of the current image.
this._applyImageProperties(this._form);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
this.get('host').showFilepicker('image', this._filepickerCallback, this);
}, this);
return content;
},
/**
* Update the dialogue after an image was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
_filepickerCallback: function(params) {
if (params.url !== '') {
var input = Y.one('#' + CSS.INPUTURL);
var input = this._form.one('.' + CSS.INPUTURL),
self = this;
input.set('value', params.url);
// Auto set the width and height.
var image = new Image();
image.onload = function() {
Y.one('#' + CSS.INPUTWIDTH).set('value', this.width);
Y.one('#' + CSS.INPUTHEIGHT).set('value', this.height);
Y.one('#' + CSS.IMAGEPREVIEW).set('src', this.src);
Y.one('#' + CSS.IMAGEPREVIEW).setStyle('display', 'inline');
self._form.one('.' + CSS.INPUTWIDTH).set('value', this.width);
self._form.one('.' + CSS.INPUTHEIGHT).set('value', this.height);
self._form.one('.' + CSS.IMAGEPREVIEW).set('src', this.src);
self._form.one('.' + CSS.IMAGEPREVIEW).setStyle('display', 'inline');
// Centre the dialogue once the preview image has loaded.
self.getDialogue().centerDialogue();
};
image.src = params.url;
}
},
url_changed: function() {
var input = Y.one('#' + CSS.INPUTURL);
if (input.get('value') !== '') {
// Auto set the width and height.
var image = new Image();
image.onload = function() {
var input;
input = Y.one('#' + CSS.INPUTWIDTH);
if (input.get('value') === '') {
input.set('value', this.width);
}
input = Y.one('#' + CSS.INPUTHEIGHT);
if (input.get('value') === '') {
input.set('value', this.height);
}
input = Y.one('#' + CSS.IMAGEPREVIEW);
input.set('src', this.src);
input.setStyle('display', 'inline');
};
image.src = input.get('value');
}
},
set_image: function(e, elementid) {
var form = e.currentTarget.ancestor('.atto_form'),
url = form.one('#' + CSS.INPUTURL).get('value'),
alt = form.one('#' + CSS.INPUTALT).get('value'),
width = form.one('#' + CSS.INPUTWIDTH).get('value'),
height = form.one('#' + CSS.INPUTHEIGHT).get('value'),
alignment = form.one('#' + CSS.INPUTALIGNMENT).get('value'),
presentation = form.one('#' + CSS.IMAGEPRESENTATION).get('checked'),
imagehtml;
e.preventDefault();
if (alt === '' && !presentation) {
form.one('#' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('#' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('#' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
return;
} else {
form.one('#' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('#' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('#' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
}
M.atto_image.dialogue.hide();
M.editor_atto.focus(elementid);
if (url !== '') {
if (this.lastselectedimage) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(this.lastselectedimage));
} else {
M.editor_atto.set_selection(M.atto_image.selection);
}
imagehtml = '<img src="' + Y.Escape.html(url) + '" alt="' + Y.Escape.html(alt) + '"';
if (width) {
imagehtml += ' width="' + Y.Escape.html(width) + '"';
}
if (height) {
imagehtml += ' height="' + Y.Escape.html(height) + '"';
}
if (presentation) {
imagehtml += ' role="presentation"';
}
if (alignment) {
imagehtml += ' style="' + alignment + '"';
}
imagehtml += '/>';
M.editor_atto.insert_html_at_focus_point(imagehtml);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
}
},
/**
* Gets the properties of the currently selected image.
*
* The first image only if multiple images are selected.
*
* @method _get_selected_image_properties
* @private
* @param {string} elementid
* @returns {object}
*/
_get_selected_image_properties: function(elementid) {
var properties = {
src: null,
alt :null,
width: null,
height: null,
align: null,
display: 'inline',
presentation: false
},
images = M.editor_atto.currentlyselected[elementid],
i, image, width, height, style;
if (images) {
images = images.filter('img');
}
if (images && images.size()) {
image = images.item(0);
this.lastselectedimage = image;
style = image.getAttribute('style');
width = parseInt(image.getAttribute('width'), 10);
height = parseInt(image.getAttribute('height'), 10);
if (width > 0) {
properties.width = width;
}
if (height > 0) {
properties.height = height;
}
for (i in ALIGNMENTS) {
if (ALIGNMENTS[i].test(style)) {
properties.align = ALIGNMENTS[i];
break;
}
}
properties.src = image.getAttribute('src');
properties.alt = image.getAttribute('alt') || '';
properties.presentation = (image.get('role') === 'presentation');
return properties;
}
return false;
},
get_form_content: function(elementid) {
// String collection for quick refernce.
var str = {
alignment: M.util.get_string('alignment', 'atto_image'),
alt: M.util.get_string('enteralt', 'atto_image'),
browse: M.util.get_string('browserepositories', 'atto_image'),
create: M.util.get_string('createimage', 'atto_image'),
height: M.util.get_string('height', 'atto_image'),
presentation: M.util.get_string('presentation', 'atto_image'),
presentationrequired: M.util.get_string('presentationoraltrequired', 'atto_image'),
preview: M.util.get_string('preview', 'atto_image'),
width: M.util.get_string('width', 'atto_image'),
url: M.util.get_string('enterurl', 'atto_image')
},
html,
i;
// Start the form.
html = '<form class="atto_form">' +
'<label for="' + CSS.INPUTURL + '">' + str.url + '</label>' +
'<input class="fullwidth" type="url" value="" id="' + CSS.INPUTURL + '" size="32"/>' +
'<br/>';
if (M.editor_atto.can_show_filepicker(elementid, 'image')) {
// Add the repository browser button.
html += '<button id="' + CSS.IMAGEBROWSER + '" data-editor="' + Y.Escape.html(elementid) + '" type="button">' + str.browse + '</button>' +
'<br/>';
}
// Add the Alt box.
html += '<div style="display:none" role="alert" id="' + CSS.IMAGEALTWARNING + '" class="warning">' + str.presentationrequired + '</div>' +
'<label for="' + CSS.INPUTALT + '">' + str.alt + '</label>' +
'<input class="fullwidth" type="text" value="" id="' + CSS.INPUTALT + '" size="32"/>' +
'<br/>';
// Add the presentation select box.
html += '<input type="checkbox" id="' + CSS.IMAGEPRESENTATION + '"/>' +
'<label class="sameline" for="' + CSS.IMAGEPRESENTATION + '">' + str.presentation + '</label>' +
'<br/>';
// Add the width entry box.
html += '<label class="sameline" for="' + CSS.INPUTWIDTH + '">' + str.width + '</label>' +
'<input type="text" value="" id="' + CSS.INPUTWIDTH + '" size="10"/>' +
'<br/>';
// Add the height entry box.
html += '<label class="sameline" for="' + CSS.INPUTHEIGHT + '">' + str.height + '</label>' +
'<input type="text" value="" id="' + CSS.INPUTHEIGHT + '" size="10"/>' +
'<br/>';
// Add the alignment selector.
html += '<label class="sameline" for="' + CSS.INPUTALIGNMENT + '">' + str.alignment + '</label>' +
'<select id="' + CSS.INPUTALIGNMENT + '">';
for (i in ALIGNMENTS) {
html += ALIGNMENTS[i].to_select_option();
}
html += '</select>' +
'<br/>';
// Add the image preview.
html += '<label for="' + CSS.IMAGEPREVIEW + '">' + str.preview + '</label>' +
'<img src="#" width="200" id="' + CSS.IMAGEPREVIEW + '" alt="" style="display: none;"/>' +
'<div class="mdl-align">' +
'<br/>';
// Add the submit button and close the form.
html += '<button id="' + CSS.INPUTSUMBIT + '" type="submit">' + str.create + '</button>' +
'</div>' +
'</form>';
var content = Y.Node.create(html);
this._apply_image_properties(content, elementid);
content.one('#' + CSS.INPUTURL).on('blur', M.atto_image.url_changed, this);
content.one('#' + CSS.INPUTSUMBIT).on('click', M.atto_image.set_image, this, elementid);
if (M.editor_atto.can_show_filepicker(elementid, 'image')) {
content.one('#' + CSS.IMAGEBROWSER).on('click', M.atto_image.open_filepicker);
}
return content;
},
/**
* Applies properties of an existing image to the image dialogue for editing.
*
* @method _apply_image_properties
* @private
* @method _applyImageProperties
* @param {Node} form
* @param {string} elementid
* @private
*/
_apply_image_properties: function(form, elementid) {
var properties = this._get_selected_image_properties(elementid),
img = form.one('#' + CSS.IMAGEPREVIEW);
_applyImageProperties: function(form) {
var properties = this._getSelectedImageProperties(),
img = form.one('.' + CSS.IMAGEPREVIEW);
if (properties === false) {
img.setStyle('display', 'none');
@@ -453,23 +302,174 @@ M.atto_image = M.atto_image || {
img.setStyle('display', properties.display);
}
if (properties.width) {
form.one('#' + CSS.INPUTWIDTH).set('value', properties.width);
form.one('.' + CSS.INPUTWIDTH).set('value', properties.width);
}
if (properties.height) {
form.one('#' + CSS.INPUTHEIGHT).set('value', properties.height);
form.one('.' + CSS.INPUTHEIGHT).set('value', properties.height);
}
if (properties.alt) {
form.one('#' + CSS.INPUTALT).set('value', properties.alt);
form.one('.' + CSS.INPUTALT).set('value', properties.alt);
}
if (properties.src) {
form.one('#' + CSS.INPUTURL).set('value', properties.src);
form.one('.' + CSS.INPUTURL).set('value', properties.src);
img.setAttribute('src', properties.src);
}
if (properties.presentation) {
form.one('#' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
form.one('.' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
}
},
/**
* Gets the properties of the currently selected image.
*
* The first image only if multiple images are selected.
*
* @method _getSelectedImageProperties
* @return {object}
* @private
*/
_getSelectedImageProperties: function() {
var properties = {
src: null,
alt :null,
width: null,
height: null,
align: null,
display: 'inline',
presentation: false
},
// Get the current selection.
images = this.get('host').getSelectedNodes(),
i, width, height, style;
if (images) {
images = images.filter('img');
}
if (images && images.size()) {
image = images.item(0);
this._selectedImage = image;
style = image.getAttribute('style');
width = parseInt(image.getAttribute('width'), 10);
height = parseInt(image.getAttribute('height'), 10);
if (width > 0) {
properties.width = width;
}
if (height > 0) {
properties.height = height;
}
for (i in ALIGNMENTS) {
if (ALIGNMENTS[i].name === style) {
properties.align = ALIGNMENTS[i];
break;
}
}
properties.src = image.getAttribute('src');
properties.alt = image.getAttribute('alt') || '';
properties.presentation = (image.get('role') === 'presentation');
return properties;
}
// No image selected - clean up.
this._selectedImage = null;
return false;
},
/**
* Update the form when the URL was changed. This includes updating the
* height, width, and image preview.
*
* @method _urlChanged
* @private
*/
_urlChanged: function() {
var input = this._form.one('.' + CSS.INPUTURL),
self = this;
if (input.get('value') !== '') {
// Auto set the width and height.
var image = new Image();
image.onload = function() {
var input;
input = self._form.one('.' + CSS.INPUTWIDTH);
if (input.get('value') === '') {
input.set('value', this.width);
}
input = self._form.one('.' + CSS.INPUTHEIGHT);
if (input.get('value') === '') {
input.set('value', this.height);
}
input = self._form.one('.' + CSS.IMAGEPREVIEW);
input.set('src', this.src);
input.setStyle('display', 'inline');
};
image.src = input.get('value');
}
},
/**
* Update the image in the contenteditable.
*
* @method _setImage
* @param {EventFacade} e
* @private
*/
_setImage: function(e) {
var form = this._form,
url = form.one('.' + CSS.INPUTURL).get('value'),
alt = form.one('.' + CSS.INPUTALT).get('value'),
width = form.one('.' + CSS.INPUTWIDTH).get('value'),
height = form.one('.' + CSS.INPUTHEIGHT).get('value'),
alignment = form.one('.' + CSS.INPUTALIGNMENT).get('value'),
presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'),
imagehtml,
host = this.get('host');
e.preventDefault();
if (alt === '' && !presentation) {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
return;
} else {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
}
this.getDialogue({
focusAfterHide: null
}).hide();
// Focus on the editor in preparation for inserting the image.
host.focus();
if (url !== '') {
if (this._selectedImage) {
host.setSelection(host.getSelectionFromNode(this._selectedImage));
} else {
host.setSelection(this._currentSelection);
}
template = Y.Handlebars.compile(IMAGETEMPLATE);
imagehtml = template({
url: url,
alt: alt,
width: width,
height: height,
presentation: presentation,
alignment: alignment
});
this.get('host').insertContentAtFocusPoint(imagehtml);
this.markUpdated();
}
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+394 -394
View File
@@ -13,14 +13,29 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
var SELECTORS = {
TAGS: 'img'
},
CSS = {
/*
* @package atto_image
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module moodle-atto_image_alignment-button
*/
/**
* Atto image selection tool.
*
* @namespace M.atto_image
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var CSS = {
INPUTALIGNMENT: 'atto_image_alignment',
INPUTALT: 'atto_image_altentry',
INPUTHEIGHT: 'atto_image_heightentry',
INPUTSUMBIT: 'atto_image_urlentrysubmit',
INPUTSUBMIT: 'atto_image_urlentrysubmit',
INPUTURL: 'atto_image_urlentry',
INPUTWIDTH: 'atto_image_widthentry',
IMAGEALTWARNING: 'atto_image_altwarning',
@@ -28,416 +43,250 @@ var SELECTORS = {
IMAGEPRESENTATION: 'atto_image_presentation',
IMAGEPREVIEW: 'atto_image_preview'
},
ALIGNMENTS,
ALIGNMENT;
ALIGNMENTS = [
// Vertical alignment.
{
name: 'baseline',
str: 'alignment_baseline',
value: 'vertical-align'
}, {
name: 'sub',
str: 'alignment_sub',
value: 'vertical-align'
}, {
name: 'super',
str: 'alignment_super',
value: 'vertical-align'
}, {
name: 'top',
str: 'alignment_top',
value: 'vertical-align'
}, {
name: 'text-top',
str: 'alignment_texttop',
value: 'vertical-align'
}, {
name: 'middle',
str: 'alignment_middle',
value: 'vertical-align'
}, {
name: 'bottom',
str: 'alignment_bottom',
value: 'vertical-align'
}, {
name: 'text-bottom',
str: 'alignment_textbottom',
value: 'vertical-align'
},
/**
* Alignment class to aid with image alignment.
*
* @class ALIGNMENT
* @constructor
* @param {String} value
* @param {String} style
*/
ALIGNMENT = function(value, style) {
this.value = value;
this.style = style;
this.regex = new RegExp(this._regex_escape(style) + ' *: *' + this._regex_escape(value));
};
ALIGNMENT.prototype = {
/**
* The value of this alignment instance.
* @property value
* @type {String}
*/
value: null,
/**
* The style this alignment instance will use.
* @property style
* @type {String}
*/
style: null,
/**
* A regex to match this alignment instance in use.
* @property regex
* @type {RegExp}
*/
regex: null,
/**
* Tests a given style string to check if this instance is used within it.
* @method test
* @param {String} str
* @returns {Boolean}
*/
test: function(str) {
return this.regex.test(str);
},
/**
* Escapes a string for use in a RegExp definition.
* @method _regex_escape
* @private
* @param str
* @returns {String}
*/
_regex_escape: function(str) {
return str.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
},
/**
* Applys this style to a given node.
* @method apply
* @param {Node} node
*/
apply: function(node) {
var style = node.getAttribute('style');
if (style !== '' && style.substr(style.length - 1, 1) !== ';') {
style += ';';
// Floats.
{
name: 'left',
str: 'alignment_left',
value: 'float'
}, {
name: 'right',
str: 'alignment_right',
value: 'float'
}
style += this.style + ': ' + this.value + ';';
},
];
var COMPONENTNAME = 'atto_image',
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth {{CSS.INPUTURL}}" type="url" id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
'<br/>' +
// Add the repository browser button.
'{{#if showFilepicker}}' +
'<button class="{{CSS.IMAGEBROWSER}}" type="button">{{get_string "browserepositories" component}}</button>' +
'{{/if}}' +
// Add the Alt box.
'<div style="display:none" role="alert" class="warning {{CSS.IMAGEALTWARNING}}">' +
'{{get_string "presentationoraltrequired" component}}' +
'</div>' +
'<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
'<input class="fullwidth {{CSS.INPUTALT}}" type="text" value="" id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' +
'<br/>' +
// Add the presentation select box.
'<input type="checkbox" class="{{CSS.IMAGEPRESENTATION}}" id="{{elementid}}_{{CSS.IMAGEPRESENTATION}}"/>' +
'<label class="sameline" for="{{elementid}}_{{CSS.IMAGEPRESENTATION}}">{{get_string "presentation" component}}</label>' +
'<br/>' +
// Add the width entry box.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTWIDTH}}">{{get_string "width" component}}</label>' +
'<input type="text" class="{{CSS.INPUTWIDTH}} id="{{elementid}}_{{CSS.INPUTWIDTH}}" size="10"/>' +
'<br/>' +
// Add the height entry box.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTHEIGHT}}">{{get_string "height" component}}</label>' +
'<input type="text" class="{{CSS.INPUTHEIGHT}}" id="{{elementid}}_{{CSS.INPUTHEIGHT}}" size="10"/>' +
'<br/>' +
// Add the alignment selector.
'<label class="sameline" for="{{elementid}}_{{CSS.INPUTALIGNMENT}}">{{get_string "alignment" component}}</label>' +
'<select class="{{CSS.INPUTALIGNMENT}}" id="{{elementid}}_{{CSS.INPUTALIGNMENT}}">' +
'{{#each alignments}}' +
'<option value="{{value}}">{{get_string str ../component}}</option>' +
'{{/each}}' +
'</select>' +
'<br/>' +
// Add the image preview.
'<label for="{{elementid}}_{{CSS.IMAGEPREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="mdl-align">' +
'<img src="#" width="200" class="{{CSS.IMAGEPREVIEW}}" id="{{elementid}}_{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' +
'<br/>' +
// Add the submit button and close the form.
'<button class="{{CSS.INPUTSUBMIT}}" type="submit">{{get_string "createimage" component}}</button>' +
'</div>' +
'</form>',
IMAGETEMPLATE = '' +
'<img src="{{url}}" alt="{{alt}}" ' +
'{{#if width}}width="{{width}}" {{/if}}' +
'{{#if height}}height="{{height}}" {{/if}}' +
'{{#if presentation}}role="presentation" {{/if}}' +
'{{#if alignment}}style="{{alignment}}" {{/if}}' +
'/>';
Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property _currentSelection
* @type Range
* @private
*/
_currentSelection: null,
/**
* Returns this alignment instance as a select option.
* @method to_select_option
* @returns {string}
* The most recently selected image.
*
* @param _selectedImage
* @type Node
* @private
*/
to_select_option: function() {
var str = M.util.get_string('alignment_'+this.value.replace('-', ''), 'atto_image');
value = this.style + ': '+ this.value;
return '<option value="' + value + '">' + str + '</option>';
}
};
_selectedImage: null,
/**
* An array containing all of the valid alignments an image can have.
* @type {ALIGNMENT[]}
*/
ALIGNMENTS = [
// Vertical alignment.
new ALIGNMENT('baseline', 'vertical-align'),
new ALIGNMENT('sub', 'vertical-align'),
new ALIGNMENT('super', 'vertical-align'),
new ALIGNMENT('top', 'vertical-align'),
new ALIGNMENT('text-top', 'vertical-align'),
new ALIGNMENT('middle', 'vertical-align'),
new ALIGNMENT('bottom', 'vertical-align'),
new ALIGNMENT('text-bottom', 'vertical-align'),
// Floats.
new ALIGNMENT('left', 'float'),
new ALIGNMENT('right', 'float')
];
/**
* A reference to the currently open form.
*
* @param _form
* @type Node
* @private
*/
_form: null,
/**
* Atto text editor image plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_image = M.atto_image || {
dialogue: null,
selection: null,
currentlyselected : {},
lastselectedimage : null,
init: function(params) {
var display_chooser = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_image.selection = M.editor_atto.get_selection();
if (M.atto_image.selection !== false) {
var dialogue;
if (!M.atto_image.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_image.dialogue;
}
dialogue.set('bodyContent', M.atto_image.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createimage', 'atto_image'));
dialogue.render();
dialogue.centerDialogue();
M.atto_image.dialogue = dialogue;
dialogue.show();
}
};
var iconurl = M.util.image_url('e/insert_edit_image', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'image', iconurl, params.group, display_chooser);
M.editor_atto.currentlyselected = M.editor_atto.currentlyselected || {};
M.editor_atto.currentlyselected[params.elementid] = null;
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes, false)) {
M.editor_atto.add_widget_highlight(e.elementid, 'image');
M.editor_atto.currentlyselected[e.elementid] = e.selectedNodes;
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'image');
M.editor_atto.currentlyselected[e.elementid] = null;
}
initializer: function() {
this.addButton({
icon: 'e/insert_edit_image',
callback: this._displayDialogue,
tags: 'img',
tagMatchRequiresAll: false
});
},
open_filepicker: function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
M.editor_atto.show_filepicker(elementid, 'image', M.atto_image.filepicker_callback);
/**
* Display the image editing tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createimage', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
filepicker_callback: function(params) {
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @return {Node} The content to place in the dialogue.
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE),
content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
CSS: CSS,
component: COMPONENTNAME,
showFilepicker: this.get('host').canShowFilepicker('image'),
alignments: ALIGNMENTS
}));
this._form = content;
// Configure the view of the current image.
this._applyImageProperties(this._form);
this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
this.get('host').showFilepicker('image', this._filepickerCallback, this);
}, this);
return content;
},
/**
* Update the dialogue after an image was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
_filepickerCallback: function(params) {
if (params.url !== '') {
var input = Y.one('#' + CSS.INPUTURL);
var input = this._form.one('.' + CSS.INPUTURL),
self = this;
input.set('value', params.url);
// Auto set the width and height.
var image = new Image();
image.onload = function() {
Y.one('#' + CSS.INPUTWIDTH).set('value', this.width);
Y.one('#' + CSS.INPUTHEIGHT).set('value', this.height);
Y.one('#' + CSS.IMAGEPREVIEW).set('src', this.src);
Y.one('#' + CSS.IMAGEPREVIEW).setStyle('display', 'inline');
self._form.one('.' + CSS.INPUTWIDTH).set('value', this.width);
self._form.one('.' + CSS.INPUTHEIGHT).set('value', this.height);
self._form.one('.' + CSS.IMAGEPREVIEW).set('src', this.src);
self._form.one('.' + CSS.IMAGEPREVIEW).setStyle('display', 'inline');
// Centre the dialogue once the preview image has loaded.
self.getDialogue().centerDialogue();
};
image.src = params.url;
}
},
url_changed: function() {
var input = Y.one('#' + CSS.INPUTURL);
if (input.get('value') !== '') {
// Auto set the width and height.
var image = new Image();
image.onload = function() {
var input;
input = Y.one('#' + CSS.INPUTWIDTH);
if (input.get('value') === '') {
input.set('value', this.width);
}
input = Y.one('#' + CSS.INPUTHEIGHT);
if (input.get('value') === '') {
input.set('value', this.height);
}
input = Y.one('#' + CSS.IMAGEPREVIEW);
input.set('src', this.src);
input.setStyle('display', 'inline');
};
image.src = input.get('value');
}
},
set_image: function(e, elementid) {
var form = e.currentTarget.ancestor('.atto_form'),
url = form.one('#' + CSS.INPUTURL).get('value'),
alt = form.one('#' + CSS.INPUTALT).get('value'),
width = form.one('#' + CSS.INPUTWIDTH).get('value'),
height = form.one('#' + CSS.INPUTHEIGHT).get('value'),
alignment = form.one('#' + CSS.INPUTALIGNMENT).get('value'),
presentation = form.one('#' + CSS.IMAGEPRESENTATION).get('checked'),
imagehtml;
e.preventDefault();
if (alt === '' && !presentation) {
form.one('#' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('#' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('#' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
return;
} else {
form.one('#' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('#' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('#' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
}
M.atto_image.dialogue.hide();
M.editor_atto.focus(elementid);
if (url !== '') {
if (this.lastselectedimage) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(this.lastselectedimage));
} else {
M.editor_atto.set_selection(M.atto_image.selection);
}
imagehtml = '<img src="' + Y.Escape.html(url) + '" alt="' + Y.Escape.html(alt) + '"';
if (width) {
imagehtml += ' width="' + Y.Escape.html(width) + '"';
}
if (height) {
imagehtml += ' height="' + Y.Escape.html(height) + '"';
}
if (presentation) {
imagehtml += ' role="presentation"';
}
if (alignment) {
imagehtml += ' style="' + alignment + '"';
}
imagehtml += '/>';
M.editor_atto.insert_html_at_focus_point(imagehtml);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
}
},
/**
* Gets the properties of the currently selected image.
*
* The first image only if multiple images are selected.
*
* @method _get_selected_image_properties
* @private
* @param {string} elementid
* @returns {object}
*/
_get_selected_image_properties: function(elementid) {
var properties = {
src: null,
alt :null,
width: null,
height: null,
align: null,
display: 'inline',
presentation: false
},
images = M.editor_atto.currentlyselected[elementid],
i, image, width, height, style;
if (images) {
images = images.filter('img');
}
if (images && images.size()) {
image = images.item(0);
this.lastselectedimage = image;
style = image.getAttribute('style');
width = parseInt(image.getAttribute('width'), 10);
height = parseInt(image.getAttribute('height'), 10);
if (width > 0) {
properties.width = width;
}
if (height > 0) {
properties.height = height;
}
for (i in ALIGNMENTS) {
if (ALIGNMENTS[i].test(style)) {
properties.align = ALIGNMENTS[i];
break;
}
}
properties.src = image.getAttribute('src');
properties.alt = image.getAttribute('alt') || '';
properties.presentation = (image.get('role') === 'presentation');
return properties;
}
return false;
},
get_form_content: function(elementid) {
// String collection for quick refernce.
var str = {
alignment: M.util.get_string('alignment', 'atto_image'),
alt: M.util.get_string('enteralt', 'atto_image'),
browse: M.util.get_string('browserepositories', 'atto_image'),
create: M.util.get_string('createimage', 'atto_image'),
height: M.util.get_string('height', 'atto_image'),
presentation: M.util.get_string('presentation', 'atto_image'),
presentationrequired: M.util.get_string('presentationoraltrequired', 'atto_image'),
preview: M.util.get_string('preview', 'atto_image'),
width: M.util.get_string('width', 'atto_image'),
url: M.util.get_string('enterurl', 'atto_image')
},
html,
i;
// Start the form.
html = '<form class="atto_form">' +
'<label for="' + CSS.INPUTURL + '">' + str.url + '</label>' +
'<input class="fullwidth" type="url" value="" id="' + CSS.INPUTURL + '" size="32"/>' +
'<br/>';
if (M.editor_atto.can_show_filepicker(elementid, 'image')) {
// Add the repository browser button.
html += '<button id="' + CSS.IMAGEBROWSER + '" data-editor="' + Y.Escape.html(elementid) + '" type="button">' + str.browse + '</button>' +
'<br/>';
}
// Add the Alt box.
html += '<div style="display:none" role="alert" id="' + CSS.IMAGEALTWARNING + '" class="warning">' + str.presentationrequired + '</div>' +
'<label for="' + CSS.INPUTALT + '">' + str.alt + '</label>' +
'<input class="fullwidth" type="text" value="" id="' + CSS.INPUTALT + '" size="32"/>' +
'<br/>';
// Add the presentation select box.
html += '<input type="checkbox" id="' + CSS.IMAGEPRESENTATION + '"/>' +
'<label class="sameline" for="' + CSS.IMAGEPRESENTATION + '">' + str.presentation + '</label>' +
'<br/>';
// Add the width entry box.
html += '<label class="sameline" for="' + CSS.INPUTWIDTH + '">' + str.width + '</label>' +
'<input type="text" value="" id="' + CSS.INPUTWIDTH + '" size="10"/>' +
'<br/>';
// Add the height entry box.
html += '<label class="sameline" for="' + CSS.INPUTHEIGHT + '">' + str.height + '</label>' +
'<input type="text" value="" id="' + CSS.INPUTHEIGHT + '" size="10"/>' +
'<br/>';
// Add the alignment selector.
html += '<label class="sameline" for="' + CSS.INPUTALIGNMENT + '">' + str.alignment + '</label>' +
'<select id="' + CSS.INPUTALIGNMENT + '">';
for (i in ALIGNMENTS) {
html += ALIGNMENTS[i].to_select_option();
}
html += '</select>' +
'<br/>';
// Add the image preview.
html += '<label for="' + CSS.IMAGEPREVIEW + '">' + str.preview + '</label>' +
'<img src="#" width="200" id="' + CSS.IMAGEPREVIEW + '" alt="" style="display: none;"/>' +
'<div class="mdl-align">' +
'<br/>';
// Add the submit button and close the form.
html += '<button id="' + CSS.INPUTSUMBIT + '" type="submit">' + str.create + '</button>' +
'</div>' +
'</form>';
var content = Y.Node.create(html);
this._apply_image_properties(content, elementid);
content.one('#' + CSS.INPUTURL).on('blur', M.atto_image.url_changed, this);
content.one('#' + CSS.INPUTSUMBIT).on('click', M.atto_image.set_image, this, elementid);
if (M.editor_atto.can_show_filepicker(elementid, 'image')) {
content.one('#' + CSS.IMAGEBROWSER).on('click', M.atto_image.open_filepicker);
}
return content;
},
/**
* Applies properties of an existing image to the image dialogue for editing.
*
* @method _apply_image_properties
* @private
* @method _applyImageProperties
* @param {Node} form
* @param {string} elementid
* @private
*/
_apply_image_properties: function(form, elementid) {
var properties = this._get_selected_image_properties(elementid),
img = form.one('#' + CSS.IMAGEPREVIEW);
_applyImageProperties: function(form) {
var properties = this._getSelectedImageProperties(),
img = form.one('.' + CSS.IMAGEPREVIEW);
if (properties === false) {
img.setStyle('display', 'none');
@@ -451,20 +300,171 @@ M.atto_image = M.atto_image || {
img.setStyle('display', properties.display);
}
if (properties.width) {
form.one('#' + CSS.INPUTWIDTH).set('value', properties.width);
form.one('.' + CSS.INPUTWIDTH).set('value', properties.width);
}
if (properties.height) {
form.one('#' + CSS.INPUTHEIGHT).set('value', properties.height);
form.one('.' + CSS.INPUTHEIGHT).set('value', properties.height);
}
if (properties.alt) {
form.one('#' + CSS.INPUTALT).set('value', properties.alt);
form.one('.' + CSS.INPUTALT).set('value', properties.alt);
}
if (properties.src) {
form.one('#' + CSS.INPUTURL).set('value', properties.src);
form.one('.' + CSS.INPUTURL).set('value', properties.src);
img.setAttribute('src', properties.src);
}
if (properties.presentation) {
form.one('#' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
form.one('.' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
}
},
/**
* Gets the properties of the currently selected image.
*
* The first image only if multiple images are selected.
*
* @method _getSelectedImageProperties
* @return {object}
* @private
*/
_getSelectedImageProperties: function() {
var properties = {
src: null,
alt :null,
width: null,
height: null,
align: null,
display: 'inline',
presentation: false
},
// Get the current selection.
images = this.get('host').getSelectedNodes(),
i, width, height, style;
if (images) {
images = images.filter('img');
}
if (images && images.size()) {
image = images.item(0);
this._selectedImage = image;
style = image.getAttribute('style');
width = parseInt(image.getAttribute('width'), 10);
height = parseInt(image.getAttribute('height'), 10);
if (width > 0) {
properties.width = width;
}
if (height > 0) {
properties.height = height;
}
for (i in ALIGNMENTS) {
if (ALIGNMENTS[i].name === style) {
properties.align = ALIGNMENTS[i];
break;
}
}
properties.src = image.getAttribute('src');
properties.alt = image.getAttribute('alt') || '';
properties.presentation = (image.get('role') === 'presentation');
return properties;
}
// No image selected - clean up.
this._selectedImage = null;
return false;
},
/**
* Update the form when the URL was changed. This includes updating the
* height, width, and image preview.
*
* @method _urlChanged
* @private
*/
_urlChanged: function() {
var input = this._form.one('.' + CSS.INPUTURL),
self = this;
if (input.get('value') !== '') {
// Auto set the width and height.
var image = new Image();
image.onload = function() {
var input;
input = self._form.one('.' + CSS.INPUTWIDTH);
if (input.get('value') === '') {
input.set('value', this.width);
}
input = self._form.one('.' + CSS.INPUTHEIGHT);
if (input.get('value') === '') {
input.set('value', this.height);
}
input = self._form.one('.' + CSS.IMAGEPREVIEW);
input.set('src', this.src);
input.setStyle('display', 'inline');
};
image.src = input.get('value');
}
},
/**
* Update the image in the contenteditable.
*
* @method _setImage
* @param {EventFacade} e
* @private
*/
_setImage: function(e) {
var form = this._form,
url = form.one('.' + CSS.INPUTURL).get('value'),
alt = form.one('.' + CSS.INPUTALT).get('value'),
width = form.one('.' + CSS.INPUTWIDTH).get('value'),
height = form.one('.' + CSS.INPUTHEIGHT).get('value'),
alignment = form.one('.' + CSS.INPUTALIGNMENT).get('value'),
presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'),
imagehtml,
host = this.get('host');
e.preventDefault();
if (alt === '' && !presentation) {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
return;
} else {
form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
}
this.getDialogue({
focusAfterHide: null
}).hide();
// Focus on the editor in preparation for inserting the image.
host.focus();
if (url !== '') {
if (this._selectedImage) {
host.setSelection(host.getSelectionFromNode(this._selectedImage));
} else {
host.setSelection(this._currentSelection);
}
template = Y.Handlebars.compile(IMAGETEMPLATE);
imagehtml = template({
url: url,
alt: alt,
width: width,
height: height,
presentation: presentation,
alignment: alignment
});
this.get('host').insertContentAtFocusPoint(imagehtml);
this.markUpdated();
}
}
};
});
@@ -1,8 +1,7 @@
{
"moodle-atto_image-button": {
"requires": [
"node",
"escape"
]
}
"moodle-atto_image-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,63 +15,58 @@ YUI.add('moodle-atto_indent-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor indent plugin.
*
/*
* @package atto_indent
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_indent = M.atto_indent || {
init : function(params) {
var iconurl, indent, outdent;
indent = function(e, elementid) {
var editable;
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
/**
* @module moodle-atto_indent-button
*/
/**
* Atto text editor indent plugin.
*
* @namespace M.atto_indent
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_indent').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addButton({
// This is adding a <blockquote> which is not ideal but that is the easiest to put in place
// for now. When disabling the styleWithCSS, some browser will use <blockquote> so we cannot
// rely on it for <div>s, and that would not work when indenting lists either....
// Handling it ourselves is even worse as it would require to get a parent and wrap
// a div with a margin around it. Considering that multiple <p> should end up in the
// same <div>, that table cells should not be wrapped, and that lists work differently too.
document.execCommand('indent', false, null);
icon: 'e/increase_indent',
title: 'indent',
buttonName: 'indent',
callback: function() {
document.execCommand('indent', false, null);
// Some browsers add style attributes to the blockquote, let's get rid of them.
// It is really tricky to figure out what blockquote was just added, so removing
// the styles on all of them seems OK.
// Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
// IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
editable = M.editor_atto.get_editable_node(elementid);
editable.all('blockquote').removeAttribute('style');
// Some browsers add style attributes to the blockquote, let's get rid of them.
// It is really tricky to figure out what blockquote was just added, so removing
// the styles on all of them seems OK.
// Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
// IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
this.editor.all('blockquote').removeAttribute('style');
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
outdent = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
// Mark the text as having been updated.
this.markUpdated();
}
document.execCommand('outdent', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
});
iconurl = M.util.image_url('e/decrease_indent', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'indent', iconurl, params.group, outdent,
'outdent', M.util.get_string('outdent', 'atto_indent'));
iconurl = M.util.image_url('e/increase_indent', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'indent', iconurl, params.group, indent,
'indent', M.util.get_string('indent', 'atto_indent'));
this.addBasicButton({
exec: 'outdent',
icon: 'e/decrease_indent',
title: 'outdent'
});
}
};
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_indent-button",function(e,t){M.atto_indent=M.atto_indent||{init:function(e){var t,n,r;n=function(e,t){var n;e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("indent",!1,null),n=M.editor_atto.get_editable_node(t),n.all("blockquote").removeAttribute("style"),M.editor_atto.text_updated(t)},r=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("outdent",!1,null),M.editor_atto.text_updated(t)},t=M.util.image_url("e/decrease_indent","core"),M.editor_atto.add_toolbar_button(e.elementid,"indent",t,e.group,r,"outdent",M.util.get_string("outdent","atto_indent")),t=M.util.image_url("e/increase_indent","core"),M.editor_atto.add_toolbar_button(e.elementid,"indent",t,e.group,n,"indent",M.util.get_string("indent","atto_indent"))}}},"@VERSION@",{requires:["node"]});
YUI.add("moodle-atto_indent-button",function(e,t){e.namespace("M.atto_indent").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addButton({icon:"e/increase_indent",title:"indent",buttonName:"indent",callback:function(){document.execCommand("indent",!1,null),this.editor.all("blockquote").removeAttribute("style"),this.markUpdated()}}),this.addBasicButton({exec:"outdent",icon:"e/decrease_indent",title:"outdent"})}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,63 +15,58 @@ YUI.add('moodle-atto_indent-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor indent plugin.
*
/*
* @package atto_indent
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_indent = M.atto_indent || {
init : function(params) {
var iconurl, indent, outdent;
indent = function(e, elementid) {
var editable;
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
/**
* @module moodle-atto_indent-button
*/
/**
* Atto text editor indent plugin.
*
* @namespace M.atto_indent
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_indent').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addButton({
// This is adding a <blockquote> which is not ideal but that is the easiest to put in place
// for now. When disabling the styleWithCSS, some browser will use <blockquote> so we cannot
// rely on it for <div>s, and that would not work when indenting lists either....
// Handling it ourselves is even worse as it would require to get a parent and wrap
// a div with a margin around it. Considering that multiple <p> should end up in the
// same <div>, that table cells should not be wrapped, and that lists work differently too.
document.execCommand('indent', false, null);
icon: 'e/increase_indent',
title: 'indent',
buttonName: 'indent',
callback: function() {
document.execCommand('indent', false, null);
// Some browsers add style attributes to the blockquote, let's get rid of them.
// It is really tricky to figure out what blockquote was just added, so removing
// the styles on all of them seems OK.
// Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
// IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
editable = M.editor_atto.get_editable_node(elementid);
editable.all('blockquote').removeAttribute('style');
// Some browsers add style attributes to the blockquote, let's get rid of them.
// It is really tricky to figure out what blockquote was just added, so removing
// the styles on all of them seems OK.
// Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
// IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
this.editor.all('blockquote').removeAttribute('style');
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
outdent = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
// Mark the text as having been updated.
this.markUpdated();
}
document.execCommand('outdent', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
});
iconurl = M.util.image_url('e/decrease_indent', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'indent', iconurl, params.group, outdent,
'outdent', M.util.get_string('outdent', 'atto_indent'));
iconurl = M.util.image_url('e/increase_indent', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'indent', iconurl, params.group, indent,
'indent', M.util.get_string('indent', 'atto_indent'));
this.addBasicButton({
exec: 'outdent',
icon: 'e/decrease_indent',
title: 'outdent'
});
}
};
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+35 -40
View File
@@ -13,60 +13,55 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor indent plugin.
*
/*
* @package atto_indent
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_indent = M.atto_indent || {
init : function(params) {
var iconurl, indent, outdent;
indent = function(e, elementid) {
var editable;
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
/**
* @module moodle-atto_indent-button
*/
/**
* Atto text editor indent plugin.
*
* @namespace M.atto_indent
* @class button
* @extends M.editor_atto.EditorPlugin
*/
Y.namespace('M.atto_indent').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addButton({
// This is adding a <blockquote> which is not ideal but that is the easiest to put in place
// for now. When disabling the styleWithCSS, some browser will use <blockquote> so we cannot
// rely on it for <div>s, and that would not work when indenting lists either....
// Handling it ourselves is even worse as it would require to get a parent and wrap
// a div with a margin around it. Considering that multiple <p> should end up in the
// same <div>, that table cells should not be wrapped, and that lists work differently too.
document.execCommand('indent', false, null);
icon: 'e/increase_indent',
title: 'indent',
buttonName: 'indent',
callback: function() {
document.execCommand('indent', false, null);
// Some browsers add style attributes to the blockquote, let's get rid of them.
// It is really tricky to figure out what blockquote was just added, so removing
// the styles on all of them seems OK.
// Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
// IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
editable = M.editor_atto.get_editable_node(elementid);
editable.all('blockquote').removeAttribute('style');
// Some browsers add style attributes to the blockquote, let's get rid of them.
// It is really tricky to figure out what blockquote was just added, so removing
// the styles on all of them seems OK.
// Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
// IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
this.editor.all('blockquote').removeAttribute('style');
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
outdent = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
// Mark the text as having been updated.
this.markUpdated();
}
document.execCommand('outdent', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
});
iconurl = M.util.image_url('e/decrease_indent', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'indent', iconurl, params.group, outdent,
'outdent', M.util.get_string('outdent', 'atto_indent'));
iconurl = M.util.image_url('e/increase_indent', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'indent', iconurl, params.group, indent,
'indent', M.util.get_string('indent', 'atto_indent'));
this.addBasicButton({
exec: 'outdent',
icon: 'e/decrease_indent',
title: 'outdent'
});
}
};
});
@@ -1,5 +1,7 @@
{
"moodle-atto_indent-button": {
"requires": ["node"]
}
"moodle-atto_indent-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,50 +15,37 @@ YUI.add('moodle-atto_italic-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_italic
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_italic-button
*/
var SELECTORS = {
TAGS : 'i'
};
/**
* Atto text editor italic plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_italic
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_italic = M.atto_italic || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('italic', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/italic', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'italic', iconurl, params.group, click);
M.editor_atto.add_button_shortcut({action: 'italic', keys: 73});
Y.namespace('M.atto_italic').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'italic',
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'italic');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'italic');
}
// Key code for the keyboard shortcut which triggers this button:
keys: '73',
// Watch the following tags and add/remove highlighting as appropriate:
tags: 'i'
});
}
};
});
}, '@VERSION@', {"requires": ["node", "moodle-editor_atto-editor-shortcut"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_italic-button",function(e,t){var n={TAGS:"i"};M.atto_italic=M.atto_italic||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("italic",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/italic","core");M.editor_atto.add_toolbar_button(e.elementid,"italic",r,e.group,t),M.editor_atto.add_button_shortcut({action:"italic",keys:73}),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"italic"):M.editor_atto.remove_widget_highlight(e.elementid,"italic")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
YUI.add("moodle-atto_italic-button",function(e,t){e.namespace("M.atto_italic").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addBasicButton({exec:"italic",keys:"73",tags:"i"})}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,50 +15,37 @@ YUI.add('moodle-atto_italic-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_italic
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_italic-button
*/
var SELECTORS = {
TAGS : 'i'
};
/**
* Atto text editor italic plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_italic
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_italic = M.atto_italic || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('italic', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/italic', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'italic', iconurl, params.group, click);
M.editor_atto.add_button_shortcut({action: 'italic', keys: 73});
Y.namespace('M.atto_italic').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'italic',
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'italic');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'italic');
}
// Key code for the keyboard shortcut which triggers this button:
keys: '73',
// Watch the following tags and add/remove highlighting as appropriate:
tags: 'i'
});
}
};
});
}, '@VERSION@', {"requires": ["node", "moodle-editor_atto-editor-shortcut"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+20 -33
View File
@@ -13,47 +13,34 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_italic
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_italic-button
*/
var SELECTORS = {
TAGS : 'i'
};
/**
* Atto text editor italic plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_italic
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_italic = M.atto_italic || {
init : function(params) {
var click = function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
document.execCommand('italic', false, null);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
var iconurl = M.util.image_url('e/italic', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'italic', iconurl, params.group, click);
M.editor_atto.add_button_shortcut({action: 'italic', keys: 73});
Y.namespace('M.atto_italic').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
this.addBasicButton({
exec: 'italic',
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'italic');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'italic');
}
// Key code for the keyboard shortcut which triggers this button:
keys: '73',
// Watch the following tags and add/remove highlighting as appropriate:
tags: 'i'
});
}
};
});
@@ -1,5 +1,7 @@
{
"moodle-atto_italic-button": {
"requires": ["node", "moodle-editor_atto-editor-shortcut"]
}
"moodle-atto_italic-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -15,226 +15,231 @@ YUI.add('moodle-atto_link-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_link
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_link-button
*/
var SELECTORS = {
TAGS : 'a'
};
/**
* Atto text editor link plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_link
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_link = M.atto_link || {
/**
* The window used to get the link details.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
var COMPONENTNAME = 'atto_link',
CSS = {
NEWWINDOW: 'atto_link_openinnewwindow'
},
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth url" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>' +
// Add the repository browser button.
'{{#if showFilepicker}}' +
'<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button>' +
'<br/>' +
'{{/if}}' +
'<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
'<label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label>' +
'<br/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button type="submit" class="submit">{{get_string "createlink" component}}</button>' +
'</div>' +
'</form>';
Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The selection object returned by the browser.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property selection
* @property _currentSelection
* @type Range
* @default null
* @private
*/
selection : null,
_currentSelection: null,
/**
* Display the chooser dialogue.
* A reference to the dialogue content.
*
* @method init
* @param Event e
* @param string elementid
* @property _content
* @type Node
* @private
*/
display_chooser : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_link.selection = M.editor_atto.get_selection();
if (M.atto_link.selection !== false && (!M.atto_link.selection.collapsed)) {
var dialogue;
if (!M.atto_link.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_link.dialogue;
}
_content: null,
dialogue.render();
dialogue.set('bodyContent', M.atto_link.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createlink', 'atto_link'));
M.atto_link.resolve_anchors();
dialogue.show();
M.atto_link.dialogue = dialogue;
}
initializer: function() {
this.addButton({
icon: 'e/insert_edit_link',
callback: this._displayDialogue,
tags: 'a'
});
},
/**
* Add this button to the form.
* Display the link editor.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/insert_edit_link', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'link', iconurl, params.group, this.display_chooser);
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'link');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'link');
}
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false || this._currentSelection.collapsed) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createlink', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent());
// Resolve anchors in the selected text.
this._resolveAnchors();
dialogue.show();
},
/**
* If there is selected text and it is part of an anchor link,
* extract the url (and target) from the link (and set them in the form).
*
* @method resolve_anchors
* @method _resolveAnchors
* @private
*/
resolve_anchors : function() {
_resolveAnchors: function() {
// Find the first anchor tag in the selection.
var selectednode = M.editor_atto.get_selection_parent_node(),
var selectednode = this.get('host').getSelectionParentNode(),
anchornodes,
anchornode,
url;
url,
target;
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
return;
}
anchornodes = M.atto_link.find_selected_anchors(Y.one(selectednode));
anchornodes = this._findSelectedAnchors(Y.one(selectednode));
if (anchornodes.length > 0) {
anchornode = anchornodes[0];
M.atto_link.selection = M.editor_atto.get_selection_from_node(anchornode);
this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
url = anchornode.getAttribute('href');
target = anchornode.getAttribute('target');
if (url !== '') {
Y.one('#atto_link_urlentry').set('value', url);
this._content.one('.url').setAttribute('value', url);
}
if (target === '_blank') {
Y.one('#atto_link_openinnewwindow').set('checked', 'checked');
this._content.one('.newwindow').setAttribute('checked', 'checked');
} else {
Y.one('#atto_link_openinnewwindow').set('checked', '');
this._content.one('.newwindow').removeAttribute('checked');
}
}
},
/**
* Open the repository file picker.
* Update the dialogue after an image was selected in the File Picker.
*
* @method open_filepicker
* @param Event e
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
open_filepicker : function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
_filepickerCallback: function(params) {
this.getDialogue()
.set('focusAfterHide', null)
.hide();
M.editor_atto.show_filepicker(elementid, 'link', M.atto_link.filepicker_callback);
},
/**
* Called by the file picker when a link has been chosen.
*
* @method filepicker_callback
* @param {Object} params - contains selected url.
*/
filepicker_callback : function(params) {
M.atto_link.dialogue.hide();
if (params.url !== '') {
M.editor_atto.set_selection(M.atto_link.selection);
this.get('host').setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, params.url);
}
},
/**
* The OK button has been pressed - make the changes to the source.
* The link was inserted, so make changes to the editor source.
*
* @method set_link
* @param Event e
* @method _setLink
* @param {EventFacade} e
* @private
*/
set_link : function(e, elementid) {
_setLink: function(e) {
var input,
target,
selectednode,
anchornodes,
value;
e.preventDefault();
M.atto_link.dialogue.hide();
var host = this.get('host');
input = e.currentTarget.ancestor('.atto_form').one('input[type=url]');
e.preventDefault();
this.getDialogue({
focusAfterHide: null
}).hide();
input = this._content.one('.url');
value = input.get('value');
if (value !== '') {
M.editor_atto.set_selection(M.atto_link.selection);
this.editor.focus();
host.setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, value);
// Now set the target.
selectednode = M.editor_atto.get_selection_parent_node();
selectednode = host.getSelectionParentNode();
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
return;
}
anchornodes = M.atto_link.find_selected_anchors(Y.one(selectednode));
anchornodes = this._findSelectedAnchors(Y.one(selectednode));
Y.Array.each(anchornodes, function(anchornode) {
target = e.currentTarget.ancestor('.atto_form').one('input[type=checkbox]');
target = this._content.one('.newwindow');
if (target.get('checked')) {
anchornode.setAttribute('target', '_blank');
} else {
anchornode.removeAttribute('target');
}
});
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
}, this);
this.markUpdated();
}
},
/**
* Look up and down for the nearest anchor tags that are least partly contained in the selection.
*
* @method find_selected_anchors
* @param Node node
* @return Node|false
* @method _findSelectedAnchors
* @param {Node} node The node to search under for the selected anchor.
* @return {Node|Boolean} The Node, or false if not found.
* @private
*/
find_selected_anchors : function(node) {
var tagname = node.get('tagName'), hit, hits;
_findSelectedAnchors: function(node) {
var tagname = node.get('tagName'),
hit, hits;
// Direct hit.
if (tagname && tagname.toLowerCase() === 'a') {
return [node];
}
// Search down but check that each node is part of the selection.
hits = [];
node.all('a').each(function(n) {
if (!hit && M.editor_atto.selection_contains_node(n)) {
if (!hit && this.get('host').selectionContainsNode(n)) {
hits.push(n);
}
});
@@ -250,44 +255,33 @@ M.atto_link = M.atto_link || {
},
/**
* Return the HTML of the form to show in the dialogue.
* Generates the content of the dialogue.
*
* @method get_form_content
* @param string elementid
* @return string
* @method _getDialogueContent
* @return {Node} Node containing the dialogue content
* @private
*/
get_form_content : function(elementid) {
var html = '<form class="atto_form">' +
'<label for="atto_link_urlentry">' + M.util.get_string('enterurl', 'atto_link') +
'</label>' +
'<input class="fullwidth" type="url" value="" id="atto_link_urlentry" size="32"/><br/>';
if (M.editor_atto.can_show_filepicker(elementid, 'link')) {
html += '<button id="openlinkbrowser" data-editor="' + Y.Escape.html(elementid) + '" type="button" >' +
M.util.get_string('browserepositories', 'atto_link') +
'</button>' +
'<br/>';
}
html += '<input type="checkbox" id="atto_link_openinnewwindow"/>' +
'<label class="sameline" for="atto_link_openinnewwindow">' + M.util.get_string('openinnewwindow', 'atto_link') +
'</label>' +
'<br/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button type="submit" id="atto_link_urlentrysubmit">' +
M.util.get_string('createlink', 'atto_link') +
'</button>' +
'</div>' +
'</form>';
_getDialogueContent: function() {
var canShowFilepicker = this.get('host').canShowFilepicker('link'),
template = Y.Handlebars.compile(TEMPLATE);
var content = Y.Node.create(html);
this._content = Y.Node.create(template({
showFilepicker: canShowFilepicker,
component: COMPONENTNAME,
CSS: CSS
}));
content.one('#atto_link_urlentrysubmit').on('click', M.atto_link.set_link, this, elementid);
if (M.editor_atto.can_show_filepicker(elementid, 'link')) {
content.one('#openlinkbrowser').on('click', M.atto_link.open_filepicker);
this._content.one('.submit').on('click', this._setLink, this);
if (canShowFilepicker) {
this._content.one('.openlinkbrowser').on('click', function(e) {
e.preventDefault();
this.get('host').showFilepicker('link', this._filepickerCallback, this);
}, this);
}
return content;
return this._content;
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_link-button",function(e,t){var n={TAGS:"a"};M.atto_link=M.atto_link||{dialogue:null,selection:null,display_chooser:function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),M.atto_link.selection=M.editor_atto.get_selection();if(M.atto_link.selection!==!1&&!M.atto_link.selection.collapsed){var n;M.atto_link.dialogue?n=M.atto_link.dialogue:n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0}),n.render(),n.set("bodyContent",M.atto_link.get_form_content(t)),n.set("headerContent",M.util.get_string("createlink","atto_link")),M.atto_link.resolve_anchors(),n.show(),M.atto_link.dialogue=n}},init:function(e){var t=M.util.image_url("e/insert_edit_link","core");M.editor_atto.add_toolbar_button(e.elementid,"link",t,e.group,this.display_chooser),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"link"):M.editor_atto.remove_widget_highlight(e.elementid,"link")})},resolve_anchors:function(){var t=M.editor_atto.get_selection_parent_node(),n,r,i;if(!t)return;n=M.atto_link.find_selected_anchors(e.one(t)),n.length>0&&(r=n[0],M.atto_link.selection=M.editor_atto.get_selection_from_node(r),i=r.getAttribute("href"),target=r.getAttribute("target"),i!==""&&e.one("#atto_link_urlentry").set("value",i),target==="_blank"?e.one("#atto_link_openinnewwindow").set("checked","checked"):e.one("#atto_link_openinnewwindow").set("checked",""))},open_filepicker:function(e){var t=this.getAttribute("data-editor");e.preventDefault(),M.editor_atto.show_filepicker(t,"link",M.atto_link.filepicker_callback)},filepicker_callback:function(e){M.atto_link.dialogue.hide(),e.url!==""&&(M.editor_atto.set_selection(M.atto_link.selection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,e.url))},set_link:function(t,n){var r,i,s,o,u;t.preventDefault(),M.atto_link.dialogue.hide(),r=t.currentTarget.ancestor(".atto_form").one("input[type=url]"),u=r.get("value");if(u!==""){M.editor_atto.set_selection(M.atto_link.selection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,u),s=M.editor_atto.get_selection_parent_node();if(!s)return;o=M.atto_link.find_selected_anchors(e.one(s)),e.Array.each(o,function(e){i=t.currentTarget.ancestor(".atto_form").one("input[type=checkbox]"),i.get("checked")?e.setAttribute("target","_blank"):e.removeAttribute("target")}),M.editor_atto.text_updated(n)}},find_selected_anchors:function(e){var t=e.get("tagName"),n,r;return t&&t.toLowerCase()==="a"?[e]:(r=[],e.all("a").each(function(e){!n&&M.editor_atto.selection_contains_node(e)&&r.push(e)}),r.length>0?r:(n=e.ancestor("a"),n?[n]:[]))},get_form_content:function(t){var n='<form class="atto_form"><label for="atto_link_urlentry">'+M.util.get_string("enterurl","atto_link")+"</label>"+'<input class="fullwidth" type="url" value="" id="atto_link_urlentry" size="32"/><br/>';M.editor_atto.can_show_filepicker(t,"link")&&(n+='<button id="openlinkbrowser" data-editor="'+e.Escape.html(t)+'" type="button" >'+M.util.get_string("browserepositories","atto_link")+"</button>"+"<br/>"),n+='<input type="checkbox" id="atto_link_openinnewwindow"/><label class="sameline" for="atto_link_openinnewwindow">'+M.util.get_string("openinnewwindow","atto_link")+"</label>"+"<br/>"+'<div class="mdl-align">'+"<br/>"+'<button type="submit" id="atto_link_urlentrysubmit">'+M.util.get_string("createlink","atto_link")+"</button>"+"</div>"+"</form>";var r=e.Node.create(n);return r.one("#atto_link_urlentrysubmit").on("click",M.atto_link.set_link,this,t),M.editor_atto.can_show_filepicker(t,"link")&&r.one("#openlinkbrowser").on("click",M.atto_link.open_filepicker),r}}},"@VERSION@",{requires:["node","escape"]});
YUI.add("moodle-atto_link-button",function(e,t){var n="atto_link",r={NEWWINDOW:"atto_link_openinnewwindow"},i='<form class="atto_form"><label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label><input class="fullwidth url" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>{{#if showFilepicker}}<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button><br/>{{/if}}<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/><label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label><br/><div class="mdl-align"><br/><button type="submit" class="submit">{{get_string "createlink" component}}</button></div></form>';e.namespace("M.atto_link").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,initializer:function(){this.addButton({icon:"e/insert_edit_link",callback:this._displayDialogue,tags:"a"})},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1||this._currentSelection.collapsed)return;var e=this.getDialogue({headerContent:M.util.get_string("createlink",n),focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()),this._resolveAnchors(),e.show()},_resolveAnchors:function(){var t=this.get("host").getSelectionParentNode(),n,r,i,s;if(!t)return;n=this._findSelectedAnchors(e.one(t)),n.length>0&&(r=n[0],this._currentSelection=this.get("host").getSelectionFromNode(r),i=r.getAttribute("href"),s=r.getAttribute("target"),i!==""&&this._content.one(".url").setAttribute("value",i),s==="_blank"?this._content.one(".newwindow").setAttribute("checked","checked"):this._content.one(".newwindow").removeAttribute("checked"))},_filepickerCallback:function(e){this.getDialogue().set("focusAfterHide",null).hide(),e.url!==""&&(this.get("host").setSelection(this._currentSelection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,e.url))},_setLink:function(t){var n,r,i,s,o,u=this.get("host");t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),n=this._content.one(".url"),o=n.get("value");if(o!==""){this.editor.focus(),u.setSelection(this._currentSelection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,o),i=u.getSelectionParentNode();if(!i)return;s=this._findSelectedAnchors(e.one(i)),e.Array.each(s,function(e){r=this._content.one(".newwindow"),r.get("checked")?e.setAttribute("target","_blank"):e.removeAttribute("target")},this),this.markUpdated()}},_findSelectedAnchors:function(e){var t=e.get("tagName"),n,r;return t&&t.toLowerCase()==="a"?[e]:(r=[],e.all("a").each(function(e){!n&&this.get("host").selectionContainsNode(e)&&r.push(e)}),r.length>0?r:(n=e.ancestor("a"),n?[n]:[]))},_getDialogueContent:function(){var t=this.get("host").canShowFilepicker("link"),s=e.Handlebars.compile(i);return this._content=e.Node.create(s({showFilepicker:t,component:n,CSS:r})),this._content.one(".submit").on("click",this._setLink,this),t&&this._content.one(".openlinkbrowser").on("click",function(e){e.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -15,226 +15,231 @@ YUI.add('moodle-atto_link-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_link
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_link-button
*/
var SELECTORS = {
TAGS : 'a'
};
/**
* Atto text editor link plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_link
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_link = M.atto_link || {
/**
* The window used to get the link details.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
var COMPONENTNAME = 'atto_link',
CSS = {
NEWWINDOW: 'atto_link_openinnewwindow'
},
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth url" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>' +
// Add the repository browser button.
'{{#if showFilepicker}}' +
'<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button>' +
'<br/>' +
'{{/if}}' +
'<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
'<label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label>' +
'<br/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button type="submit" class="submit">{{get_string "createlink" component}}</button>' +
'</div>' +
'</form>';
Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The selection object returned by the browser.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property selection
* @property _currentSelection
* @type Range
* @default null
* @private
*/
selection : null,
_currentSelection: null,
/**
* Display the chooser dialogue.
* A reference to the dialogue content.
*
* @method init
* @param Event e
* @param string elementid
* @property _content
* @type Node
* @private
*/
display_chooser : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_link.selection = M.editor_atto.get_selection();
if (M.atto_link.selection !== false && (!M.atto_link.selection.collapsed)) {
var dialogue;
if (!M.atto_link.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_link.dialogue;
}
_content: null,
dialogue.render();
dialogue.set('bodyContent', M.atto_link.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createlink', 'atto_link'));
M.atto_link.resolve_anchors();
dialogue.show();
M.atto_link.dialogue = dialogue;
}
initializer: function() {
this.addButton({
icon: 'e/insert_edit_link',
callback: this._displayDialogue,
tags: 'a'
});
},
/**
* Add this button to the form.
* Display the link editor.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/insert_edit_link', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'link', iconurl, params.group, this.display_chooser);
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'link');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'link');
}
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false || this._currentSelection.collapsed) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createlink', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent());
// Resolve anchors in the selected text.
this._resolveAnchors();
dialogue.show();
},
/**
* If there is selected text and it is part of an anchor link,
* extract the url (and target) from the link (and set them in the form).
*
* @method resolve_anchors
* @method _resolveAnchors
* @private
*/
resolve_anchors : function() {
_resolveAnchors: function() {
// Find the first anchor tag in the selection.
var selectednode = M.editor_atto.get_selection_parent_node(),
var selectednode = this.get('host').getSelectionParentNode(),
anchornodes,
anchornode,
url;
url,
target;
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
return;
}
anchornodes = M.atto_link.find_selected_anchors(Y.one(selectednode));
anchornodes = this._findSelectedAnchors(Y.one(selectednode));
if (anchornodes.length > 0) {
anchornode = anchornodes[0];
M.atto_link.selection = M.editor_atto.get_selection_from_node(anchornode);
this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
url = anchornode.getAttribute('href');
target = anchornode.getAttribute('target');
if (url !== '') {
Y.one('#atto_link_urlentry').set('value', url);
this._content.one('.url').setAttribute('value', url);
}
if (target === '_blank') {
Y.one('#atto_link_openinnewwindow').set('checked', 'checked');
this._content.one('.newwindow').setAttribute('checked', 'checked');
} else {
Y.one('#atto_link_openinnewwindow').set('checked', '');
this._content.one('.newwindow').removeAttribute('checked');
}
}
},
/**
* Open the repository file picker.
* Update the dialogue after an image was selected in the File Picker.
*
* @method open_filepicker
* @param Event e
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
open_filepicker : function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
_filepickerCallback: function(params) {
this.getDialogue()
.set('focusAfterHide', null)
.hide();
M.editor_atto.show_filepicker(elementid, 'link', M.atto_link.filepicker_callback);
},
/**
* Called by the file picker when a link has been chosen.
*
* @method filepicker_callback
* @param {Object} params - contains selected url.
*/
filepicker_callback : function(params) {
M.atto_link.dialogue.hide();
if (params.url !== '') {
M.editor_atto.set_selection(M.atto_link.selection);
this.get('host').setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, params.url);
}
},
/**
* The OK button has been pressed - make the changes to the source.
* The link was inserted, so make changes to the editor source.
*
* @method set_link
* @param Event e
* @method _setLink
* @param {EventFacade} e
* @private
*/
set_link : function(e, elementid) {
_setLink: function(e) {
var input,
target,
selectednode,
anchornodes,
value;
e.preventDefault();
M.atto_link.dialogue.hide();
var host = this.get('host');
input = e.currentTarget.ancestor('.atto_form').one('input[type=url]');
e.preventDefault();
this.getDialogue({
focusAfterHide: null
}).hide();
input = this._content.one('.url');
value = input.get('value');
if (value !== '') {
M.editor_atto.set_selection(M.atto_link.selection);
this.editor.focus();
host.setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, value);
// Now set the target.
selectednode = M.editor_atto.get_selection_parent_node();
selectednode = host.getSelectionParentNode();
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
return;
}
anchornodes = M.atto_link.find_selected_anchors(Y.one(selectednode));
anchornodes = this._findSelectedAnchors(Y.one(selectednode));
Y.Array.each(anchornodes, function(anchornode) {
target = e.currentTarget.ancestor('.atto_form').one('input[type=checkbox]');
target = this._content.one('.newwindow');
if (target.get('checked')) {
anchornode.setAttribute('target', '_blank');
} else {
anchornode.removeAttribute('target');
}
});
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
}, this);
this.markUpdated();
}
},
/**
* Look up and down for the nearest anchor tags that are least partly contained in the selection.
*
* @method find_selected_anchors
* @param Node node
* @return Node|false
* @method _findSelectedAnchors
* @param {Node} node The node to search under for the selected anchor.
* @return {Node|Boolean} The Node, or false if not found.
* @private
*/
find_selected_anchors : function(node) {
var tagname = node.get('tagName'), hit, hits;
_findSelectedAnchors: function(node) {
var tagname = node.get('tagName'),
hit, hits;
// Direct hit.
if (tagname && tagname.toLowerCase() === 'a') {
return [node];
}
// Search down but check that each node is part of the selection.
hits = [];
node.all('a').each(function(n) {
if (!hit && M.editor_atto.selection_contains_node(n)) {
if (!hit && this.get('host').selectionContainsNode(n)) {
hits.push(n);
}
});
@@ -250,44 +255,33 @@ M.atto_link = M.atto_link || {
},
/**
* Return the HTML of the form to show in the dialogue.
* Generates the content of the dialogue.
*
* @method get_form_content
* @param string elementid
* @return string
* @method _getDialogueContent
* @return {Node} Node containing the dialogue content
* @private
*/
get_form_content : function(elementid) {
var html = '<form class="atto_form">' +
'<label for="atto_link_urlentry">' + M.util.get_string('enterurl', 'atto_link') +
'</label>' +
'<input class="fullwidth" type="url" value="" id="atto_link_urlentry" size="32"/><br/>';
if (M.editor_atto.can_show_filepicker(elementid, 'link')) {
html += '<button id="openlinkbrowser" data-editor="' + Y.Escape.html(elementid) + '" type="button" >' +
M.util.get_string('browserepositories', 'atto_link') +
'</button>' +
'<br/>';
}
html += '<input type="checkbox" id="atto_link_openinnewwindow"/>' +
'<label class="sameline" for="atto_link_openinnewwindow">' + M.util.get_string('openinnewwindow', 'atto_link') +
'</label>' +
'<br/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button type="submit" id="atto_link_urlentrysubmit">' +
M.util.get_string('createlink', 'atto_link') +
'</button>' +
'</div>' +
'</form>';
_getDialogueContent: function() {
var canShowFilepicker = this.get('host').canShowFilepicker('link'),
template = Y.Handlebars.compile(TEMPLATE);
var content = Y.Node.create(html);
this._content = Y.Node.create(template({
showFilepicker: canShowFilepicker,
component: COMPONENTNAME,
CSS: CSS
}));
content.one('#atto_link_urlentrysubmit').on('click', M.atto_link.set_link, this, elementid);
if (M.editor_atto.can_show_filepicker(elementid, 'link')) {
content.one('#openlinkbrowser').on('click', M.atto_link.open_filepicker);
this._content.one('.submit').on('click', this._setLink, this);
if (canShowFilepicker) {
this._content.one('.openlinkbrowser').on('click', function(e) {
e.preventDefault();
this.get('host').showFilepicker('link', this._filepickerCallback, this);
}, this);
}
return content;
return this._content;
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
+142 -148
View File
@@ -13,226 +13,231 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/*
* @package atto_link
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Selectors.
*
* @type {Object}
* @module moodle-atto_link-button
*/
var SELECTORS = {
TAGS : 'a'
};
/**
* Atto text editor link plugin.
*
* @package editor-atto
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @namespace M.atto_link
* @class button
* @extends M.editor_atto.EditorPlugin
*/
M.atto_link = M.atto_link || {
/**
* The window used to get the link details.
*
* @property dialogue
* @type M.core.dialogue
* @default null
*/
dialogue : null,
var COMPONENTNAME = 'atto_link',
CSS = {
NEWWINDOW: 'atto_link_openinnewwindow'
},
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth url" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>' +
// Add the repository browser button.
'{{#if showFilepicker}}' +
'<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button>' +
'<br/>' +
'{{/if}}' +
'<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
'<label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label>' +
'<br/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button type="submit" class="submit">{{get_string "createlink" component}}</button>' +
'</div>' +
'</form>';
Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The selection object returned by the browser.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property selection
* @property _currentSelection
* @type Range
* @default null
* @private
*/
selection : null,
_currentSelection: null,
/**
* Display the chooser dialogue.
* A reference to the dialogue content.
*
* @method init
* @param Event e
* @param string elementid
* @property _content
* @type Node
* @private
*/
display_chooser : function(e, elementid) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_link.selection = M.editor_atto.get_selection();
if (M.atto_link.selection !== false && (!M.atto_link.selection.collapsed)) {
var dialogue;
if (!M.atto_link.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_link.dialogue;
}
_content: null,
dialogue.render();
dialogue.set('bodyContent', M.atto_link.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createlink', 'atto_link'));
M.atto_link.resolve_anchors();
dialogue.show();
M.atto_link.dialogue = dialogue;
}
initializer: function() {
this.addButton({
icon: 'e/insert_edit_link',
callback: this._displayDialogue,
tags: 'a'
});
},
/**
* Add this button to the form.
* Display the link editor.
*
* @method init
* @param {Object} params
* @method _displayDialogue
* @private
*/
init : function(params) {
var iconurl = M.util.image_url('e/insert_edit_link', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'link', iconurl, params.group, this.display_chooser);
// Attach an event listner to watch for "changes" in the contenteditable.
// This includes cursor changes, we check if the button should be active or not, based
// on the text selection.
M.editor_atto.on('atto:selectionchanged', function(e) {
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
M.editor_atto.add_widget_highlight(e.elementid, 'link');
} else {
M.editor_atto.remove_widget_highlight(e.elementid, 'link');
}
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false || this._currentSelection.collapsed) {
return;
}
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createlink', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent());
// Resolve anchors in the selected text.
this._resolveAnchors();
dialogue.show();
},
/**
* If there is selected text and it is part of an anchor link,
* extract the url (and target) from the link (and set them in the form).
*
* @method resolve_anchors
* @method _resolveAnchors
* @private
*/
resolve_anchors : function() {
_resolveAnchors: function() {
// Find the first anchor tag in the selection.
var selectednode = M.editor_atto.get_selection_parent_node(),
var selectednode = this.get('host').getSelectionParentNode(),
anchornodes,
anchornode,
url;
url,
target;
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
return;
}
anchornodes = M.atto_link.find_selected_anchors(Y.one(selectednode));
anchornodes = this._findSelectedAnchors(Y.one(selectednode));
if (anchornodes.length > 0) {
anchornode = anchornodes[0];
M.atto_link.selection = M.editor_atto.get_selection_from_node(anchornode);
this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
url = anchornode.getAttribute('href');
target = anchornode.getAttribute('target');
if (url !== '') {
Y.one('#atto_link_urlentry').set('value', url);
this._content.one('.url').setAttribute('value', url);
}
if (target === '_blank') {
Y.one('#atto_link_openinnewwindow').set('checked', 'checked');
this._content.one('.newwindow').setAttribute('checked', 'checked');
} else {
Y.one('#atto_link_openinnewwindow').set('checked', '');
this._content.one('.newwindow').removeAttribute('checked');
}
}
},
/**
* Open the repository file picker.
* Update the dialogue after an image was selected in the File Picker.
*
* @method open_filepicker
* @param Event e
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
open_filepicker : function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
_filepickerCallback: function(params) {
this.getDialogue()
.set('focusAfterHide', null)
.hide();
M.editor_atto.show_filepicker(elementid, 'link', M.atto_link.filepicker_callback);
},
/**
* Called by the file picker when a link has been chosen.
*
* @method filepicker_callback
* @param {Object} params - contains selected url.
*/
filepicker_callback : function(params) {
M.atto_link.dialogue.hide();
if (params.url !== '') {
M.editor_atto.set_selection(M.atto_link.selection);
this.get('host').setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, params.url);
}
},
/**
* The OK button has been pressed - make the changes to the source.
* The link was inserted, so make changes to the editor source.
*
* @method set_link
* @param Event e
* @method _setLink
* @param {EventFacade} e
* @private
*/
set_link : function(e, elementid) {
_setLink: function(e) {
var input,
target,
selectednode,
anchornodes,
value;
e.preventDefault();
M.atto_link.dialogue.hide();
var host = this.get('host');
input = e.currentTarget.ancestor('.atto_form').one('input[type=url]');
e.preventDefault();
this.getDialogue({
focusAfterHide: null
}).hide();
input = this._content.one('.url');
value = input.get('value');
if (value !== '') {
M.editor_atto.set_selection(M.atto_link.selection);
this.editor.focus();
host.setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, value);
// Now set the target.
selectednode = M.editor_atto.get_selection_parent_node();
selectednode = host.getSelectionParentNode();
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
return;
}
anchornodes = M.atto_link.find_selected_anchors(Y.one(selectednode));
anchornodes = this._findSelectedAnchors(Y.one(selectednode));
Y.Array.each(anchornodes, function(anchornode) {
target = e.currentTarget.ancestor('.atto_form').one('input[type=checkbox]');
target = this._content.one('.newwindow');
if (target.get('checked')) {
anchornode.setAttribute('target', '_blank');
} else {
anchornode.removeAttribute('target');
}
});
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
}, this);
this.markUpdated();
}
},
/**
* Look up and down for the nearest anchor tags that are least partly contained in the selection.
*
* @method find_selected_anchors
* @param Node node
* @return Node|false
* @method _findSelectedAnchors
* @param {Node} node The node to search under for the selected anchor.
* @return {Node|Boolean} The Node, or false if not found.
* @private
*/
find_selected_anchors : function(node) {
var tagname = node.get('tagName'), hit, hits;
_findSelectedAnchors: function(node) {
var tagname = node.get('tagName'),
hit, hits;
// Direct hit.
if (tagname && tagname.toLowerCase() === 'a') {
return [node];
}
// Search down but check that each node is part of the selection.
hits = [];
node.all('a').each(function(n) {
if (!hit && M.editor_atto.selection_contains_node(n)) {
if (!hit && this.get('host').selectionContainsNode(n)) {
hits.push(n);
}
});
@@ -248,41 +253,30 @@ M.atto_link = M.atto_link || {
},
/**
* Return the HTML of the form to show in the dialogue.
* Generates the content of the dialogue.
*
* @method get_form_content
* @param string elementid
* @return string
* @method _getDialogueContent
* @return {Node} Node containing the dialogue content
* @private
*/
get_form_content : function(elementid) {
var html = '<form class="atto_form">' +
'<label for="atto_link_urlentry">' + M.util.get_string('enterurl', 'atto_link') +
'</label>' +
'<input class="fullwidth" type="url" value="" id="atto_link_urlentry" size="32"/><br/>';
if (M.editor_atto.can_show_filepicker(elementid, 'link')) {
html += '<button id="openlinkbrowser" data-editor="' + Y.Escape.html(elementid) + '" type="button" >' +
M.util.get_string('browserepositories', 'atto_link') +
'</button>' +
'<br/>';
}
html += '<input type="checkbox" id="atto_link_openinnewwindow"/>' +
'<label class="sameline" for="atto_link_openinnewwindow">' + M.util.get_string('openinnewwindow', 'atto_link') +
'</label>' +
'<br/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button type="submit" id="atto_link_urlentrysubmit">' +
M.util.get_string('createlink', 'atto_link') +
'</button>' +
'</div>' +
'</form>';
_getDialogueContent: function() {
var canShowFilepicker = this.get('host').canShowFilepicker('link'),
template = Y.Handlebars.compile(TEMPLATE);
var content = Y.Node.create(html);
this._content = Y.Node.create(template({
showFilepicker: canShowFilepicker,
component: COMPONENTNAME,
CSS: CSS
}));
content.one('#atto_link_urlentrysubmit').on('click', M.atto_link.set_link, this, elementid);
if (M.editor_atto.can_show_filepicker(elementid, 'link')) {
content.one('#openlinkbrowser').on('click', M.atto_link.open_filepicker);
this._content.one('.submit').on('click', this._setLink, this);
if (canShowFilepicker) {
this._content.one('.openlinkbrowser').on('click', function(e) {
e.preventDefault();
this.get('host').showFilepicker('link', this._filepickerCallback, this);
}, this);
}
return content;
return this._content;
}
};
});
@@ -1,8 +1,7 @@
{
"moodle-atto_link-button": {
"requires": [
"node",
"escape"
]
}
"moodle-atto_link-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -34,6 +34,7 @@ $accepted_types = optional_param('accepted_types', '*', PARAM_RAW); // TODO Not
$return_types = optional_param('return_types', null, PARAM_INT);
$areamaxbytes = optional_param('areamaxbytes', FILE_AREA_MAX_BYTES_UNLIMITED, PARAM_INT);
$contextid = optional_param('context', SYSCONTEXTID, PARAM_INT);
$elementid = optional_param('elementid', '', PARAM_TEXT);
$context = context::instance_by_id($contextid);
if ($context->contextlevel == CONTEXT_MODULE) {
@@ -74,7 +75,7 @@ $options = array(
'accepted_types' => $accepted_types,
'areamaxbytes' => $areamaxbytes,
'return_types' => $return_types,
'context' => $context
'context' => $context,
);
$usercontext = context_user::instance($USER->id);
@@ -86,7 +87,7 @@ foreach ($files as $file) {
}
$mform = new atto_managefiles_manage_form(null,
array('options' => $options, 'draftitemid' => $itemid, 'files' => $filenames),
array('options' => $options, 'draftitemid' => $itemid, 'files' => $filenames, 'elementid' => $elementid),
'post', '', array('id' => 'atto_managefiles_manageform'));
if ($data = $mform->get_data()) {
@@ -102,7 +103,7 @@ if ($data = $mform->get_data()) {
}
$filenames = array_diff_key($filenames, $data->deletefile);
$mform = new atto_managefiles_manage_form(null,
array('options' => $options, 'draftitemid' => $itemid, 'files' => $filenames),
array('options' => $options, 'draftitemid' => $itemid, 'files' => $filenames, 'elementid' => $data->elementid),
'post', '', array('id' => 'atto_managefiles_manageform'));
}
}
@@ -38,12 +38,13 @@ require_once($CFG->libdir."/formslib.php");
class atto_managefiles_manage_form extends moodleform {
function definition() {
global $PAGE;
global $PAGE, $USER;
$mform = $this->_form;
$mform->setDisableShortforms(true);
$itemid = $this->_customdata['draftitemid'];
$elementid = $this->_customdata['elementid'];
$options = $this->_customdata['options'];
$files = $this->_customdata['files'];
@@ -63,6 +64,8 @@ class atto_managefiles_manage_form extends moodleform {
$mform->setType('context', PARAM_INT);
$mform->addElement('hidden', 'areamaxbytes');
$mform->setType('areamaxbytes', PARAM_INT);
$mform->addElement('hidden', 'elementid');
$mform->setType('elementid', PARAM_TEXT);
$mform->addElement('filemanager', 'files_filemanager', '', null, $options);
@@ -87,11 +90,17 @@ class atto_managefiles_manage_form extends moodleform {
$mform->addElement('submit', 'delete', get_string('deleteselected', 'atto_managefiles'));
$PAGE->requires->yui_module('moodle-atto_managefiles-usedfiles', 'M.atto_managefiles.usedfiles.init',
array(array_flip($files)));
array(array(
'files' => array_flip($files),
'usercontext' => context_user::instance($USER->id)->id,
'itemid' => $itemid,
'elementid' => $elementid,
)));
$this->set_data(array(
'files_filemanager' => $itemid,
'itemid' => $itemid,
'elementid' => $elementid,
'subdirs' => $options['subdirs'],
'maxbytes' => $options['maxbytes'],
'areamaxbytes' => $options['areamaxbytes'],
@@ -16,152 +16,117 @@ YUI.add('moodle-atto_managefiles-button', function (Y, NAME) {
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor managefiles plugin.
*
* @package atto_managefiles
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_managefiles = M.atto_managefiles || {
/**
* @module moodle-atto-managefiles-button
*/
/**
* Atto text editor managefiles plugin.
*
* @namespace M.atto_link
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var LOGNAME = 'atto_managefiles';
Y.namespace('M.atto_managefiles').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* The parameters for each instance of Atto.
*
* @type {Object} Where keys are the element ID of each editor.
*/
params: {},
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init : function(params) {
if (params.disabled) {
initializer: function() {
if (this.get('disabled')) {
return;
}
// Get the itemid from the filepicker options.
if (!params.area.itemid
&& M.editor_atto.filepickeroptions[params.elementid]
&& M.editor_atto.filepickeroptions[params.elementid].image
&& M.editor_atto.filepickeroptions[params.elementid].image.itemid) {
params.area.itemid = M.editor_atto.filepickeroptions[params.elementid].image.itemid;
var host = this.get('host'),
area = this.get('area'),
options = host.get('filepickeroptions');
if (options.image && options.image.itemid) {
area.itemid = options.image.itemid;
this.set('area', area);
} else {
console.log('Plugin managefiles not available because itemid is missing.');
Y.log('Plugin managefiles not available because itemid is missing.',
'warn', LOGNAME);
return;
}
M.atto_managefiles.params[params.elementid] = params;
var click = function(e, elementid) {
var dialogue,
iframe;
this.addButton({
icon: 'e/manage_files',
callback: this._displayDialogue
});
},
e.preventDefault();
M.atto_managefiles.currentElementId = elementid;
/**
* Display the manage files.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function(e) {
e.preventDefault();
// Initialising the dialogue.
if (!M.atto_managefiles.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
var dialogue = this.getDialogue({
headerContent: M.util.get_string('managefiles', LOGNAME),
width: '800px',
focusAfterHide: true
});
// Setting up the basics of the dialogue.
dialogue.set('headerContent', M.util.get_string('managefiles', 'atto_managefiles'));
M.atto_managefiles.dialogue = dialogue;
dialogue.render();
dialogue.centerDialogue();
} else {
dialogue = M.atto_managefiles.dialogue;
}
var iframe = Y.Node.create('<iframe></iframe>');
// We set the height here because otherwise it is really small. That might not look
// very nice on mobile devices, but we considered that enough for now.
iframe.setStyles({
height: '700px',
border: 'none',
width: '100%'
});
iframe.setAttribute('src', this._getIframeURL());
dialogue.set('bodyContent', iframe)
.show();
iframe = Y.Node.create('<iframe></iframe>');
// We set the height here because otherwise it is really small. That might not look
// very nice on mobile devices, but we considered that enough for now.
iframe.setStyle('height', '700px');
iframe.setStyle('border', 'none');
iframe.setStyle('width', '100%');
iframe.setAttribute('src', M.atto_managefiles.getIframeURL());
dialogue.set('bodyContent', iframe);
dialogue.show();
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
// Add toolbar button.
var iconurl = M.util.image_url('e/manage_files', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'managefiles', iconurl, params.group, click);
this.markUpdated();
},
/**
* Returns the URL to the file manager.
*
* @param _getIframeURL
* @return {String} URL
* @private
*/
getIframeURL: function() {
var key,
params,
url = '';
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/managefiles/manage.php?';
params = M.atto_managefiles.params[M.atto_managefiles.currentElementId];
for (key in params.area) {
url += encodeURIComponent(key) + '=' + encodeURIComponent(params.area[key]) + '&';
}
return url;
},
/**
* Return the list of files used in the area.
*
* @return {Object} List of files used where the keys are the name of the files, the value is true.
*/
getUsedFiles: function() {
var elementid = M.atto_managefiles.currentElementId,
editableNode = M.editor_atto.get_editable_node(elementid),
content = editableNode.getHTML(),
params = M.atto_managefiles.params[elementid],
baseUrl = M.cfg.wwwroot + '/draftfile.php/' + params.usercontext + '/user/draft/' + params.area.itemid + '/',
pattern = new RegExp(baseUrl.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&') + "(.+?)[\\?\"']", 'gm'),
filename = '',
match = '',
usedFiles = {};
while ((match = pattern.exec(content)) !== null) {
filename = unescape(match[1]);
usedFiles[filename] = true;
}
return usedFiles;
_getIframeURL: function() {
var args = Y.mix({
elementid: this.get('host').get('elementid')
},
this.get('area'));
return M.cfg.wwwroot + '/lib/editor/atto/plugins/managefiles/manage.php?' +
Y.QueryString.stringify(args);
}
};
}, {
ATTRS: {
disabled: {
value: true
},
area: {
value: {}
}
}
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_managefiles-button",function(e,t){M.atto_managefiles=M.atto_managefiles||{currentElementId:null,dialogue:null,params:{},init:function(t){if(t.disabled)return;if(!(!t.area.itemid&&M.editor_atto.filepickeroptions[t.elementid]&&M.editor_atto.filepickeroptions[t.elementid].image&&M.editor_atto.filepickeroptions[t.elementid].image.itemid)){console.log("Plugin managefiles not available because itemid is missing.");return}t.area.itemid=M.editor_atto.filepickeroptions[t.elementid].image.itemid,M.atto_managefiles.params[t.elementid]=t;var n=function(t,n){var r,i;t.preventDefault(),M.atto_managefiles.currentElementId=n,M.atto_managefiles.dialogue?r=M.atto_managefiles.dialogue:(r=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0,width:"800px"}),r.set("headerContent",M.util.get_string("managefiles","atto_managefiles")),M.atto_managefiles.dialogue=r,r.render(),r.centerDialogue()),i=e.Node.create("<iframe></iframe>"),i.setStyle("height","700px"),i.setStyle("border","none"),i.setStyle("width","100%"),i.setAttribute("src",M.atto_managefiles.getIframeURL()),r.set("bodyContent",i),r.show(),M.editor_atto.text_updated(n)},r=M.util.image_url("e/manage_files","core");M.editor_atto.add_toolbar_button(t.elementid,"managefiles",r,t.group,n)},getIframeURL:function(){var e,t,n="";n=M.cfg.wwwroot+"/lib/editor/atto/plugins/managefiles/manage.php?",t=M.atto_managefiles.params[M.atto_managefiles.currentElementId];for(e in t.area)n+=encodeURIComponent(e)+"="+encodeURIComponent(t.area[e])+"&";return n},getUsedFiles:function(){var e=M.atto_managefiles.currentElementId,t=M.editor_atto.get_editable_node(e),n=t.getHTML(),r=M.atto_managefiles.params[e],i=M.cfg.wwwroot+"/draftfile.php/"+r.usercontext+"/user/draft/"+r.area.itemid+"/",s=new RegExp(i.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g,"\\$&")+"(.+?)[\\?\"']","gm"),o="",u="",a={};while((u=s.exec(n))!==null)o=unescape(u[1]),a[o]=!0;return a}}},"@VERSION@",{requires:["node"]});
YUI.add("moodle-atto_managefiles-button",function(e,t){var n="atto_managefiles";e.namespace("M.atto_managefiles").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,initializer:function(){if(this.get("disabled"))return;var e=this.get("host"),t=this.get("area"),n=e.get("filepickeroptions");if(!n.image||!n.image.itemid)return;t.itemid=n.image.itemid,this.set("area",t),this.addButton({icon:"e/manage_files",callback:this._displayDialogue})},_displayDialogue:function(t){t.preventDefault();var r=this.getDialogue({headerContent:M.util.get_string("managefiles",n),width:"800px",focusAfterHide:!0}),i=e.Node.create("<iframe></iframe>");i.setStyles({height:"700px",border:"none",width:"100%"}),i.setAttribute("src",this._getIframeURL()),r.set("bodyContent",i).show(),this.markUpdated()},_getIframeURL:function(){var t=e.mix({elementid:this.get("host").get("elementid")},this.get("area"));return M.cfg.wwwroot+"/lib/editor/atto/plugins/managefiles/manage.php?"+e.QueryString.stringify(t)}},{ATTRS:{disabled:{value:!0},area:{value:{}}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -16,152 +16,115 @@ YUI.add('moodle-atto_managefiles-button', function (Y, NAME) {
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor managefiles plugin.
*
* @package atto_managefiles
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_managefiles = M.atto_managefiles || {
/**
* @module moodle-atto-managefiles-button
*/
/**
* Atto text editor managefiles plugin.
*
* @namespace M.atto_link
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var LOGNAME = 'atto_managefiles';
Y.namespace('M.atto_managefiles').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* The parameters for each instance of Atto.
*
* @type {Object} Where keys are the element ID of each editor.
*/
params: {},
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init : function(params) {
if (params.disabled) {
initializer: function() {
if (this.get('disabled')) {
return;
}
// Get the itemid from the filepicker options.
if (!params.area.itemid
&& M.editor_atto.filepickeroptions[params.elementid]
&& M.editor_atto.filepickeroptions[params.elementid].image
&& M.editor_atto.filepickeroptions[params.elementid].image.itemid) {
params.area.itemid = M.editor_atto.filepickeroptions[params.elementid].image.itemid;
var host = this.get('host'),
area = this.get('area'),
options = host.get('filepickeroptions');
if (options.image && options.image.itemid) {
area.itemid = options.image.itemid;
this.set('area', area);
} else {
console.log('Plugin managefiles not available because itemid is missing.');
return;
}
M.atto_managefiles.params[params.elementid] = params;
var click = function(e, elementid) {
var dialogue,
iframe;
this.addButton({
icon: 'e/manage_files',
callback: this._displayDialogue
});
},
e.preventDefault();
M.atto_managefiles.currentElementId = elementid;
/**
* Display the manage files.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function(e) {
e.preventDefault();
// Initialising the dialogue.
if (!M.atto_managefiles.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
var dialogue = this.getDialogue({
headerContent: M.util.get_string('managefiles', LOGNAME),
width: '800px',
focusAfterHide: true
});
// Setting up the basics of the dialogue.
dialogue.set('headerContent', M.util.get_string('managefiles', 'atto_managefiles'));
M.atto_managefiles.dialogue = dialogue;
dialogue.render();
dialogue.centerDialogue();
} else {
dialogue = M.atto_managefiles.dialogue;
}
var iframe = Y.Node.create('<iframe></iframe>');
// We set the height here because otherwise it is really small. That might not look
// very nice on mobile devices, but we considered that enough for now.
iframe.setStyles({
height: '700px',
border: 'none',
width: '100%'
});
iframe.setAttribute('src', this._getIframeURL());
dialogue.set('bodyContent', iframe)
.show();
iframe = Y.Node.create('<iframe></iframe>');
// We set the height here because otherwise it is really small. That might not look
// very nice on mobile devices, but we considered that enough for now.
iframe.setStyle('height', '700px');
iframe.setStyle('border', 'none');
iframe.setStyle('width', '100%');
iframe.setAttribute('src', M.atto_managefiles.getIframeURL());
dialogue.set('bodyContent', iframe);
dialogue.show();
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
// Add toolbar button.
var iconurl = M.util.image_url('e/manage_files', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'managefiles', iconurl, params.group, click);
this.markUpdated();
},
/**
* Returns the URL to the file manager.
*
* @param _getIframeURL
* @return {String} URL
* @private
*/
getIframeURL: function() {
var key,
params,
url = '';
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/managefiles/manage.php?';
params = M.atto_managefiles.params[M.atto_managefiles.currentElementId];
for (key in params.area) {
url += encodeURIComponent(key) + '=' + encodeURIComponent(params.area[key]) + '&';
}
return url;
},
/**
* Return the list of files used in the area.
*
* @return {Object} List of files used where the keys are the name of the files, the value is true.
*/
getUsedFiles: function() {
var elementid = M.atto_managefiles.currentElementId,
editableNode = M.editor_atto.get_editable_node(elementid),
content = editableNode.getHTML(),
params = M.atto_managefiles.params[elementid],
baseUrl = M.cfg.wwwroot + '/draftfile.php/' + params.usercontext + '/user/draft/' + params.area.itemid + '/',
pattern = new RegExp(baseUrl.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&') + "(.+?)[\\?\"']", 'gm'),
filename = '',
match = '',
usedFiles = {};
while ((match = pattern.exec(content)) !== null) {
filename = unescape(match[1]);
usedFiles[filename] = true;
}
return usedFiles;
_getIframeURL: function() {
var args = Y.mix({
elementid: this.get('host').get('elementid')
},
this.get('area'));
return M.cfg.wwwroot + '/lib/editor/atto/plugins/managefiles/manage.php?' +
Y.QueryString.stringify(args);
}
};
}, {
ATTRS: {
disabled: {
value: true
},
area: {
value: {}
}
}
});
}, '@VERSION@', {"requires": ["node"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -16,13 +16,23 @@ YUI.add('moodle-atto_managefiles-usedfiles', function (Y, NAME) {
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor managefiles usedfiles plugin.
*
* @package atto_managefiles
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module moodle-atto_managefiles-usedfiles
*/
/**
* Atto text editor managefiles usedfiles plugin.
*
* @namespace M.atto_managefiles
* @class usedfiles
*/
/**
* CSS constants.
*
@@ -47,26 +57,58 @@ var SELECTORS = {
M.atto_managefiles = M.atto_managefiles || {};
M.atto_managefiles.usedfiles = M.atto_managefiles.usedfiles || {
/**
* The user context.
*
* @property _usercontext
* @type Number
* @private
*/
_usercontext: null,
/**
* Area Item ID.
*
* @property _itemid
* @type String
* @private
*/
_itemid: null,
/**
* The editor elementid
*
* @property _elementid
* @type String
* @private
*/
_elementid: null,
/**
* Init function.
*
* @param {Object} allFiles The keys are the file names, the values are the hashes.
* @return {Void}
*/
init: function(allFiles) {
init: function(config) {
this._usercontext = config.usercontext;
this._itemid = config.itemid;
this._elementid = config.elementid;
var allFiles = config.files;
var form = Y.one(SELECTORS.FORM),
usedFiles,
missingFilesTxt,
i;
if (!form || !window.parent
|| !window.parent.M.atto_managefiles) {
if (!form || !window.parent) {
Y.log("Unable to find parent window", 'warn', 'moodle-atto_managemedia-usedfiles');
return;
}
usedFiles = window.parent.M.atto_managefiles.getUsedFiles();
unusedFiles = M.atto_managefiles.usedfiles.findUnusedFiles(allFiles, usedFiles);
missingFiles = M.atto_managefiles.usedfiles.findMissingFiles(allFiles, usedFiles);
usedFiles = this._getUsedFiles();
unusedFiles = this.findUnusedFiles(allFiles, usedFiles);
missingFiles = this.findMissingFiles(allFiles, usedFiles);
// There are some unused files.
if (unusedFiles.length > 0) {
@@ -97,6 +139,29 @@ M.atto_managefiles.usedfiles = M.atto_managefiles.usedfiles || {
}
},
/**
* Return the list of files used in the area.
*
* @method _getUsedFiles
* @return {Object} List of files used where the keys are the name of the files, the value is true.
* @private
*/
_getUsedFiles: function() {
var content = Y.one(window.parent.document.getElementById(this._elementid + 'editable')),
baseUrl = M.cfg.wwwroot + '/draftfile.php/' + this._usercontext + '/user/draft/' + this._itemid + '/',
pattern = new RegExp(baseUrl.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&') + "(.+?)[\\?\"']", 'gm'),
filename = '',
match = '',
usedFiles = {};
while ((match = pattern.exec(content.get('innerHTML'))) !== null) {
filename = unescape(match[1]);
usedFiles[filename] = true;
}
return usedFiles;
},
/**
* Return an array of unused files.
*
@@ -1 +1 @@
YUI.add("moodle-atto_managefiles-usedfiles",function(e,t){var n={HASMISSINGFILES:"has-missing-files",HASUNUSEDFILES:"has-unused-files"},r={FILEANCESTOR:".fitem_fcheckbox",FORM:"#atto_managefiles_manageform",MISSINGFILES:".missing-files"};M.atto_managefiles=M.atto_managefiles||{},M.atto_managefiles.usedfiles=M.atto_managefiles.usedfiles||{init:function(t){var i=e.one(r.FORM),s,o,u;if(!i||!window.parent||!window.parent.M.atto_managefiles)return;s=window.parent.M.atto_managefiles.getUsedFiles(),unusedFiles=M.atto_managefiles.usedfiles.findUnusedFiles(t,s),missingFiles=M.atto_managefiles.usedfiles.findMissingFiles(t,s),unusedFiles.length>0?(i.all('input[type=checkbox][name^="deletefile"]').each(function(t){e.Array.indexOf(unusedFiles,t.getData("filename"))===-1&&t.ancestor(r.FILEANCESTOR).remove()}),i.addClass(n.HASUNUSEDFILES)):i.removeClass(n.HASUNUSEDFILES);if(missingFiles.length>0){o="<ul>";for(u=0;u<missingFiles.length;u++)o+="<li>"+e.Escape.html(missingFiles[u])+"</li>";o+="</ul>",i.one(r.MISSINGFILES).setHTML("").append(o),i.addClass(n.HASMISSINGFILES)}else i.removeClass(n.HASMISSINGFILES)},findUnusedFiles:function(e,t){var n,r=[];for(n in e)t[n]||r.push(n);return r},findMissingFiles:function(e,t){var n,r=[];for(n in t)e[n]||r.push(n);return r}}},"@VERSION@",{requires:["node","escape"]});
YUI.add("moodle-atto_managefiles-usedfiles",function(e,t){var n={HASMISSINGFILES:"has-missing-files",HASUNUSEDFILES:"has-unused-files"},r={FILEANCESTOR:".fitem_fcheckbox",FORM:"#atto_managefiles_manageform",MISSINGFILES:".missing-files"};M.atto_managefiles=M.atto_managefiles||{},M.atto_managefiles.usedfiles=M.atto_managefiles.usedfiles||{_usercontext:null,_itemid:null,_elementid:null,init:function(t){this._usercontext=t.usercontext,this._itemid=t.itemid,this._elementid=t.elementid;var i=t.files,s=e.one(r.FORM),o,u,a;if(!s||!window.parent)return;o=this._getUsedFiles(),unusedFiles=this.findUnusedFiles(i,o),missingFiles=this.findMissingFiles(i,o),unusedFiles.length>0?(s.all('input[type=checkbox][name^="deletefile"]').each(function(t){e.Array.indexOf(unusedFiles,t.getData("filename"))===-1&&t.ancestor(r.FILEANCESTOR).remove()}),s.addClass(n.HASUNUSEDFILES)):s.removeClass(n.HASUNUSEDFILES);if(missingFiles.length>0){u="<ul>";for(a=0;a<missingFiles.length;a++)u+="<li>"+e.Escape.html(missingFiles[a])+"</li>";u+="</ul>",s.one(r.MISSINGFILES).setHTML("").append(u),s.addClass(n.HASMISSINGFILES)}else s.removeClass(n.HASMISSINGFILES)},_getUsedFiles:function(){var t=e.one(window.parent.document.getElementById(this._elementid+"editable")),n=M.cfg.wwwroot+"/draftfile.php/"+this._usercontext+"/user/draft/"+this._itemid+"/",r=new RegExp(n.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g,"\\$&")+"(.+?)[\\?\"']","gm"),i="",s="",o={};while((s=r.exec(t.get("innerHTML")))!==null)i=unescape(s[1]),o[i]=!0;return o},findUnusedFiles:function(e,t){var n,r=[];for(n in e)t[n]||r.push(n);return r},findMissingFiles:function(e,t){var n,r=[];for(n in t)e[n]||r.push(n);return r}}},"@VERSION@",{requires:["node","escape"]});
@@ -16,13 +16,23 @@ YUI.add('moodle-atto_managefiles-usedfiles', function (Y, NAME) {
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor managefiles usedfiles plugin.
*
* @package atto_managefiles
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module moodle-atto_managefiles-usedfiles
*/
/**
* Atto text editor managefiles usedfiles plugin.
*
* @namespace M.atto_managefiles
* @class usedfiles
*/
/**
* CSS constants.
*
@@ -47,26 +57,57 @@ var SELECTORS = {
M.atto_managefiles = M.atto_managefiles || {};
M.atto_managefiles.usedfiles = M.atto_managefiles.usedfiles || {
/**
* The user context.
*
* @property _usercontext
* @type Number
* @private
*/
_usercontext: null,
/**
* Area Item ID.
*
* @property _itemid
* @type String
* @private
*/
_itemid: null,
/**
* The editor elementid
*
* @property _elementid
* @type String
* @private
*/
_elementid: null,
/**
* Init function.
*
* @param {Object} allFiles The keys are the file names, the values are the hashes.
* @return {Void}
*/
init: function(allFiles) {
init: function(config) {
this._usercontext = config.usercontext;
this._itemid = config.itemid;
this._elementid = config.elementid;
var allFiles = config.files;
var form = Y.one(SELECTORS.FORM),
usedFiles,
missingFilesTxt,
i;
if (!form || !window.parent
|| !window.parent.M.atto_managefiles) {
if (!form || !window.parent) {
return;
}
usedFiles = window.parent.M.atto_managefiles.getUsedFiles();
unusedFiles = M.atto_managefiles.usedfiles.findUnusedFiles(allFiles, usedFiles);
missingFiles = M.atto_managefiles.usedfiles.findMissingFiles(allFiles, usedFiles);
usedFiles = this._getUsedFiles();
unusedFiles = this.findUnusedFiles(allFiles, usedFiles);
missingFiles = this.findMissingFiles(allFiles, usedFiles);
// There are some unused files.
if (unusedFiles.length > 0) {
@@ -97,6 +138,29 @@ M.atto_managefiles.usedfiles = M.atto_managefiles.usedfiles || {
}
},
/**
* Return the list of files used in the area.
*
* @method _getUsedFiles
* @return {Object} List of files used where the keys are the name of the files, the value is true.
* @private
*/
_getUsedFiles: function() {
var content = Y.one(window.parent.document.getElementById(this._elementid + 'editable')),
baseUrl = M.cfg.wwwroot + '/draftfile.php/' + this._usercontext + '/user/draft/' + this._itemid + '/',
pattern = new RegExp(baseUrl.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&') + "(.+?)[\\?\"']", 'gm'),
filename = '',
match = '',
usedFiles = {};
while ((match = pattern.exec(content.get('innerHTML'))) !== null) {
filename = unescape(match[1]);
usedFiles[filename] = true;
}
return usedFiles;
},
/**
* Return an array of unused files.
*
@@ -1,15 +1,10 @@
{
"name": "moodle-atto_managefiles-button",
"builds": {
"moodle-atto_managefiles-button": {
"jsfiles": [
"button.js"
]
},
"moodle-atto_managefiles-usedfiles": {
"jsfiles": [
"usedfiles.js"
]
"name": "moodle-atto_managefiles-button",
"builds": {
"moodle-atto_managefiles-button": {
"jsfiles": [
"button.js"
]
}
}
}
}
@@ -14,149 +14,114 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor managefiles plugin.
*
* @package atto_managefiles
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_managefiles = M.atto_managefiles || {
/**
* @module moodle-atto-managefiles-button
*/
/**
* Atto text editor managefiles plugin.
*
* @namespace M.atto_link
* @class button
* @extends M.editor_atto.EditorPlugin
*/
var LOGNAME = 'atto_managefiles';
Y.namespace('M.atto_managefiles').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* The ID of the current editor.
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @type {String}
* @property _currentSelection
* @type Range
* @private
*/
currentElementId: null,
_currentSelection: null,
/**
* The dialogue to select a character.
*
* @type {M.core.dialogue}
*/
dialogue: null,
/**
* The parameters for each instance of Atto.
*
* @type {Object} Where keys are the element ID of each editor.
*/
params: {},
/**
* Init.
*
* @param {Object} params
*
* @return {Void}
*/
init : function(params) {
if (params.disabled) {
initializer: function() {
if (this.get('disabled')) {
return;
}
// Get the itemid from the filepicker options.
if (!params.area.itemid
&& M.editor_atto.filepickeroptions[params.elementid]
&& M.editor_atto.filepickeroptions[params.elementid].image
&& M.editor_atto.filepickeroptions[params.elementid].image.itemid) {
params.area.itemid = M.editor_atto.filepickeroptions[params.elementid].image.itemid;
var host = this.get('host'),
area = this.get('area'),
options = host.get('filepickeroptions');
if (options.image && options.image.itemid) {
area.itemid = options.image.itemid;
this.set('area', area);
} else {
console.log('Plugin managefiles not available because itemid is missing.');
Y.log('Plugin managefiles not available because itemid is missing.',
'warn', LOGNAME);
return;
}
M.atto_managefiles.params[params.elementid] = params;
var click = function(e, elementid) {
var dialogue,
iframe;
this.addButton({
icon: 'e/manage_files',
callback: this._displayDialogue
});
},
e.preventDefault();
M.atto_managefiles.currentElementId = elementid;
/**
* Display the manage files.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function(e) {
e.preventDefault();
// Initialising the dialogue.
if (!M.atto_managefiles.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true,
width: '800px'
});
var dialogue = this.getDialogue({
headerContent: M.util.get_string('managefiles', LOGNAME),
width: '800px',
focusAfterHide: true
});
// Setting up the basics of the dialogue.
dialogue.set('headerContent', M.util.get_string('managefiles', 'atto_managefiles'));
M.atto_managefiles.dialogue = dialogue;
dialogue.render();
dialogue.centerDialogue();
} else {
dialogue = M.atto_managefiles.dialogue;
}
var iframe = Y.Node.create('<iframe></iframe>');
// We set the height here because otherwise it is really small. That might not look
// very nice on mobile devices, but we considered that enough for now.
iframe.setStyles({
height: '700px',
border: 'none',
width: '100%'
});
iframe.setAttribute('src', this._getIframeURL());
dialogue.set('bodyContent', iframe)
.show();
iframe = Y.Node.create('<iframe></iframe>');
// We set the height here because otherwise it is really small. That might not look
// very nice on mobile devices, but we considered that enough for now.
iframe.setStyle('height', '700px');
iframe.setStyle('border', 'none');
iframe.setStyle('width', '100%');
iframe.setAttribute('src', M.atto_managefiles.getIframeURL());
dialogue.set('bodyContent', iframe);
dialogue.show();
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
};
// Add toolbar button.
var iconurl = M.util.image_url('e/manage_files', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'managefiles', iconurl, params.group, click);
this.markUpdated();
},
/**
* Returns the URL to the file manager.
*
* @param _getIframeURL
* @return {String} URL
* @private
*/
getIframeURL: function() {
var key,
params,
url = '';
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/managefiles/manage.php?';
params = M.atto_managefiles.params[M.atto_managefiles.currentElementId];
for (key in params.area) {
url += encodeURIComponent(key) + '=' + encodeURIComponent(params.area[key]) + '&';
}
return url;
},
/**
* Return the list of files used in the area.
*
* @return {Object} List of files used where the keys are the name of the files, the value is true.
*/
getUsedFiles: function() {
var elementid = M.atto_managefiles.currentElementId,
editableNode = M.editor_atto.get_editable_node(elementid),
content = editableNode.getHTML(),
params = M.atto_managefiles.params[elementid],
baseUrl = M.cfg.wwwroot + '/draftfile.php/' + params.usercontext + '/user/draft/' + params.area.itemid + '/',
pattern = new RegExp(baseUrl.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&') + "(.+?)[\\?\"']", 'gm'),
filename = '',
match = '',
usedFiles = {};
while ((match = pattern.exec(content)) !== null) {
filename = unescape(match[1]);
usedFiles[filename] = true;
}
return usedFiles;
_getIframeURL: function() {
var args = Y.mix({
elementid: this.get('host').get('elementid')
},
this.get('area'));
return M.cfg.wwwroot + '/lib/editor/atto/plugins/managefiles/manage.php?' +
Y.QueryString.stringify(args);
}
};
}, {
ATTRS: {
disabled: {
value: true
},
area: {
value: {}
}
}
});
@@ -1,5 +1,7 @@
{
"moodle-atto_managefiles-button": {
"requires": ["node"]
}
"moodle-atto_managefiles-button": {
"requires": [
"moodle-editor_atto-plugin"
]
}
}
@@ -1,5 +0,0 @@
{
"moodle-atto_managefiles-usedfiles": {
"requires": ["node", "escape"]
}
}
@@ -0,0 +1,10 @@
{
"name": "moodle-atto_managefiles-usedfiles",
"builds": {
"moodle-atto_managefiles-usedfiles": {
"jsfiles": [
"usedfiles.js"
]
}
}
}
@@ -14,13 +14,23 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor managefiles usedfiles plugin.
*
* @package atto_managefiles
* @copyright 2014 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module moodle-atto_managefiles-usedfiles
*/
/**
* Atto text editor managefiles usedfiles plugin.
*
* @namespace M.atto_managefiles
* @class usedfiles
*/
/**
* CSS constants.
*
@@ -45,26 +55,58 @@ var SELECTORS = {
M.atto_managefiles = M.atto_managefiles || {};
M.atto_managefiles.usedfiles = M.atto_managefiles.usedfiles || {
/**
* The user context.
*
* @property _usercontext
* @type Number
* @private
*/
_usercontext: null,
/**
* Area Item ID.
*
* @property _itemid
* @type String
* @private
*/
_itemid: null,
/**
* The editor elementid
*
* @property _elementid
* @type String
* @private
*/
_elementid: null,
/**
* Init function.
*
* @param {Object} allFiles The keys are the file names, the values are the hashes.
* @return {Void}
*/
init: function(allFiles) {
init: function(config) {
this._usercontext = config.usercontext;
this._itemid = config.itemid;
this._elementid = config.elementid;
var allFiles = config.files;
var form = Y.one(SELECTORS.FORM),
usedFiles,
missingFilesTxt,
i;
if (!form || !window.parent
|| !window.parent.M.atto_managefiles) {
if (!form || !window.parent) {
Y.log("Unable to find parent window", 'warn', 'moodle-atto_managemedia-usedfiles');
return;
}
usedFiles = window.parent.M.atto_managefiles.getUsedFiles();
unusedFiles = M.atto_managefiles.usedfiles.findUnusedFiles(allFiles, usedFiles);
missingFiles = M.atto_managefiles.usedfiles.findMissingFiles(allFiles, usedFiles);
usedFiles = this._getUsedFiles();
unusedFiles = this.findUnusedFiles(allFiles, usedFiles);
missingFiles = this.findMissingFiles(allFiles, usedFiles);
// There are some unused files.
if (unusedFiles.length > 0) {
@@ -95,6 +137,29 @@ M.atto_managefiles.usedfiles = M.atto_managefiles.usedfiles || {
}
},
/**
* Return the list of files used in the area.
*
* @method _getUsedFiles
* @return {Object} List of files used where the keys are the name of the files, the value is true.
* @private
*/
_getUsedFiles: function() {
var content = Y.one(window.parent.document.getElementById(this._elementid + 'editable')),
baseUrl = M.cfg.wwwroot + '/draftfile.php/' + this._usercontext + '/user/draft/' + this._itemid + '/',
pattern = new RegExp(baseUrl.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&') + "(.+?)[\\?\"']", 'gm'),
filename = '',
match = '',
usedFiles = {};
while ((match = pattern.exec(content.get('innerHTML'))) !== null) {
filename = unescape(match[1]);
usedFiles[filename] = true;
}
return usedFiles;
},
/**
* Return an array of unused files.
*
@@ -0,0 +1,8 @@
{
"moodle-atto_managefiles-usedfiles": {
"requires": [
"node",
"escape"
]
}
}
@@ -15,110 +15,158 @@ YUI.add('moodle-atto_media-button', function (Y, NAME) {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Atto text editor media plugin.
*
* @package editor-atto
/*
* @package atto_media
* @copyright 2013 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.atto_media = M.atto_media || {
dialogue : null,
selection : null,
init : function(params) {
if (!M.editor_atto.can_show_filepicker(params.elementid, 'media')) {
// Do not show this button if we can't browse repositories.
/**
* @module moodle-atto_media-button
*/
/**
* Atto media selection tool.
*
* @namespace M.atto_media
* @class Button
* @extends M.editor_atto.EditorPlugin
*/
var COMPONENTNAME = 'atto_media',
TEMPLATE = '' +
'<form class="atto_form">' +
'<label for="{{elementid}}_atto_media_urlentry">{{get_string "enterurl" component}}</label>' +
'<input class="fullwidth urlentry" type="url" id="{{elementid}}_atto_media_urlentry" size="32"/><br/>' +
'<button class="openmediabrowser" type="button">{{get_string "browserepositories" component}}</button>' +
'<label for="{{elementid}}_atto_media_nameentry">{{get_string "entername" component}}</label>' +
'<input class="fullwidth nameentry" type="text" id="{{elementid}}_atto_media_nameentry" size="32" required="true"/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="submit" type="submit">{{get_string "createmedia" component}}</button>' +
'</div>' +
'</form>';
Y.namespace('M.atto_media').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
/**
* A reference to the current selection at the time that the dialogue
* was opened.
*
* @property _currentSelection
* @type Range
* @private
*/
_currentSelection: null,
/**
* A reference to the dialogue content.
*
* @property _content
* @type Node
* @private
*/
_content: null,
initializer: function() {
this.addButton({
icon: 'e/insert_edit_video',
callback: this._displayDialogue
});
},
/**
* Display the media editing tool.
*
* @method _displayDialogue
* @private
*/
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false) {
return;
}
var display_chooser = function(e, elementid) {
var dialogue = this.getDialogue({
headerContent: M.util.get_string('createmedia', COMPONENTNAME),
focusAfterHide: true
});
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent())
.show();
},
/**
* Return the dialogue content for the tool, attaching any required
* events.
*
* @method _getDialogueContent
* @return {Node} The content to place in the dialogue.
* @private
*/
_getDialogueContent: function() {
var template = Y.Handlebars.compile(TEMPLATE);
this._content = Y.Node.create(template({
component: COMPONENTNAME,
elementid: this.get('host').get('elementid')
}));
this._content.one('.submit').on('click', this._setMedia, this);
this._content.one('.openmediabrowser').on('click', function(e) {
e.preventDefault();
if (!M.editor_atto.is_active(elementid)) {
M.editor_atto.focus(elementid);
}
M.atto_media.selection = M.editor_atto.get_selection();
if (M.atto_media.selection !== false) {
var dialogue;
if (!M.atto_media.dialogue) {
dialogue = new M.core.dialogue({
visible: false,
modal: true,
close: true,
draggable: true
});
} else {
dialogue = M.atto_media.dialogue;
}
this.get('host').showFilepicker('media', this._filepickerCallback, this);
}, this);
dialogue.render();
dialogue.set('bodyContent', M.atto_media.get_form_content(elementid));
dialogue.set('headerContent', M.util.get_string('createmedia', 'atto_media'));
dialogue.centerDialogue();
dialogue.show();
M.atto_media.dialogue = dialogue;
}
};
var iconurl = M.util.image_url('e/insert_edit_video', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'media', iconurl, params.group, display_chooser);
return this._content;
},
open_browser : function(e) {
var elementid = this.getAttribute('data-editor');
e.preventDefault();
M.editor_atto.show_filepicker(elementid, 'media', M.atto_media.filepicker_callback);
},
filepicker_callback : function(params) {
/**
* Update the dialogue after an media was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* @private
*/
_filepickerCallback: function(params) {
if (params.url !== '') {
var input = Y.one('#atto_media_urlentry');
input.set('value', params.url);
input = Y.one('#atto_media_nameentry');
input.set('value', params.file);
this._content.one('.urlentry')
.set('value', params.url);
this._content.one('.nameentry')
.set('value', params.file);
}
},
set_media : function(e, elementid) {
e.preventDefault();
M.atto_media.dialogue.hide();
var input = e.currentTarget.ancestor('.atto_form').one('#atto_media_urlentry');
var url = input.get('value');
input = e.currentTarget.ancestor('.atto_form').one('#atto_media_nameentry');
var name = input.get('value');
/**
* Update the media in the contenteditable.
*
* @method setMedia
* @param {EventFacade} e
* @private
*/
_setMedia: function(e) {
e.preventDefault();
this.getDialogue({
focusAfterHide: null
}).hide();
var form = e.currentTarget.ancestor('.atto_form'),
url = form.one('.urlentry').get('value'),
name = form.one('.nameentry').get('value'),
host = this.get('host');
if (url !== '' && name !== '') {
M.editor_atto.set_selection(M.atto_media.selection);
host.setSelection(this._currentSelection);
var mediahtml = '<a href="' + Y.Escape.html(url) + '">' + name + '</a>';
M.editor_atto.insert_html_at_focus_point(mediahtml);
// Clean the YUI ids from the HTML.
M.editor_atto.text_updated(elementid);
host.insertContentAtFocusPoint(mediahtml);
this.markUpdated();
}
},
get_form_content : function(elementid) {
var content = Y.Node.create('<form class="atto_form">' +
'<label for="atto_media_urlentry">' + M.util.get_string('enterurl', 'atto_media') +
'</label>' +
'<input class="fullwidth" type="url" value="" id="atto_media_urlentry" size="32"/><br/>' +
'<button id="openmediabrowser" data-editor="' + Y.Escape.html(elementid) + '" type="button">' +
M.util.get_string('browserepositories', 'atto_media') +
'</button>' +
'<label for="atto_media_nameentry">' + M.util.get_string('entername', 'atto_media') +
'</label>' +
'<input class="fullwidth" type="text" value="" id="atto_media_nameentry" size="32" required="true"/>' +
'<div class="mdl-align">' +
'<br/>' +
'<button id="atto_media_urlentrysubmit" type="submit">' +
M.util.get_string('createmedia', 'atto_media') +
'</button>' +
'</div>' +
'</form>');
content.one('#atto_media_urlentrysubmit').on('click', M.atto_media.set_media, this, elementid);
content.one('#openmediabrowser').on('click', M.atto_media.open_browser);
return content;
}
};
});
}, '@VERSION@', {"requires": ["node", "escape"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
@@ -1 +1 @@
YUI.add("moodle-atto_media-button",function(e,t){M.atto_media=M.atto_media||{dialogue:null,selection:null,init:function(e){if(!M.editor_atto.can_show_filepicker(e.elementid,"media"))return;var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),M.atto_media.selection=M.editor_atto.get_selection();if(M.atto_media.selection!==!1){var n;M.atto_media.dialogue?n=M.atto_media.dialogue:n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0}),n.render(),n.set("bodyContent",M.atto_media.get_form_content(t)),n.set("headerContent",M.util.get_string("createmedia","atto_media")),n.centerDialogue(),n.show(),M.atto_media.dialogue=n}},n=M.util.image_url("e/insert_edit_video","core");M.editor_atto.add_toolbar_button(e.elementid,"media",n,e.group,t)},open_browser:function(e){var t=this.getAttribute("data-editor");e.preventDefault(),M.editor_atto.show_filepicker(t,"media",M.atto_media.filepicker_callback)},filepicker_callback:function(t){if(t.url!==""){var n=e.one("#atto_media_urlentry");n.set("value",t.url),n=e.one("#atto_media_nameentry"),n.set("value",t.file)}},set_media:function(t,n){t.preventDefault(),M.atto_media.dialogue.hide();var r=t.currentTarget.ancestor(".atto_form").one("#atto_media_urlentry"),i=r.get("value");r=t.currentTarget.ancestor(".atto_form").one("#atto_media_nameentry");var s=r.get("value");if(i!==""&&s!==""){M.editor_atto.set_selection(M.atto_media.selection);var o='<a href="'+e.Escape.html(i)+'">'+s+"</a>";M.editor_atto.insert_html_at_focus_point(o),M.editor_atto.text_updated(n)}},get_form_content:function(t){var n=e.Node.create('<form class="atto_form"><label for="atto_media_urlentry">'+M.util.get_string("enterurl","atto_media")+"</label>"+'<input class="fullwidth" type="url" value="" id="atto_media_urlentry" size="32"/><br/>'+'<button id="openmediabrowser" data-editor="'+e.Escape.html(t)+'" type="button">'+M.util.get_string("browserepositories","atto_media")+"</button>"+'<label for="atto_media_nameentry">'+M.util.get_string("entername","atto_media")+"</label>"+'<input class="fullwidth" type="text" value="" id="atto_media_nameentry" size="32" required="true"/>'+'<div class="mdl-align">'+"<br/>"+'<button id="atto_media_urlentrysubmit" type="submit">'+M.util.get_string("createmedia","atto_media")+"</button>"+"</div>"+"</form>");return n.one("#atto_media_urlentrysubmit").on("click",M.atto_media.set_media,this,t),n.one("#openmediabrowser").on("click",M.atto_media.open_browser),n}}},"@VERSION@",{requires:["node","escape"]});
YUI.add("moodle-atto_media-button",function(e,t){var n="atto_media",r='<form class="atto_form"><label for="{{elementid}}_atto_media_urlentry">{{get_string "enterurl" component}}</label><input class="fullwidth urlentry" type="url" id="{{elementid}}_atto_media_urlentry" size="32"/><br/><button class="openmediabrowser" type="button">{{get_string "browserepositories" component}}</button><label for="{{elementid}}_atto_media_nameentry">{{get_string "entername" component}}</label><input class="fullwidth nameentry" type="text" id="{{elementid}}_atto_media_nameentry" size="32" required="true"/><div class="mdl-align"><br/><button class="submit" type="submit">{{get_string "createmedia" component}}</button></div></form>';e.namespace("M.atto_media").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,initializer:function(){this.addButton({icon:"e/insert_edit_video",callback:this._displayDialogue})},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1)return;var e=this.getDialogue({headerContent:M.util.get_string("createmedia",n),focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()},_getDialogueContent:function(){var t=e.Handlebars.compile(r);return this._content=e.Node.create(t({component:n,elementid:this.get("host").get("elementid")})),this._content.one(".submit").on("click",this._setMedia,this),this._content.one(".openmediabrowser").on("click",function(e){e.preventDefault(),this.get("host").showFilepicker("media",this._filepickerCallback,this)},this),this._content},_filepickerCallback:function(e){e.url!==""&&(this._content.one(".urlentry").set("value",e.url),this._content.one(".nameentry").set("value",e.file))},_setMedia:function(t){t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide();var n=t.currentTarget.ancestor(".atto_form"),r=n.one(".urlentry").get("value"),i=n.one(".nameentry").get("value"),s=this.get("host");if(r!==""&&i!==""){s.setSelection(this._currentSelection);var o='<a href="'+e.Escape.html(r)+'">'+i+"</a>";s.insertContentAtFocusPoint(o),this.markUpdated()}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});

Some files were not shown because too many files have changed in this diff Show More