MDL-44164 atto: Refactor the world
This commit is contained in:
committed by
Damyon Wiese
parent
8951d61411
commit
6246779510
@@ -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)));
|
||||
|
||||
}
|
||||
|
||||
+176
-191
@@ -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
@@ -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"]});
|
||||
|
||||
+171
-186
@@ -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"]});
|
||||
|
||||
+175
-190
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+233
-226
@@ -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
@@ -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"]});
|
||||
|
||||
+233
-226
@@ -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"]});
|
||||
|
||||
+232
-225
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+59
-60
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
Vendored
+59
-60
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+89
-83
@@ -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
@@ -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@");
|
||||
|
||||
+89
-83
@@ -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
@@ -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;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Vendored
+21
-34
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
+21
-34
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+90
-114
@@ -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 = {
|
||||
['­', '­', 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"]});
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+90
-114
@@ -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 = {
|
||||
['­', '­', 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
@@ -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 = {
|
||||
['­', '­', 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+22
-19
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
Vendored
+22
-19
@@ -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
@@ -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.';
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+72
-70
@@ -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
@@ -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"]});
|
||||
|
||||
+72
-70
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+114
-134
@@ -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
@@ -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"]});
|
||||
|
||||
+114
-134
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+283
-211
@@ -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"]});
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+282
-211
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+67
-70
@@ -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
@@ -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@");
|
||||
|
||||
+67
-70
@@ -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
@@ -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();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Vendored
+91
-33
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
+91
-33
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+395
-395
@@ -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"]});
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+395
-395
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+36
-41
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
Vendored
+36
-41
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+21
-34
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
Vendored
+21
-34
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+143
-149
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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"]});
|
||||
|
||||
+143
-149
@@ -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
@@ -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'],
|
||||
|
||||
+82
-117
@@ -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
@@ -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"]});
|
||||
|
||||
+80
-117
@@ -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"]});
|
||||
|
||||
+73
-8
@@ -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
@@ -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"]});
|
||||
|
||||
+72
-8
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+81
-116
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
-8
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+134
-86
@@ -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"]});
|
||||
|
||||
Vendored
+1
-1
@@ -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
Reference in New Issue
Block a user