mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 12:40:01 +01:00
MDL-43867 Atto: Accessibility improvements.
1/ Set the aria-labelledby attribute on the contenteditable div (find the label from original textarea) 2/ Store/restore the selection for the contenteditable div when it is focused. This allows you to select some text, then go to the toolbar and click a button, and the selection will be restored before the button effect is applied. 3/ Add an accessibility helper plugin. From testing in all screenreaders, I found that all of their support for contenteditable is not great. They treat it like a textbox - which means you can type and edit text, but it tells you nothing about the styles, links or images in the editor. So I added a button to the toolbar, that is only accessible when navigating via keyboard, that opens an accesssibility helper dialogue. The dialogue shows the list of current styles, a global list of all links, and a global list of all images. Choosing an image or link from here, will focus on the editable region, and select the link/image. 4/ Add an accessibility checker plugin to Atto. Checks for images with no alt, images and links with filenames as alternate text/link text, and contrast ratios less than WCAG 2.0 AA.
This commit is contained in:
parent
dad09216bc
commit
26f8822d5c
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'atto_accessibilitychecker', language 'en'.
|
||||
*
|
||||
* @package atto_accessibilitychecker
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Accessibility checker';
|
||||
$string['nowarnings'] = 'Congratulations, no accessibility problems found!';
|
||||
$string['report'] = 'Accessibility report:';
|
||||
$string['imagesmissingalt'] = 'Images require alternative text. To fix this warning, add an alt attribute to your img tags. An empty alt attribute may be used, but only when the image is purely decorative and carries no information.';
|
||||
$string['needsmorecontrast'] = 'The colors of the foreground and background text do not have enough contrast. To fix this warning, change either foreground or background color of the text so that it is easier to read.';
|
40
lib/editor/atto/plugins/accessibilitychecker/lib.php
Normal file
40
lib/editor/atto/plugins/accessibilitychecker/lib.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Atto text editor integration version file.
|
||||
*
|
||||
* @package atto_accessibilitychecker
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Initialise this plugin
|
||||
* @param string $elementid
|
||||
*/
|
||||
function atto_accessibilitychecker_strings_for_js() {
|
||||
global $PAGE;
|
||||
|
||||
$PAGE->requires->strings_for_js(array('nowarnings',
|
||||
'report',
|
||||
'imagesmissingalt',
|
||||
'needsmorecontrast'),
|
||||
'atto_accessibilitychecker');
|
||||
}
|
||||
|
29
lib/editor/atto/plugins/accessibilitychecker/version.php
Normal file
29
lib/editor/atto/plugins/accessibilitychecker/version.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Atto text editor integration version file.
|
||||
*
|
||||
* @package atto_accessibilitychecker
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2014012800; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2013110500; // Requires this Moodle version.
|
||||
$plugin->component = 'atto_accessibilitychecker'; // Full name of the plugin (used for diagnostics).
|
@ -0,0 +1,245 @@
|
||||
YUI.add('moodle-atto_accessibilitychecker-button', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <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 <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
|
||||
/**
|
||||
* The window used to display the accessibility ui.
|
||||
*
|
||||
* @property dialogue
|
||||
* @type M.core.dialogue
|
||||
* @default null
|
||||
*/
|
||||
dialogue : null,
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
|
||||
dialogue.centerDialogue();
|
||||
|
||||
dialogue.show();
|
||||
M.atto_accessibilitychecker.dialogue = dialogue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this button to the form.
|
||||
*
|
||||
* @method init
|
||||
* @param {Object} params
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
add_warnings : function(list, description, nodes) {
|
||||
var warning, fails, i;
|
||||
|
||||
if (nodes.length > 0) {
|
||||
warning = Y.Node.create('<p>' + description + '</p>');
|
||||
fails = Y.Node.create('<ol></ol>');
|
||||
i = 0;
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
fails.append(Y.Node.create('<li>' + Y.Escape.html(nodes[i].get('outerHTML')) + '</li>'));
|
||||
}
|
||||
|
||||
warning.append(fails);
|
||||
list.append(warning);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a css color to a luminance value.
|
||||
*
|
||||
* @method get_luminance_from_css_color
|
||||
* @param {String} colortext
|
||||
* @return {Integer}
|
||||
*/
|
||||
get_luminance_from_css_color : function(colortext) {
|
||||
var color;
|
||||
|
||||
if (colortext === 'transparent') {
|
||||
colortext = '#ffffff';
|
||||
}
|
||||
color = Y.Color.toArray(Y.Color.toRGB(colortext));
|
||||
|
||||
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
|
||||
var part1 = function(a) {
|
||||
a = parseInt(a, 10) / 255.0;
|
||||
if (a <= 0.03928) {
|
||||
a = a/12.92;
|
||||
} else {
|
||||
a = Math.pow(((a + 0.055)/1.055), 2.4);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
var r1 = part1(color[0]),
|
||||
g1 = part1(color[1]),
|
||||
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);
|
||||
|
||||
// Missing alternatives.
|
||||
var missingalt = [], dodgycontrast = [];
|
||||
|
||||
// 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') {
|
||||
missingalt.push(img);
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), missingalt);
|
||||
|
||||
// Contrast ratios.
|
||||
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 < dodgycontrast.length; i++) {
|
||||
if (node.ancestors('*').indexOf(dodgycontrast[i]) !== -1) {
|
||||
// Do not add node - it already has a parent in the list.
|
||||
found = true;
|
||||
break;
|
||||
} else if (dodgycontrast[i].ancestors('*').indexOf(node) !== -1) {
|
||||
// Replace the existing node with this one because it is higher up the DOM.
|
||||
dodgycontrast[i] = node;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
dodgycontrast.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), dodgycontrast);
|
||||
|
||||
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"]});
|
@ -0,0 +1 @@
|
||||
YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){M.atto_accessibilitychecker=M.atto_accessibilitychecker||{dialogue:null,display_ui:function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t);var n;M.atto_accessibilitychecker.dialogue?n=M.atto_accessibilitychecker.dialogue:(n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0,width:"800px"}),n.set("headerContent",M.util.get_string("pluginname","atto_accessibilitychecker")),n.render()),n.set("bodyContent",M.atto_accessibilitychecker.get_report(t)),n.centerDialogue(),n.show(),M.atto_accessibilitychecker.dialogue=n},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){var i,s,o;if(r.length>0){i=e.Node.create("<p>"+n+"</p>"),s=e.Node.create("<ol></ol>"),o=0;for(o=0;o<r.length;o++)s.append(e.Node.create("<li>"+e.Escape.html(r[o].get("outerHTML"))+"</li>"));i.append(s),t.append(i)}},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=[],o;r.all("img").each(function(e){o=e.getAttribute("alt"),(typeof o=="undefined"||o==="")&&e.getAttribute("role")!=="presentation"&&i.push(e)},this),this.add_warnings(n,M.util.get_string("imagesmissingalt","atto_accessibilitychecker"),i);var u,a,f,l,c;return r.all("*").each(function(t){if(e.Lang.trim(t.get("text"))!==""){u=t.getComputedStyle("color"),a=t.getComputedStyle("backgroundColor"),f=this.get_luminance_from_css_color(u),l=this.get_luminance_from_css_color(a),f>l?c=(f+.05)/(l+.05):c=(l+.05)/(f+.05);if(c<=4.5){var n=0,r=!1;for(n=0;n<s.length;n++){if(t.ancestors("*").indexOf(s[n])!==-1){r=!0;break}if(s[n].ancestors("*").indexOf(t)!==-1){s[n]=t,r=!0;break}}r||s.push(t)}}},this),this.add_warnings(n,M.util.get_string("needsmorecontrast","atto_accessibilitychecker"),s),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"]});
|
@ -0,0 +1,240 @@
|
||||
YUI.add('moodle-atto_accessibilitychecker-button', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <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 <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
|
||||
/**
|
||||
* The window used to display the accessibility ui.
|
||||
*
|
||||
* @property dialogue
|
||||
* @type M.core.dialogue
|
||||
* @default null
|
||||
*/
|
||||
dialogue : null,
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
|
||||
dialogue.centerDialogue();
|
||||
|
||||
dialogue.show();
|
||||
M.atto_accessibilitychecker.dialogue = dialogue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this button to the form.
|
||||
*
|
||||
* @method init
|
||||
* @param {Object} params
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
add_warnings : function(list, description, nodes) {
|
||||
var warning, fails, i;
|
||||
|
||||
if (nodes.length > 0) {
|
||||
warning = Y.Node.create('<p>' + description + '</p>');
|
||||
fails = Y.Node.create('<ol></ol>');
|
||||
i = 0;
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
fails.append(Y.Node.create('<li>' + Y.Escape.html(nodes[i].get('outerHTML')) + '</li>'));
|
||||
}
|
||||
|
||||
warning.append(fails);
|
||||
list.append(warning);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a css color to a luminance value.
|
||||
*
|
||||
* @method get_luminance_from_css_color
|
||||
* @param {String} colortext
|
||||
* @return {Integer}
|
||||
*/
|
||||
get_luminance_from_css_color : function(colortext) {
|
||||
var color;
|
||||
|
||||
if (colortext === 'transparent') {
|
||||
colortext = '#ffffff';
|
||||
}
|
||||
color = Y.Color.toArray(Y.Color.toRGB(colortext));
|
||||
|
||||
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
|
||||
var part1 = function(a) {
|
||||
a = parseInt(a, 10) / 255.0;
|
||||
if (a <= 0.03928) {
|
||||
a = a/12.92;
|
||||
} else {
|
||||
a = Math.pow(((a + 0.055)/1.055), 2.4);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
var r1 = part1(color[0]),
|
||||
g1 = part1(color[1]),
|
||||
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);
|
||||
|
||||
// Missing alternatives.
|
||||
var missingalt = [], dodgycontrast = [];
|
||||
|
||||
// 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') {
|
||||
missingalt.push(img);
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), missingalt);
|
||||
|
||||
// Contrast ratios.
|
||||
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 < dodgycontrast.length; i++) {
|
||||
if (node.ancestors('*').indexOf(dodgycontrast[i]) !== -1) {
|
||||
// Do not add node - it already has a parent in the list.
|
||||
found = true;
|
||||
break;
|
||||
} else if (dodgycontrast[i].ancestors('*').indexOf(node) !== -1) {
|
||||
// Replace the existing node with this one because it is higher up the DOM.
|
||||
dodgycontrast[i] = node;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
dodgycontrast.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), dodgycontrast);
|
||||
|
||||
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"]});
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "moodle-atto_accessibilitychecker-button",
|
||||
"builds": {
|
||||
"moodle-atto_accessibilitychecker-button": {
|
||||
"jsfiles": [
|
||||
"button.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
240
lib/editor/atto/plugins/accessibilitychecker/yui/src/button/js/button.js
vendored
Normal file
240
lib/editor/atto/plugins/accessibilitychecker/yui/src/button/js/button.js
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <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 <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
|
||||
/**
|
||||
* The window used to display the accessibility ui.
|
||||
*
|
||||
* @property dialogue
|
||||
* @type M.core.dialogue
|
||||
* @default null
|
||||
*/
|
||||
dialogue : null,
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
|
||||
dialogue.centerDialogue();
|
||||
|
||||
dialogue.show();
|
||||
M.atto_accessibilitychecker.dialogue = dialogue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this button to the form.
|
||||
*
|
||||
* @method init
|
||||
* @param {Object} params
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
add_warnings : function(list, description, nodes) {
|
||||
var warning, fails, i;
|
||||
|
||||
if (nodes.length > 0) {
|
||||
warning = Y.Node.create('<p>' + description + '</p>');
|
||||
fails = Y.Node.create('<ol></ol>');
|
||||
i = 0;
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
fails.append(Y.Node.create('<li>' + Y.Escape.html(nodes[i].get('outerHTML')) + '</li>'));
|
||||
}
|
||||
|
||||
warning.append(fails);
|
||||
list.append(warning);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a css color to a luminance value.
|
||||
*
|
||||
* @method get_luminance_from_css_color
|
||||
* @param {String} colortext
|
||||
* @return {Integer}
|
||||
*/
|
||||
get_luminance_from_css_color : function(colortext) {
|
||||
var color;
|
||||
|
||||
if (colortext === 'transparent') {
|
||||
colortext = '#ffffff';
|
||||
}
|
||||
color = Y.Color.toArray(Y.Color.toRGB(colortext));
|
||||
|
||||
// Algorithm from "http://www.w3.org/TR/WCAG20-GENERAL/G18.html".
|
||||
var part1 = function(a) {
|
||||
a = parseInt(a, 10) / 255.0;
|
||||
if (a <= 0.03928) {
|
||||
a = a/12.92;
|
||||
} else {
|
||||
a = Math.pow(((a + 0.055)/1.055), 2.4);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
var r1 = part1(color[0]),
|
||||
g1 = part1(color[1]),
|
||||
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);
|
||||
|
||||
// Missing alternatives.
|
||||
var missingalt = [], dodgycontrast = [];
|
||||
|
||||
// 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') {
|
||||
missingalt.push(img);
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), missingalt);
|
||||
|
||||
// Contrast ratios.
|
||||
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 < dodgycontrast.length; i++) {
|
||||
if (node.ancestors('*').indexOf(dodgycontrast[i]) !== -1) {
|
||||
// Do not add node - it already has a parent in the list.
|
||||
found = true;
|
||||
break;
|
||||
} else if (dodgycontrast[i].ancestors('*').indexOf(node) !== -1) {
|
||||
// Replace the existing node with this one because it is higher up the DOM.
|
||||
dodgycontrast[i] = node;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
dodgycontrast.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), dodgycontrast);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"moodle-atto_accessibilitychecker-button": {
|
||||
"requires": ["node", "escape", "color-base"]
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'atto_accessibilityhelper', language 'en'.
|
||||
*
|
||||
* @package atto_accessibilityhelper
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Accessibility helper';
|
||||
$string['liststyles'] = 'Styles for current selection:';
|
||||
$string['nostyles'] = 'No styles';
|
||||
$string['listlinks'] = 'Links in text editor:';
|
||||
$string['nolinks'] = 'No links';
|
||||
$string['selectlink'] = 'Select link';
|
||||
$string['listimages'] = 'Images in text editor:';
|
||||
$string['noimages'] = 'No images';
|
||||
$string['selectimage'] = 'Select image';
|
44
lib/editor/atto/plugins/accessibilityhelper/lib.php
Normal file
44
lib/editor/atto/plugins/accessibilityhelper/lib.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Atto text editor integration version file.
|
||||
*
|
||||
* @package atto_accessibilityhelper
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Initialise this plugin
|
||||
* @param string $elementid
|
||||
*/
|
||||
function atto_accessibilityhelper_strings_for_js() {
|
||||
global $PAGE;
|
||||
|
||||
$PAGE->requires->strings_for_js(array('liststyles',
|
||||
'nostyles',
|
||||
'listlinks',
|
||||
'nolinks',
|
||||
'selectlink',
|
||||
'listimages',
|
||||
'noimages',
|
||||
'selectimage'),
|
||||
'atto_accessibilityhelper');
|
||||
}
|
||||
|
29
lib/editor/atto/plugins/accessibilityhelper/version.php
Normal file
29
lib/editor/atto/plugins/accessibilityhelper/version.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Atto text editor integration version file.
|
||||
*
|
||||
* @package atto_accessibilityhelper
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2014012800; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2013110500; // Requires this Moodle version.
|
||||
$plugin->component = 'atto_accessibilityhelper'; // Full name of the plugin (used for diagnostics).
|
@ -0,0 +1,294 @@
|
||||
YUI.add('moodle-atto_accessibilityhelper-button', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <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}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
LISTSTYLES: '#atto_accessibilityhelper_liststyles',
|
||||
LISTLINKS: '#atto_accessibilityhelper_listlinks',
|
||||
LISTIMAGES: '#atto_accessibilityhelper_listimages'
|
||||
};
|
||||
/**
|
||||
* Atto text editor accessibilityhelper 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 editor-atto
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
|
||||
/**
|
||||
* The window used to display the accessibility ui.
|
||||
*
|
||||
* @property dialogue
|
||||
* @type M.core.dialogue
|
||||
* @default null
|
||||
*/
|
||||
dialogue : null,
|
||||
|
||||
/**
|
||||
* 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_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;
|
||||
}
|
||||
|
||||
dialogue.set('bodyContent', M.atto_accessibilityhelper.get_content(elementid));
|
||||
dialogue.centerDialogue();
|
||||
|
||||
dialogue.show();
|
||||
M.atto_accessibilityhelper.dialogue = dialogue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this button to the form.
|
||||
*
|
||||
* @method init
|
||||
* @param {Object} params
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for selecting an image.
|
||||
*
|
||||
* @method image_selected
|
||||
* @param Event e
|
||||
* @param string elementid
|
||||
*/
|
||||
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) {
|
||||
selectimage = Y.Node.create('<a href="#" title="' +
|
||||
M.util.get_string('selectimage', 'atto_accessibilityhelper') + '">' +
|
||||
Y.Escape.html(image.getAttribute('alt')) +
|
||||
'</a>');
|
||||
|
||||
selectimage.setData('sourceimage', image);
|
||||
selectimage.on('click', this.image_selected, this, elementid);
|
||||
|
||||
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>');
|
||||
}
|
||||
// Append the list of current styles.
|
||||
return list;
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for selecting a link.
|
||||
*
|
||||
* @method link_selected
|
||||
* @param Event e
|
||||
* @param string elementid
|
||||
*/
|
||||
link_selected : function(e, elementid) {
|
||||
e.preventDefault();
|
||||
|
||||
M.atto_accessibilityhelper.dialogue.hide();
|
||||
|
||||
var link = e.target.getData('sourcelink');
|
||||
var selection = M.editor_atto.get_selection_from_node(link);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["node", "escape"]});
|
@ -0,0 +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){s=e.Node.create('<a href="#" title="'+M.util.get_string("selectimage","atto_accessibilityhelper")+'">'+e.Escape.html(r.getAttribute("alt"))+"</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"]});
|
@ -0,0 +1,294 @@
|
||||
YUI.add('moodle-atto_accessibilityhelper-button', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <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}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
LISTSTYLES: '#atto_accessibilityhelper_liststyles',
|
||||
LISTLINKS: '#atto_accessibilityhelper_listlinks',
|
||||
LISTIMAGES: '#atto_accessibilityhelper_listimages'
|
||||
};
|
||||
/**
|
||||
* Atto text editor accessibilityhelper 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 editor-atto
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
|
||||
/**
|
||||
* The window used to display the accessibility ui.
|
||||
*
|
||||
* @property dialogue
|
||||
* @type M.core.dialogue
|
||||
* @default null
|
||||
*/
|
||||
dialogue : null,
|
||||
|
||||
/**
|
||||
* 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_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;
|
||||
}
|
||||
|
||||
dialogue.set('bodyContent', M.atto_accessibilityhelper.get_content(elementid));
|
||||
dialogue.centerDialogue();
|
||||
|
||||
dialogue.show();
|
||||
M.atto_accessibilityhelper.dialogue = dialogue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this button to the form.
|
||||
*
|
||||
* @method init
|
||||
* @param {Object} params
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for selecting an image.
|
||||
*
|
||||
* @method image_selected
|
||||
* @param Event e
|
||||
* @param string elementid
|
||||
*/
|
||||
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) {
|
||||
selectimage = Y.Node.create('<a href="#" title="' +
|
||||
M.util.get_string('selectimage', 'atto_accessibilityhelper') + '">' +
|
||||
Y.Escape.html(image.getAttribute('alt')) +
|
||||
'</a>');
|
||||
|
||||
selectimage.setData('sourceimage', image);
|
||||
selectimage.on('click', this.image_selected, this, elementid);
|
||||
|
||||
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>');
|
||||
}
|
||||
// Append the list of current styles.
|
||||
return list;
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for selecting a link.
|
||||
*
|
||||
* @method link_selected
|
||||
* @param Event e
|
||||
* @param string elementid
|
||||
*/
|
||||
link_selected : function(e, elementid) {
|
||||
e.preventDefault();
|
||||
|
||||
M.atto_accessibilityhelper.dialogue.hide();
|
||||
|
||||
var link = e.target.getData('sourcelink');
|
||||
var selection = M.editor_atto.get_selection_from_node(link);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["node", "escape"]});
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "moodle-atto_accessibilityhelper-button",
|
||||
"builds": {
|
||||
"moodle-atto_accessibilityhelper-button": {
|
||||
"jsfiles": [
|
||||
"button.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
289
lib/editor/atto/plugins/accessibilityhelper/yui/src/button/js/button.js
vendored
Normal file
289
lib/editor/atto/plugins/accessibilityhelper/yui/src/button/js/button.js
vendored
Normal file
@ -0,0 +1,289 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <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}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
LISTSTYLES: '#atto_accessibilityhelper_liststyles',
|
||||
LISTLINKS: '#atto_accessibilityhelper_listlinks',
|
||||
LISTIMAGES: '#atto_accessibilityhelper_listimages'
|
||||
};
|
||||
/**
|
||||
* Atto text editor accessibilityhelper 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 editor-atto
|
||||
* @copyright 2014 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
M.atto_accessibilityhelper = M.atto_accessibilityhelper || {
|
||||
/**
|
||||
* The window used to display the accessibility ui.
|
||||
*
|
||||
* @property dialogue
|
||||
* @type M.core.dialogue
|
||||
* @default null
|
||||
*/
|
||||
dialogue : null,
|
||||
|
||||
/**
|
||||
* 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_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;
|
||||
}
|
||||
|
||||
dialogue.set('bodyContent', M.atto_accessibilityhelper.get_content(elementid));
|
||||
dialogue.centerDialogue();
|
||||
|
||||
dialogue.show();
|
||||
M.atto_accessibilityhelper.dialogue = dialogue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this button to the form.
|
||||
*
|
||||
* @method init
|
||||
* @param {Object} params
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for selecting an image.
|
||||
*
|
||||
* @method image_selected
|
||||
* @param Event e
|
||||
* @param string elementid
|
||||
*/
|
||||
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) {
|
||||
selectimage = Y.Node.create('<a href="#" title="' +
|
||||
M.util.get_string('selectimage', 'atto_accessibilityhelper') + '">' +
|
||||
Y.Escape.html(image.getAttribute('alt')) +
|
||||
'</a>');
|
||||
|
||||
selectimage.setData('sourceimage', image);
|
||||
selectimage.on('click', this.image_selected, this, elementid);
|
||||
|
||||
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>');
|
||||
}
|
||||
// Append the list of current styles.
|
||||
return list;
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for selecting a link.
|
||||
*
|
||||
* @method link_selected
|
||||
* @param Event e
|
||||
* @param string elementid
|
||||
*/
|
||||
link_selected : function(e, elementid) {
|
||||
e.preventDefault();
|
||||
|
||||
M.atto_accessibilityhelper.dialogue.hide();
|
||||
|
||||
var link = e.target.getData('sourcelink');
|
||||
var selection = M.editor_atto.get_selection_from_node(link);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"moodle-atto_accessibilityhelper-button": {
|
||||
"requires": ["node", "escape"]
|
||||
}
|
||||
}
|
@ -140,9 +140,12 @@ M.atto_table = M.atto_table || {
|
||||
this.lasttarget = e.target.ancestor('td, th');
|
||||
this.controlmenu.show();
|
||||
this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
|
||||
var bodynode = this.controlmenu.get('boundingBox');
|
||||
if (bodynode.one('a')) {
|
||||
bodynode.one('a').focus();
|
||||
}
|
||||
|
||||
if (addhandlers) {
|
||||
var bodynode = this.controlmenu.get('boundingBox');
|
||||
bodynode.delegate('click', this.handle_table_control, 'a', this, elementid);
|
||||
bodynode.delegate('key', this.handle_table_control, 'down:enter,space', 'a', this, elementid);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -140,9 +140,12 @@ M.atto_table = M.atto_table || {
|
||||
this.lasttarget = e.target.ancestor('td, th');
|
||||
this.controlmenu.show();
|
||||
this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
|
||||
var bodynode = this.controlmenu.get('boundingBox');
|
||||
if (bodynode.one('a')) {
|
||||
bodynode.one('a').focus();
|
||||
}
|
||||
|
||||
if (addhandlers) {
|
||||
var bodynode = this.controlmenu.get('boundingBox');
|
||||
bodynode.delegate('click', this.handle_table_control, 'a', this, elementid);
|
||||
bodynode.delegate('key', this.handle_table_control, 'down:enter,space', 'a', this, elementid);
|
||||
}
|
||||
|
@ -138,9 +138,12 @@ M.atto_table = M.atto_table || {
|
||||
this.lasttarget = e.target.ancestor('td, th');
|
||||
this.controlmenu.show();
|
||||
this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
|
||||
var bodynode = this.controlmenu.get('boundingBox');
|
||||
if (bodynode.one('a')) {
|
||||
bodynode.one('a').focus();
|
||||
}
|
||||
|
||||
if (addhandlers) {
|
||||
var bodynode = this.controlmenu.get('boundingBox');
|
||||
bodynode.delegate('click', this.handle_table_control, 'a', this, elementid);
|
||||
bodynode.delegate('key', this.handle_table_control, 'down:enter,space', 'a', this, elementid);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ if ($ADMIN->fulltree) {
|
||||
'paragraph = indent, outdent, orderedlist, unorderedlist' . "\n" .
|
||||
'links = link, unlink' . "\n" .
|
||||
'insert = table, image, media, managefiles, charmap, emoticon' . "\n" .
|
||||
'other = html';
|
||||
'other = html, accessibilitychecker, accessibilityhelper';
|
||||
$setting = new admin_setting_configtextarea('editor_atto/toolbar',
|
||||
$name,
|
||||
$desc,
|
||||
|
@ -114,6 +114,13 @@ M.editor_atto = M.editor_atto || {
|
||||
*/
|
||||
widgets : {},
|
||||
|
||||
/**
|
||||
* List of saved selections per editor instance.
|
||||
*/
|
||||
selections : {},
|
||||
|
||||
focusfromclick : false,
|
||||
|
||||
/**
|
||||
* Toggle a menu.
|
||||
* @param event e
|
||||
@ -141,6 +148,7 @@ M.editor_atto = M.editor_atto || {
|
||||
overlay.show();
|
||||
var icon = e.target.ancestor('button', true).one('img');
|
||||
overlay.align(icon, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
|
||||
overlay.get('boundingBox').one('a').focus();
|
||||
}
|
||||
},
|
||||
|
||||
@ -453,15 +461,25 @@ M.editor_atto = M.editor_atto || {
|
||||
var wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
|
||||
var atto = Y.Node.create('<div id="' + params.elementid + 'editable" ' +
|
||||
'contenteditable="true" ' +
|
||||
'role="textbox" ' +
|
||||
'spellcheck="true" ' +
|
||||
'aria-live="off" ' +
|
||||
'class="' + CSS.CONTENT + '" />');
|
||||
|
||||
var cssfont = '';
|
||||
var toolbar = Y.Node.create('<div class="' + CSS.TOOLBAR + '" id="' + params.elementid + '_toolbar" role="toolbar"/>');
|
||||
var toolbar = Y.Node.create('<div class="' + CSS.TOOLBAR + '" id="' + params.elementid + '_toolbar" role="toolbar" aria-live="off"/>');
|
||||
|
||||
// Editable content wrapper.
|
||||
var content = Y.Node.create('<div class="' + CSS.CONTENTWRAPPER + '" />');
|
||||
var textarea = M.editor_atto.get_textarea_node(params.elementid);
|
||||
var label = Y.one('[for="' + params.elementid + '"]');
|
||||
|
||||
// Add a labelled-by attribute to the contenteditable.
|
||||
if (label) {
|
||||
label.generateID();
|
||||
atto.setAttribute('aria-labelledby', label.get("id"));
|
||||
toolbar.setAttribute('aria-labelledby', label.get("id"));
|
||||
}
|
||||
|
||||
content.appendChild(atto);
|
||||
|
||||
@ -489,11 +507,33 @@ M.editor_atto = M.editor_atto || {
|
||||
atto.setStyle('color', textarea.getStyle('color'));
|
||||
atto.setStyle('lineHeight', textarea.getStyle('lineHeight'));
|
||||
atto.setStyle('fontSize', textarea.getStyle('fontSize'));
|
||||
|
||||
// Disable odd inline CSS styles.
|
||||
try {
|
||||
document.execCommand("styleWithCSS", 0, false);
|
||||
} catch (e1) {
|
||||
try {
|
||||
document.execCommand("useCSS", 0, true);
|
||||
} catch (e2) {
|
||||
try {
|
||||
document.execCommand('styleWithCSS', false, false);
|
||||
}
|
||||
catch (e3) {
|
||||
// We did our best.
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hide the old textarea.
|
||||
textarea.hide();
|
||||
atto.on('keydown', this.save_selection, this, params.elementid);
|
||||
atto.on('mouseup', this.save_selection, this, params.elementid);
|
||||
atto.on('focus', this.restore_selection, this, params.elementid);
|
||||
// Do not restore selection when focus is from a click event.
|
||||
atto.on('mousedown', function() { this.focusfromclick = true; }, this);
|
||||
|
||||
// Copy the current value back to the textarea when focus leaves us.
|
||||
// Copy the current value back to the textarea when focus leaves us and save the current selection.
|
||||
atto.on('blur', function() {
|
||||
this.focusfromclick = false;
|
||||
this.text_updated(params.elementid);
|
||||
}, this);
|
||||
|
||||
@ -662,6 +702,35 @@ M.editor_atto = M.editor_atto || {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the current selection on blur, allows more reliable keyboard navigation.
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
save_selection : function(event, elementid) {
|
||||
if (this.is_active(elementid)) {
|
||||
var sel = this.get_selection();
|
||||
if (sel.length > 0) {
|
||||
this.selections[elementid] = sel;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore any current selection when the editor gets focus again.
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
restore_selection : function(event, elementid) {
|
||||
event.preventDefault();
|
||||
if (!this.focusfromclick) {
|
||||
if (typeof this.selections[elementid] !== "undefined") {
|
||||
this.set_selection(this.selections[elementid]);
|
||||
}
|
||||
}
|
||||
this.focusfromclick = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the selection object that can be passed back to set_selection.
|
||||
* @return range (browser dependent)
|
||||
|
File diff suppressed because one or more lines are too long
@ -114,6 +114,13 @@ M.editor_atto = M.editor_atto || {
|
||||
*/
|
||||
widgets : {},
|
||||
|
||||
/**
|
||||
* List of saved selections per editor instance.
|
||||
*/
|
||||
selections : {},
|
||||
|
||||
focusfromclick : false,
|
||||
|
||||
/**
|
||||
* Toggle a menu.
|
||||
* @param event e
|
||||
@ -141,6 +148,7 @@ M.editor_atto = M.editor_atto || {
|
||||
overlay.show();
|
||||
var icon = e.target.ancestor('button', true).one('img');
|
||||
overlay.align(icon, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
|
||||
overlay.get('boundingBox').one('a').focus();
|
||||
}
|
||||
},
|
||||
|
||||
@ -453,15 +461,25 @@ M.editor_atto = M.editor_atto || {
|
||||
var wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
|
||||
var atto = Y.Node.create('<div id="' + params.elementid + 'editable" ' +
|
||||
'contenteditable="true" ' +
|
||||
'role="textbox" ' +
|
||||
'spellcheck="true" ' +
|
||||
'aria-live="off" ' +
|
||||
'class="' + CSS.CONTENT + '" />');
|
||||
|
||||
var cssfont = '';
|
||||
var toolbar = Y.Node.create('<div class="' + CSS.TOOLBAR + '" id="' + params.elementid + '_toolbar" role="toolbar"/>');
|
||||
var toolbar = Y.Node.create('<div class="' + CSS.TOOLBAR + '" id="' + params.elementid + '_toolbar" role="toolbar" aria-live="off"/>');
|
||||
|
||||
// Editable content wrapper.
|
||||
var content = Y.Node.create('<div class="' + CSS.CONTENTWRAPPER + '" />');
|
||||
var textarea = M.editor_atto.get_textarea_node(params.elementid);
|
||||
var label = Y.one('[for="' + params.elementid + '"]');
|
||||
|
||||
// Add a labelled-by attribute to the contenteditable.
|
||||
if (label) {
|
||||
label.generateID();
|
||||
atto.setAttribute('aria-labelledby', label.get("id"));
|
||||
toolbar.setAttribute('aria-labelledby', label.get("id"));
|
||||
}
|
||||
|
||||
content.appendChild(atto);
|
||||
|
||||
@ -489,11 +507,33 @@ M.editor_atto = M.editor_atto || {
|
||||
atto.setStyle('color', textarea.getStyle('color'));
|
||||
atto.setStyle('lineHeight', textarea.getStyle('lineHeight'));
|
||||
atto.setStyle('fontSize', textarea.getStyle('fontSize'));
|
||||
|
||||
// Disable odd inline CSS styles.
|
||||
try {
|
||||
document.execCommand("styleWithCSS", 0, false);
|
||||
} catch (e1) {
|
||||
try {
|
||||
document.execCommand("useCSS", 0, true);
|
||||
} catch (e2) {
|
||||
try {
|
||||
document.execCommand('styleWithCSS', false, false);
|
||||
}
|
||||
catch (e3) {
|
||||
// We did our best.
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hide the old textarea.
|
||||
textarea.hide();
|
||||
atto.on('keydown', this.save_selection, this, params.elementid);
|
||||
atto.on('mouseup', this.save_selection, this, params.elementid);
|
||||
atto.on('focus', this.restore_selection, this, params.elementid);
|
||||
// Do not restore selection when focus is from a click event.
|
||||
atto.on('mousedown', function() { this.focusfromclick = true; }, this);
|
||||
|
||||
// Copy the current value back to the textarea when focus leaves us.
|
||||
// Copy the current value back to the textarea when focus leaves us and save the current selection.
|
||||
atto.on('blur', function() {
|
||||
this.focusfromclick = false;
|
||||
this.text_updated(params.elementid);
|
||||
}, this);
|
||||
|
||||
@ -662,6 +702,35 @@ M.editor_atto = M.editor_atto || {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the current selection on blur, allows more reliable keyboard navigation.
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
save_selection : function(event, elementid) {
|
||||
if (this.is_active(elementid)) {
|
||||
var sel = this.get_selection();
|
||||
if (sel.length > 0) {
|
||||
this.selections[elementid] = sel;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore any current selection when the editor gets focus again.
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
restore_selection : function(event, elementid) {
|
||||
event.preventDefault();
|
||||
if (!this.focusfromclick) {
|
||||
if (typeof this.selections[elementid] !== "undefined") {
|
||||
this.set_selection(this.selections[elementid]);
|
||||
}
|
||||
}
|
||||
this.focusfromclick = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the selection object that can be passed back to set_selection.
|
||||
* @return range (browser dependent)
|
||||
|
73
lib/editor/atto/yui/src/editor/js/editor.js
vendored
73
lib/editor/atto/yui/src/editor/js/editor.js
vendored
@ -112,6 +112,13 @@ M.editor_atto = M.editor_atto || {
|
||||
*/
|
||||
widgets : {},
|
||||
|
||||
/**
|
||||
* List of saved selections per editor instance.
|
||||
*/
|
||||
selections : {},
|
||||
|
||||
focusfromclick : false,
|
||||
|
||||
/**
|
||||
* Toggle a menu.
|
||||
* @param event e
|
||||
@ -139,6 +146,7 @@ M.editor_atto = M.editor_atto || {
|
||||
overlay.show();
|
||||
var icon = e.target.ancestor('button', true).one('img');
|
||||
overlay.align(icon, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
|
||||
overlay.get('boundingBox').one('a').focus();
|
||||
}
|
||||
},
|
||||
|
||||
@ -451,15 +459,25 @@ M.editor_atto = M.editor_atto || {
|
||||
var wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
|
||||
var atto = Y.Node.create('<div id="' + params.elementid + 'editable" ' +
|
||||
'contenteditable="true" ' +
|
||||
'role="textbox" ' +
|
||||
'spellcheck="true" ' +
|
||||
'aria-live="off" ' +
|
||||
'class="' + CSS.CONTENT + '" />');
|
||||
|
||||
var cssfont = '';
|
||||
var toolbar = Y.Node.create('<div class="' + CSS.TOOLBAR + '" id="' + params.elementid + '_toolbar" role="toolbar"/>');
|
||||
var toolbar = Y.Node.create('<div class="' + CSS.TOOLBAR + '" id="' + params.elementid + '_toolbar" role="toolbar" aria-live="off"/>');
|
||||
|
||||
// Editable content wrapper.
|
||||
var content = Y.Node.create('<div class="' + CSS.CONTENTWRAPPER + '" />');
|
||||
var textarea = M.editor_atto.get_textarea_node(params.elementid);
|
||||
var label = Y.one('[for="' + params.elementid + '"]');
|
||||
|
||||
// Add a labelled-by attribute to the contenteditable.
|
||||
if (label) {
|
||||
label.generateID();
|
||||
atto.setAttribute('aria-labelledby', label.get("id"));
|
||||
toolbar.setAttribute('aria-labelledby', label.get("id"));
|
||||
}
|
||||
|
||||
content.appendChild(atto);
|
||||
|
||||
@ -487,11 +505,33 @@ M.editor_atto = M.editor_atto || {
|
||||
atto.setStyle('color', textarea.getStyle('color'));
|
||||
atto.setStyle('lineHeight', textarea.getStyle('lineHeight'));
|
||||
atto.setStyle('fontSize', textarea.getStyle('fontSize'));
|
||||
|
||||
// Disable odd inline CSS styles.
|
||||
try {
|
||||
document.execCommand("styleWithCSS", 0, false);
|
||||
} catch (e1) {
|
||||
try {
|
||||
document.execCommand("useCSS", 0, true);
|
||||
} catch (e2) {
|
||||
try {
|
||||
document.execCommand('styleWithCSS', false, false);
|
||||
}
|
||||
catch (e3) {
|
||||
// We did our best.
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hide the old textarea.
|
||||
textarea.hide();
|
||||
atto.on('keydown', this.save_selection, this, params.elementid);
|
||||
atto.on('mouseup', this.save_selection, this, params.elementid);
|
||||
atto.on('focus', this.restore_selection, this, params.elementid);
|
||||
// Do not restore selection when focus is from a click event.
|
||||
atto.on('mousedown', function() { this.focusfromclick = true; }, this);
|
||||
|
||||
// Copy the current value back to the textarea when focus leaves us.
|
||||
// Copy the current value back to the textarea when focus leaves us and save the current selection.
|
||||
atto.on('blur', function() {
|
||||
this.focusfromclick = false;
|
||||
this.text_updated(params.elementid);
|
||||
}, this);
|
||||
|
||||
@ -660,6 +700,35 @@ M.editor_atto = M.editor_atto || {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the current selection on blur, allows more reliable keyboard navigation.
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
save_selection : function(event, elementid) {
|
||||
if (this.is_active(elementid)) {
|
||||
var sel = this.get_selection();
|
||||
if (sel.length > 0) {
|
||||
this.selections[elementid] = sel;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore any current selection when the editor gets focus again.
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
restore_selection : function(event, elementid) {
|
||||
event.preventDefault();
|
||||
if (!this.focusfromclick) {
|
||||
if (typeof this.selections[elementid] !== "undefined") {
|
||||
this.set_selection(this.selections[elementid]);
|
||||
}
|
||||
}
|
||||
this.focusfromclick = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the selection object that can be passed back to set_selection.
|
||||
* @return range (browser dependent)
|
||||
|
Loading…
x
Reference in New Issue
Block a user